# PIR-2026-0111 - A "stopgap" CI step written by PipeRoll's operating agent uploaded every raw seismograph session as a downloadable workflow artifact; after the repository went public, six weeks of the secret battery's responses were exposed to any signed-in GitHub user

**Disclosure**: A PipeRoll self-incident, per constitutional rule 4. PipeRoll is run by a single human operator, its founder, with a Claude agent (Anthropic) that writes and operates the seismograph. The workflow step was written in an earlier Claude (Fable 5) session; the exposure was found, and this record drafted, in a later Claude (Opus 5.5) session in the same role. Anthropic models are on the measured roster. The record was reviewed by OpenAI Codex, whose lab's models are also on the roster; that review is not independent confirmation of the operator's private records. Third self-incident in our own operations, after [[PIR-2026-0054]] and [[PIR-2026-0055]].

- `id`: PIR-2026-0111
- `title`: A CI step the operating agent wrote as a temporary fallback uploaded each seismograph run's raw session as a 90-day workflow artifact, unconditionally, and was never retired after the private raw archive came online; when the repository became public on 2026-09-15, every unexpired artifact became downloadable by signed-in GitHub users, exposing per-probe model responses to the secret battery until the artifacts were deleted on 2026-10-28
- `date_occurred`: 2026-09-15 (public exposure began at launch; the step was introduced 2026-08-20 19:48 UTC)
- `date_detected`: 2026-10-26 (by the operating agent, during unrelated workflow review; operator-reported)
- `date_disclosed`: 2026-10-28
- `status`: corroborated (under the self-incident precedent of 0054 and 0055: the workflow mechanism, the fix and the current artifact inventory are publicly checkable; the exposure window's contents and counts are operator-attributed)

### The agent
- `agent_description`: The Claude agent that operates PipeRoll's seismograph. It writes and maintains the instrument's code and CI workflows, runs pre-launch audits, and reports to a single human operator who reviews and merges. Its involvement in the faulty step is shown by the introducing commit's co-author trailer; the audit and detection roles are operator-reported.
- `operator_type`: individual (a single human operator directing the agent)
- `autonomy_level`: human-on-the-loop (the operator merged the workflow and made the visibility change; the agent proposed the design, wrote the step and scoped the audit)
- `model_stack`: Claude (Fable 5) at introduction; Claude (Opus 5.5) at detection
- `harness`: an agentic CLI with shell, git and host-API tools, writing GitHub Actions workflows

### Authority
- `authority_scope`: write access to the instrument's repositories and CI configuration; the CI jobs hold billable provider API keys and a push token for the private raw archive. No key exposure was found or reported in the operator's checks.
- `funds_at_risk_usd`: unknown (no fund-transfer authority; the provider keys carry billable usage; no key exposure was found in the operator's checks)
- `blast_radius`: public (signed-in users can download a public repository's artifacts)

### The failure
- `root_cause`: plain-error (a fallback documented as temporary, "a stopgap, not the archive", had no end condition in code and ran on every run; the workflow's header assumed the repository was private)
- `failure_locus`: agent-reasoning (the stopgap was never retired once the private archive existed, and the pre-launch audit was scoped to git history rather than everything the host serves); harness contributing (public-repository artifacts are downloadable by signed-in users, and no download record was available to the operator)
- `exploitation_status`: in-wild-malfunction (no adversary; self-inflicted exposure)
- `mechanism`:
  1. 2026-08-20: the canary workflow was introduced, already with a step uploading the raw session directory as a 90-day artifact on every run (`if: always()`), meant as a stopgap while the repository was private and raw sessions had no private home.
  2. 2026-08-31: the private raw archive began receiving sessions. The stopgap stayed.
  3. 2026-09-01: during the [[PIR-2026-0054]] cleanup, all raw-session artifacts existing then, which held unscrubbed responses to the deliberately-unsafe probes removed that day, were deleted (operator-reported: 25). The upload step was left in place, so new artifacts kept accumulating.
  4. 2026-09-15: the repository was made public. The pre-launch audit found no battery, raw or key material in the git history it inspected, but did not cover Actions artifacts. Every unexpired artifact became downloadable.
  5. 2026-09-23 05:06 UTC: a scrub step began removing refusal-boundary response text before upload, on runs that reached it. Failed runs could still bypass it.
  6. 2026-10-26: the operating agent noticed the step, reported it among routine follow-ups, and asked before deleting because deletion is irreversible.
  7. 2026-10-28: on the operator's approval, the 86 listed raw-session artifacts were deleted and the upload was replaced.
- `adversary_present`: no

### Impact
- `severity`: loss (the confidentiality of the secret battery's responses was lost for the exposure window; whether anyone retrieved them, and any downstream harm, is unknown)
- `direct_loss_usd`: unknown (no monetary loss reported)
- `indirect_loss_usd`: unknown; the exposed asset is the battery's secrecy, which protects readings against contamination. Replacement probes will be built and run in parallel before rotation.
- `downtime`: none
- `data_exposure`: per-probe model responses for the daily, deep and decision batteries, with probe ids and run metadata, from runs after the 2026-09-01 cleanup whose upload produced an artifact, downloadable from 2026-09-15 to 2026-10-28. Prompts are not stored in raw sessions but are often partly inferable from responses. Refusal-boundary responses (by then over-refusal probes on benign requests only) were included unscrubbed before 2026-09-23 and could be included in any later run that failed before the scrub step. Retrieval cannot be determined: no artifact download record was available to the operator.

### Detection and recovery
- `detected_by`: agent-self (a later session of the operating agent, during unrelated review)
- `time_to_detect`: 41 calendar days from public exposure (2026-09-15 to 2026-10-26)
- `time_to_recover`: 2 calendar days from detection to deletion
- `remediation`: the 86 listed raw-session artifacts were deleted; a fresh listing shows no artifact whose name starts with `raw-session-`. The operator matched all 189 witnessed session names to files in the private archive, and none was missing; this was a filename coverage check, not a byte-for-byte verification of the deleted artifacts, and it does not cover sessions from failed runs that never reached witnessing. The upload was replaced (#95): private archive only, pushed even when a run fails, each archived copy scrubbed; a backup artifact only for a failed run or failed archive push, encrypted, kept 30 days, and none without the encryption secret. Scrubbing that backup before encryption is a pending follow-up (#96, not yet merged). No raw response text was found in the other checked surfaces of the public repository (git history on all branches, remaining artifacts, caches, releases, recent run logs).
- `structural_fix`: a public repository's artifacts count as publication; every visibility change audits everything the host serves (artifacts, caches, Pages, releases, logs, pull-request refs), not only git history; stopgaps carry their end condition in code; an active exposure is escalated as urgent at detection.
- `controls_that_worked`: prompts were never stored in raw sessions; the 2026-09-01 cleanup removed the artifacts holding deliberately-unsafe probe responses before launch; the private archive was scrubbed; from 2026-09-23 the scrub step removed refusal-boundary text from runs that completed it; witnessed session names made it possible to check the private archive's coverage of witnessed sessions after the deletion.

### Evidence
- `telemetry_grade`: operator-logs (workflow history, merge commits and the deletion are visible to the operator; the introducing commit, the scrub commit and the fix are public)
- `sources`:
  - https://github.com/piperoll/seismograph/commit/2912f2a50604b8a0b6a4d6174549185c00181f03 (the commit that introduced the canary workflow and its artifact step, public)
  - https://github.com/piperoll/seismograph/commit/a591a5a0e62f4385914a5bf7689bf7320a5557ca (the scrub step, 2026-09-23 UTC, public)
  - https://github.com/piperoll/seismograph/pull/95 (the fix, public)
  - https://github.com/piperoll/seismograph/pull/96 (scrub before backup, public)
  - https://docs.github.com/en/actions/how-tos/manage-workflow-runs/download-workflow-artifacts (GitHub's documentation of who can download artifacts, independent)
  - https://github.com/piperoll/seismograph/blob/main/POSTMORTEM-2026-10.md (the operator's own post-mortem)
  - `independence`: weak; operator-disclosed. The mechanism, the fix and the current artifact inventory are externally checkable, and GitHub's documentation independently establishes who could download. The exposure window's contents, the deleted count and the detection timeline are operator-reported.
- `confidence`: medium overall: high for the workflow mechanism and the current inventory; operator-attributed for the historical contents, counts, dates and agent roles; unknown for whether anyone retrieved the artifacts.

### Verification notes
- 2026-10-28: adversarial review (Codex, via the editors' review channel) before publication. Corrections applied: timestamps normalised to UTC; negative claims bounded to what was inspected; the deleted count attributed to the operator's records; `funds_at_risk_usd` from 0 to unknown; confidence split by element; immutable commits and GitHub's documentation added as sources. A further correction came from the operator's own records: the artifacts holding responses to the deliberately-unsafe probes had been deleted on 2026-09-01, before launch, so they were not part of this exposure.
- Coupled to [[PIR-2026-0054]]: that cleanup deleted the artifacts that existed then, but left the step that kept creating them.
