Skip to content

feat(agent route): answer only Claude Code and Codex (ADR-0013 step 3) - #113

Merged
nniiovoo merged 1 commit into
mainfrom
feat/agent-program-check
Nov 10, 2026
Merged

nniiovoo merged 1 commit into
mainfrom
feat/agent-program-check

Conversation

@nniiovoo

Copy link
Copy Markdown
Owner

ADR-0013 step 3: the agent key route (ADR-0012) now answers only the agent's tool, enote-agent-keys, with the code hash the owner approved, when Claude Code or Codex started it. Before this, any program running in the agent account could use the route.

What it checks

Each connection gets these checks after the existing account (uid) check. Any failure closes the connection without a reply, the same way a wrong account is handled.

  1. The tool: the connecting program is valid, and its code hash matches the one saved in Settings.
  2. Its starter: the program that started the tool is on ALLOWED_STARTERS, with identity, team and a Developer ID category, as macOS validated them:
    • com.anthropic.claude-code, team Q6L2SF6YDW
    • codex, team 2DC432GLL2
  3. Both programs are closed to outside code:
    • they have hardened runtime;
    • they are not debuggable or being debugged;
    • their entitlements name neither allow-dyld-environment-variables nor get-task-allow.
  4. Re-check: after the decision, the tool's audit token must still be valid and its parent unchanged. This stops a reused process number from slipping through.

Everything is read from the kernel's record of the running programs, never from their files:

  • csops_audittoken, through the socket's LOCAL_PEERTOKEN, for the tool;
  • sysctl(KERN_PROC_PID) for its parent, with the kinfo_proc size and p_pid checked;
  • csops for the parent.

The ADR-0013 spike (#112) found that the Security framework's checks are refused under the App Sandbox, and so is proc_pidinfo for another account.

Choices made

  • The starter list is fixed in code. Settings holds only the tool's hash, so nothing the agent account can write can widen the list.
  • Claude Code's disable-library-validation is accepted, as you decided in the spike. Only the two entitlements that let outside code in are refused.
  • The hash comes from the setup script. scripts/agent-keys-setup.sh prints it as the CDHash= line, and refuses a tool that wasn't signed with hardened runtime. You paste that line into Settings → "The agent tool's code hash".
  • Start needs a hash, and changing it ends a grant. Start refuses without a hash (not_set_up).
  • The setup script no longer prints the Claude desktop app's chat config. That app isn't on the list.
  • ProgramCheck::Off exists only in debug builds. It is used by tests, and by ENOTE_DEV_AGENT_ANY_PROGRAM=1 for trying the route in one account.

Tests

  • Every branch of judge_programs, using made-up identities.
  • The kernel readers against a running /bin/sleep.
  • On a real socket, Apple's nc stands in for the tool and bash for its starter:
    • answered when both are listed;
    • closed without a reply for the real list, a wrong tool hash, or a direct connection with no starter.
  • Saving, checking and normalizing the tool hash, and how it ends a grant.

./scripts/check.sh passed locally in full. That includes clippy, cargo deny, the secret scan, the network-source check, the WebView build and all 38 agent route tests.

Not verified

  • Not run from real Claude Code or Codex. Nothing has run in a real agent account yet. The positive tests use Apple's programs, with a test-only starter list.
  • Not tried in a Developer ID-signed, notarized build. That includes whether the kernel reads behave the same there as in the spike's sandboxed test app.
  • Settings is only type-checked. The new field hasn't been looked at by hand.
  • No security review yet.

Fictional data and stand-in programs only.

🤖 Generated with Claude Code

The agent key route now answers, inside the agent account, only the
agent's tool with the code hash the owner approved in Settings, started
by Claude Code or Codex as validated by macOS (Developer ID, teams
Q6L2SF6YDW and 2DC432GLL2). Both must have hardened runtime, be neither
debuggable nor debugged, and name neither allow-dyld-environment-
variables nor get-task-allow in their entitlements. Claude Code's
disabled library validation is accepted, as the owner decided.

Everything is read from the kernel's record of the running programs,
never from their files, since the ADR-0013 spike showed the Security
framework's checks are refused under the App Sandbox while these work:
csops_audittoken through the connection's audit token for the tool,
sysctl(KERN_PROC_PID) for its parent (kinfo_proc size and fields
checked; proc_pidinfo is refused for other accounts) and csops for the
parent, then a re-check that the tool and its parent are unchanged.
Anything else is closed without a reply, as a wrong account is.

- agent_route.rs: ProgramIdentity, ProgramPolicy, judge_programs on
  every platform; check_program and the kernel readers on a Mac; the
  listener runs the check after the account check. ProgramCheck::Off
  exists only in debug builds (ENOTE_DEV_AGENT_ANY_PROGRAM, tests).
- Settings: "The agent tool's code hash" (the CDHash line), kept in
  settings.json; Start refuses without it; changing it ends a grant.
  The Start dialog says Claude Code or Codex.
- agent-keys-setup.sh prints the tool's code hash and refuses a tool
  not signed with hardened runtime; it no longer prints the Claude
  desktop app's chat config, which isn't on the list.
- Tests: every branch of the decision; the kernel readers against a
  running /bin/sleep; on a real socket, Apple's nc as the tool and
  bash as its starter, answered when both are listed and closed without
  a reply for the real list, a wrong tool hash, or a direct connection.
- Docs: ADR-0013 As built, the guide, the security model, AGENTS.md,
  CONTEXT.md.

Fictional data and stand-in programs only; not yet run from Claude Code
or Codex in a real agent account, nor in a Developer ID-signed build.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@nniiovoo
nniiovoo merged commit d4dc0e9 into main Nov 10, 2026
2 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