Skip to content

Add signed Codex PreToolUse adapter - #162

Draft
Lrd0036 wants to merge 4 commits into
GenAI-Security-Project:integrationfrom
Lrd0036:refimpl/codex-pretool
Draft

Lrd0036 wants to merge 4 commits into
GenAI-Security-Project:integrationfrom
Lrd0036:refimpl/codex-pretool

Conversation

@Lrd0036

@Lrd0036 Lrd0036 commented Sep 19, 2026

Copy link
Copy Markdown

What changed

Add a signed Codex PreToolUse adapter: tool calls become steps/toolCallRequest, and Guardian decisions become native allow, deny, or supported parameter overrides. It reuses #22's Python signing, handshake, binding, and decision helpers, with configuration, mapping, CI registration, and runnable verification.

Draft — depends on #22. This branch is #22 at 7174a033c15f69ee58caaa5eb0a19279592171c7 plus the original Codex commit 3d4bdb3 and the ServerHello repair d077978 described below. The full PR diff temporarily includes the inherited adapter stack. For the original Codex slice, review the Codex commit: 15 files, 1,063 insertions, 9 deletions. The optional shared-transport cleanup is excluded.

Before marking this ready, I will rebase the Codex slice and any still-needed repair changes onto integration after #22 lands, reconcile upstream changes, and rerun the checks and live enforcement on that final base. The original results below remain tied to 3d4bdb3; the review follow-up records separate verification of the repaired tree.

Which issue does this implement

Refs #89 (status:accepted, help wanted). This implements the tool-call slice and leaves the issue open for lifecycle/result coverage.

Base branch

  • integration, because this adds an adapter, tests, and CI.

The draft exposes the completed slice while its dependency is open; it is not ready to merge.

Type of change

  • Reference implementation or adapter
  • Documentation
  • Tooling or CI

I tested this — original commit (historical)

Rerun against Codex-only commit 3d4bdb3e84665e6db13835bc075ee8ec876bc14b:

  • Synced onto the final integration base (pending Add adapters/ with Claude Code, Cursor, and NAT reference implementations #22).
  • uv run --locked pytest -v: 205 passed, one case-insensitive-filesystem skip.
  • uv run --locked mkdocs build --strict: passed.
  • python run_conformance.py claude cursor nat codex: 291 passed, no failures, three expected live Claude/Cursor skips. All 35 NAT tests and all 26 Codex tests ran.
  • Real Codex CLI 0.142.5 plus the unchanged shipped Python Guardian: hooks-disabled control, ALLOW, DENY, and Guardian outage all passed with filesystem assertions. The denied command safely printed a literal policy trigger; it ran with hooks disabled and was blocked with ACS enabled. Signing used a temporary 0600 key file.
  • Real CLI plus the signed fixture Guardian: ALLOW/DENY/MODIFY produced the expected filesystem effects, with emitted signatures and schemas independently validated.

The local model endpoint supplies a fixed tool call; these runs test execution enforcement, not model inference. Live runs used explicit inline hook configuration and the one-shot hook-trust bypass. Project-file hook discovery and interactive trust remain unverified. An isolated project-file probe sent no Guardian requests and was not counted as a pass. The shipped teaching policy does not generate MODIFY, so that path has fixture coverage only. MCP/patch translation has subprocess coverage, not live host coverage.

Review follow-up — d077978

Repair commit d07797805945801e443ab04eaf3a1f8c5c332ff8 reads ServerHello directly from result for fresh and cached handshakes, corrects both Python producers, migrates affected fixtures, and adds 13 regressions. Request-ID binding, signature verification, and whole-envelope caching remain in place. Cached legacy wrappers trigger renegotiation.

Separate local verification on the repaired working tree, before committing:

  • Adapter gate: 304 passed, 3 expected live Claude/Cursor skips; all 35 NAT and 26 Codex tests ran. The 13 new regressions passed; on the original tree, 6 failed and 3 errored, with 4 passing.
  • Repository guards: 205 passed, 1 filesystem skip. Strict MkDocs build passed.
  • Real Codex CLI 0.142.5: signed fixture ALLOW/DENY/MODIFY passed; repaired shipped Python Guardian control/ALLOW/DENY/outage passed. The live-host limitations stated above still apply.

The local security assessment reviewed an uncommitted snapshot. Reconstructing the saved repair on 3d4bdb3 reproduces its snapshot SHA-256 a126300c22a821a33c5b84c29b7b4b37ec7cf2f9c624879a609da69db67bc9e7 and the published commit's complete Git tree 95757e340f91579273287a33e582997d5c5f9e3e. No source changed between that snapshot and publication; the local commit amendment changed only its message. These are pre-commit local results linked by identical source content, not post-push CI results.

TypeScript coverage remains open. A separate local probe against the actual TypeScript Guardian validated the direct ServerHello shape and observed an unsigned response; configured-key Codex refused it. Its documented endpoint lacks authentication and request signing. This proves shape compatibility and unsigned-peer rejection, not successful authenticated integration. That probe is not committed coverage in d077978. Proposed follow-up for reviewer agreement: a repository test against the real TypeScript process for the wire shape and signed-client rejection; positive signed interoperability remains pending TypeScript signing support. Signature verification will remain enabled.

Public CI for d077978: Deploy Pages and adapter-tests both report action_required, with zero jobs reported as of 2026-09-20. They establish neither passing validation nor test failures. Maintainer action and completed checks are still pending.

Checklist

  • Commits are signed off with git commit -s.
  • Prose follows STYLE.md.
  • No secrets, credentials, or private service URLs in the patch; localhost URLs and dummy fixture credentials are intentional.

Security and scope

Missing signing configuration, invalid or misbound decisions, and unsupported modifications deny. ASK/DEFER become audited denial because this slice has no supported approval/resumption path. Temporary Guardian failures follow the configured posture; the example sets default-deny.

The adapter advertises one method, HTTP, and no ACS-Core profile. Results, lifecycle events, hosted tools, continued process input, and hook-disabled/untrusted paths remain outside coverage. No specification or policy-engine changes are included in the Codex commit.

bar-capsule and others added 3 commits September 8, 2026 14:49
One squashed commit carrying the full PR GenAI-Security-Project#22 branch, rebuilt on current
main (DCO: all history signed; branch is a direct child of main).

Adapters (each: acs_adapter, wire.py, mapping.md, README, tests):
- Claude Code: hook-to-steps translation with explicit turn tracking
  (turnStart/turnEnd, per-session state), native allow/deny/ask,
  modify via merged updatedInput, defer substituted to deny + audit.
  SubagentStop is deliberately unmapped: steps/subagentStop requires
  final_chain_hash, which a chain-less framework cannot honestly
  produce; a separate schema PR proposes making it optional.
  ADAPTER_VERSION 0.1.3.
- Cursor: documented-field payload builders (docs.cursor.com,
  2026-08-22), turn tracking, beforeSubmitPrompt exit-2 blocking,
  failure_type-to-exit_status mapping, fail-closed contradictory
  modifications per §6.3.
- NAT: pre/post-invoke middleware with negotiated timeouts, durable
  audit sink, redaction-or-deny output gate.

Shared infrastructure:
- acs_common: RFC 8785 (JCS) + HKDF per-session HMAC signing (§10),
  handshake with signed ServerHello binding and negative cache,
  total decision normalization, §6.3 composition-violation check.
- example_guardian: signed error envelopes, capped regex scanning,
  durable file-locked replay state, subagent gate no weaker than the
  generic tool gate. Binds --port 0 and announces the assigned port
  on stdout so test spawns own their port by construction.
- Conformance + emission suites (230 checks) driving both CLI
  adapters and the Guardian; NAT covered by its own suite (35).

Guardian refusal handling is deliberately stricter than v0.1 spec
text (always fail closed); tracked as spec issue GenAI-Security-Project#32.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: Bar Kaduri <bar@capsule.security>
Translate tool calls through the shared ACS signing and handshake helpers.
Apply allow, deny, and supported parameter modifications using Codex's native
hook output; deny unsupported ASK/DEFER and unusable modifications.

Include configuration, mapping, emission tests, CLI enforcement verification,
and conformance-runner/CI registration. Advertise only the implemented method
and transport, with no ACS-Core profile claim.

Refs GenAI-Security-Project#89; first tool-call slice.
Based on GenAI-Security-Project#22.

Signed-off-by: Lrd0036 <lrd0036@auburn.edu>
@github-project-automation github-project-automation Bot moved this to Needs triage in ACS Project Tracker Sep 19, 2026
@acs-board-reconciler acs-board-reconciler Bot moved this from Needs triage to In progress in ACS Project Tracker Sep 19, 2026
@President

President commented Sep 20, 2026

Copy link
Copy Markdown

Thanks for the Codex slice. I found one interoperability issue in ensure_session_handshake.

The current code reads response.get("result") and then looks for a nested payload field:

server_hello = (response.get("result") or {}).get("payload")

ACS v0.1 returns ServerHello directly as result; there is no payload field inside it. The code should read the result itself:

server_hello = response.get("result")

The TypeScript Guardian uses this standard handshake shape too. Could this be corrected and covered by an interoperability test against the TypeScript Guardian?

Read ServerHello directly from the JSON-RPC result on fresh and cached
handshakes. Update both Python response producers and the affected
fixtures while retaining response binding, signature verification, and
whole-envelope cache validation.

Add focused regressions for direct-result negotiation, cache reuse,
tampering, unsigned responses, request binding, legacy wrappers,
schema conformance, and the shipped Python Guardian round trip.

Signed-off-by: Lrd0036 <lrd0036@auburn.edu>
@Lrd0036

Lrd0036 commented Sep 20, 2026

Copy link
Copy Markdown
Author

Yes! Thanks for catching this. d077978 fixes both fresh and cached reads, both Python response producers, and the affected fixtures. It adds 13 regressions while preserving signature verification, request binding, and whole-envelope caching. The repaired local adapter gate passed 304 tests with three expected live-product skips; the PR description now separates those results from the original commit's results.

The TypeScript portion is still open. I ran the TypeScript Guardian, and its direct ServerHello validates against the handshake schema... but the response is unsigned. Its README documents the authentication/signing bit. With a nonempty key and signature verification enabled, Codex rejects that peer. This is wire-shape compatibility and rejection coverage, not a successful signed integration, and the diagnostic is not committed in this repair.

Would a committed test that launches the TypeScript Guardian, validates its direct-result shape, and asserts rejection by the signed Python/Codex path meet the coverage expected for this repair, with positive signed end-to-end interoperability tracked separately pending TypeScript signing support?

The new head's Deploy Pages and adapter-tests runs both show action_required with no jobs reported, so public CI has not validated the repaired tree yet. Could a maintainer enable those runs?

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

Labels

None yet

Projects

Status: In progress

Development

Successfully merging this pull request may close these issues.

3 participants