Repository navigation
fix(artifacts): catch escaped and unnamed run credentials, and withhold a rejected report (#1217) - #1291
Closed
mrthankyou wants to merge 2 commits into
Closed
fix(artifacts): catch escaped and unnamed run credentials, and withhold a rejected report (#1217)#1291mrthankyou wants to merge 2 commits into
mrthankyou wants to merge 2 commits into
Conversation
…ld a rejected report (#1217) The artifact secret gate matched configured credentials byte for byte, so a value holding a quote, a backslash, a tab or a line break never matched the JSON that escapes it, and a verified report could publish it. The gate now also scans a JSON publication's decoded strings, joined by NUL so adjacent fields never form one phrase. Configured values are also found percent-encoded, with Markdown backslash escapes, or broken across lines. The verifier, synchronization and the rejected-report check now match every credential an agent can see: the configured api_key_env variables and the allowlisted variables that hold a credential, not only names like *_API_KEY. The value of ULTRAFUZZ_PROVIDER_CREDENTIAL_ENV_NAMES, a list of names, is no longer treated as a credential. The Modal public bundle, eval diagnostics and recovery lifecycle checks reuse the same scan. A final report the gate rejects is withheld instead of failing the run: the report verifier tolerates failure in the engine, the failure records reason_code ARTIFACT_SECRET_GATE_REJECTED, the run ends succeeded when everything else did, status reports report-withheld-secret, and `resume --retry-failed` regenerates the report. Any other report verification failure, and a withheld report under --require-complete, still fail the run. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ped escaped credentials (#1217) Review follow-up: - The unchecked report fallback rescanned a rejected draft against the current environment, so once the credential it matched was unset or rotated, `ultrafuzz report` could publish the withheld draft. A report attempt whose failure records ARTIFACT_SECRET_GATE_REJECTED now stays unavailable until a fresh attempt replaces it. - The line-break check compared a configured value only against the Markdown-unescaped text, which had already lost the value's own backslash. The text as written is now checked too. - The compileSmithersWorkflow continueOnFail count expects the report verifier's own continue policy. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This was referenced Oct 14, 2026
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.
Closes #1217.
Summary
The artifact secret gate matched configured credentials byte for byte against the published bytes. JSON escapes a quote, a backslash, a tab and a line break, so a credential holding one never matched
report.json, and a verified report could publish it. Auditing the gate turned up more gaps than the issue names. This PR closes the ones inside the gate, and changes what happens when the gate rejects the final report.1. The gate reads decoded JSON (the issue)
report.mdneeds no separate pass: it is a projection ofreport.jsonand is always checked beside it.Every credential format in the existing gate test, embedded in a
report.jsonfield, was re-run through the old and new gate. Sixteen of eighteen were already caught; two leaked onunstable:unstable"Authorization: Bearer …")\"tab or line break2. The gate knows every credential an agent can see
All gate calls used
sensitiveEnvironmentValues(process.env)with no explicit names, so only variables named like*_API_KEYor*_TOKENwere matched exactly. A configuredapi_key_envsuch asAZURE_OPENAI_KEY(which the Codex adapter hands the agent asCODEX_API_KEY), or an allowlisted variable whose value is a credential, could be published verbatim.publicationForbiddenSecretValues(artifacts) adds the controller'sULTRAFUZZ_PROVIDER_CREDENTIAL_ENV_NAMESandULTRAFUZZ_SENSITIVE_AGENT_ENV_NAMES. The verifier uses it.runPublicationForbiddenSecretValues(runtime) derives the same names from the run'ssmithers/resolved-config.jsonand the allowlist, for checks outside the controller: synchronization and the rejected-report reader.sensitiveAllowlistedEnvironmentVariableNamesmoves there fromstart-run.tsunchanged.ULTRAFUZZ_PROVIDER_CREDENTIAL_ENV_NAMESmatches the credential-name pattern, so its value — a list of names likeANTHROPIC_API_KEY— was itself treated as a secret, and any output mentioning that variable name failed the gate. The two name lists are now excluded from the values.verified-output.ts) stays patterns-only on purpose: it runs on every later read, and matching the reader's current environment could make an already-verified run unreadable after the environment changes.3. Encodings agents produce by accident
A configured value is also found percent-encoded (an RPC URL), with Markdown backslash escapes (
\_in agent-written.md), or broken across lines. Deliberate encodings (base64, hex, case changes) are out of reach of any scan and are documented as such.4. Modal public checks
The Modal public bundle, public eval diagnostics and recovery lifecycle checks had the same escaped-JSON hole. They now reuse the gate's check through the new
publicationContainsSecrets. Their inputs are already redacted upstream, so this is defense in depth.5. A rejected final report is withheld, not a failed campaign
Failing the gate used to fail the report node and therefore the whole run, at its last step. Now:
continueOnFail, so the engine endssucceeded-with-failures.NodeFailederror carriescode: ARTIFACT_SECRET_GATE_REJECTED(set only by the controller's gate, never by agent output), the report node's failure records a new optionalreason_codeand the node is non-blocking: the run endssucceededwhen everything else did.statusreports the report unavailable with the new reasonreport-withheld-secret, synchronization adds aTERMINAL_REPORT_WITHHELDwarning naming the recovery command, andultrafuzz reportexits non-zero as for any unavailable report. The unchecked fallback is withheld too.ultrafuzz resume <run-id> --retry-failedregenerates the report. For a failed verifier it resets the producing agent (retryProducerForFailedVerifier), so the retry writes a fresh report rather than re-checking the withheld one, and it reads failed tasks fromfailedChildKeysfor a success-terminal run.--require-complete.Schema changes: optional
reason_codeon node failure provenance (run-state.schema.json), andreport-withheld-secretinreport-publication-status.schema.json. A new failure category was avoided because it would change the public eval diagnostics schema.Not in this PR
engine-logs/) and gate-rejected files fromartifacts/without any scan. This is the largest remaining leak path, but it is a separate subsystem with a product decision attached.rt_refresh token) are caught only when configured; section 2 makes the configured ones count. Unlabelled 64-hex stays allowed because it matches every transaction hash.Testing
All suites run in a Linux container (macOS-only Modal failures exist on
unstabletoo; filed as #1290).@ultrafuzz/artifacts@ultrafuzz/security@ultrafuzz/cli@ultrafuzz/modalpublication-secret-values,unverified-report,final-report-markdownruntime.test.tssync, report, redaction and #1217 testssmithers-report-retry.integrationgenerated-workflow-verifierunstableNew tests:
--require-completestill fails; a simulated--retry-failedpublishes the verified report.The new tests fail on
unstable's gate. Strict lint, Prettier and schema snapshots are clean.Not verified live:
resume --retry-failedon a withheld report is checked in code and in simulated sync tests, not in a real campaign. To check it live, launch any run with a credential-named variable whose value is the run ID (for exampleE2E_RUN_TOKEN=<run-id>, which every report contains), confirm the run endssucceededwithreport-withheld-secret, then unset it and runultrafuzz resume <run-id> --retry-failed.🤖 Generated with Claude Code

Confidence Score: 5/5
The PR appears safe to merge; both previous findings are fixed and no new blocking issues were found.Summary
The PR strengthens secret checks and withholds rejected final reports without failing otherwise successful runs.
Diagram
%%{init: {'theme': 'neutral'}}%% flowchart TD A[Report draft] --> B{Secret check} B -->|Pass| C[Publish verified report] B -->|Reject| D[Save rejection reason] D --> E[Withhold report and unchecked fallback] E --> F[Retry report agent] F --> AReviews (2) · Last reviewed commit: "fix(artifacts): keep a gate-withheld rep..." · Reviewed by Greptile