Skip to content

Harden author-side payload gating #33

Description

@DocksDocks

Goal

Fix the three verified gating defects - prefix-evadable no-author-scripts matcher, silently skipped declared payload directories, uncorroborated agent-scorer output - and settle the cross-tool hook claim by corroborating the documented Codex compatibility contract.

Mode: plan-and-implement

Research

  • Finding 1 (verified): scripts/skills/no-author-scripts.mjs:24 uses a negative lookbehind that rejects a match when a slash character precedes scripts, so any author-script path spelled with a prefix (for example ./scripts/ci.mjs) evades the guard while bare scripts/ci.mjs fails. The slash exclusion is partly deliberate: it permits the shipped bundled path write-skill/scripts/skill-guard.mjs named at plugins/docks/skills/productivity/skill-maintenance/SKILL.md:148, which consumers really have. The fix must keep bundled plugin-internal script references legal while catching author scripts under any prefix. A fixed tail list rots as the tree moves (the current tree already holds scripts/ci-target.mjs, scripts/capture-tdd-red.mjs, and scripts/plans/ beyond the first draft's list), so the hardened matcher derives the author-script tails from the on-disk repository scripts/ inventory at guard runtime, while an allowlisted bundled-path shape (a plugin-internal path segment before scripts) stays legal.
  • Finding 2 (verified): scripts/ci.mjs:430 and :432 gate skills and agents behind fs.existsSync, so a descriptor that declares a skills or agents root whose directory is missing gets no validation and no failure. Fix: declared-but-absent roots become explicit fail lines.
  • Finding 3 (verified): scripts/ci.mjs:440-448 parses scripts/agents/score.mjs --per-file stdout without checking the subprocess exit status, corroborating row names against on-disk agent files, or rejecting non-finite scores; an empty agents directory passes vacuously (0 >= 0). The skill path has vacuity coverage in scripts/tests/unit/skill-score-vacuity.test.mjs; the agents path has none. Fix mirrors the skill checks: fail on non-zero scorer exit, corroborate row count and names against the directory listing, reject NaN, and add unit coverage.
  • Finding 4 withdrawn after an executable probe: malforming plugins/docks/hooks/hooks.json makes claude plugin validate ./plugins/docks print "Validation failed" - ci.mjs:406 already gates hooks JSON syntax; registering it in extraJson would be redundant.
  • Finding 5 resolved by documentation: the official Codex plugin documentation states that Codex sets CLAUDE_PLUGIN_ROOT for compatibility, so the invocation at plugins/docks/hooks/hooks.json:9 is portable by documented contract and the cross-tool claim in plugins/docks/hooks/context-tree-nudge.mjs:2-10 stands. Step 4 corroborates that documented contract against the current official documentation and records the citation; the hook changes only if current documentation contradicts the shipped claim. An unobservable runtime probe is not evidence for narrowing support.
  • Root AGENTS.md documents the guard's verify loop ("append a line naming node scripts/ci.mjs to a non-allowlisted skill body -> the guard run must FAIL"); the hardened matcher must keep that loop true and extend it to prefixed spellings.
  • All three code fixes live in author-side tooling (scripts/), shared repo-wide; the hook step may touch plugins/docks/hooks/. Repo-wide tooling plus a possible docks payload touch means the full node scripts/ci.mjs gate is authoritative.

Steps

# Id Task Files Depends Effect Status Done when
1 matcher Harden the no-author-scripts matcher against prefixed author-script paths while keeping bundled */scripts/ references legal scripts/skills/no-author-scripts.mjs - local done Planting ./scripts/ci.mjs or node scripts/ci.mjs in a non-allowlisted skill body fails the guard naming that file; skill-maintenance's bundled skill-guard reference still passes
2 presence Fail the gate when a descriptor declares a skills or agents root whose directory is absent scripts/ci.mjs - local done A descriptor with a missing declared root produces an explicit fail line instead of a silent skip
3 scorer Check agent-scorer exit status, corroborate rows against on-disk agents, reject non-finite scores, add vacuity unit coverage, and register the new suite (unit-agent-score-vacuity with matching selector and expected minimum) in the test-contracts registry scripts/ci.mjs, scripts/tests/unit/agent-score-vacuity.test.mjs, scripts/config/test-contracts.json - local done Scorer failure or row/name mismatch fails the gate; the new unit test passes, covers the empty-directory vacuous pass, and the discovered-versus-registered contract check stays green
4 hook Corroborate the documented Codex CLAUDE_PLUGIN_ROOT compatibility contract against current official documentation and record the citation; change the hook only if documentation contradicts the shipped claim plugins/docks/hooks/hooks.json, plugins/docks/hooks/context-tree-nudge.mjs - probe done The documented contract is cited in the verification record and the shipped claim matches it, or a documented contradiction is fixed in the hook files
5 gate Re-sync content hashes on drift and run the full gate plugins/docks/skills/ 1, 2, 3, 4 local done Full node scripts/ci.mjs exits 0

Acceptance

ID Command Expected
A1 node scripts/ci.mjs Exit 0 with preserved exit code
A2 Plant ./scripts/ci.mjs in a non-allowlisted docks skill body, run node scripts/skills/no-author-scripts.mjs, revert Guard fails naming that file; after revert it passes
A3 node --test scripts/tests/unit/agent-score-vacuity.test.mjs Exit 0; covers empty-dir vacuity, scorer failure, and name mismatch
A4 node scripts/tests/ci-plugin-targeting.mjs Exit 0 - targeting contracts unaffected

Do not touch

  • docs/plans/finished/ - frozen history
  • Validator floors in scripts/config/scoring.json - hardening adds checks, never loosens floors
  • plugins/plan-lifecycle/ - out of scope

Open questions

None

Review

Review records are stored in issue comments.

Verification Results

  • A1 node scripts/ci.mjs - exit 0 with preserved exit code, after two hardening-exposed repairs: the ci-plugin-targeting stub scorer printed .md-suffixed names the real scripts/agents/score.mjs never emits (fixture drift caught by the new corroboration; stub aligned), and one lint error (assignment-in-expression) in the new matcher loop was restructured to matchAll.
  • A2 guard plant - node ./scripts/ci.mjs planted in the non-allowlisted skill-maintenance body fails the guard naming that file (also verified for node scripts/ci.mjs, ./scripts/skills/guard.mjs, and a repo-absolute spelling); after revert the guard passes and the bundled write-skill/scripts/skill-guard.mjs reference stays legal.
  • A3 node --test scripts/tests/unit/agent-score-vacuity.test.mjs - exit 0; 7 subprocess contracts: green control, missing skills root, missing agents root, empty agents dir, scorer exit 3, row-name mismatch, NaN score.
  • A4 node scripts/tests/ci-plugin-targeting.mjs - exit 0; targeting contracts intact with the aligned stub.
  • Step 4 (probe, user-authorized in session): official Codex documentation confirms CLAUDE_PLUGIN_ROOT is set as a compatibility alias for PLUGIN_ROOT (openai/codex codex-rs/hooks/src/engine/discovery.rs; Codex platform hooks documentation). No contradiction with the shipped claim; hook files unchanged.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions