sec(crypto-gate): the discovery gate could not read half its corpus (BACKLOG #1172) - #790
Open
wshallwshall wants to merge 2 commits into
Open
sec(crypto-gate): the discovery gate could not read half its corpus (BACKLOG #1172)#790wshallwshall wants to merge 2 commits into
wshallwshall wants to merge 2 commits into
Conversation
…BACKLOG #1172) The ASVS 11.1.3 crypto-inventory gate is `import ast` over `*.py`, so a weak-PRNG draw in the TypeScript extension was invisible to it by construction. It did not merely omit `ide/` from WALK_ROOTS; adding the directory there would have been a no-op that looked like a fix, because the Python AST scanner finds zero `.py` files under it and reports clean. Add a second arm to the same required context. `check_non_python_randomness` walks `ide/` for `.ts`/`.js` randomness sources and diffs them against `NON_PYTHON_INVENTORY` the same bidirectional way, so a weak draw in the shipped extension now blocks a merge. The extension's own negative test could not do that from a leg `ci-gate` does not require. Three properties, each pinned by a test and each proven by a mutation control run against the shipped code rather than asserted: * it FINDS a planted `Math.random()` in a `.ts` file. Deleting the pattern reds 6 tests. * it does NOT flag the legitimate `randomBytes` draw in `ide/src/cspNonce.ts`, which is the hard case: that file names `Math.random` twice in prose to explain why it is unusable. Disabling the comment skip reds the false-positive guard. * it REFUSES an empty walk. A scan reaching no files, and an inventory anchor that has gone missing, both red. Disabling the empty-walk refusal reds 1 test. A weak source has no inventory row available to it by construction. Registering one is the move #1172 names as a disqualified pass for ASVS 11.5.1, so the weak check runs before the inventory diff and no row can silence it. The randomness half is the whole claim the arm supports. The extension's TLS floor is still outside both arms, and PowerShell under `scripts/` is still outside the Python one; that gap stays filed under #1164. Also on #1172, which stays OPEN because its closing act is a vaulted scorecard re-score that no engine checkout may perform: * correct the dead clause at the top of the row. It asserted that the extension mints its CSP nonces from `Math.random()`, which was true when filed and was superseded by PR #422 on 2026-08-17. Struck rather than deleted, because the evidence that the row was once wrong is what stops the next reader trusting the rest of it unchecked. * record the five zero-margin values the item's caveat said its census flags. An AST census over the five walk roots finds 21 CSPRNG call sites outside tests and resolves exactly five distinct values to 128 bits: four in the engine (sandbox request id, upload file id, STOW-RS boundary, WS-Security nonce) and one in the console (CSP nonce). AST rather than grep, which returns 36 lines because a docstring naming a draw matches as readily as a draw. * write the handoff for the two acts that remain: the ASVS 11.5.1 scorecard re-score and the scope-completeness adversarial pass that was never run, naming the engine-side evidence and where it lives without reproducing any vaulted content. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
wshallwshall
enabled auto-merge
September 4, 2026 08:54
github-merge-queue
Bot
removed this pull request from the merge queue due to no response for status checks
Sep 4, 2026
Two scoring-table rows conflicted and the correct side differs per row, so neither
--ours nor --theirs is right for the file.
#1172 <- this branch. Its cell QUOTES main's text ("What it said: the concrete IDE
defect is closed by cspNonce.ts ... plus the cross-language inventory question")
and then reports that both remainders have since shipped -- the recovery-code
shortfall by PR 603, and the inventory arm on 2026-09-03, which is this branch's
own change. It incorporates main's statement and supersedes it.
#1205 <- origin/main. Identical prefix plus an "Updated 2026-09-03" paragraph recording
that the --print-keys sub-step was broken and is now fixed (PR 808). A strict
superset of this branch's cell.
Verified with parse_items from scripts/docs/backlog_status_check.py rather than a
hand-rolled scan: 423 items, matching main, and the ADDED/LOST item-set difference against
both parents is empty.
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.
What and why
scripts/security/crypto_inventory_check.pyisimport astover*.py. That makes it Python-only by construction, so a weak-PRNG draw in the shipped TypeScript extension could not be seen however the walk-set was spelled. The file's own comment already said so, and said correctly that addingide/toWALK_ROOTSwould be a no-op that looks like a fix.This adds a second arm to the same required context (
crypto-inventory (ASVS 11.1.3 discovery gate),.github/required-contexts.txt:118).check_non_python_randomnesswalkside/for.ts/.jsrandomness sources and diffs them againstNON_PYTHON_INVENTORYbidirectionally. A weak draw in the extension now blocks a merge; the extension's own negative test could not, because it rides a legci-gatedoes not require.The three properties, and the mutation control for each
Each was proven by mutating the shipped code and watching the tests red, not by reading the assertions.
Math.random()in a.tsfileMath.randompatternThe gate was also run end to end against a planted offender file in the real tree: exit 1 naming
ide/src/__planted_offender.ts, exit 0 again once removed.ide/src/cspNonce.tsis the hard negative case on purpose. It namesMath.randomtwice in prose to explain why it is unusable, and the scan must read that as prose while still readingrandomBytes(18)as a documented draw. It does.A weak source has no inventory row available to it. The weak check runs before the inventory diff, so registering the site cannot silence it. That is #1172's disqualified move ("narrowing the inventory's declared scope so the hit stops being visible") refused in code rather than in prose, and
test_a_weak_draw_cannot_be_registered_awayproves it by mutating the inventory to "document" the offender and asserting the gate still reds.Empty-scan refusal has two independent limbs, because this project's named failure shape is an empty scan and a clean scan looking alike: a walk reaching zero files is a violation, and the inventory anchor (
ide/src/cspNonce.ts->randomBytes) reds through the stale direction if the walk ever stops reaching it. The success line now prints the corpus size, not just the verdict.Scope of the claim
This is a randomness inventory and that is all it supports. The extension's TLS floor (
ide/src/engineClient.ts) is still outside both arms, and PowerShell underscripts/is still outside the Python one -- 56.ps1files, re-measured, against a control of 53 matchingparam(. That gap stays filed under #1164; it is named in the item so a completeness pass does not re-derive it as new.Backlog #1172, which stays OPEN
Its closing act is a scorecard re-score, and that record lives in the separate vault clone. No vault read or write was performed and no vaulted content appears in this diff.
Math.random(). Struck, not deleted, with what superseded it: it was TRUE when filed on 2026-08-08 and superseded by PR docs+fix: record the strict default-off assessment rule, close the security-header escape paths, and harden the VS Code extension #422 (c49a8a7b) on 2026-08-17. Re-measured at HEAD: zeroMath.random(call sites underide/, positive control of 6 bare occurrences (five comments, one test-name string) and 110 files walked.pipeline/sandbox.py:847(dispatch request id),uploads.py:497(upload file id, non-enumerability explicitly load-bearing atapi/app.py:3969),transports/dicomweb.py:329+:333(STOW-RS boundary),transports/soap.py:288(WS-Security nonce), andmessagefoundry_webconsole/_security.py:205->:324(console CSP nonce). AST rather than grep: the same pattern grepped returns 36 lines, because a docstring naming a draw matches as readily as a draw. Three neighbours are excluded with reasons in the item, andauth/service.py:611has LEFT the list at 192 bits since PR sec(auth): recovery codes were a 68-bit second factor, and the floor that would have caught it did not exist (BACKLOG #1172) #603.--rootrefusal trap, without reproducing cell content.Checks run
Run with the primary checkout's venv, because this worktree has no
.venv(pytestcould not importpydanticfrom the bare interpreter). Called out per the "say so rather than claim it passed" rule; the gate script itself is stdlib-only, and every path the tests read resolves from this worktree, not the primary.ruff check .-- passedruff format --check .-- 1220 files already formattedmypy messagefoundry messagefoundry_webconsole --exclude 'messagefoundry/tray/'(the CI invocation) -- 283 files, no issuesmypyon the two changed files -- no issues. Note CI does not type-checktests/orscripts/; they are clean anyway.pytest tests/test_crypto_inventory_scanner.py tests/test_crypto_inventory_doc.py tests/test_security_static.py tests/test_recovery_code_entropy.py-- 99 passed, 1 skippedpython scripts/security/crypto_inventory_check.py-- exit 0scripts/docs/backlog_status_check.py(657 items, each one status),link_check.py(4991 links resolve),asvs_tally_lint.py,citation_line_check.py,dangling_citation_check.py,verdict_divergence_check.py-- all rc 0#1172's banner verified withparse_items, not a hand-rolled scan: open, one banner,closing-act: scorecard-rescoreunchangedBACKLOG.mdNot run here and needing a reader after this process exits: the full
pytestsuite, and every hosted-runner-only leg.banner_sha_check.pyreports 11 findings, all on other items (#1005, #1221, #1232, #1320, #1322, #1323, #1331, #1025, #1094) and none in this diff -- pre-existing, not introduced.Open questions for whoever cuts the next brief
Math.randomever be allowed inide/? I chose "no row can silence it" and documented the escape as "make the non-security intent structural, or file it". Today the tree has zero, so nothing is blocked. If a reviewer prefers a reviewed-exception dict instead, that is a one-constant change.crypto-inventoryas of this PR. The rest ofextension-hardening.test.ts(webview receivers, URL allowlisting) still does not.🤖 Generated with Claude Code