Repository navigation
fix(ci): keep failure text after quoted auth headers and redact more secret forms - #2613
Conversation
…secret forms An auth-scheme value no longer takes a closing quote, backslash or bracket, and an unclosed quoted value ends with its line, so a quoted Bearer header no longer takes the assertion lines after it (finding 1556). Object and array values after a credential key are redacted to their close on the same line, a passphrase or Authorization header after a colon takes every word, short scheme values with a digit are redacted, and pwd, passphrase, accessKey, sig and signature are keys (finding 1559).
withinDiagnosticDeadline now takes a function and runs it with an AbortSignal in async-local storage. kubectlFor passes that signal to execFile, so a read still running at the deadline is killed and a later one is refused, and the startup diagnostic stops writing to stderr once the deadline passed. A nested diagnostic also stops at the outer deadline (finding 1557). The unit test uses a fake stalled kubectl on PATH; no cluster.
#2607 noted the deploy refusal only inside the archived 2026-10-13 entry, so operators who read entries dated after their commit missed it (finding 1558). Add a 2026-10-16 entry and move the two oldest notices to the existing archive page to stay under the word cap.
execFile rejects an aborted read when it sends the kill, before the child is reaped, so 3 of 20 runs saw the pid still alive. Poll until it is gone, within 10 s of the 30 s stall.
|
🦞👀 Pull request received. I will update this pull request when review starts. ClawSweeper review in progressClawSweeper is reviewing this revision. This supersedes any previous blocked status. |
|
I found and fixed a redaction boundary regression in this PR, using only invented values. On All 53 owning runner tests passed locally, including the result/diagnostics artifact check; all 22 kubectl helper tests passed separately. The PR description now describes the actual boundary and limitations. Please review the new head; my work is not an independent approval, and the earlier CI result does not cover this commit. No actual credential disclosure is inferred. Follow-up: the author’s |
- A multi-word phrase stops at the next credential key, so that key's quoted value is still redacted (password: x token="..." published it). Stopping there also keeps the scan linear. - A bracket value ignores brackets inside strings (plain and serialized JSON), and takes text glued to its close. - An auth-scheme value may open with a quote, which is kept, and may hold an escaped "/". - An aborted kubectl read is never retried.
Keeps the pushed privacy-first rules (an unclosed quoted value and a bracket value run past newlines; a mismatched bracket suppresses the rest) with the review notes: the bracket scan uses the typed close stack with string handling from the pushed change, the next key still ends a phrase, text glued to a close joins the value, and an auth-scheme value keeps a quote, backslash or closing bracket that more of the value follows.
|
Free's clanker here. Internal review summary for this PR (reviewed head
|
… field
A delimiter run joined the value whenever any value character followed, so
the closing quote in {"authorization":"Bearer ...","status":503} was taken
with the comma, and the quoted-value scan then hid the rest of the failure.
A run now joins the value only before token text (letters, digits, ._~+/=-),
which keeps escaped slashes, a)b and log-escaped quotes inside it.
|
Closing the review record: the Claw status comment still says “review in progress,” but that job is terminal. Its final status was GitHub records head |
Problem
Follow-ups from an audit of #2605 and #2607 (findings 1556-1559, all low):
Bearer/Basic/Tokenshape matched 8+ non-space characters, so it also took the closing quote of a quoted header. The quoted-value scan then found no closing quote and redacted to the end of the whole text.redactLogLine/redactFailureonrequest failed {"authorization":"Bearer …"}\nexpected status 503, got 200returned onlyrequest failed {"authorization":"[redacted]: the assertion lines were gone. Nothing leaked, but the job-log and results failure text lost what it was for.{"credentials":{"key":"…"}},{"tokens":["…"]}), every word after the first inpassword: my secret phrase, aBearervalue under 8 characters, and values aftersig=,pwd=,passphrase=andaccessKey=. Environment values were already replaced, so the exposure was fixture or Pod-log secrets.withinDiagnosticDeadlineraced the diagnostic against its deadline but never cancelled it. Past the deadline, kubectl reads kept running (up to 20 s x 2 attempts each), the startup diagnostic kept writing to stderr during teardown, and the open children kept the test process alive.Change
scripts/ci/failure-redaction.mjsBearer token expiredstays readable.{or[is redacted through its matching close, including nested or multiline values. A bracket inside a quoted string does not end redaction.password,passwd,passphrase,pwdorauthorizationand a colon, every word up to the end of the phrase (newline, quote,,,;,},],&) is redacted.=stays one word, as in logfmt and query strings.passphrase,pwd,access[_-]?key,sig,signature.tests/helpers/kubernetes-real.mjs:withinDiagnosticDeadline(diagnostic)now takes a function and runs it with anAbortSignalheld inAsyncLocalStorage.kubectlForpasses that signal toexecFile, so a read still running at the deadline is killed and a later one is refused before kubectl starts. A nested diagnostic also stops at the outer deadline.diagnosticDeadlinePassed()lets a diagnostic stop writing.tests/integration/sandbox-driver-openshell-k3d-real.test.mjs: the three callers pass functions, and the startup diagnostic writes nothing to stderr after its deadline.docs/guides/deploy/breaking-changes.md: a2026-10-16: stored model provider settings are checked at deployentry. The two oldest 2026-10-13 notices move to the existingbreaking-changes-archive.mdto stay under the word cap (2337 / 2018 words bywc). No new page and nodocs/docs.jsonchange.Verification
a quoted auth header keeps the failure text after it,the redactor removes structured, multi-word and short secret values, and the two new deadline tests (main's helper takes a promise and has no cancellation).execFile,a failure diagnostic's kubectl reads stop at its deadlinefails (the fake 30 s kubectl holds the read past the test timeout).kubectlonPATH(no cluster). It checks that the read rejects withAbortError, that a second read is refused without starting kubectl, and that the process is gone within 10 s.tests/conformance/kubernetes-real-kubectl.test.mjs20/20. The redaction tests intests/integration/ci-runner.test.mjs20/20. The wholeci-runner.test.mjs(53/53) andqa-fixture-inputs.test.mjs(24/24) pass.Bearer token expired,token expired,secretName: occ-gateway-password,tokenCount: 3,design=compact,SyntaxError: Unexpected token '}'.checks-baseline-1andchecks-baseline-2).git merge-treeagainst this head shows the same conflicts as against main. test(openshell): prove model, tool and gateway outages fail closed #2393 has the same single 14-line hunk in the k3d test. chore(openshell): bump pinned release to 0.1.4 #2440, fix(ci): retain nested failure causes in diagnostics #2002 and Draft implementation: managed outbound HTTPS for dedicated Gateways #2518 merge cleanly. Credential Proxy Implementation #2487's conflicts are in migration files only.Risks
password:in prose (invalid password: too short, got 3keeps only, got 3), anypwd=value including aPWD=path, and aToken/Bearerword followed by a short value with a digit. These are on the safe side.withinDiagnosticDeadlinenow takes a function instead of a promise. All callers are in this PR.Final redaction-boundary correction (
afb00802)A CI failure message is not necessarily one line of JSON. Review with invented values caught two failure boundaries in the initial PR: a newline in an open quoted value, and a closing bracket inside quoted text in a structured value. The fix preserves following diagnostics when the value actually ends. It does not claim arbitrary log text is safe to publish.
b8290043and passed after the correction; it checks both the short result and the full diagnostics artifact. Local owning results: all 53 CI-runner cases passed, and separately all 22 kubectl-helper cases passed. Earlier sparse-checkout runs lacked fixture files; the complete files above are the relevant results, not a retroactive claim those invocations passed.Internal review notes and merge (
78b644f9,7cbb7628)The internal review of
b8290043asked for these changes. They are merged withafb00802's rules, which are kept as pushed: an unclosed value stays private through the end of the bounded input, and the bracket scan uses a typed close stack with string handling.password:orauthorization:ends at the next credential key, so that key's quoted value is still redacted (password: x token="…"published it). Stopping there also keeps the phrase scan linear; an earlier draft took about 280 ms on 16 KiB of repeatedpassword:.token: [a]…) belongs to the value.curl -H "Authorization: Bearer '…'"). A quote, backslash or closing bracket ends the value only when no more of the value follows, so\/in a Basic value,a)bandmap[Authorization:[Basic …]]are all handled.7cbb7628, at 2 CPUs:ci-runner.test.mjs53/53; the redaction tests, including the runner diagnostics-artifact case, 20/20;kubernetes-real-kubectl.test.mjs20/20 (on78b644f9, unchanged since);qa-fixture-inputs.test.mjs24/24. Prettier and ESLint are clean.7e99351fcloses the remaining synthetic regression from that corpus: an escaped quote or adjacent closing delimiters within an auth-shaped log value no longer exposes the suffix. This concerns diagnostic text, not a claim that the string was a valid HTTP header or that a real credential was exposed. Actual closing delimiters and the failure on the following line stay readable.7e99351f, all 53 CI-runner cases passed, and the baseline lane passed 1,677 tests with none skipped. Independent review is still required; this is test evidence, not an approval or a claim arbitrary logs are safe to publish.Sibling-field boundary (
adc11b99)The review thread on
7e99351fwas right:{"authorization":"Bearer …","status":503}still lost the rest of the failure. The closing quote and comma joined the value, and the quoted scan then ran to the end. A delimiter run now joins the value only before token text. New rows for the JSON and node-inspect sibling-field forms, each followed by an assertion line, fail on7e99351fand pass here. At 2 CPUs:ci-runner.test.mjs53/53, redaction tests 20/20. On the synthetic main-vs-head corpus nothing that main redacted is published, and the worst 16 KiB input takes 1.8 ms.Recorded CI failure output on the sibling-field correction
I ran the real
scripts/ci/run-tests.mjsand reporter twice, changing only the checkout from7e99351ftoadc11b99. The input was an invented canary, not an environment value that would be automatically masked. Each run exited 1 intentionally, after exercising JSON and inspected-object sibling fields, an escaped quote, a multiline quoted value, and a structured value containing a closing brace.Actual JSON-case failure text read from the results artifact (the second line is part of the input assertion):
The invented value was absent from terminal output and all five messages in both result files on both revisions. On the corrected revision, I also asserted the following diagnostic survived in every short result and every detailed message. The detailed report intentionally suppresses a whole credential-bearing line where appropriate. This is evidence about these failure shapes, not a guarantee arbitrary logs are safe to publish; no CI rerun or real secret was needed.