feat(harness-ops): follow whole-module loads and computed keys in the parser reader's flow check - #5970
Conversation
…their reads
The parser reader failed an export hop whenever any code loaded the
exporting file whole (import(), require(), import*as, export*). It now
follows each such load in the new `namespace` helper op and accepts only
reads of other exports by name: a member read that is not a call, an
object pattern without rest, a record property (`{names:ns}`) bound to a
variable read only by name, and Promise.all destructuring. Promise.all,
await and record lookups add the built-ins they rely on to the trusted
names the sink rule checks, a global write to a trusted name is now a
sink, and a namespace settled through a promise requires the module to
export no `then`. Load sites now come from the AST (`loads` op) instead
of a regex over raw text.
On 2.1.284-2.1.288 every load of the Explore/Plan array's re-exporting
chunk passes; the lists still read partial because of the sink rule.
Refs #5901
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…trusted name The sink rule counted every computed-key write or define on an object that is not provably fresh as able to write any trusted built-in name. It now works out what the key can evaluate to: literals, numbers (arithmetic, `++`, unary minus), boolean and typeof results, and variables every write of which is one of those. Such a key counts only for the trusted names it spells; any other key still counts for all. On 2.1.288 this clears about a fifth of the computed-key writes (1136 hits in 201 modules down to 877 in 178); the rest have receivers and keys only an interprocedural analysis could pin down. Refs #5901 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…mp to 3.1.0 - A member read through an optional chain, `(ns?.f)()`, still passes `ns` as `this`, so the namespace and record walks refuse it as a method call. - `exports()` answers None for a module with `export*`, whose names it cannot list, so a `then` re-exported through it is not missed. - extraction.md describes the namespace walk, key provenance and the sinks that remain on 2.1.284-2.1.288, which keep the Explore and Plan lists partial under the parser and the default reader at regex. - harness-ops 3.0.1 -> 3.1.0 with a CHANGELOG entry. Refs #5901 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
PR body contract — issue linkage This PR body conforms to the issue-linkage contract. Nothing to do. |
…rom a string `runInThisContext` (on vm or a vm.Script) and `compileFunction` run a string as code in this realm, where it could patch a built-in the flow trusts, but the sink rule did not see them. A read or destructured key of either from any object, a named import of either, and a `vm` or `node:vm` load used other than by named reads are now sinks like eval and Function. On 2.1.288 five modules hit it; `--reader compare` output is unchanged on 2.1.284-2.1.288. Refs #5901 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…r cannot name Two wrong-value gaps the #5970 verifier found, both also on main: - Code run in a new vm context still reaches this realm (`this.constructor.constructor("...")()` inside `runInNewContext` patches the outer Array.prototype). Any `vm`/`node:vm` load except an import naming only `isContext`, and any vm runner's name read from any object, is now a sink. - A load the AST scan did not recognize (an aliased `require` or `import.meta.require`, `require.call`, `(0,require)(...)`, `import(x)` with a non-literal specifier) could load the exporting file whole and enumerate it unseen. The `loads` op now reports such a module, and it fails every export hop. `--reader compare` output is unchanged on 2.1.284-2.1.288. Refs #5901 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Claude finished @kyle-sexton's task in 1m 44s —— View job Security review (
|
|
Claude finished @kyle-sexton's task in 7m 38s —— View job Code review (
|
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: f2b395cf89
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
…eta as an unknown load Codex on #5970: `const{require:r}=import.meta;r("/a.js")` hid the load, so a namespace enumerated through `r` read a wrong literal. `import.meta` used other than by a named read, and any destructured `require` key, now count as a load the parser cannot name, failing every export hop. `--reader compare` output is unchanged on 2.1.284-2.1.288. Refs #5901 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Claude finished @kyle-sexton's task in 3m 44s —— View job Code review (
|
|
Claude finished @kyle-sexton's task in 2m 19s —— View job Security review (
|
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 989896145d
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
# Conflicts: # plugins/harness-ops/CHANGELOG.md
|
@codex review |
|
Claude finished @kyle-sexton's task in 44s —— View job Security review (
|
|
Claude finished @kyle-sexton's task in 1m 21s —— View job Code review (
|
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 5bada6befb
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
# Conflicts: # plugins/harness-ops/.claude-plugin/plugin.json # plugins/harness-ops/CHANGELOG.md
|
@codex review |
|
Claude finished @kyle-sexton's task in 1m 35s —— View job Security review (
|
|
Claude finished @kyle-sexton's task in 1m 36s —— View job Code review (
|
|
Codex Review: Didn't find any major issues. Already looking forward to the next diff. Reviewed commit: ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
If Codex has suggestions, it will comment; otherwise it will react with 👍. Codex can also answer questions or update the PR. Try commenting "@codex address that feedback". |
Refs: #5901
This PR leaves the issue open: the default reader is not flipped.
Summary
#5901 asked for the parser reader to read the Explore and Plan
disallowed_toolsliteral on 2.1.284-2.1.288 and then become the inventory default. Two causes kept both lists partial: whole-module (namespace) loads of the re-exporting chunk, and the sink rule. This PR removes the first and narrows the second. Under the brief's stop rule it does not flip the default: the sink rule still fires on every build, and some of what it fires on cannot be cleared without assuming something the analysis cannot prove (details under Fix).namespaceop follows eachimport(),require(),import.meta.require()andimport*asload of the exporting file to its reads. It accepts only reads of other exports by name: a member read that is not a call, an object pattern without rest, a{names:ns}record read only by name or tested, andawait Promise.all([...])destructured by an array pattern. The built-ins those shapes rely on join the trusted names the sink rule watches. A global write to a trusted name is now a sink. A namespace settled through a promise needs its module to export nothen. On every installed build, all 13-14 loads of the re-exporting chunk pass.vm/node:vmload except an import naming onlyisContextis a sink likeevalandFunction, and so is a vm runner's name (runInThisContext,runInNewContext,runInContext,compileFunction,SourceTextModule,SyntheticModule) read from any object. Code in a new context still reaches this realm's prototypes throughthis.constructor.constructor.requireorimport.meta.require,.call, a comma callee, orimport(x)with no literal specifier. Theloadsop now reports any module that holds one, and that module fails every export hop.typeofresults, or variables written only with those.--readerstaysregex; README, SKILL.md and native-drift.md are unchanged because the default did not change.Fix
What still blocks a literal read, measured on 2.1.288 with the flow's own trusted names (
some,includes,has). Module counts are lower bounds, because each module reports at most 20 hits:thisin methods, call results; clearing them needs interprocedural points-to over the 40 MB bundleObject.defineProperty(o,k,...))__proto__=,setPrototypeOf)Hn=Object.defineProperty.bind(Object))Function(self,scope,code)(...)), protobufjs'sinquiremakes a directevalcall, and 3 modules call a member.eval(...)whose receiver cannot be shown to be something other than the global object. Clearing ajv's case would mean assuming generated code never patches a built-in prototype. That assumption is unsoundThe namespace stage needed no unsound assumption. The sink stage does, for ajv at least.
Verification
INVENTORY_REQUIRE_ACORN=1full harness-ops suite: all 13test_*.pymodules (python3 -m unittestper directory) and 39 of the 40*.test.shpass.audit-install-state/scripts/install_state.test.shfails here and on origin/main the same way: under Python 3.14,python -m unittest <absolute path>.pyfails to import the module. This PR does not touch that skill, andtest_install_state.pypasses when run as a module.test_reader_findings.py:test_a_namespace_read_only_by_name_keeps_the_literal(7 shapes).test_a_namespace_read_other_than_by_name_stays_partial(adversarial, one or more per acceptance rule).test_what_a_namespace_load_trusts_stays_checked(a replacedPromise,Promise.resolve,then,Promise.prototype.constructor, anObject.prototypegetter, athenexport, anexport*).test_a_computed_key_known_to_name_no_trusted_name_clearsandtest_a_computed_key_that_may_name_a_trusted_name_stays_a_sink.test_a_bundle_calling_vm_run_in_this_context_stays_partialcovers 11 spellings:runInThisContext,runInNewContext(the verifier's probe),runInContext,Script#runInNewContext,SourceTextModule, a destructuredcompileFunction, a computed read and an escaping alias. A plain named import ofisContextstays literal.test_a_load_the_parser_cannot_name_stays_partialcovers the verifier's gap-1 probes, which read a wrong literal before this change: an aliasedimport.meta.require,require.call,(0,require),import.meta.require.call,import(s)andrequire(s). As controls,typeof require,require.resolve, arequireparameter and a{require:1}key stay literal.INVENTORY_REQUIRE_ACORN=1unittest passes for all 13 harness-ops Python modules.scripts/run-ruff.sh checkandformat --checkare clean.--reader compare --self-checkon 2.1.288 printsOK, withreader compare: ok, 2167 of 2167 modules parse.--reader compare --self-checkon 2.1.284 printsreader compare: ok, 2151 of 2151 modules parse. The overall verdict isDEGRADED, the same as on main: there is a version advisory, and the builtin_plugins canaries are absent on that build.{names:...}case intest_the_module_table_names_the_exporters_own_filenow reads literal when the record is never read, and partial with a computed read.python3 inventory.py --binary <build> --reader compare --binary-onlyon this branch (rerun at f2b395c) and on origin/main 57ca27a:The 4 wrong->unresolved entries are the Explore and Plan
disallowed_toolsanddisallowed_tools_sourceon every build, the same as on main.scripts/run-ruff.sh checkandformat --checkare clean;markdownlint-cli2is clean on the changed docs;scripts/check-changelog-parity.sh --check-bump origin/mainpasses.Related
Known gaps, unchanged by this PR:
Symbol.iteratorand the array iterator'snextare not trusted names, so the sink rule does not watch them for a spread.requireandimport()are recognized by name only. A shadowing local binding, or a loader reached through another global such asmodule.requireor acreateRequireresult, is caught only when it is aliased or called with a non-literal.🤖 Generated with Claude Code