harden(bases): runtime-computed-member escape test + scope scanner claim

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
This commit is contained in:
Aaron Bockelie 2026-05-16 23:03:43 -05:00
parent b9d99edfd7
commit 73b3276453
2 changed files with 26 additions and 4 deletions

View file

@ -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

View file

@ -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__")]',
];