workspace: tamper detection via snapshot/diff#48
Merged
Conversation
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]>
coronarium report
events by kind
denied samples (first 10)
📊 Open the full HTML report locallyrm -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 |
4 tasks
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]>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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
postinstallrewriting source files or.git/config.Design notes
size as a fallback for oversized-skip files.
.git,node_modules,target,dist,build,vendor,__pycache__,.venv,venv,.next,.turbo,.cache). Deliberately doesn'thonour
.gitignore— an attacker can write into it.--skipextends the list for your own artefacts.
fires on retarget; the linked file is hashed independently if
it's also under the root).
Diff still fires on size delta but a same-size content swap is
a known miss — bump
--max-file-bytesor pass 0 for unlimitedif that matters.
+/~/-prefixes) or--format json.Exits non-zero on any drift;
--allow-driftsuppresses foraudit-only steps.
Integration into
coronarium run(snapshot before / diff afterexec) is intentionally deferred — the standalone form composes
with any build, not just an eBPF-supervised one.
Test plan
cargo fmt --all -- --checkcargo clippy --workspace --all-targets -- -D warningscargo test --workspace— 11 new unit tests incoronarium-core::tamper(118 total in core)+c ~a -band exits 1🤖 Generated with Claude Code