Repository navigation
Conversation
Kusari Analysis Results:
Both analyses recommend PROCEED. The code analysis found no code issues, exposed secrets, or workflow issues. The dependency findings are limited to test fixture requirements.txt files, which lowers urgency. The only new package, darnit-amber 0.1.1, is the project's own package, though it has no scorecard data. Numpy 1.26.4 is flagged end of life but has no advisories. The unpinned numpy in tests/darnit_amber/fixtures/compound_repo/requirements.txt lists 8 older advisories (e.g. CVE-2019-6446, CVE-2021-41495, CVE-2017-12852). If the unpinned entry is not deliberate, pin it with pip install numpy==1.26.4. Click 8.1.7 has one advisory, CVE-2026-7246, which requires local access, high privileges and user interaction (EPSS 0.009). It has NO_FIX available, so it is unactionable and does not change the recommendation. No KEV entries, blocked packages, or license concerns were found. Combined, the risk profile is low and the PR is safe to merge. Note View full detailed analysis result for more information on the output and the checks that were run.
Found this helpful? Give it a 👍 or 👎 reaction! |
|
@kusari-inspector feedback The flagged pins (requests 2.31.0, numpy 1.0) are test fixtures for the requirements pin classifier; they are old on purpose and are never installed. They come from the #532 renamnot from this PR's commits. |
|
Thank you for your feedback! 📝 Your message has been submitted for review. |
|
Kusari PR Analysis rerun based on - 079fdf8 performed at: 2026-10-28T14:59:58Z - link to updated analysis |
|
Kusari PR Analysis rerun based on - e20bebb performed at: 2026-10-28T15:06:34Z - link to updated analysis |
|
Kusari PR Analysis rerun based on - 3690844 performed at: 2026-10-30T15:29:10Z - link to updated analysis |
|
Kusari PR Analysis rerun based on - b18a2d9 performed at: 2026-10-30T15:46:47Z - link to updated analysis |
|
Kusari PR Analysis rerun based on - 073b9cc performed at: 2026-10-30T15:48:26Z - link to updated analysis |
|
Kusari PR Analysis rerun based on - 43d3e9e performed at: 2026-10-30T15:56:11Z - link to updated analysis |
43d3e9e to
bf56017
Compare
|
Kusari PR Analysis rerun based on - bf56017 performed at: 2026-10-30T17:13:31Z - link to updated analysis |
012a659 to
ad3fa54
Compare
mlieberman85
left a comment
There was a problem hiding this comment.
Requesting changes. The framework/implementation split holds, and the tests never trigger a Sigstore login. But verify reports verified: true for content nothing signs, and I got false REPRODUCED results several ways. I know this is a draft PR, so take this as early feedback. I used a local key with --no-timestamp, with no keyless signing, TSA or Rekor. Items that use the PR's own fixtures verify them with --offline. Where I patched code or injected a test root to reach a path, the item says so.
Blocking
B1. verified: true covers the Witness envelopes, not the predicate.
verify (rerun.py:181-240) checks the envelopes and their timestamps, and binds the id, the command, the signer fingerprint and the output digests. It never checks claims, verdict, audit.results, environment, entry, signing.identity, or the subject's name and commit against anything signed. The bundle id is content-addressed, but anyone can compute a new id over edited content that reuses the researcher's envelopes. On the PR's own fixtures I added a REPRODUCED claim, set verdict: REPRODUCED, set every audit result to PASS and recomputed the id:
- key fixture:
verify <new id> --pubkey tests/darnit_amber/fixtures/published-signing.pub --offlinegave exit 0,verified: true. - identity fixture, with
signing.identityalso rewritten to another address: exit 0,verified: true,identity_verified: true. The forged address is whatsigning.identityshows, while the certificate subject beside it is still the real one, and nothing compares them.
claim writes derived bundles nobody signs, and verify treats them the same way. Fix: sign the amber/run/v1 statement itself (DSSE, same signer, timestamped; claim signs its derived statement as the claimer). Or have verify recompute every derived field from signed inputs and report anything it can't recompute as unverified.
B2. Claims are measured from files the experiment didn't produce.
measure_claim (run.py:229-260, :474-475) reads repo / measure.file from the checkout whether or not Witness recorded it as a product. A repo with a committed results/table.txt (speedup: 2.4), whose experiment only wrote logs/run.log ("benchmark binary not found; skipping"), got table3-speedup: measured 2.4, REPRODUCED and overall REPRODUCED. That is exactly the case of artifacts that ship precomputed results. Fix: measure only captured products, from the captured bytes, as claim.py does.
B3. Non-numeric comparisons.
_coerce (run.py:222-226) falls back to strings. Measured 9 ms against > 10 gave REPRODUCED ("measured 9 ms > 10"). >= 10 crashed with TypeError: unsupported operand type(s) for -: 'str' and 'int' after both Witness steps had run, and nothing was stored. < and > also ignore tolerance. Fix: ordering operators require two numbers, otherwise NOT_COMPARABLE with a reason, and measure_claim never raises.
B4. Stored outputs are never checked against their digest, so a false claim needs no JSON editing.
claim.py:33-34 measures from read_blob without comparing it to out["sha256"], and verify never hashes stored blobs. On a copy of the PR's key fixture, whose verdict is NOT_COMPARABLE, I wrote score: 99 to <store>/<id>/outputs/result.txt and ran claim <id> --id x --file result.txt --regex 'score: ([0-9]+)' --threshold '>= 50'. It gave REPRODUCED, and verify on the new id with the published key gave exit 0. With the S3 store, anyone with bucket write access can do this. Fix: hash on every read.
B5. Secrets go into the bundle.
run.py:324 passes the full environment, minus SIGSTORE_ID_TOKEN, to Witness. Witness masked AWS_SECRET_ACCESS_KEY, GITHUB_TOKEN, ANTHROPIC_API_KEY and MY_DB_PASSWORD, but stored a fake RESEARCH_SERVICE_CREDENTIAL verbatim in both envelopes, along with hostname, username, HOME and PWD. Masking also doesn't reach output: an experiment that echoes $MY_DB_PASSWORD stores the value verbatim in the command-run attestation, while the environment attestor masks it. A clone URL with a token in it goes verbatim into subject.name and entry.artifact.clone (run with a fake token through git config url.<path>.insteadOf). #700 lists the environment, hostname and user as non-blocking, but bundles exist to be shared, so I'd block on it. Fix: strip userinfo from URLs, and give the environment attestor an allowlist, since a blocklist misses names it doesn't know. Witness passes its environment to the child, so don't narrow what the experiment itself sees. Document that stdout and stderr are recorded.
B6. A test writes a real signing key into the developer's data directory.
test_default_key_is_generated_once_with_tight_permissions (tests/darnit_amber/test_run_bundle.py:256-268) sets XDG_DATA_HOME, which user_data_root() ignores on macOS (platform_paths.py:80). The test fails, after default_key() has already written signing.key/signing.pub under ~/Library/Application Support/darnit/amber/. Any later darnit-amber run on that machine signs with it. Linux CI won't catch this. Fix: monkeypatch user_data_root, ideally in the autouse _isolate_operator_config in tests/conftest.py.
B7. rerun verifies nothing and runs the bundle's command on the host.
A bundle with no envelopes, an edited command and a made-up id still executes, with the full environment, no container and no confirmation. A command of cat "$HOME/Library/Application Support/darnit/amber/signing.key" > o.txt put the default private key into the new bundle's stored outputs (I confirmed the hash, in a throwaway HOME). Fix: verify first, print the command and require consent until #677's container lands, and say so in the README.
B8. --pubkey is ignored for certificate-bearing envelopes.
verify 97e67cac... --out tests/darnit_amber/fixtures --pubkey <unrelated key> --offline gives exit 0, verified: true, with no signer_matches_statement (rerun.py:163-164, :225). The key fixture rejects the same key. There's also no way to name an expected identity: with a test root I injected as _TRUST_ROOT, a self-made attacker identity verified. So anyone pinning a researcher's key or identity accepts a bundle from any Fulcio identity. Fix: honor --pubkey, and require the expected identity and issuer, as cosign does.
Should fix
- Dirty tree recorded as a clean commit. The subject
gitCommitis HEAD even with a modified tree; only the git status inside the envelope shows it. Refuse a dirty tree, or record it and don't call the result reproducible from the commit. measure.fileisn't constrained./etc/hostsand a../-relative path to it were both read and recorded.run.pyusessafe_relfor outputs but not formeasure.file.environment.darnitis null in the e2e bundle._version("darnit")looks up the wrong name; the distribution isdarnit-core.runon a local path leavesamber-audit.jsonin the checkout. It would also overwrite a tracked file of that name.rerunof a local-path bundle uses the current checkout, not the recorded commit. I ran at one commit, committed a change and reran: the new code ran. README:64 says "same commit, same command, fresh clone".- The overall verdict may be too lenient. One REPRODUCED plus NOT_COMPARABLE gives REPRODUCED, and
test_run_bundle.py:130pins that, so it looks deliberate. The README says the worst claim decides but doesn't rank NOT_COMPARABLE. A claim that couldn't be measured ("output not produced", "regex did not match") isn't reproduced, so I'd count it against the verdict and change that test. - A failing experiment produces no bundle. A command that exits 3 returns exit 1 with an empty store, so a crash can't be recorded as a failed reproduction.
--publishfailure loses the run. Withpublish_envelopepatched to raise: exit 1 and an empty store, despite the comment atrun.py:488-491. Store first, keeping in mind thatpublicationis inside the statement and feeds the id.- The timestamp check uses
all()over the stamps that exist. On the key fixture with one envelope's stamp stripped and the id recomputed: exit 0. With both stripped: exit 2. - Error handling.
cli.py:153catches onlyRunError, so a missing--claims, a bad regex and the B3 TypeError all print tracebacks.
Scope and process
- 129 files: #532's rename (114 of them), #677 steps A-C, claims, an S3 store, Fulcio and Rekor. I'd land #532 first, then split out the S3 store and
--publish/--identity. - New hard dependencies for every darnit-mcp install: boto3, sigstore (through the private
sigstore._internal), pyopenssl, rfc3161-client. At least the S3 store should be an extra. - It differs from #677's decisions: no container (1), key signing by default rather than keyless (3), and an S3 store rather than GHCR (4). You flagged the bucket on #677; the other two should be decided there as well. There's no new spec under
specs/for the run bundle, and no CHANGELOG entry for it. run,verify,claimandrerunare CLI only, with no MCP tools. Worth a design discussion, since the product is MCP plus agent.- Five commits carry
Co-Authored-By: Claudewhere AI_POLICY.md asks forAssisted-by(the project's CLAUDE.md rules it out), and 16 lack Signed-off-by, so DCO fails. The PR conflicts with main (CHANGELOG.md, test_plan_contract.py), and no CI workflow has run on this head, only DCO and Kusari Inspector. - Both new fixtures contain your home path, and the identity fixture also contains your email. Flagging it in case you didn't mean to commit them.
Tests on ad3fa54, with HOME pointed at a temp directory: 5322 passed, 11 skipped, 1 failed (B6). ruff and validate_sync pass.
Drafted with AI assistance (Claude), reviewed and filed by me. Measurements quoted were executed and verified.
Package darnit_amber, framework and MCP server name amber, class AmberImplementation, amber.toml, design doc amber-attestation-system.md. Control ids (RE-*), handler names (repro_*) and the audit_reproducibility MCP tool are unchanged. A transitional darnit-reproducibility package (no entry points, depends on darnit-amber) ships for one release and darnit-mcp depends on it, so upgrading darnit-mcp replaces the old plugin instead of leaving it installed beside the new one; darnit_reproducibility re-exports darnit_amber with a DeprecationWarning. CHANGELOG lists the migration steps (uninstall, -f amber, MCP configs, [plugins].allowed, [[compose]] source). Assisted-by: Claude:claude-fable-5-1 Signed-off-by: Marc-cn <130138935+Marc-cn@users.noreply.github.com>
ad3fa54 to
30f938d
Compare
…g#677) Adds a darnit-amber console script with one subcommand, run: it clones or uses an artifact, executes the Amber audit under witness run (environment, git, command-run and product attestors), and writes one bundle through the existing AttestationStore (local-fs): an in-toto v1 statement with the amber/run/v1 predicate (environment, command, claims, audit results, outputs) plus the Witness DSSE envelope. The bundle id is the sha256 of the canonical statement. Claims from a corpus file are carried through with verdict NOT_COMPARABLE; measuring them, running inside the pinned darnit image, and Sigstore keyless signing are the next PRs in darnitdevorg#677. Signed-off-by: Marc-cn <130138935+Marc-cn@users.noreply.github.com> Assisted-by: Claude:claude-fable-5-1 Signed-off-by: Marco De Vincenzi <md6796@nyu.edu>
…e s3 Per darnitdevorg#677 discussion, run bundles go to a bucket. Adds S3AttestationStore (boto3; AWS S3, GCS interop, R2, MinIO via endpoint_url), registered under darnit.stores.attestation as 's3', with read and list_bundles as additive extensions of the v0 protocol. darnit-amber run gains --store {local-fs,s3}, --bucket, --prefix and --endpoint-url. Tests run against moto in-process. Signed-off-by: Marc-cn <130138935+Marc-cn@users.noreply.github.com> Assisted-by: Claude:claude-fable-5-1 Signed-off-by: Marco De Vincenzi <md6796@nyu.edu>
…utputs Signed-off-by: Marc-cn <130138935+Marc-cn@users.noreply.github.com> Assisted-by: Claude:claude-fable-5-1 Signed-off-by: Marco De Vincenzi <md6796@nyu.edu>
verify: the bundle id is the sha256 of its statement, the declared outputs match the Witness product digests, and each envelope's signature checks against --pubkey. rerun: read the bundle, clone the same commit, run the same command under Witness, compare every output digest, and store a new bundle that cites the first. Tested on SPF from a clean python:3.12-slim container with only the wheels and the witness binary: both outputs identical. Signed-off-by: Marc-cn <130138935+Marc-cn@users.noreply.github.com> Assisted-by: Claude:claude-fable-5-1 Signed-off-by: Marco De Vincenzi <md6796@nyu.edu>
A claim's measure block names an output file and a regex (group 1 is the value; optional scale). The value is compared to the claim's threshold (==, !=, <, <=, >, >=; numeric with optional tolerance, else string): REPRODUCED or NOT_REPRODUCED. No measure, a missing output, no match, or comparable: false give NOT_COMPARABLE with a reason. The bundle carries a verdict over all claims and, per claim, whether it matched the corpus entry's expected verdict. On the corpus: AEADProtocols gpg/consistency_SED is REPRODUCED, SPF is NOT_COMPARABLE with its 71.3 recorded. Signed-off-by: Marc-cn <130138935+Marc-cn@users.noreply.github.com> Assisted-by: Claude:claude-fable-5-1 Signed-off-by: Marco De Vincenzi <md6796@nyu.edu>
A rerun re-measures the first run's claims and reports first/rerun value and verdict per claim. Drift now means a verdict changed (or an output changed when nothing was measured), so a timing report that differs byte for byte no longer counts as drift on its own. Assisted-by: Claude:claude-fable-5-1 Signed-off-by: Marco De Vincenzi <md6796@nyu.edu>
Assisted-by: Claude:claude-fable-5-1 Signed-off-by: Marco De Vincenzi <md6796@nyu.edu>
…amples/ Assisted-by: Claude:claude-fable-5-1 Signed-off-by: Marco De Vincenzi <md6796@nyu.edu>
No entry file needed: artifact defaults to ., the subject comes from git remote and HEAD, outputs are the files the command created or changed (Witness product attestor). The entry darnit used is recorded in the bundle under entry.source = inferred | file. Assisted-by: Claude:claude-fable-5-1 Signed-off-by: Marco De Vincenzi <md6796@nyu.edu>
run keeps each captured output (up to 10 MB) next to the bundle, so a reviewer sees the number and a claim can be added later. claim measures the regex against the stored output and writes a new bundle with derived_from set; the first bundle is never changed. rerun of the new bundle re-measures and compares values. Assisted-by: Claude:claude-fable-5-1 Signed-off-by: Marco De Vincenzi <md6796@nyu.edu>
Assisted-by: Claude:claude-fable-5-1 Signed-off-by: Marco De Vincenzi <md6796@nyu.edu>
Assisted-by: Claude:claude-fable-5-1 Signed-off-by: Marco De Vincenzi <md6796@nyu.edu>
…inside the checkout Assisted-by: Claude:claude-fable-5-1 Signed-off-by: Marco De Vincenzi <md6796@nyu.edu>
--key is optional: the key is generated once under darnit's data root (ed25519, 0600) and the bundle records its fingerprint, never a path. Every Witness envelope is countersigned by an RFC 3161 timestamp authority (Sigstore's by default; --tsa, --no-timestamp), so a bundle proves when its signature existed without anything but a hash leaving the machine. verify decodes each timestamp token and checks that it covers the signature; the TSA's own chain is not verified yet. --identity (Fulcio) and --publish (Rekor) stay opt-in, to come. Assisted-by: Claude:claude-fable-5-1 Signed-off-by: Marco De Vincenzi <md6796@nyu.edu>
… trusted root Each RFC 3161 token's signer must chain to a timestamp authority in Sigstore's trusted root (refreshed through TUF; sigstore-python's bundled copy when offline), checked the way sigstore-python does, and the token's time must fall in that authority's validity period. A bundle is verified only when every timestamp both covers its signature and passes this check. Assisted-by: Claude:claude-fable-5-1 Signed-off-by: Marco De Vincenzi <md6796@nyu.edu>
…login A researcher who passes --identity signs with no key: Amber gets an OIDC token ($SIGSTORE_ID_TOKEN, the CI's ambient token, or an out-of-band login that works without a browser), hands it to Witness through a 0600 file, and Fulcio issues a ten-minute certificate bound to that identity. Witness keeps the certificate and chain in the envelope; the bundle records identity, issuer and certificate digest under `signing`. verify checks such a signature with the certificate's key, walks the chain to the Fulcio certificates in the same Sigstore trusted root the timestamp check already loads, and requires the trusted timestamp to fall inside the certificate's validity window, which is why --identity refuses --no-timestamp. --pubkey is not needed for these bundles. The verification path is tested with a chain built in the test (a login cannot run in CI); the Witness invocation is tested for the flags it passes and for keeping the token off argv. Assisted-by: Claude:claude-fable-5-1 Signed-off-by: Marco De Vincenzi <md6796@nyu.edu>
…-identity bundle offline Witness requests a Fulcio certificate per step, so signing now lists certificates_sha256 per envelope instead of the first one only. tests/darnit_amber/fixtures/6de52228….intoto.json is a bundle made on 2026-10-30 with a real login (GitHub); the test verifies it against the trusted root without network or a login, which the certificate's ten-minute life would otherwise make impossible. Assisted-by: Claude:claude-fable-5-1 Signed-off-by: Marco De Vincenzi <md6796@nyu.edu>
…cks inclusion With --publish each Witness envelope is posted to Rekor as a dsse entry, with the Fulcio certificate or the signing key's public half as the verifier, and the bundle keeps the log index, integrated time and the log's full response (inclusion proof, signed checkpoint, signed entry timestamp) under `publication`. Only the envelope is published; the experiment's outputs stay in the researcher's store. verify checks that the entry is this envelope (payload hash and signatures), verifies the inclusion proof and checkpoint against Rekor's key in Sigstore's trusted root with sigstore-python's TransparencyLogEntry, and, unless --offline, re-fetches the entry by index to confirm the log still holds it. A proof for an entry that is not ours is not checked. The README now states what `verified` establishes in each mode. Assisted-by: Claude:claude-fable-5-1 Signed-off-by: Marco De Vincenzi <md6796@nyu.edu>
tests/darnit_amber/fixtures/047999f7….intoto.json was made on 2026-10-30 with the default key and --publish (Rekor log indexes 3596892495 and 3596895974); the public half of that key sits next to it. The test checks signature, timestamp and Rekor inclusion with no network. Assisted-by: Claude:claude-fable-5-1 Signed-off-by: Marco De Vincenzi <md6796@nyu.edu>
…signature, bind envelopes to the statement A cold review of the branch found three ways verify could say `verified` for a bundle it had not actually checked: - The certificate chain walk accepted any certificate whose name matched and whose signature verified as an issuer, without CA constraints. A Fulcio leaf has an empty subject, so anyone holding any Fulcio certificate could issue a leaf naming someone else and pass it as an intermediate. The chain is now validated by OpenSSL in strict mode against the trusted root at the timestamp's time, as sigstore-python does, and the leaf must carry the code-signing EKU. - A key-signed bundle verified with no --pubkey: the skipped check was a string, and `verified` only aggregated booleans. It is now `signatures_verified: false`, and the key is also checked against the statement's `key_fingerprint`. ECDSA and RSA keys verify too; before, only ed25519 did. - The Witness envelopes were not bound to the statement. The predicate now carries `witness_envelopes_sha256`, so the bundle id covers them; verify requires every named envelope, present and matching, the certificate digests to match `signing.certificates_sha256`, and the experiment envelope's command to match the statement's. Also from the review: output paths from a bundle are confined to the checkout (an absolute path escaped the store, and claim would have read it); rerun computes its comparison before the id, so one bundle is written and its outputs live under that id; the entry's artifact.clone/ref drive the checkout when the CLI names none, as the README said; S3 list_bundles no longer lists output blobs; the OIDC token is withheld from the environment Witness attests; sigstore is pinned below 5 because publish uses its private Rekor client; the fixture tests use the offline trusted root. The README states what Rekor stores (hashes, signature, signer; not the envelope), what Witness's environment attestation records, and that a --no-timestamp bundle cannot be verified. The two real fixtures are removed here and regenerated under the new bundle format in the next commit. Assisted-by: Claude:claude-fable-5-1 Signed-off-by: Marco De Vincenzi <md6796@nyu.edu>
…e bound-envelope format Made on 2026-10-30 under a scrubbed environment (env -i): identity bundle 97e67cac… (GitHub login), published bundle 7b3039f7… (Rekor log indexes 3599179102 and 3599180143) with the public half of its key. Both verify offline against the trusted root sigstore ships. uv.lock follows the pyopenssl and sigstore pins of the previous commit. Assisted-by: Claude:claude-fable-5-1 Signed-off-by: Marco De Vincenzi <md6796@nyu.edu>
…fy what the caller names Answers the 2026-11-01 review of darnitdevorg#678 (B1-B8 and the should-fix list): - B1: the statement is signed (witness sign, same signer and timestamp authority) as statement_envelope; verify requires the signed payload to be the canonical statement byte for byte. A derived claim bundle is signed by the claimer (claim_signer). publication moves beside the statement so the bundle is stored before any Rekor call. - B2: claims are measured from the bytes Witness recorded as products, never from the checkout; measure.file goes through safe_rel. - B3: ordering operators need two numbers, tolerance applies to all of them, measurement never raises. - B4: every read of a stored output checks its digest; verify reports stored_outputs_match. - B5: clone URLs lose their userinfo; the environment is an allowlist in the statement and Witness's environment attestor is not used. - B6: tests/conftest.py replaces user_data_root for every test. - B7: rerun verifies first, shows the command, needs consent (--yes), and clones a local path at the recorded commit. - B8: --pubkey must be the key in a certificate; --expect-identity and --expect-issuer are required for an identity bundle. - Dirty checkouts are refused (--allow-dirty records no commit); the audit JSON is written outside the checkout; a failing experiment still gets a bundle through a wrapper that records the exit status; every signature needs a trusted timestamp; the verdict is REPRODUCED only when every claim is; darnit-core is the distribution looked up; the CLI reports errors instead of tracebacks. Assisted-by: Claude:claude-fable-5-1 Signed-off-by: Marco De Vincenzi <md6796@nyu.edu>
…, boto3 as the s3 extra Assisted-by: Claude:claude-fable-5-1 Signed-off-by: Marco De Vincenzi <md6796@nyu.edu>
Assisted-by: Claude:claude-fable-5-1 Signed-off-by: Marco De Vincenzi <md6796@nyu.edu>
Made on 2026-11-04 under env -i with a throwaway HOME, so neither bundle carries a path, host name or user name: identity bundle fcaecc71… (GitHub login, md6796@nyu.edu is the Fulcio subject and is meant to be there), published bundle c3e2fda5… (Rekor log indexes 3691869540, 3691869544, 3691869564 for the statement and the two steps) with the public half of its key and the stored output of each. Both verify offline against the trusted root sigstore ships. Assisted-by: Claude:claude-fable-5-1 Signed-off-by: Marco De Vincenzi <md6796@nyu.edu>
30f938d to
3f509be
Compare
|
Thanks for the review,every finding reproduced, and all of them are addressed on 3f509be (rebased on current main through #532's new head). The 21 earlier commits are rewritten with Blocking:
Should fix: dirty checkouts refused ( Process: spec at Not done here, as you suggested: the split into PRs once #532 lands (S3 store, |
Implements the run-bundle flow from #677: a researcher records an experiment once; anyone can verify it, and rerun it. Spec:
specs/046-amber-run-bundle/spec.md.Commands
darnit-amber run -- ./experiment.shdarnit-amber claim <id> --file --regex --thresholddarnit-amber verify <id> --pubkey k.pub/--expect-identity --expect-issuerdarnit-amber rerun <id> …Verdicts:
REPRODUCEDonly when every claim is,NOT_REPRODUCEDwhen any is, elseNOT_COMPARABLE(--not-comparable "reason"for hardware-bound numbers: recorded, not judged; ordering operators need two numbers). Claims are measured only from files Witness recorded as products of the experiment, never from the checkout. An entry file (--claims entry.yaml) gives the same information up front;packages/darnit-amber/examples/has three.Signing, time and what is public
Nothing leaves the machine by default except a hash.
--identityverifyrequires the expected identity and issuer--publishBundles record no host name, user name, credentials or paths of the researcher; clone URLs lose their userinfo. Witness keeps the command's stdout/stderr (README says so).
Tested end to end
--identitytests/darnit_amber/fixtures/fcaecc71….intoto.json--publishtests/darnit_amber/fixtures/c3e2fda5….intoto.jsonBoth fixtures were made under
env -iwith a throwaway HOME and verify offline in the test suite, so the Sigstore paths are covered in CI without a login or network.What's in the PR
run.py,claim.py,rerun.py: run, Witness capture, statement signing, claims, verify, rerunidentity.py,publish.py: Fulcio and Rekors3_store.py: S3-compatibleAttestationStore(entry points3;boto3is thedarnit-amber[s3]extra)cli.py:darnit-amber run | claim | verify | reruntest_run_bundle.py; full suite 5270 passed on the VMReview
A cold read (2026-10-30) found three ways
verifycould reportverifiedwithout having checked what it claimed; fixed with regression tests. The 2026-11-01 review found eight more (the predicate was unsigned, claims measured from committed files, string comparisons, unchecked stored outputs, secrets in the environment, a test writing a real key,rerunexecuting unverified bundles,--pubkeyignored for certificates) and a should-fix list; all addressed, each with a test, see the comment of 2026-11-04. Follow-ups in #700.Not in this PR
Pinned darnit image (#677 step B), registry for artifact images, corpus index,
darnit-amber list, MCP tools,claim --identity. Stacked on #532 (this branch carries its commit); to be split into smaller PRs once #532 lands.Assisted by Claude Code