Skip to content

fix(agent): separate Claude tool process authority - #3116

Open
daryllimyt wants to merge 1 commit into
agent/fix-nsjail-bind-mount-safetyfrom
daryl/eng-1552-claude-runtime-hardening
Open

fix(agent): separate Claude tool process authority#3116
daryllimyt wants to merge 1 commit into
agent/fix-nsjail-bind-mount-safetyfrom
daryl/eng-1552-claude-runtime-hardening

Conversation

@daryllimyt

@daryllimyt daryllimyt commented Jul 22, 2026

Copy link
Copy Markdown
Contributor

Summary

  • run Claude Code as UID 1000 with a private writable runtime home
  • demote native Bash commands and root/subagent stdio MCP servers to UID 1001 with zero capabilities and a separate private home
  • keep /work as the only writable filesystem shared by both identities
  • make Claude's built-in and MCP tool inventory explicit and fail closed
  • retain native Write/Edit while denying paths that do not resolve beneath /work
  • disable user/project settings and require strict runtime-owned MCP configuration

Stack

Verification

  • 158 focused unit tests passed; one Linux-only test skipped on macOS
  • Ruff check and format passed
  • basedpyright passed with zero errors or warnings
  • executor test image built successfully
  • real Linux/nsjail UID test passed: Claude UID 1000 retains only CAP_SETUID; tool UID 1001 has no capabilities; private homes are mutually inaccessible; /work remains shared
  • nsjail session smoke harness completed successfully

Merge blocker

This PR is intentionally a draft. The required race exercise proved that PreToolUse path validation is not atomic with Anthropic's native Write/Edit execution: a concurrent UID 1001 process can replace a validated /work directory component with a pre-created symlink before UID 1000 performs the mutation, causing the operation to reach Claude's private home.

Do not merge until native mutation is made atomic with path enforcement or authoritative session state is moved outside Claude's writable home. Mount-wide nosymfollow is not included because it would disable legitimate symlink use throughout /work.


Summary by cubic

Isolates Claude from model-controlled tools by running Claude as UID 1000 with a private home and demoting Bash and stdio MCP processes to UID 1001 with no capabilities, sharing only /work. Tightens runtime to a fail-closed tool inventory and enforces safe filesystem writes per ENG-1552.

  • New Features

    • nsjail UID/GID maps: Claude UID 1000 retains only CAP_SETUID; tools run as UID 1001 with zero caps; shared setgid /work (02770); tmpfs-backed private homes at /home/agent and /home/tools.
    • Trusted shim wraps Bash and stdio MCP servers: verifies identity, demotes to tool UID, clears all caps, preserves no_new_privs, and passes argv/env via a base64 payload; blocks env boundary overrides; tools get a private HOME/TMP and cannot read Claude state.
    • Fail-closed inventories: built-in tools limited to a known set; stdio MCP tools must be explicitly listed; PreToolUse denies MCP calls not in the runtime inventory; removes wildcard acceptance.
    • Safe native writes: Write/Edit paths are canonicalized under /work and reject traversal, null bytes, missing parents, and symlink escapes; Bash commands are executed via the trusted wrapper.
    • Runtime env hardening: Claude process uses private XDG dirs and TMP under /home/agent; only /work is shared; direct mode prepares the same dirs (without UID isolation) for parity.
    • nsjail config updates: explicit uidmap/gidmap, CAP_SETUID, strict tmpfs modes, mounts for /home/agent, /home/tools, and /work with correct ownership and permissions.
  • Dependencies

    • Docker: install uidmap; create tools user (UID 1001) and subordinate UID range for apiuser; validate newuidmap and /etc/subuid at runtime.

Written for commit 863c020. Summary will update on new commits.

Review in cubic

@daryllimyt daryllimyt added fix Bug fix security Security related issue agents LLM agents infrastructure Infra updates and fixes labels Jul 22, 2026
@daryllimyt
daryllimyt marked this pull request as ready for review July 24, 2026 23:10
@zeropath-ai

zeropath-ai Bot commented Jul 24, 2026

Copy link
Copy Markdown

No security or compliance issues detected. Reviewed everything up to 863c020.

Security Overview
Detected Code Changes
Change Type Relevant files
Enhancement ► tracecat/agent/runtime/claude_code/runtime.py
    Add _ToolCommandSpec dataclass, introduce native tool command wrapping, and related constants (NATIVE_MUTATION_TOOLS, BASE_NATIVE_TOOL_INVENTORY, JAILED_TOOL_WRAPPER_COMMAND/ SCRIPT/ MODE)
Enhancement ► tracecat/agent/runtime/claude_code/runtime.py
    Introduce _tool_command_spec, _wrapped_bash_input, and integrate tool-wrapping logic into tool invocation flow
Enhancement ► tracecat/agent/runtime/claude_code/transport.py
    Update Claude env overlay to use private home/work paths (HOME, XDG_CONFIG_HOME, XDG_CACHE_HOME, XDG_STATE_HOME, TMPDIR/TEMP/TMP) via runtime path mapping
Enhancement ► tracecat/agent/sandbox/config.py
    Add UID/GID constants (AGENT_CLAUDE_UID, AGENT_TOOL_UID, AGENT_SHARED_GID, AGENT_TOOL_OUTSIDE_UID) and update base env with new traces; include UID mappings and tool UID configuration pieces
Enhancement ► tracecat/agent/sandbox/nsjail.py
    Introduce NEWUIDMAP_PATH and SUBUID_PATH references; prepare for UID mapping validation integration
Enhancement ► tracecat/agent/runtime/session_paths.py
    Define JAILED_TOOL_HOME_DIR; adjust directory setup for tool private state
Enhancement ► tracecat/agent/sandbox/config.py
    Expose AGENT_CLAUDE_UID, AGENT_TOOL_UID, AGENT_SHARED_GID, AGENT_TOOL_OUTSIDE_UID; extend AGENT_SANDBOX_BASE_ENV with new TRACECAT__... variables; update NSJAIL config snippets to reflect new UID/GID mappings and private tool home mounts; add ephemeral private tool home mount config
Enhancement ► tracecat/agent/sandbox/nsjail.py
    Add support for subordinate UID mapping helpers; introduce _prepare_runtime_directories helper for private Claude home and shared work directory with specific permissions
Enhancement ► tests/unit/test_agent_sandbox_entrypoint.py
    Add tests referencing _tool_environment, private tool env behavior, and tool wrapper payload decoding helpers
Enhancement ► tests/unit/test_agent_runtime.py
    Add decode_tool_wrapper_payload helper for asserting tool wrapper payloads and adjust tests to accommodate new private tool handling
Enhancement ► tests/unit/test_agent_runtime_broker.py
    Add test for transport env overlay using private Claude home and work paths
Enhancement ► tests/unit/test_nsjail_seccomp.py
    Add tests verifying Claude/tool UID separation, private tool UID mapping expectations, and related config generation checks

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 863c0207e9

ℹ️ 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".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +1260 to +1261
resolved_parent = candidate.parent.resolve(strict=True)
resolved_parent.relative_to(host_root)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Make native Write/Edit path enforcement atomic

When a UID 1001 Bash or stdio MCP process can modify a directory beneath /work concurrently, it can replace a directory component with a symlink after resolved_parent is validated but before Claude's UID 1000 native Write/Edit tool opens the returned path. The native operation then follows the new symlink and can modify Claude-private state under /home/agent, defeating the authority separation; the path must be enforced atomically with the mutation rather than checked in this separate hook.

Useful? React with 👍 / 👎.

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

Labels

agents LLM agents fix Bug fix infrastructure Infra updates and fixes security Security related issue

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant