Skip to content

sec(crypto-gate): the discovery gate could not read half its corpus (BACKLOG #1172) - #790

Open
wshallwshall wants to merge 2 commits into
mainfrom
worktree-agent-ad52300b369acb2d3
Open

sec(crypto-gate): the discovery gate could not read half its corpus (BACKLOG #1172)#790
wshallwshall wants to merge 2 commits into
mainfrom
worktree-agent-ad52300b369acb2d3

Conversation

@wshallwshall

Copy link
Copy Markdown
Collaborator

What and why

scripts/security/crypto_inventory_check.py is import ast over *.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 adding ide/ to WALK_ROOTS would 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_randomness walks ide/ for .ts/.js randomness sources and diffs them against NON_PYTHON_INVENTORY bidirectionally. A weak draw in the extension now blocks a merge; the extension's own negative test could not, because it rides a leg ci-gate does 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.

Property Mutation Result
FINDS a planted Math.random() in a .ts file delete the Math.random pattern 6 tests red
REFUSES an empty walk disable the empty-walk refusal 1 test red
Does NOT flag the legitimate draw disable the comment skip false-positive guard reds

The 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.ts is the hard negative case on purpose. It names Math.random twice in prose to explain why it is unusable, and the scan must read that as prose while still reading randomBytes(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_away proves 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 under scripts/ is still outside the Python one -- 56 .ps1 files, re-measured, against a control of 53 matching param(. 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.

  1. Corrected the dead clause. The row's top asserted that the extension mints CSP nonces from 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: zero Math.random( call sites under ide/, positive control of 6 bare occurrences (five comments, one test-name string) and 110 files walked.
  2. The five zero-margin values ARE reproducible from this repo, and they land on exactly the 4-engine + 1-console shape the item's caveat predicted. An AST census over the five walk roots finds 21 CSPRNG call sites outside tests; resolving each named byte-count constant leaves six call sites at 16 bytes, which are five distinct values: pipeline/sandbox.py:847 (dispatch request id), uploads.py:497 (upload file id, non-enumerability explicitly load-bearing at api/app.py:3969), transports/dicomweb.py:329+:333 (STOW-RS boundary), transports/soap.py:288 (WS-Security nonce), and messagefoundry_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, and auth/service.py:611 has 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.
  3. Handoff written for the two acts I may not perform: the ASVS 11.5.1 scorecard re-score and the scope-completeness adversarial pass. The item now names the engine-side evidence by path, the read command, and the --root refusal trap, without reproducing cell content.
  4. Also corrected: the item's live score-table "Why" cell, which still said the gate "excludes ide/ by invariant rather than inventorying it". V/D/quadrant/tier are unchanged -- re-scoring is an assessor decision, not a side effect of an edit, so the distribution census is untouched.

Checks run

Run with the primary checkout's venv, because this worktree has no .venv (pytest could not import pydantic from 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 . -- passed
  • ruff format --check . -- 1220 files already formatted
  • mypy messagefoundry messagefoundry_webconsole --exclude 'messagefoundry/tray/' (the CI invocation) -- 283 files, no issues
  • mypy on the two changed files -- no issues. Note CI does not type-check tests/ or scripts/; 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 skipped
  • python scripts/security/crypto_inventory_check.py -- exit 0
  • scripts/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 with parse_items, not a hand-rolled scan: open, one banner, closing-act: scorecard-rescore unchanged
  • zero non-ASCII in every added line across the whole diff, against a positive control of 2095 such lines in the unchanged BACKLOG.md

Not run here and needing a reader after this process exits: the full pytest suite, and every hosted-runner-only leg. banner_sha_check.py reports 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

  1. Should a genuinely non-security Math.random ever be allowed in ide/? 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.
  2. The extension hardening tier riding the merge gate is still open from this item's own proposed-work list, and is now partly moot for randomness specifically: that property rides crypto-inventory as of this PR. The rest of extension-hardening.test.ts (webview receivers, URL allowlisting) still does not.

🤖 Generated with Claude Code

…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 wshallwshall added the reviewed A reviewer has read this. Removed automatically when new commits arrive. label Sep 3, 2026
@github-actions github-actions Bot added the ci-red A required check went red. Attribute it before retrying. label Sep 4, 2026
@wshallwshall wshallwshall added reviewed A reviewer has read this. Removed automatically when new commits arrive. and removed reviewed A reviewer has read this. Removed automatically when new commits arrive. labels Sep 4, 2026
@wshallwshall
wshallwshall added this pull request to the merge queue Sep 4, 2026
@github-merge-queue
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.
@github-actions github-actions Bot removed the reviewed A reviewer has read this. Removed automatically when new commits arrive. label Sep 4, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ci-red A required check went red. Attribute it before retrying.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant