From 73b3276453cbad18951b520d57af4602661eae84 Mon Sep 17 00:00:00 2001 From: Aaron Bockelie Date: Sat, 16 May 2026 23:03:43 -0500 Subject: [PATCH] harden(bases): runtime-computed-member escape test + scope scanner claim MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Advisor review follow-up on #186: - Add SECURITY cases note[("cons"+"tructor")] / file[("__pro"+"to__")]: runtime-computed member names our static pre-walk intentionally does NOT catch (property is BinaryExpression, not Identifier/Literal) — they must fail closed via expression-eval's own access guard. Test-grounds the defense-in-depth claim instead of asserting it. 60/60 green. - CLAUDE.md 'Known accepted review findings': the Bases new Function is REMOVED (ADR-201, #180/#185/#186), not pending #175. Documents the honest caveat — grep 'new Function' main.js != 0 by design: residual is transitive ajv@6.14.0 validator codegen + a deprecation shim, a pre-existing library-internal class not reachable from vault content, present on the reviewed 0.11.25, non-gating. Scanner finding was attributed to the Bases path; this closes that path. Refs #180, ADR-201 --- CLAUDE.md | 24 ++++++++++++++++++++---- tests/fixtures/base-corpus.ts | 6 ++++++ 2 files changed, 26 insertions(+), 4 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index afba271..d29f614 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -471,12 +471,28 @@ by reverting the decision behind them; they were analysed and accepted: - **"… scan not available" disclosures** — neutral. Obsidian's malware/dependency/obfuscation scanners did not run; not a failure. -**Dynamic code execution** (`new Function` in the Bases evaluator) is a real -architectural decision, not an accepted finding — tracked for an ADR in #175. +**Dynamic code execution** — the Bases-evaluator `new Function` (the +exploitable vector: arbitrary JS from a synced/shared `.base`) is **removed** +per **ADR-201**, implemented in #180 (PR #185 corpus/baseline, PR #186 +expression-eval swap). The evaluator now parses with `expression-eval` (jsep +grammar, no globals) plus a tested `constructor`/`__proto__`/`prototype`/ +`this` denylist; a differential corpus proves behavioural parity. + +Caveat for future scans: `grep "new Function" main.js` is **not** zero by +design. The residual occurrences are transitive-dependency codegen — ajv +@6.14.0 schema-validator compilation + a library deprecation shim — a +different, library-internal class **not reachable from vault content**. They +were already in 0.11.25's bundle, the release that passed full review with +only the fs Warning, so they were non-gating then. The "Dynamic Code +Execution" Recommendation was attributed to the Bases path specifically; +this clears that path. Whether a heuristic re-scan re-flags the ajv-class +residual is unknown until the next scan (scorecard blind — #183); if it +does, it is the *transitive* class above, not the closed Bases vector. Actionable findings became issues/PRs: #163/#164/#170 (SSL + attestation, -shipped), #171/#173 (build-dep + CSS, shipped), #174 (js-yaml), #175 (eval -ADR), #176 (this doc). Scorecard CI-gate idea: #165. +shipped), #171/#173 (build-dep + CSS, shipped), #174 (js-yaml, shipped), +#180 (sandboxed Bases evaluator, PR #185/#186), #176 (this doc). Scorecard +CI-gate idea: #165. ## Important Notes diff --git a/tests/fixtures/base-corpus.ts b/tests/fixtures/base-corpus.ts index 0e43735..b62ab59 100644 --- a/tests/fixtures/base-corpus.ts +++ b/tests/fixtures/base-corpus.ts @@ -227,4 +227,10 @@ export const SECURITY_EXPRESSIONS: string[] = [ 'this', 'note.__proto__', 'note.constructor', + // Runtime-computed member name: our static pre-walk sees a BinaryExpression + // property (not Identifier/Literal) and does NOT catch this — it must fail + // closed via expression-eval's own access guard. Pins that the two layers + // compose, so the "defense-in-depth" claim is test-grounded, not asserted. + 'note[("cons" + "tructor")]', + 'file[("__pro" + "to__")]', ];