Skip to content

Conversation

@Claude
Copy link
Contributor

@Claude Claude AI commented Feb 13, 2026

The entrypoint process (PID 1) retained sensitive tokens in its environment at /proc/1/environ even after the agent cached them via the one-shot-token library. Malicious code could read tokens from the parent process environment throughout execution.

Changes

Entrypoint execution model (containers/agent/entrypoint.sh):

  • Changed from exec to background execution (&) in both chroot and non-chroot modes
  • Added 5-second delay after agent start to allow token caching via one-shot-token library
  • Call unset_sensitive_tokens() to clear tokens from parent shell
  • Wait for agent completion and preserve exit code

Token unsetting function (containers/agent/entrypoint.sh:145-176):

  • Unsets 14 sensitive token types from entrypoint's environment after agent initialization
  • Logs each token cleared from /proc/1/environ

Token list sync (containers/agent/one-shot-token/src/lib.rs):

  • Added GITHUB_PERSONAL_ACCESS_TOKEN and CLAUDE_CODE_OAUTH_TOKEN to default token list
  • Now matches entrypoint.sh token inventory

Timeline

t=0s:    Entrypoint starts agent in background with tokens in environment
t=0-5s:  Agent reads tokens via getenv(), one-shot-token library caches them
t=5s:    Entrypoint unsets all tokens from /proc/1/environ
t=5s+:   Agent continues with cached tokens, /proc/1/environ is clean

Security Impact

  • Before: Tokens accessible via /proc/1/environ for entire agent execution
  • After: Tokens accessible via /proc/1/environ only during first 5 seconds
  • Agent behavior unchanged - still reads tokens via getenv() (cached values)

Testing

Integration tests added in tests/integration/token-unset.test.ts verify:

  • Individual tokens (GITHUB_TOKEN, OPENAI_API_KEY, ANTHROPIC_API_KEY) cleared from /proc/1/environ
  • Multiple tokens cleared simultaneously
  • Agent retains read access via getenv() after unsetting
  • Behavior consistent in both chroot and non-chroot modes

Claude AI and others added 2 commits February 13, 2026 06:04
The entrypoint (PID 1) now unsets all sensitive tokens from its own
environment after starting the agent command. This prevents tokens
from being accessible via /proc/1/environ after the agent has
initialized.

Changes:
- Added unset_sensitive_tokens() function to entrypoint.sh
- Modified both chroot and non-chroot execution paths to:
  1. Start agent command in background (not using exec)
  2. Wait 5 seconds for agent to cache tokens via one-shot-token lib
  3. Unset all sensitive tokens from parent shell
  4. Wait for agent to complete and exit with its exit code
- Updated one-shot-token library to include GITHUB_PERSONAL_ACCESS_TOKEN
  and CLAUDE_CODE_OAUTH_TOKEN in default token list
- Added test script to verify tokens are cleared from /proc/1/environ

Security impact: Closes vulnerability where tokens remained accessible
in /proc/1/environ even after agent cached them via one-shot-token
library.

Co-authored-by: lpcox <[email protected]>
Added comprehensive documentation explaining the security fix that
prevents tokens from being accessible via /proc/1/environ after the
agent has started.

Also added integration tests to verify the fix works correctly.

Co-authored-by: lpcox <[email protected]>
@Claude Claude AI changed the title [WIP] Fix token visibility issue in agent entrypoint fix: unset sensitive tokens from entrypoint environ after agent starts Feb 13, 2026
@Claude Claude AI requested a review from lpcox February 13, 2026 06:07
@github-actions
Copy link
Contributor

github-actions bot commented Feb 13, 2026

Chroot tests passed! Smoke Chroot - All security and functionality tests succeeded.

@github-actions
Copy link
Contributor

github-actions bot commented Feb 13, 2026

🎬 THE ENDSmoke Claude MISSION: ACCOMPLISHED! The hero saves the day! ✨

@github-actions
Copy link
Contributor

github-actions bot commented Feb 13, 2026

📰 VERDICT: Smoke Copilot has concluded. All systems operational. This is a developing story. 🎤

@github-actions
Copy link
Contributor

github-actions bot commented Feb 13, 2026

✅ Coverage Check Passed

Overall Coverage

Metric Base PR Delta
Lines 82.88% 83.04% 📈 +0.16%
Statements 82.88% 83.03% 📈 +0.15%
Functions 82.74% 82.74% ➡️ +0.00%
Branches 74.87% 74.97% 📈 +0.10%
📁 Per-file Coverage Changes (1 files)
File Lines (Before → After) Statements (Before → After)
src/docker-manager.ts 84.7% → 85.3% (+0.61%) 84.2% → 84.8% (+0.60%)

Coverage comparison generated by scripts/ci/compare-coverage.ts

@github-actions
Copy link
Contributor

Build Test Results: C++

Project CMake Build Status
fmt PASS
json PASS

Overall: PASS

All C++ projects built successfully.

AI generated by Build Test C++

@github-actions
Copy link
Contributor

Smoke Test Results

Last 2 Merged PRs:

Test Results:

  • ✅ GitHub MCP: Retrieved PR data
  • ✅ Playwright: Navigated to GitHub, title verified
  • ✅ File Write: Created test file successfully
  • ✅ Bash: Verified file content

Status: PASS

AI generated by Smoke Claude

@github-actions
Copy link
Contributor

Smoke Test Results

Last 2 Merged PRs:

Test Results:

  • ✅ GitHub MCP (retrieved PRs)
  • ✅ Playwright (verified GitHub title)
  • ✅ File creation (smoke-test-copilot-21976652633.txt)
  • ✅ Bash tools (file verified)

Status: PASS 🎉

cc: @Claude @lpcox

AI generated by Smoke Copilot

@github-actions
Copy link
Contributor

Go Build Test Results

Project Download Tests Status
color 1/1 PASS
env 1/1 PASS
uuid 1/1 PASS

Overall: PASS

All Go projects successfully downloaded dependencies and passed tests.

AI generated by Build Test Go

@github-actions
Copy link
Contributor

Build Test: Node.js ✅

All Node.js build tests passed successfully.

Project Install Tests Status
clsx PASS PASS ✅
execa PASS PASS ✅
p-limit PASS PASS ✅

Overall: PASS ✅

AI generated by Build Test Node.js

@github-actions
Copy link
Contributor

Deno Build Test Results

Project Tests Status
oak 1/1 ✅ PASS
std 1/1 ✅ PASS

Overall: ✅ PASS

All Deno tests completed successfully.

AI generated by Build Test Deno

@github-actions
Copy link
Contributor

Bun Build Test Results

Project Install Tests Status
elysia 1/1 PASS
hono 1/1 PASS

Overall: PASS

All tests completed successfully with Bun v1.3.9.

AI generated by Build Test Bun

@github-actions
Copy link
Contributor

.NET Build Test Results

Project Restore Build Run Status
hello-world PASS
json-parse PASS

Overall: PASS

All .NET projects built and ran successfully.

AI generated by Build Test .NET

@github-actions
Copy link
Contributor

Node.js Build Test Results

Project Install Tests Status
clsx PASS PASS
execa PASS PASS
p-limit PASS PASS

Overall: PASS

All Node.js projects successfully installed dependencies and passed their test suites.

AI generated by Build Test Node.js

@github-actions
Copy link
Contributor

Deno Build Test Results

Project Tests Status
oak 1/1 ✅ PASS
std 1/1 ✅ PASS

Overall: ✅ PASS

All Deno tests completed successfully.

AI generated by Build Test Deno

@github-actions
Copy link
Contributor

Smoke Test Results: PASS

✅ GitHub MCP: #816 "Add debug logging for BASE_URL environment variables in agent container", #817 "fix(agent): use AWF_API_PROXY_IP env var for api-proxy iptables rules"
✅ Playwright: github.com title contains "GitHub"
✅ File Write: Created /tmp/gh-aw/agent/smoke-test-claude-21978261178.txt
✅ Bash Tool: Verified file content

Status: All tests passed

AI generated by Smoke Claude

@github-actions
Copy link
Contributor

.NET Build Test Results

Project Restore Build Run Status
hello-world PASS
json-parse PASS

Overall: PASS

All .NET projects successfully restored, built, and executed with expected output.

AI generated by Build Test .NET

@github-actions
Copy link
Contributor

Security Review: Signal Handling Gap

I've reviewed this PR and identified one security concern that should be addressed:

🔴 Issue: Missing Signal Handler for Child Process

File: containers/agent/entrypoint.sh
Lines: 449-468 (chroot mode), 484-499 (non-chroot mode)

Problem: The entrypoint script now runs the agent command in the background but does not install signal handlers to forward SIGTERM/SIGINT to the child process. This creates a potential security issue where:

  1. When Docker sends SIGTERM to the container (during docker stop or cleanup), the entrypoint (PID 1) receives it but does not forward it to the agent child process
  2. The agent process continues running while the entrypoint waits in sleep 5 or wait $AGENT_PID
  3. After Docker's grace period (~10 seconds), Docker sends SIGKILL, which immediately terminates both processes
  4. This prevents graceful cleanup and could leave tokens exposed in /proc/1/environ if the container is killed during the first 5 seconds

Current Code (containers/agent/entrypoint.sh:449-468):

chroot /host /bin/bash -c "
  cd '${CHROOT_WORKDIR}' 2>/dev/null || cd /
  trap '${CLEANUP_CMD}' EXIT
  ${LD_PRELOAD_CMD}
  exec capsh --drop=${CAPS_TO_DROP} --user=${HOST_USER} -- -c 'exec ${SCRIPT_FILE}'
" &
AGENT_PID=$!

# Wait for agent to initialize and cache tokens (5 seconds)
sleep 5

# Unset all sensitive tokens from parent shell environment
unset_sensitive_tokens

# Wait for agent command to complete and capture its exit code
wait $AGENT_PID
exit $?

Security Impact:

  • Medium severity: The 5-second window where tokens remain in /proc/1/environ cannot be interrupted gracefully
  • If a container is stopped during this window, SIGKILL prevents unset_sensitive_tokens() from running
  • Graceful shutdown via SIGTERM is not handled, preventing proper cleanup

Recommended Fix:

Add signal handlers before starting the background process:

# Setup signal handler to forward signals to agent process
cleanup_and_exit() {
  if [ -n "$AGENT_PID" ]; then
    kill -TERM "$AGENT_PID" 2>/dev/null || true
    wait "$AGENT_PID" 2>/dev/null || true
  fi
  exit 143  # Standard exit code for SIGTERM
}

trap cleanup_and_exit TERM INT

# SECURITY: Run agent command in background, then unset tokens from parent shell
chroot /host /bin/bash -c "..." &
AGENT_PID=$!

# Wait for agent to initialize and cache tokens (5 seconds)
sleep 5

# Unset all sensitive tokens from parent shell environment
unset_sensitive_tokens

# Wait for agent command to complete and capture its exit code
wait $AGENT_PID
EXIT_CODE=$?

# Clean up trap
trap - TERM INT

exit $EXIT_CODE

This ensures that:

  1. SIGTERM/SIGINT are properly forwarded to the agent process
  2. The entrypoint can perform graceful cleanup even when interrupted
  3. The container stops predictably and cleanly

✅ Other Security Aspects Reviewed

The following aspects of this PR were reviewed and no security concerns found:

  • Token list completeness: Covers major token types (GitHub, OpenAI, Anthropic, Codex)
  • Execution mode coverage: Both chroot and non-chroot modes are handled
  • Exit code preservation: Agent's exit code is correctly captured and returned
  • Logging to stderr: Token unsetting messages go to stderr, not polluting stdout
  • No capability changes: No weakening of capability dropping or privilege dropping
  • No iptables changes: Network filtering rules unchanged
  • No Squid config changes: Domain ACL enforcement unchanged

The core security improvement (clearing tokens from /proc/1/environ) is sound and improves the security posture by reducing the window where tokens are accessible via the parent process environment.


Recommendation: Add signal handlers as shown above before merging this PR.

AI generated by Security Guard

@github-actions
Copy link
Contributor

Smoke Test Results ✅

Last 2 Merged PRs:

Test Results:

  • ✅ GitHub MCP: Retrieved merged PRs
  • ✅ Playwright: Page title contains "GitHub"
  • ✅ File Write: Created test file successfully
  • ✅ Bash Tool: Verified file content

Status: PASS

cc: @Mossaka

AI generated by Smoke Copilot

@github-actions
Copy link
Contributor

Add debug logging for BASE_URL environment variables in agent container
fix(agent): use AWF_API_PROXY_IP env var for api-proxy iptables rules
GitHub MCP merged PR review ✅
Safeinputs GH PR list ✅
Playwright title check ✅
Tavily search ❌
File write + cat ✅
Discussion comment ✅
Build (npm ci && npm run build) ✅
Overall: FAIL

AI generated by Smoke Codex

@github-actions
Copy link
Contributor

Chroot Test Results

Runtime Host Version Chroot Version Match?
Python Python 3.12.12 Python 3.12.3 ❌ NO
Node.js v24.13.0 v20.20.0 ❌ NO
Go go1.22.12 go1.22.12 ✅ YES

Overall Result: ❌ Tests Failed

The chroot mode is not providing transparent access to host binaries for Python and Node.js. Version mismatches detected.

AI generated by Smoke Chroot

Addresses Security Guard review: when running the agent in the
background, SIGTERM/SIGINT from Docker stop were not being forwarded
to the child process. This adds trap handlers in both chroot and
non-chroot code paths to ensure graceful shutdown during the token
unsetting window.

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
@github-actions
Copy link
Contributor

github-actions bot commented Feb 13, 2026

📰 VERDICT: Smoke Copilot has concluded. All systems operational. This is a developing story. 🎤

@github-actions
Copy link
Contributor

github-actions bot commented Feb 13, 2026

🎬 THE ENDSmoke Claude MISSION: ACCOMPLISHED! The hero saves the day! ✨

@github-actions
Copy link
Contributor

github-actions bot commented Feb 13, 2026

Chroot tests passed! Smoke Chroot - All security and functionality tests succeeded.

@github-actions
Copy link
Contributor

github-actions bot commented Feb 13, 2026

✨ The prophecy is fulfilled... Smoke Codex has completed its mystical journey. The stars align. 🌟

@github-actions
Copy link
Contributor

Rust Build Test Results

Project Build Tests Status
fd 1/1 PASS
zoxide 1/1 PASS

Overall: PASS

All Rust projects built and tested successfully.

AI generated by Build Test Rust

@github-actions
Copy link
Contributor

C++ Build Test Results

Project CMake Build Status
fmt PASS
json PASS

Overall: PASS

All C++ projects built successfully.

AI generated by Build Test C++

@github-actions
Copy link
Contributor

.NET Build Test Results

Project Restore Build Run Status
hello-world PASS
json-parse PASS

Overall: PASS

All .NET projects built and ran successfully.

AI generated by Build Test .NET

@github-actions
Copy link
Contributor

Smoke Test Results

Last 2 Merged PRs:

Test Results:

  • ✅ GitHub MCP: Retrieved last 2 merged PRs
  • ✅ Playwright: Page title contains "GitHub"
  • ✅ File Writing: Created test file successfully
  • ✅ Bash Tool: Verified file contents

Overall Status: PASS

cc: @Mossaka

AI generated by Smoke Copilot

@github-actions
Copy link
Contributor

Go Build Test Results

Project Download Tests Status
color 1/1 PASS
env 1/1 PASS
uuid 1/1 PASS

Overall: PASS

All Go projects successfully downloaded dependencies and passed tests.

AI generated by Build Test Go

@github-actions
Copy link
Contributor

Bun Build Test Results

Project Install Tests Status
elysia 1/1 PASS
hono 1/1 PASS

Overall: PASS

All Bun projects built and tested successfully.

AI generated by Build Test Bun

@github-actions
Copy link
Contributor

Smoke Test Results for Claude (Run #21978456132)

Last 2 Merged PRs:

Test Results:

  • ✅ GitHub MCP: Retrieved PR data
  • ✅ Playwright: Navigated to github.com, verified title contains "GitHub"
  • ✅ File Write: Created /tmp/gh-aw/agent/smoke-test-claude-21978456132.txt
  • ✅ Bash Tool: Read file content successfully

Overall Status: PASS

AI generated by Smoke Claude

@github-actions
Copy link
Contributor

PR titles: [WIP] Fix failing GitHub Actions workflow agent; [WIP] Fix failing GitHub Actions workflow due to unset ANTHROPIC_BASE_URL
GitHub MCP (last 2 merged PRs): ✅
SafeInputs gh pr list: ✅
Playwright title check: ✅
Tavily search: ❌ (tool unavailable)
File write: ✅
Bash cat verify: ✅
Discussion comment: ✅
Build (npm ci && npm run build): ✅
Overall: FAIL

AI generated by Smoke Codex

@github-actions
Copy link
Contributor

Security Review: PR #809 - Token Unsetting from Entrypoint

Summary

This PR introduces a 5-second window of vulnerability where sensitive tokens remain accessible in /proc/1/environ. While the PR attempts to improve security by clearing tokens from the entrypoint's environment, the implementation creates a timing-based race condition that weakens the security posture compared to the previous implementation.


Critical Security Concern

🔴 5-Second Token Exposure Window

Files affected:

  • containers/agent/entrypoint.sh (lines 470-474 and 512-516)

Issue:
The PR removes exec from the command execution and introduces a 5-second sleep before unsetting tokens:

# Line 462-474 (chroot mode)
chroot /host /bin/bash -c "..." &
AGENT_PID=$!

# Wait for agent to initialize and cache tokens (5 seconds)
sleep 5

# Unset all sensitive tokens from parent shell environment
unset_sensitive_tokens

Security impact:

  1. Before this PR: Tokens were accessible in /proc/1/environ throughout agent execution, but the entrypoint process was replaced via exec, so PID 1 became the agent process itself
  2. After this PR: Tokens are accessible in /proc/1/environ for the first 5 seconds, then cleared. However, the entrypoint remains as PID 1 (parent process)

Vulnerability details:

  • Time-of-check-time-of-use (TOCTOU) vulnerability: Malicious code that executes within the first 5 seconds can read tokens from /proc/1/environ
  • Predictable timing: The 5-second delay is hardcoded and known to attackers
  • Race condition: Fast-starting malicious code can reliably read tokens before unsetting occurs
  • Attack surface: Any code that runs immediately on container start (e.g., malicious shell initialization scripts, compromised binaries in PATH) can read tokens

Example attack scenario:

# Malicious .bashrc or .profile
(cat /proc/1/environ | tr '\0' '\n' | grep -E 'TOKEN|KEY|SECRET' > /tmp/stolen-tokens) &

This would execute immediately when the shell starts, well before the 5-second sleep completes.


Additional Concerns

⚠️ Process Management Changes

File: containers/agent/entrypoint.sh (lines 449-457, 496-504)

Change: Removed exec and added signal handlers

Before:

exec chroot /host /bin/bash -c "..."

After:

chroot /host /bin/bash -c "..." &
AGENT_PID=$!
trap cleanup_and_exit TERM INT

Security implications:

  1. Process hierarchy change: The entrypoint no longer replaces itself with the agent command, creating a parent-child process relationship instead
  2. Signal handling complexity: Added signal forwarding logic that could introduce edge cases
  3. Exit code handling: While exit codes are preserved, the additional process layer increases complexity

Note: The signal handling appears to be implemented correctly (forwards SIGTERM/SIGINT to agent, waits for completion, exits with agent's exit code). However, the added complexity increases the attack surface and maintenance burden.


⚠️ C Library Replacement (Lower Priority)

Files affected:

  • containers/agent/one-shot-token/one-shot-token.c (new file, 432 lines)
  • containers/agent/one-shot-token/src/lib.rs (deleted, 403 lines)
  • containers/agent/one-shot-token/build.sh (complete rewrite)

Change: Replaced Rust-based one-shot-token library with C implementation

Security implications:

  1. Memory safety: C is memory-unsafe compared to Rust. Manual memory management increases risk of:

    • Use-after-free vulnerabilities
    • Buffer overflows
    • Memory leaks
    • Double-free bugs
  2. Obfuscation approach: The C implementation uses XOR obfuscation (key=0x5A) for default token names to prevent strings reconnaissance. This provides minimal security - easily reversed by a determined attacker.

  3. Code complexity: The C implementation includes manual mutex handling, thread-local storage, and dlsym resolution that could introduce subtle bugs.

Mitigation: The C code appears to be carefully written with proper error handling and thread safety. However, the inherent memory unsafety of C increases long-term maintenance risk.

Recommendation: Consider keeping the Rust implementation for better memory safety guarantees, or at minimum add comprehensive fuzzing/testing for the C library.


Recommendations

Immediate Actions Required

  1. Remove the 5-second sleep window or reduce it to the absolute minimum (e.g., 100-500ms)
  2. Justify the timing requirement - provide evidence that 5 seconds is necessary for agent initialization
  3. Consider alternative approaches:
    • Keep the original exec behavior and accept that tokens remain in /proc/1/environ (but PID 1 becomes the agent)
    • Use a more sophisticated synchronization mechanism (e.g., inotify, signals) instead of hardcoded sleep
    • Move token management entirely into the one-shot-token library instead of the entrypoint

Long-term Improvements

  1. Add runtime detection: Detect if /proc/1/environ is being read during the vulnerable window and log/alert
  2. Consider Rust library: Evaluate reverting to the Rust implementation for better memory safety
  3. Security testing: Add red-team testing to verify tokens cannot be exfiltrated during the 5-second window

Verdict

⚠️ This PR weakens the security posture and should be revised before merging.

The 5-second token exposure window is a timing-based vulnerability that provides a predictable attack window for credential exfiltration. The previous implementation (using exec) did not have this vulnerability because the entrypoint process was immediately replaced by the agent process.

While the intent to clear tokens from /proc/1/environ is commendable, the implementation introduces a race condition that is worse than the original behavior.

AI generated by Security Guard

@github-actions
Copy link
Contributor

Java Build Test Results ✅

Project Compile Tests Status
gson 1/1 PASS
caffeine 1/1 PASS

Overall: PASS

All Java projects compiled and tested successfully through the AWF firewall with Maven proxy configured.

AI generated by Build Test Java

@github-actions
Copy link
Contributor

🔍 Chroot Version Comparison Results

Runtime Host Version Chroot Version Match?
Python 3.12.12 3.12.3 ❌ NO
Node.js v24.13.0 v20.20.0 ❌ NO
Go go1.22.12 go1.22.12 ✅ YES

Overall Status:FAILED - Not all runtime versions match between host and chroot environment.

Details

  • Python mismatch: Host has patch version 3.12.12, chroot has 3.12.3
  • Node.js mismatch: Host has v24.13.0, chroot has v20.20.0 (different major version)
  • Go match: Both environments have go1.22.12 ✅

The chroot environment should provide transparent access to host binaries, but version mismatches indicate the chroot isolation may not be working correctly for Python and Node.js.

AI generated by Smoke Chroot

@github-actions
Copy link
Contributor

Deno Build Test Results

Project Tests Status
oak 1/1 ✅ PASS
std 1/1 ✅ PASS

Overall: ✅ PASS

All Deno tests completed successfully.

AI generated by Build Test Deno

@Mossaka Mossaka merged commit 9582c57 into main Feb 13, 2026
93 checks passed
@Mossaka Mossaka deleted the claude/unset-tokens-after-agent-start branch February 13, 2026 07:49
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants