Skip to content

workspace: tamper detection via snapshot/diff#48

Merged
bokuweb merged 1 commit into
mainfrom
claude/workspace-tamper
May 10, 2026
Merged

workspace: tamper detection via snapshot/diff#48
bokuweb merged 1 commit into
mainfrom
claude/workspace-tamper

Conversation

@bokuweb

@bokuweb bokuweb commented May 10, 2026

Copy link
Copy Markdown
Owner

Summary

Closes harden-runner parity gap #9. New standalone subcommands let
you detect file edits made during a build by anything the
supervised step ran — typical use case is catching a malicious
dependency's postinstall rewriting source files or .git/config.

coronarium workspace snapshot \$WORKSPACE -o /tmp/before.json
<run the build>
coronarium workspace diff /tmp/before.json \$WORKSPACE   # exits 1 on drift

Design notes

  • Hash: SHA-256 of every regular file under the root, plus
    size as a fallback for oversized-skip files.
  • Skip list: hardcoded build-artefact dirs (.git, node_modules,
    target, dist, build, vendor, __pycache__, .venv,
    venv, .next, .turbo, .cache). Deliberately doesn't
    honour .gitignore — an attacker can write into it. --skip
    extends the list for your own artefacts.
  • Symlinks: recorded by target string, not followed (the diff
    fires on retarget; the linked file is hashed independently if
    it's also under the root).
  • Oversized files (default 64 MiB+): size-only entry, no hash.
    Diff still fires on size delta but a same-size content swap is
    a known miss — bump --max-file-bytes or pass 0 for unlimited
    if that matters.
  • Output: text (with +/~/- prefixes) or --format json.
    Exits non-zero on any drift; --allow-drift suppresses for
    audit-only steps.

Integration into coronarium run (snapshot before / diff after
exec) is intentionally deferred — the standalone form composes
with any build, not just an eBPF-supervised one.

Test plan

  • cargo fmt --all -- --check
  • cargo clippy --workspace --all-targets -- -D warnings
  • cargo test --workspace — 11 new unit tests in coronarium-core::tamper (118 total in core)
  • Manual smoke: snapshot → modify/add/remove files → diff reports +c ~a -b and exits 1

🤖 Generated with Claude Code

Closes harden-runner gap #9. New standalone subcommands surface
files modified during a build by anything the supervised step ran
— typical use case is catching a malicious dependency's
postinstall rewriting source files or `.git/config`.

```
coronarium workspace snapshot $WORKSPACE -o /tmp/before.json
<run the build>
coronarium workspace diff /tmp/before.json $WORKSPACE   # exit 1 on drift
```

Implementation notes:

- SHA-256 of every regular file under the root, plus size for
  oversized-skip fallback. Hardcoded skip list of build-artefact
  dirs (`.git`, `node_modules`, `target`, `dist`, `build`,
  `vendor`, `__pycache__`, `.venv`, `venv`, `.next`, `.turbo`,
  `.cache`); deliberately doesn't honour `.gitignore` since an
  attacker can write into it. `--skip` extends the list.
- Symlinks recorded by target string, not followed.
- Files >64 MiB get a size-only entry by default — diff still
  fires on size delta but identical-size content swap is a known
  miss. Bump `--max-file-bytes` (or pass 0 for unlimited) when
  that matters.
- Diff output: text (with +/~/- prefixes) or `--format json`.
  Non-zero exit on any drift; `--allow-drift` for audit-only mode.

Auto-integration into `coronarium run` (snapshot before / diff
after exec) is intentionally a follow-up — the standalone form
composes with any build, not just an eBPF-supervised one.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
Signed-off-by: bokuweb <[email protected]>
@github-actions

Copy link
Copy Markdown

coronarium report

metric count
observed 249
denied 4
lost 0

events by kind

kind count
exec 6
open 64
connect 2

denied samples (first 10)

kind detail
exec /usr/bin/whoami (/usr/bin/whoami)
connect 1.1.1.1:443
connect 8.8.8.8:443
📊 Open the full HTML report locally
rm -rf /tmp/coronarium-25631216621 && gh run download 25631216621 -R bokuweb/sakimori -n coronarium-report -D /tmp/coronarium-25631216621 && (open /tmp/coronarium-25631216621/coronarium-report.html 2>/dev/null || xdg-open /tmp/coronarium-25631216621/coronarium-report.html 2>/dev/null || echo "open file:///tmp/coronarium-25631216621/coronarium-report.html")

Requires the gh CLI. The command downloads the workflow artifact and opens the self-contained HTML report in your browser.

@bokuweb bokuweb merged commit 998a136 into main May 10, 2026
10 checks passed
bokuweb added a commit that referenced this pull request May 11, 2026
Closes the deferred follow-up from #48 (workspace tamper detection).
Standalone `sakimori workspace snapshot|diff` was always usable,
but you had to wrap your CI step manually:

    sakimori workspace snapshot \$WORKSPACE -o /tmp/before.json
    sakimori run -p policy.yml -- cargo test
    sakimori workspace diff /tmp/before.json \$WORKSPACE

That's now a single `sakimori run --snapshot-workspace \$WORKSPACE`:
- baseline snapshot taken before the eBPF supervisor starts (so we
  don't accidentally hash files the supervisor itself writes);
  baseline failure is fatal (silent drift would mask the whole point);
- post-run snapshot taken after the supervised command exits;
  failure here is a warn (we still want the audit log to go out);
- diff attached to ReportArgs.workspace_drift, surfaced as:
  - a `workspace_drift: { added, modified, removed }` block in the
    JSON log (only when non-empty — keeps clean runs uncluttered)
  - a "Workspace drift" section in the step summary with per-file
    rows (truncated at 25 per category with a remainder note)
  - a `| drift | N |` row in the totals table
- in `mode: block`, any drift makes the run exit non-zero with a
  GitHub Actions error annotation, the same way denied events do.
- `--snapshot-skip <NAME>` extends the built-in skip list (`.git`,
  `node_modules`, `target`, …) for repo-specific build artefacts.

Windows ETW supervisor leaves `workspace_drift: None` for now
— the schema accommodates it without a bump when added later.

4 new unit tests in `report::tests` cover the drift section
rendering (added/modified/removed, modification-note variants,
clean = no section, top-N truncation).

Signed-off-by: bokuweb <[email protected]>
Co-authored-by: Claude Opus 4.7 (1M context) <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant