Skip to content

feat: policy-gated tool approval (v1.1)#2

Open
sterling-prog wants to merge 9 commits into
mainfrom
build/v1.1-tool-approval
Open

feat: policy-gated tool approval (v1.1)#2
sterling-prog wants to merge 9 commits into
mainfrom
build/v1.1-tool-approval

Conversation

@sterling-prog
Copy link
Copy Markdown
Owner

Summary

  • Adds src/policy/tool-policy.ts — pure policy evaluation functions (no I/O)
  • Wires policy into handleServerRequest() in app-server.ts — commandExecution and fileChange are now approve/deny per rules; permissions always denied
  • Ships tool-policy.json with sane defaults: denies shells/privesc/network tools, allows read/build/git ops, protects ~/.openclaw, ~/.ssh, /etc, /var
  • 43 unit tests covering all acceptance criteria (43/43 passing)
  • CLAUDE.md updated with invariant #11, forbidden pattern #3, env vars

Test plan

  • npm test — 43/43 pass
  • CODEX_TOOL_APPROVAL=deny npm test — kill switch: all tool requests denied
  • Smoke: start proxy, send a request that triggers commandExecution/requestApproval with command: "git diff HEAD" → approved in logs
  • Smoke: send command: "sudo rm -rf /" → denied in logs
  • Smoke: send fileChange/requestApproval with grantRoot: "/home/gpu1/.openclaw/..." → denied in logs

Spec: specs/codex-proxy/v1.1-tool-approval.spec.md

🤖 Generated with Claude Code

Sterling and others added 9 commits March 21, 2026 20:22
Bug 1: add return after triggerCleanup in emitDelta when accumulatedSize
exceeds maxSize — without it, subsequent deltas still accumulate content
after the error cleanup is triggered.

Bug 2: call triggerCleanup after sendNonStreamingResponse in
onTokenUsageUpdated grace period path — slot was never released,
in-flight entry was never removed, archive never ran.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
In onAgentMessageDelta, destructured turnId was never applied to
inflight.turnId. Spec requires: delta notifications themselves carry
turnId and must set inflight.turnId if not already set, triggering
buffer replay. Without this, the race window where deltas arrive before
both turn/start response and turn/started notification leaves the buffer
unresolved until turn/started eventually arrives.

Fix: if !inflight.turnId && turnId, set inflight.turnId = turnId and
call flushDeltaBuffer before falling through to emit current delta.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The script only checked top-level keys (accessToken, access_token, token)
but the actual auth.json nests the token under tokens.access_token.
This caused hourly false alarms in #infra-agent-swarm.
…lat threadId

Three issues found and fixed during production deploy:

1. ecosystem.config.js → .cjs: package.json has "type": "module",
   PM2 can't load CommonJS module.exports in ESM context.

2. model/list and thread/list responses use `data` field, not
   `models`/`threads`. Fixed in types and app-server code.

3. TurnCompleted notification sends flat `threadId`, not nested
   `thread.id`. Made handler resilient to both shapes.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Changed ALERT_CHANNEL from #infra-agent-swarm (1475832162648461316) to
#status-updates (1485787606561062942) to reduce noise in the architecture
discussion channel.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Implements per-request approve/deny decisions for Codex CLI tool execution
requests instead of v1.0 blanket denial.

- src/policy/tool-policy.ts: pure evaluation functions for commandExecution
  and fileChange. Configurable via ~/codex-proxy/tool-policy.json, reloaded
  on SIGHUP. Fail-safe: missing/malformed config → all-deny.
- src/types/codex.ts: add CommandExecutionParams, FileChangeParams,
  NetworkApprovalContext, CommandAction, and approval response types.
- src/client/app-server.ts: wire policy evaluation into handleServerRequest().
  commandExecution and fileChange use policy; permissions always denied;
  legacy methods fall through to policy.
- tool-policy.json: default config — denylist blocks shells/privesc/network
  tools; allowlist covers read/build/git/util ops; protected paths cover
  ~/.openclaw, ~/.ssh, /etc, /var; allowedWritePaths: ~/codex-proxy and /tmp.
- CLAUDE.md: add invariant #11, forbidden pattern #3, env vars.
- tests/tool-policy.test.mjs: 43 tests covering allowlist, denylist priority,
  word-boundary matching, command chaining, protected paths, network denial,
  null/missing params, commandActions auto-approve, kill switch, fail-safe.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
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