Skip to content

CRW-601: port the hook trust entry listing from CXC v0.2.40 - #567

Merged
thisisjun786 merged 7 commits into
devfrom
codex/crw-601-hooktrust-entries
Oct 5, 2026
Merged

thisisjun786 merged 7 commits into
devfrom
codex/crw-601-hooktrust-entries

Conversation

@thisisjun786

@thisisjun786 thisisjun786 commented Oct 5, 2026 •

Copy link
Copy Markdown
Owner

What this changes

Adds ListHookTrustEntries(pluginRoot, pluginKey string) ([]HookTrustEntry, error) to internal/runtime/doctor: it lists the command hooks a plugin declares, as {Key, Hash, FileSha256} per hook, so a later change can compare them with the trust records Codex keeps in config.toml. It is a port of listHookEntries from CXC v0.2.40 and writes nothing: a read-only function with no caller yet, so nothing is registered, activated or installed.

Expected behaviour (each point is a recorded oracle case or a test; the oracle is CXC v0.2.40, commit 3c1459acadeb1906d97c00a598e1457327ae372d):

  • The plugin key and every hook path must be non-empty and free of ", \, CR and LF` ("... contains characters unsafe for a TOML quoted key").
  • .codex-plugin/plugin.json hooks that is not an array lists nothing; a non-string reference is refused.
  • A reference loses one leading ./, must be relative, and must stay inside the plugin root both lexically and after symlinks ("must be relative", "escapes plugin root", "symlink escapes plugin root"); the file's SHA-256 is FileSha256.
  • Hook files and the manifest are read as the oracle read them (Buffer.toString("utf8") then JSON.parse, through internal/pyjson with surrogates kept): events in the file's key order (array-index keys first, as Object.entries lists them), then groups and handlers in array order, with the oracle's structural errors in the oracle's order.
  • A group whose non-empty, non-* matcher is not a valid JavaScript RegExp is skipped (see the defects below for how Go approximates that); a handler is skipped when its type is not command, its command is not a string or is blank after JavaScript trim, or async is true; every other handler gets the key <pluginKey>:<path>:<event label>:<group>:<handler> and HookTrustIdentityHash of the hook (CRW-347: port the hook trust identity hash #560).

Oracle sources ported

plugins/codexclaw/components/cxc-ops/src/hook-trust.ts lines 48-57 (HookEntry) and 140-216 (normalizeHookPath, containedPluginFile, assertSafeHeaderValue, listHookEntries), and the five hook-trust.test.ts tests at 127-186. Recorded answers come from the CXC v0.2.40 dist through testdata/hooktrust/record-entries.mjs (Node v24 is test data only; the Go tests replay entries-oracle.json and never run Node).

Tests and evidence

  • Red first. Commit 0b80f1443 adds the tests against a stub that returns an error: 194 of the 200 cases recorded at that point and all seven top-level tests fail (the six that pass are JSON syntax errors, which the stub's error satisfies). The port follows in the next commit.
  • Green. go test -count=1 ./internal/runtime/doctor passes: the five ported B tests, an exact-bytes test for a reference holding a lone surrogate, and a replay of 206 recorded oracle answers (122 entries in total) covering the real CXC plugin (31 hook files, 32 entries), a snapshot of this repository's plugin, every guard above, the Object.prototype event names, encodings (invalid UTF-8, BOM, lone surrogates), path containment (dotdot, absolute, symlink to file and directory, symlinked manifest, sibling-prefix, symlinked root, relative root), and matcher validity.
  • Swap race. TestListHookTrustEntries_swapAfterTheCheckIsNotFollowed exchanges the hook file between a regular file inside the root and a symlink to an outside file while the listing runs. The first version of the port listed the outside file in 5 of 5 runs of the test; the current one listed it in none of 240,000 listings.
  • Mutation check. 16 deliberate breakages of the port (prefix check without separator, second ./ strip, decoding with strings.ToValidUTF8, event order, surrogate path, TrimSpace, matcher rewrite character, * matcher, null versus absent matcher, key spelling, async skip dropped, lookahead rewrite, digest of decoded text, error ordering, partial entries on error) each make the replay fail.
  • make lint (vet, staticcheck, gofmt), GOOS=darwin go vet ./..., CGO_ENABLED=0 go build ./..., go run -tags dev ./cmd/crw-dev ci validate, ci contracts and go test -count=1 -run TestDomain/cxc ./internal/contracttest pass; ci plugin (no --record-version) reports the same package digest as the base (635105c68ea07f89), and no file under plugins/ or any .codex-plugin directory changed. No init function, package-level variable, cgo or Node at run time. Secret scan of the pushed range with the CI gitleaks version: no leaks.

Corpus fixtures that reach this unit

listHookEntries is called from diagnoseHookTrust and retrustHooks (hook-trust.ts:333, :421). Three fixtures of contract/fixtures/cxc reach it; none can pass with this PR alone, because their entry points are the hooks retrust and doctor commands and the config.toml trust reader that follow. They stay pending and are listed as such in contract/notes/cxc/CRW-601.json (nothing is claimed identical or intentionally changed):

Fixture Entry point Passes with this PR alone?
cli__hooks__retrust_needs_bootstrap_then_writes hooks retrust (retrustHooks lists the hooks, then writes trust sections) no
cli__hooks__retrust_safety_pin_refuses_all_drifted hooks retrust (lists, compares with the recorded hash) no
cli__doctor__codex_bin_and_surface_env doctor --json hook-trust check (32 hooks listed) and its hooks retrust step no

The other fixtures that print a hook-trust check stop before listing ("enabled install key is ambiguous (0)"), and cli__hooks__retrust_without_install_key_fails fails at key resolution; none of them runs this unit.

Size

1,095 counted lines. The issue's own limit is about 1,035; the operator's allowance for finished and verified work reaches about 1,190, and the excess is tests plus the fix for Devin's two security findings. Composition: implementation hooktrust_entries.go 470; tests hooktrust_entries_test.go 322 and recorder record-entries.mjs 283 (605); documentation known-defects.md 10 and the corpus notes file 10 (20). Not counted: the recorded fixture entries-oracle.json (2627 lines, 124 KB, generated by the recorder; re-running it reproduces it byte for byte).

Defects found in the oracle (recorded in docs/port-cxc/known-defects.md, port: kept unless noted)

  1. The matcher test is V8's RegExp grammar, which Go only approximates (accept what regexp.Compile accepts, plus what it refuses only for lookaround or backreferences); the lines list the measured residual differences in both directions (measured against Node 24 over 96 patterns, 14 pinned as matcher_residual_* cases).
  2. An event key that Object.prototype defines passes the oracle's in guard and is spelled into the key as the inherited function's source.
  3. A non-empty string or array hooks is read as an object with index keys, a number or boolean hooks is an empty list, a JSON null document throws a raw TypeError, and integer-like keys come first.
  4. ./ is stripped twice, so ././x.json is keyed ./x.json but read from x.json, and ././/x.json is refused as escaping the root.
  5. port: fixed after Devin's two security findings: the manifest was read without the containment check, and a checked hook file was opened after its path was checked, so a symlink swapped in between was followed. The port applies the same containment to the manifest and, once a file is open, resolves its path again and refuses a file that is not the one it opened or no longer lies inside the root. The oracle's answer for a manifest that leaves the root stays in the fixture as intentionally_changed_* cases.
  6. A FIFO in place of the manifest or a hook file blocks the read (the oracle blocks the same way).
  7. A plugin root of / rejects every reference, because the prefix test appends the separator to a root that already ends with one.
  8. The Go reader refuses documents nested deeper than 10,000 containers (V8 reads a million).

Not in this PR

The config.toml trust reader and retrust that call this function, CLI wiring, and anything under plugins/ (no hook or skill is registered). Engine error texts (file not found, directory, JSON syntax) are Go's, not V8's: the tests compare their class.

Review notes

The implementation plan was audited by an independent reviewer over three rounds before any code was written (two FAILs folded in, then PASS). A separate model review of the final diff could not be completed: the provider's content filter stopped the request before a verdict, and the dispatch protocol offers no retry for that code. What that review had already produced was read, each difference it listed was re-checked against the oracle, and the cases and the one assertion it showed missing were added. Devin reviewed the first head (five comments: two bug, one analysis, two security). Both security findings and the test weakness are fixed in the second commit after the first push (bd4a69b); the two bug comments (the / root quirk and the matcher grammar) are oracle behaviour recorded in known-defects.md; all five threads are answered and resolved. Codex's Code Review completed on the first head with no comments; its Security Review was skipped for a usage limit. The maintainer's review covers the rest.

Built on dev at 270a056; docs/port-cxc/known-defects.md is append-only, so the branch may show a conflict there with a newer dev (both sides add a section at the end).


Devin Review

Add hooktrust_entries_test.go with the five hook-trust.test.ts B tests
(:127-186), a replay of 200 answers recorded from the CXC v0.2.40 dist by
record-entries.mjs (entries-oracle.json), and an exact-bytes test for a
reference that holds a lone surrogate. ListHookTrustEntries is a stub that
returns an error, so 194 of the 200 recorded cases and all seven top-level
tests fail; the port follows in the next commit.
Replace the stub with ListHookTrustEntries (hook-trust.ts:48-57 and
:140-216): read the plugin manifest, follow each declared hook file under
the oracle's containment rules (lexical and realpath, a second "./" strip,
absolute results of it), list events in Object.entries order, skip groups
whose matcher is not a valid RegExp (regexp.Compile, or a failure caused
only by lookaround or backreferences) and handlers that are not command
hooks, then key and hash each one with HookTrustIdentityHash. All 200
recorded oracle answers and the seven top-level tests pass.
Append the port: kept lines for the matcher grammar residual, the
inherited-function event spelling, non-object hooks values, the double
"./" strip, the unchecked manifest path and the reader depth limit to
docs/port-cxc/known-defects.md, and register the three corpus fixtures
that reach listHookEntries as pending in contract/notes/cxc/CRW-601.json:
none of them can run before the CLI entry points exist.
Use os.tmpdir() instead of a fixed /var/tmp fallback and drop the blank
line at the end of record-entries.mjs. The recorded answers are unchanged
(the recorder was re-run and its output compared byte for byte).
A probe of about 1,350 generated documents against the oracle found no
difference beyond three more matcher grammar residuals (a group name with a
non-ASCII letter, a lone surrogate in a matcher, a repeat count above 1000)
and the reader depth limit, all recorded now. Mutating the port to return
earlier entries together with an error survived the replay, so the replay
asserts that an error never comes with entries, and a case where a later
file's document fails after an earlier one listed entries joins the fixture.
Also note that a FIFO blocks the read in the oracle and in the port.
@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for security reviews. Please try again later.

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 5, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-10-05T04:05:17.469706Z cd1d2b7 PR opened
ℹ️ 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" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Devin Review found 5 potential issues.

Devin Review

Comment thread internal/runtime/doctor/hooktrust_entries.go
Comment thread internal/runtime/doctor/hooktrust_entries.go
Comment thread internal/runtime/doctor/hooktrust_entries_test.go
Comment thread internal/runtime/doctor/hooktrust_entries.go Outdated
Comment thread internal/runtime/doctor/hooktrust_entries.go Outdated
Devin's review reported two security findings and one test weakness.

- The manifest was read without the containment check the hook files get,
  and a hook file was opened after its path was checked, so a symlink put
  in place between the two was followed. hookTrustEntriesReadContained now
  checks the manifest like any hook file and, once a file is open, resolves
  its path again and refuses a file that is not the one it opened or no
  longer lies inside the plugin root. The oracle's answer for a manifest
  that leaves the root stays in the fixture as intentionally_changed_*
  cases, and a swap test (a regular file and a symlink to an outside file
  exchanged while the listing runs) fails on the previous code in every run
  and passes on this one. known-defects.md records the line as port: fixed.
- The replay accepted any error for a SyntaxError case, so a missing-file
  error passed; it now refuses a file-system error there.
- Two more oracle quirks are recorded as port: kept: a FIFO blocks the read,
  and a plugin root of / rejects every reference.
The coordinator refreshed the base. The only conflict was in docs/port-cxc/known-defects.md, where both sides
appended lines at the same place (1 block(s)); both sets are kept, dev's lines first, then this branch's.
@thisisjun786
thisisjun786 merged commit 6daa8be into dev Oct 5, 2026
10 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant