Skip to content

feat: add signed run receipts with crabbox run --attest and crabbox verify#996

Merged
steipete merged 9 commits into
openclaw:mainfrom
yetval:feat/attest-receipts
Jul 9, 2026
Merged

feat: add signed run receipts with crabbox run --attest and crabbox verify#996
steipete merged 9 commits into
openclaw:mainfrom
yetval:feat/attest-receipts

Conversation

@yetval

@yetval yetval commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

Maintainer review update

Exact reviewed head: c28da3ce2718b03c6cdbceb0e698e1e12250e154, rebased onto current main.

  • Protected both the receipt and effective signing key from collisions with lease/proof/capture/download/timing outputs, including symlink, hard-link, dangling-parent, and filesystem case aliases.
  • Made first-use key minting atomic across concurrent processes; losing callers load the winning key instead of returning transient identities.
  • Rejected --attest-key without --attest and validated override keys before any remote execution.
  • Preserved delegated provider, lease, slug, run, and actions identity from session handles.
  • Made the trust boundary visible in every result with trust=self-signed; a PASS is meaningful only after matching the fingerprint through a trusted channel.
  • Added the maintainer-owned Unreleased changelog entry with contributor credit.

Exact-head verification: go test -race ./..., go vet ./..., go build -trimpath -o /tmp/crabbox-pr996-current ./cmd/crabbox, and node scripts/check-command-docs.mjs all pass. Four autoreview rounds addressed every accepted finding; the final review is clean with no actionable findings.

The maintainer exact-head proof below exercises the final trust marker and path hardening over real loopback SSH. The longer proof later in the body was captured by the contributor at head 7ec4e381 and covers tamper/schema rejection.

Summary

Crabbox is built around run evidence: proof blocks (--emit-proof), content-addressed capsule manifests, timing JSON, JUnit summaries, artifact manifests with SHA-256 digests. None of that evidence is cryptographically signed, and there is no command to check any of it after the fact. A reviewer or an agent overseer has to trust pasted output.

This change adds the minimal provider-neutral primitive on top of what runs already collect:

  • crabbox run --attest <path> writes receipt.json after a successful run: a flat JSON record of provider, lease, command, exit code, timing, and the SHA-256 of the combined stdout and stderr stream as observed by the client, signed with a per-user Ed25519 key.
  • crabbox verify <receipt.json> recomputes the canonical bytes, checks the signature, and prints PASS or FAIL with the signer public key fingerprint and trust=self-signed. Exit 0 on PASS, 1 on signature mismatch, 2 for receipts that cannot be checked at all.
  • The signing key is minted on first use at <user config dir>/crabbox/attest/id_ed25519.pem (PKCS8 PEM, 0600, same mint-once pattern as the per-lease SSH keys). --attest-key <path> signs with an existing PKCS8 PEM Ed25519 key and fails closed; it never creates a key at an override path.

Everything uses the Go standard library (crypto/ed25519, crypto/x509, encoding/pem); no new dependencies.

Design notes

  • The signature covers the canonical encoding of every receipt field except signature itself: the object minus signature is re-marshaled as compact JSON, which sorts map keys lexicographically. Because public_key is inside the signed payload, swapping in a different key also fails verification. Empty optional fields are omitted, never emitted as empty strings, so signer and verifier agree without per-field logic.
  • The output digest is a fresh SHA-256 writer teed into the stream fan-out after the capture wrapping, so it sees the same bytes the console does, including under --capture-stdout. It deliberately does not hash the internal log ring buffer, which truncates at 8 MiB. Because the stdout and stderr streams write from separate goroutines, the digest is wrapped in a mutex-guarded writer, matching the synchronized run log buffer those streams already share, so log_sha256 is always the hash of the bytes in arrival order and can never be corrupted by concurrent writes.
  • Both run paths write receipts: the direct SSH path includes run_id and log_sha256; the delegated path preserves available session identity fields but omits log_sha256 because a delegated RunResult carries only a bounded log excerpt, and hashing an excerpt would be misleading evidence. Dispatch stays by feature, not provider name, per docs/vision.md.
  • --attest mirrors --emit-proof: path-valued, success-only, rejected with --sync-only, and preflighted with the same local output path checks.
  • Hardening from the review rounds: verify rejects receipts whose JSON contains duplicate object keys anywhere (exit 2), because JSON parsers disagree on which duplicate wins and an edited receipt could show one value to a reader while the signature still verifies against another; beyond that, receipts are strictly schema validated before any signature decision: unknown fields, missing required fields, wrong types, non-integer number spellings, unsupported schema versions, invalid timestamps, and malformed digests are all rejected as malformed (exit 2), so PASS can only ever be printed for a well-formed, integrity-checked record. The receipt and effective signing-key paths are preflighted against every writable local output with filesystem-aware alias detection; --attest/--attest-key are forbidden in watch and shard forwarding, matching --emit-proof; missing parent directories use private modes; concurrent first use publishes exactly one stable key; and delegated receipts preserve every available session identity while omitting unavailable fields. The contributor changelog edit was dropped and a maintainer-owned entry was added during landing review.

Config and secret implications

  • New key file at <user config dir>/crabbox/attest/id_ed25519.pem, created 0600 in a 0700 directory through atomic no-replace publication. It is a signing identity, not a provider credential, and never leaves the machine.
  • No secrets on argv: --attest-key takes a file path, never key material.
  • No config schema changes and no coordinator or Worker changes.

Verification

  • gofmt -l $(git ls-files '*.go') clean
  • go vet ./... clean
  • go run golang.org/x/tools/cmd/deadcode@v0.45.0 -test ./... empty
  • go test -race ./... all packages pass
  • go build -trimpath -o bin/crabbox ./cmd/crabbox builds
  • node scripts/check-command-docs.mjs passes (54 command docs, includes the new verify page and crabbox verify --help exit 0)
  • scripts/check-go-coverage.sh 90.0 passes
  • New regression tests in internal/cli/attest_test.go: receipt round trip through the real verify command dispatch, empty-optional-field omission, a tamper table (mutated exit code, mutated command, foreign public key, corrupt signature encoding, deleted signature, duplicate exit_code key with the signed value last, duplicate command key with the signed value last, truncated JSON, missing file) asserting exit codes 1 vs 2, receipt path collision preflights against capture and download destinations, key mint idempotence with 0600/0700 permission checks, and --attest-key override semantics including fail-closed on a missing path and rejection of non-Ed25519 keys. watch and shard forbidden-flag suites cover --attest and --attest-key rejection. Digest-writer regressions run under the race detector: concurrent mixed stdout/stderr writes through the shared digest produce the exact expected hash with no data race (an unsynchronized hash in the same shape fails -race immediately), and a sequential mixed-stream case pins arrival-order hashing. Schema-validation regressions cover unsupported schema versions, unknown fields, decimal integer spellings, and invalid digests; a nested --attest path regression checks created directory modes; and a delegated-receipt regression covers a provider returning successful run metadata with no LeaseID, asserting the empty fields are omitted and the receipt still verifies.

Exact-head real behavior proof

Built and exercised c28da3ce2718b03c6cdbceb0e698e1e12250e154 with an isolated home directory, the static ssh provider explicitly pinned, a real local sshd, and temporary work/output paths. Temporary paths and the local username are redacted below; no cloud lease was created.

$ git rev-parse HEAD
c28da3ce2718b03c6cdbceb0e698e1e12250e154

$ HOME=<isolated-home> /tmp/crabbox-pr996-current run --provider ssh --target macos \
    --static-host 127.0.0.1 --static-user <local-user> \
    --static-work-root <temporary-work-root> --no-sync \
    --attest <temporary-output>/receipt.json -- \
    /bin/sh -c 'printf "attest current-head\n"; uname -s'
using static target lease=static_127-0-0-1 slug=127-0-0-1 target=macos host=127.0.0.1 keep=false
running on 127.0.0.1 /bin/sh -c 'printf "attest current-head\n"; uname -s'
attest current-head
Darwin
artifact kind=receipt path=<temporary-output>/receipt.json bytes=520
lease cleanup stopped=true policy=auto lease=static_127-0-0-1 slug=127-0-0-1

$ HOME=<isolated-home> /tmp/crabbox-pr996-current verify <temporary-output>/receipt.json
PASS <temporary-output>/receipt.json signer=sha256:4ab06eb561fa17ba12b50bbe71d4c4d3a8abc4eb35b171e7c4263ef5be4a0b1c trust=self-signed

$ printf 'attest current-head\nDarwin\n' | shasum -a 256
0942612941e6d955b36963ac8de357e867e947d0db57e6c1f19070472a66aa52  -
$ jq -r '.log_sha256, .provider, .exit_code' <temporary-output>/receipt.json
sha256:0942612941e6d955b36963ac8de357e867e947d0db57e6c1f19070472a66aa52
ssh
0

$ HOME=<isolated-home> /tmp/crabbox-pr996-current run ... \
    --attest '<isolated-home>/Library/Application Support/crabbox/attest/id_ed25519.pem' -- /usr/bin/true
attest receipt and attest key paths must be different
exit status: 2

The collision attempt exits during local preflight: there is no target acquisition or remote command output. The independently computed hash exactly matches the receipt, and the verifier makes the self-signed trust boundary explicit.

Contributor real behavior proof

Behavior addressed: run evidence in Crabbox cannot be verified after the fact; there is no signed receipt and no verify command, so any pasted run result is trust-me.
Real environment tested: the real crabbox binary built from pristine main d2ad413 and from this branch at head 7ec4e38, driving a real crabbox run over loopback SSH with the static ssh provider (real sshd, real sync, real remote command execution) on macOS; nothing simulated.
Exact steps or command run after this patch: crabbox run --static-host 127.0.0.1 --static-user yuvald --attest receipts/nested/receipt.json -- /bin/sh -c 'echo attest demo; uname -s' (a nested receipt path whose directories do not exist yet), then crabbox verify on the untouched receipt, after a one-field tamper, after a duplicate-key tamper that keeps the signed value last, and after injecting an unknown field.
Evidence after fix at contributor head 7ec4e381 (current output additionally includes trust=self-signed):

# BEFORE (pristine main d2ad413b101148400232c4f048da1227f9541ce0)
$ crabbox verify receipt.json
unexpected argument verify
exit status: 2
$ crabbox run --help | grep -E "^  -attest"
(no output; exit status 1: no such flags)

# AFTER (this branch, real run over loopback SSH with the static ssh provider)
$ crabbox run --static-host 127.0.0.1 --static-user yuvald --attest receipts/nested/receipt.json -- /bin/sh -c 'echo attest demo; uname -s'
running on 127.0.0.1 /bin/sh -c 'echo attest demo; uname -s'
attest demo
Darwin
artifact kind=receipt path=receipts/nested/receipt.json bytes=503
$ ls -la receipts/nested/receipt.json   (parent directories created by --attest)
-rw-------@ receipts/nested/receipt.json
$ cat receipts/nested/receipt.json
{
  "command": "/bin/sh -c 'echo attest demo; uname -s'",
  "command_ms": 640,
  "exit_code": 0,
  "generated_at": "2026-07-07T00:01:44Z",
  "lease_id": "static_127-0-0-1",
  "log_sha256": "sha256:32470cf9166f8353de6d7ffebf19d66a16ab9a0d34c2cad840ac30dda4865c21",
  "provider": "ssh",
  "public_key": "npCheZsJNUrNHucD7VmntRwVYel4vpiuprF6uRn6T9g=",
  "schema_version": 1,
  "signature": "fsbj5E/O1yi4H1QhxyKDzeKCsvq8Nj7oVLtOHeVhNHctaf2fKBfidiOJVvq3SSKJgrKacUkuKQeAK8YL/nhABA==",
  "slug": "127-0-0-1"
}
$ crabbox verify receipts/nested/receipt.json
PASS receipts/nested/receipt.json signer=sha256:c30e2ae902253e3191a54aa8b78da9170e9a3630820c0b97c6142f475fe81295
exit status: 0
$ printf 'attest demo
Darwin
' | shasum -a 256
32470cf9166f8353de6d7ffebf19d66a16ab9a0d34c2cad840ac30dda4865c21  -
$ sed -i '' 's/"exit_code": 0/"exit_code": 1/' receipts/nested/receipt.json && crabbox verify receipts/nested/receipt.json
FAIL receipts/nested/receipt.json signer=sha256:c30e2ae902253e3191a54aa8b78da9170e9a3630820c0b97c6142f475fe81295: signature mismatch
exit status: 1
$ duplicate exit_code key inserted with the signed value last, then crabbox verify
malformed receipt: duplicate key
exit status: 2
$ unknown field review_note injected, then crabbox verify
malformed receipt: unknown field "review_note"
exit status: 2

Observed result after fix: a real run over loopback SSH created the missing receipt directories with private modes and produced a signed receipt whose output digest independently matches the observed live output; verifying the untouched receipt prints PASS with the signer fingerprint and exit 0, flipping a single field flips the same command to FAIL signature mismatch with exit 1, and both a duplicate-key edit that keeps the signed value last and an injected unknown field are rejected outright as malformed with exit 2 instead of printing PASS, where pristine main rejects the verify command entirely and has no attest flags.
What was not tested: no delegated-provider live run (the delegated receipt path is exercised in the Go suite only); no Windows host; key rotation and multi-user trust decisions are out of scope for v1.

Not in this change (deliberate v2 boundary)

A v1 receipt is client-side, self-signed evidence: it proves the record is unmodified since signing and was signed by the embedded key, whose fingerprint verify prints for out-of-band comparison. It does not prove who holds that key (no trust roots or identity binding), that the run occurred as described (the signer is the party that ran it), or that a receipt was not withheld and regenerated (no transparency log). Coordinator key custody, DSSE or in-toto envelope formats, trust roots, and a remote receipt registry are deliberately out of scope for this slice.

@clawsweeper

clawsweeper Bot commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

Codex review: needs real behavior proof before merge. Reviewed July 9, 2026, 2:12 AM ET / 06:12 UTC.

Summary
The branch adds crabbox run --attest/--attest-key, a crabbox verify command, receipt signing and validation code, docs, wrapper exclusions, local path/key hardening, and Go regression tests.

Reproducibility: not applicable. as a bug reproduction; this is a feature PR. The capability can be exercised with crabbox run --attest and crabbox verify, but the posted real-run proof should match the current head before merge.

Review metrics: 3 noteworthy metrics.

  • CLI surface added: 1 command, 2 run flags. The PR creates new user-facing signing and verification semantics that need maintainer acceptance.
  • Patch scope: 16 files, +1662/-13. The diff spans CLI integration, crypto receipt code, docs, wrapper exclusions, and regression tests.
  • Proof freshness: 3 commits after live proof head. The real loopback-SSH proof names 7ec4e38, while the reviewed head is c28da3c.

Merge readiness
Overall: 🦐 gold shrimp
Proof: 🦐 gold shrimp
Patch quality: 🦞 diamond lobster
Result: blocked until stronger real behavior proof is added.

Overall follows the weaker of proof and patch quality, so missing proof can cap an otherwise strong patch.

Rank-up moves:

  • Refresh terminal proof against current head c28da3c, including the trust=self-signed verify output and redacting private paths, keys, IPs, and non-public endpoints.
  • [P1] Get maintainer acceptance for the documented self-signed integrity-only v1 boundary before merge.

Proof guidance:

  • [P1] Needs stronger real behavior proof before merge: The PR body includes strong terminal proof for 7ec4e38, but current head c28da3c changes visible verifier output and receipt/path hardening, so the proof should be refreshed against the exact reviewed head with private details redacted. After adding proof, update the PR body; ClawSweeper should re-review automatically. If it does not, the PR author or someone with repository write access can comment @clawsweeper re-review.

Risk before merge

  • [P1] The posted real loopback-SSH proof is from 7ec4e38, while current head c28da3c changes visible verifier output and hardens receipt/key/path handling, so current-head real behavior has not been shown in the PR proof block.
  • [P1] The new verify command can be overread as identity or execution provenance even though v1 is documented as self-signed integrity evidence only.
  • [P1] Delegated-provider receipt behavior is covered by Go tests but not by a live delegated-provider run in the PR proof.

Maintainer options:

  1. Accept self-signed v1 after current-head proof (recommended)
    Require a refreshed terminal proof at c28da3c and then merge if maintainers accept the documented integrity-only PASS semantics.
  2. Pause for a stronger trust design
    Hold or close this PR if maintainers want identity binding, coordinator custody, transparency, or DSSE/in-toto before adding a verifier.

Next step before merge

  • [P1] Manual review is needed because automation cannot decide the self-signed receipt security boundary or supply current-head real behavior proof for the contributor's environment.

Maintainer decision needed

  • Question: Should Crabbox ship crabbox verify as an opt-in v1 feature that validates self-signed client-side receipts for integrity only, without identity binding, execution provenance, trust roots, or transparency logging?
  • Rationale: The implementation is narrow and documented, but the permanent meaning of a PASS result is a security/product boundary that automation should not decide.
  • Likely owner: steipete — This owner is the strongest history match for run evidence, release/security-sensitive CLI behavior, and the latest receipt hardening commits.
  • Options:
    • Land documented self-signed v1 (recommended): Merge after current-head proof if maintainers accept receipts as tamper evidence that require out-of-band fingerprint trust.
    • Hold for stronger provenance: Pause this PR until maintainers choose coordinator signing, trust roots, DSSE/in-toto, or a receipt registry before exposing a verifier.

Security
Cleared: No concrete code-level or supply-chain defect was found; the self-signed receipt semantics remain a maintainer security-boundary decision.

Review details

Best possible solution:

Land only after maintainers accept the documented self-signed integrity-only v1 and current-head terminal proof shows crabbox run --attest plus crabbox verify with trust=self-signed; otherwise hold for a stronger trust-root or coordinator-backed design.

Do we have a high-confidence way to reproduce the issue?

Not applicable as a bug reproduction; this is a feature PR. The capability can be exercised with crabbox run --attest and crabbox verify, but the posted real-run proof should match the current head before merge.

Is this the best way to solve the issue?

Unclear until maintainer security intent is confirmed. The implementation is provider-neutral and well covered by tests, but the self-signed verifier semantics are a product/security boundary.

AGENTS.md: found and applied where relevant.

Codex review notes: model internal, reasoning high; reviewed against 5d9080e21b15.

Label changes

Label changes:

  • add rating: 🦐 gold shrimp: Overall readiness is 🦐 gold shrimp; proof is 🦐 gold shrimp and patch quality is 🦞 diamond lobster.
  • add status: 📣 needs proof: The PR needs real behavior proof before ClawSweeper can clear the contributor ask. Needs stronger real behavior proof before merge: The PR body includes strong terminal proof for 7ec4e38, but current head c28da3c changes visible verifier output and receipt/path hardening, so the proof should be refreshed against the exact reviewed head with private details redacted. After adding proof, update the PR body; ClawSweeper should re-review automatically. If it does not, the PR author or someone with repository write access can comment @clawsweeper re-review.
  • remove proof: sufficient: Current real behavior proof status is insufficient, not sufficient.
  • remove rating: 🐚 platinum hermit: Current PR rating is rating: 🦐 gold shrimp, so this older rating label is no longer current.
  • remove status: 👀 ready for maintainer look: Current PR status label is status: 📣 needs proof.

Label justifications:

  • P2: This is a normal-priority opt-in CLI feature with limited immediate blast radius, not a regression or emergency.
  • merge-risk: 🚨 security-boundary: Merging creates a user-facing cryptographic verification primitive whose PASS semantics are intentionally self-signed and integrity-only.
  • rating: 🦐 gold shrimp: Overall readiness is 🦐 gold shrimp; proof is 🦐 gold shrimp and patch quality is 🦞 diamond lobster.
  • status: 📣 needs proof: The PR needs real behavior proof before ClawSweeper can clear the contributor ask. Needs stronger real behavior proof before merge: The PR body includes strong terminal proof for 7ec4e38, but current head c28da3c changes visible verifier output and receipt/path hardening, so the proof should be refreshed against the exact reviewed head with private details redacted. After adding proof, update the PR body; ClawSweeper should re-review automatically. If it does not, the PR author or someone with repository write access can comment @clawsweeper re-review.
Evidence reviewed

What I checked:

  • Repository policy read: AGENTS.md was read fully; its provider-neutral CLI, release-owned changelog, and secret/config handling guidance applies to this PR. (AGENTS.md:1, 5d9080e21b15)
  • Current main lacks the feature: Current main has no verify command or --attest run flag; exact grep hits for those new strings returned no matches in the central CLI/docs files. (internal/cli/app.go:176, 5d9080e21b15)
  • Latest release lacks the feature: The latest release tag v0.36.0 only has unrelated verify wording, not the new receipt verifier or attest flags. (internal/cli/app.go:176, 4ea85a4f1b7e)
  • Receipt implementation: PR head adds Ed25519 key minting/loading, duplicate-key rejection, strict schema validation, canonical signing bytes, and PASS/FAIL verification output with trust=self-signed. (internal/cli/attest.go:50, c28da3ce2718)
  • Run integration: PR head wires --attest and --attest-key into run preflight and writes receipts on the direct SSH success path with provider, lease, run id, command, timing, actions URL, and output digest metadata. (internal/cli/run.go:235, c28da3ce2718)
  • Regression coverage: The new tests cover receipt round trip, tamper cases, duplicate keys, schema rejection, digest concurrency, delegated receipts, nested paths, key overrides, and path collision protections. (internal/cli/attest_test.go:65, c28da3ce2718)

Likely related people:

  • steipete: Current-main blame/log history points to Peter Steinberger for the central run/proof-output surfaces, and the latest receipt hardening commits on this PR are also from this handle. (role: recent area contributor and likely security-boundary decision owner; confidence: high; commits: 4ea85a4f1b7e, c9ae5bb8f519, 2f9df302691e; files: internal/cli/run.go, internal/cli/run_download.go, internal/cli/run_local_output.go)
  • coygeek: Recent current-main history includes delegated/direct provider work touching run-related provider behavior, making this a secondary routing candidate for provider-run semantics. (role: adjacent provider/run contributor; confidence: medium; commits: b2d058605239, 878b370a5526, 89019eb7c50f; files: internal/cli/run.go, internal/cli/provider_backend.go)
What the crustacean ranks mean
  • 🦀 challenger crab: rare, exceptional readiness with strong proof, clean implementation, and convincing validation.
  • 🦞 diamond lobster: very strong readiness with only minor maintainer review expected.
  • 🐚 platinum hermit: good normal PR, likely mergeable with ordinary maintainer review.
  • 🦐 gold shrimp: useful signal, but proof or patch confidence is still limited.
  • 🦪 silver shellfish: thin signal; proof, validation, or implementation needs work.
  • 🧂 unranked krab: not merge-ready because proof is missing/unusable or there are serious correctness or safety concerns.
  • 🌊 off-meta tidepool: rating does not apply to this item.

Shiny media proof means a screenshot, video, or linked artifact directly shows the changed behavior. Runtime, network, CSP, and security claims still need visible diagnostics.

How this review workflow works
  • ClawSweeper keeps one durable marker-backed review comment per issue or PR.
  • Re-runs edit this comment so the latest verdict, findings, and automation markers stay together instead of adding duplicate bot comments.
  • A fresh review can be triggered by eligible @clawsweeper re-review comments, exact-item GitHub events, scheduled/background review runs, or manual workflow dispatch.
  • PR/issue authors and users with repository write access can comment @clawsweeper re-review or @clawsweeper re-run on an open PR or issue to request a fresh review only.
  • Maintainers can also comment @clawsweeper review to request a fresh review only.
  • Fresh-review commands do not start repair, autofix, rebase, CI repair, or automerge.
  • Maintainer-only repair and merge flows require explicit commands such as @clawsweeper autofix, @clawsweeper automerge, @clawsweeper fix ci, or @clawsweeper address review.
  • Maintainers can comment @clawsweeper explain to ask for more context, or @clawsweeper stop to stop active automation.
Review history (9 earlier review cycles; latest 8 shown)
  • reviewed 2026-07-06T22:01:47.236Z sha 83070b1 :: needs changes before merge. :: [P1] Serialize the receipt digest writer
  • reviewed 2026-07-06T22:20:41.109Z sha b7ada56 :: needs maintainer review before merge. :: none
  • reviewed 2026-07-06T22:32:25.288Z sha b7ada56 :: needs maintainer review before merge. :: none
  • reviewed 2026-07-06T22:47:45.090Z sha 2d7f4da :: needs changes before merge. :: [P2] Create receipt directories before writing
  • reviewed 2026-07-06T23:52:56.640Z sha d6923b4 :: needs changes before merge. :: [P2] Fallback delegated receipt metadata before signing
  • reviewed 2026-07-07T00:08:02.805Z sha 7ec4e38 :: needs maintainer review before merge. :: none
  • reviewed 2026-07-07T00:13:47.446Z sha 7ec4e38 :: needs maintainer review before merge. :: none
  • reviewed 2026-07-09T06:05:59.088Z sha c28da3c :: needs real behavior proof before merge. :: [P3] Remove the release-owned changelog entry

@clawsweeper clawsweeper Bot added proof: sufficient Contributor real behavior proof is sufficient. rating: 🧂 unranked krab Not merge-ready due to missing proof or serious correctness/safety concerns. status: ⏳ waiting on author ClawSweeper has contributor-facing work open and is waiting for author action. P2 Normal priority bug or improvement with limited blast radius. merge-risk: 🚨 security-boundary 🚨 Merging this PR could weaken sandboxing, authorization, credentials, or sensitive data. labels Jul 6, 2026
@yetval yetval force-pushed the feat/attest-receipts branch from d7eea6b to 83070b1 Compare July 6, 2026 21:50
@yetval

yetval commented Jul 6, 2026

Copy link
Copy Markdown
Contributor Author

All four review items are addressed in 83070b1d and the PR body proof is refreshed against that head.

  • [P1] Duplicate keys: verify now rejects receipts whose JSON contains duplicate object keys at any depth, before any signature decision, with malformed receipt: duplicate key and exit 2. The tamper table gained two regression cases where the signed value stays last (duplicate exit_code, duplicate command), and the live proof in the body shows the same edit against a real receipt now exiting 2 instead of printing PASS.
  • [P2] Output collisions: --attest is preflighted against --lease-output, --emit-proof, --capture-stdout, --capture-stderr, and every --download destination via a shared labeled collision check that --emit-proof now also uses. Covered by a new collision regression test.
  • [P2] Wrapper forwarding: --attest and --attest-key are forbidden in watch and shard forwarding, matching --emit-proof, with rows added to both existing forbidden-flag suites.
  • The CHANGELOG edit is removed; that file is byte-identical to the branch point.

The v1 trust boundary question (self-signed, no trust roots, no transparency log) is stated in the body as a deliberate v2 boundary and stays with maintainers.

@yetval

yetval commented Jul 6, 2026

Copy link
Copy Markdown
Contributor Author

@clawsweeper re-run

@clawsweeper

clawsweeper Bot commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

🦞🧹
ClawSweeper re-review requested.

I asked ClawSweeper to review this item again.
Action: item re-review queued (workflow sweep.yml, event repository_dispatch).
Result: the existing ClawSweeper review comment will be edited in place when the review finishes.

@clawsweeper clawsweeper Bot added proof: sufficient Contributor real behavior proof is sufficient. rating: 🧂 unranked krab Not merge-ready due to missing proof or serious correctness/safety concerns. status: ⏳ waiting on author ClawSweeper has contributor-facing work open and is waiting for author action. merge-risk: 🚨 security-boundary 🚨 Merging this PR could weaken sandboxing, authorization, credentials, or sensitive data. and removed rating: 🧂 unranked krab Not merge-ready due to missing proof or serious correctness/safety concerns. merge-risk: 🚨 security-boundary 🚨 Merging this PR could weaken sandboxing, authorization, credentials, or sensitive data. proof: sufficient Contributor real behavior proof is sufficient. status: ⏳ waiting on author ClawSweeper has contributor-facing work open and is waiting for author action. labels Jul 6, 2026
@yetval

yetval commented Jul 6, 2026

Copy link
Copy Markdown
Contributor Author

The digest race is fixed in b7ada56f and the body proof is refreshed against that head.

  • [P1] Serialization: the receipt digest is now a mutex-guarded writer shared by the stdout and stderr fan-outs, the same synchronization the run log buffer those streams already share uses. log_sha256 is defined as the hash of the combined stream bytes in arrival order.
  • Regression coverage runs under the race detector: one case drives concurrent mixed stdout/stderr writes through the shared digest and asserts the exact expected hash, and a sequential mixed-stream case pins arrival-order hashing. As validation of the finding, the previous shape (a bare shared hash behind both MultiWriters) fails go test -race immediately with WARNING: DATA RACE.
  • Maintainer confirmation of the self-signed v1 boundary remains open, as documented in the body and the verify command page.

@yetval

yetval commented Jul 6, 2026

Copy link
Copy Markdown
Contributor Author

@clawsweeper re-run

@clawsweeper

clawsweeper Bot commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

🦞🧹
ClawSweeper re-review requested.

I asked ClawSweeper to review this item again.
Action: item re-review queued (workflow sweep.yml, event repository_dispatch).
Result: the existing ClawSweeper review comment will be edited in place when the review finishes.

@clawsweeper clawsweeper Bot added rating: 🐚 platinum hermit Good normal PR readiness with ordinary maintainer review expected. status: 👀 ready for maintainer look ClawSweeper has no concrete contributor-facing blocker left for this PR. rating: 🦐 gold shrimp Decent PR readiness signal, but merge confidence is limited. status: ⏳ waiting on author ClawSweeper has contributor-facing work open and is waiting for author action. and removed rating: 🧂 unranked krab Not merge-ready due to missing proof or serious correctness/safety concerns. status: ⏳ waiting on author ClawSweeper has contributor-facing work open and is waiting for author action. rating: 🐚 platinum hermit Good normal PR readiness with ordinary maintainer review expected. status: 👀 ready for maintainer look ClawSweeper has no concrete contributor-facing blocker left for this PR. labels Jul 6, 2026
@yetval

yetval commented Jul 7, 2026

Copy link
Copy Markdown
Contributor Author

@clawsweeper re-review

Both remaining findings are addressed and the body proof is regenerated against head 7ec4e38.

  • Nested receipt paths: writeRunReceipt now creates missing parent directories with the same private mode proof output uses, before the atomic write. The live proof drives --attest receipts/nested/receipt.json end to end and shows the created 0700/0600 modes; a regression covers the nested path and directory mode.
  • Delegated receipts without a LeaseID: lease_id is now treated like the other run-dependent fields (omitted when a delegated provider reports none, validated only when present), and the delegated block builds receipts through a writeDelegatedRunReceipt helper mirroring writeDelegatedRunProof. A regression covers a delegated provider returning successful run metadata with no LeaseID and asserts the receipt round trips to PASS with the empty fields omitted.

Also folded in since the last review: strict receipt schema validation before any signature decision (unknown fields, missing required fields, wrong types, non-integer spellings, unsupported schema versions, invalid timestamps and digests all exit 2), with the unknown-field rejection shown live in the proof block.

@clawsweeper

clawsweeper Bot commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

🦞🧹
ClawSweeper re-review requested.

I asked ClawSweeper to review this item again.
Action: item re-review queued (workflow sweep.yml, event repository_dispatch).
Result: the existing ClawSweeper review comment will be edited in place when the review finishes.

@clawsweeper clawsweeper Bot added rating: 🐚 platinum hermit Good normal PR readiness with ordinary maintainer review expected. status: 👀 ready for maintainer look ClawSweeper has no concrete contributor-facing blocker left for this PR. and removed rating: 🦐 gold shrimp Decent PR readiness signal, but merge confidence is limited. status: ⏳ waiting on author ClawSweeper has contributor-facing work open and is waiting for author action. labels Jul 7, 2026
yetval and others added 9 commits July 9, 2026 06:19
…erify

crabbox run --attest <path> writes an Ed25519 signed receipt of a
successful run: provider, lease, command, exit code, timing, and the
SHA-256 of the combined live output stream, in canonical JSON. The new
crabbox verify command recomputes the canonical bytes, checks the
signature, and prints PASS or FAIL with the signer key fingerprint.
The signing key is minted per user on first use under the user config
dir; --attest-key signs with an existing PKCS8 PEM ed25519 key and
fails closed. Receipts stay provider neutral in core: both the direct
SSH run path and the delegated run path write them, with output digests
only where core observes the full stream.
Reject receipts whose JSON contains duplicate object keys before
signature verification, since parsers disagree on which duplicate wins
and an edited receipt could show one value while the signature checks
another. Preflight the --attest path against lease output, emit proof,
capture stdout and stderr, and download destinations. Forbid --attest
and --attest-key in watch and shard forwarding, matching --emit-proof.
Drop the CHANGELOG edit; release notes are release owned.
The receipt digest hash was shared by the stdout and stderr writers,
which write from separate goroutines during a run, so concurrent writes
could race and corrupt log_sha256 before signing. Wrap the hash in a
mutex-guarded writer, matching the synchronized run log buffer those
streams already share, and cover mixed concurrent stream writes plus
arrival-order hashing with regressions that run under the race
detector.
Writing a receipt to a nested path failed because the private output
writer expects the parent directory to exist. Create the receipt
directory with the same private mode used by proof output before
writing, matching writeRunProof, and cover a nested --attest path with
a regression that also checks the created directory mode.
Delegated providers can return successful run metadata without a
LeaseID, but receipts wrote lease_id unconditionally and verification
required it as a non-empty string, so such a receipt could never
verify. Treat lease_id like the other run-dependent fields: omit it
when empty, validate it only when present, and build delegated receipts
through a dedicated helper mirroring the delegated proof writer. Add a
regression covering a delegated result with no lease id that round
trips to a passing verification with the empty fields omitted.
@steipete steipete force-pushed the feat/attest-receipts branch from 7ec4e38 to c28da3c Compare July 9, 2026 05:58
@steipete

steipete commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

@clawsweeper re-review

@clawsweeper

clawsweeper Bot commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

🦞🧹
ClawSweeper re-review requested.

I asked ClawSweeper to review this item again.
Action: item re-review queued (workflow sweep.yml, event repository_dispatch).
Result: the existing ClawSweeper review comment will be edited in place when the review finishes.

@clawsweeper clawsweeper Bot added rating: 🦐 gold shrimp Decent PR readiness signal, but merge confidence is limited. status: 📣 needs proof The PR needs real behavior proof before ClawSweeper can clear the contributor ask. and removed proof: sufficient Contributor real behavior proof is sufficient. rating: 🐚 platinum hermit Good normal PR readiness with ordinary maintainer review expected. status: 👀 ready for maintainer look ClawSweeper has no concrete contributor-facing blocker left for this PR. labels Jul 9, 2026
@steipete

steipete commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

@clawsweeper re-review

Exact current-head behavior proof is now in the PR body for c28da3ce2718b03c6cdbceb0e698e1e12250e154: real loopback SSH execution, independent output-digest match, trust=self-signed verification, and receipt/signing-key collision rejection before target acquisition. The maintainer review also remains clean after four autoreview rounds.

@steipete

steipete commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

Maintainer decision: accept the documented opt-in, self-signed v1 boundary as integrity evidence only. PASS is not identity binding or independent execution provenance; callers must establish the signer fingerprint through a trusted channel. Exact-head live proof, full CI, and maintainer review are complete, so this is ready to land.

@steipete steipete merged commit 2a01207 into openclaw:main Jul 9, 2026
14 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

merge-risk: 🚨 security-boundary 🚨 Merging this PR could weaken sandboxing, authorization, credentials, or sensitive data. P2 Normal priority bug or improvement with limited blast radius. rating: 🦐 gold shrimp Decent PR readiness signal, but merge confidence is limited. status: 📣 needs proof The PR needs real behavior proof before ClawSweeper can clear the contributor ask.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants