Skip to content

Conversation

Copy link
Contributor

Copilot AI commented Feb 10, 2026

Two paths bypass the LD_PRELOAD one-shot token library to leak secrets: reading /proc/self/environ directly, and reading the generated docker-compose.yml via the /host mount.

/proc/self/environ scrubbing

Sensitive env vars are unset in entrypoint.sh before exec, so the new process image's /proc/self/environ is clean. Applied in both chroot and non-chroot paths. Default token list extracted to DEFAULT_SENSITIVE_TOKENS constant; overridable via AWF_ONE_SHOT_TOKENS.

docker-compose.yml redaction

redactComposeSecrets() replaces sensitive values with **REDACTED** immediately after docker compose up returns. The file has already been consumed—redaction is purely to close the read window from inside the container.

# Before: agent could read plaintext tokens
cat /host/tmp/awf-*/docker-compose.yml | grep GITHUB_TOKEN
# GITHUB_TOKEN: ghp_actual_secret

# After: redacted on disk before agent command runs
# GITHUB_TOKEN: '**REDACTED**'
  • SENSITIVE_ENV_NAMES in TypeScript and DEFAULT_SENSITIVE_TOKENS in shell are aligned and include GITHUB_PERSONAL_ACCESS_TOKEN (not in the C library defaults but passed by the CLI)
  • Both mitigations are non-fatal on failure (defense-in-depth, not load-bearing)
  • 9 unit tests added for redactComposeSecrets()
Original prompt

This section details on the original issue you should resolve

<issue_title>fix: restrict /proc/self/environ and docker-compose.yml secret exposure</issue_title>
<issue_description>## Summary

PR #607 adds runtime configuration for the one-shot token library (AWF_ONE_SHOT_TOKENS), which addresses secret leaking via getenv() (path 1 of the attack surface identified in githubnext/gh-aw-security#62).

However, two additional exposure paths remain unmitigated:

1. /proc/self/environ bypasses LD_PRELOAD

The kernel exposes all environment variables of a process through /proc/self/environ. Reading this file directly bypasses getenv() entirely, so the LD_PRELOAD one-shot token library cannot intercept it.

cat /proc/self/environ | tr '\0' '\n' | grep COPILOT_GITHUB_TOKEN

2. Docker Compose file contains plaintext tokens

AWF writes sensitive tokens (e.g., COPILOT_GITHUB_TOKEN) in plaintext into the generated docker-compose.yml at /tmp/awf-*/docker-compose.yml. Since the host filesystem is mounted into the container at /host, the agent can read:

cat /host/tmp/awf-*/docker-compose.yml | grep -A 2 COPILOT_GITHUB_TOKEN

Proposed Mitigations

  1. /proc/self/environ: Mount procfs with restricted access, or overwrite sensitive env vars in the process environment before executing the agent command (after the legitimate consumer has read them).

  2. Docker Compose file: Either:

    • Remove/redact sensitive values from the compose file after containers start
    • Use Docker secrets instead of environment variables
    • Restrict the /host mount to exclude /tmp/awf-*
    • Make the workdir path inaccessible from within the container

Related

Comments on the Issue (you are @copilot in this section)


✨ Let Copilot coding agent set things up for you — coding agent works faster and does higher quality work when set up for your repo.

Copilot AI and others added 2 commits February 10, 2026 07:50
Co-authored-by: Mossaka <5447827+Mossaka@users.noreply.github.com>
Co-authored-by: Mossaka <5447827+Mossaka@users.noreply.github.com>
Copilot AI changed the title [WIP] Fix secret exposure in environ and docker-compose.yml fix: restrict /proc/self/environ and docker-compose.yml secret exposure Feb 10, 2026
Copilot AI requested a review from Mossaka February 10, 2026 07:57
@Mossaka Mossaka marked this pull request as ready for review February 10, 2026 20:48
Copilot AI review requested due to automatic review settings February 10, 2026 20:48
@github-actions
Copy link
Contributor

github-actions bot commented Feb 10, 2026

📰 DEVELOPING STORY: Smoke Copilot reports failed. Our correspondents are investigating the incident...

@github-actions
Copy link
Contributor

github-actions bot commented Feb 10, 2026

Chroot tests failed Smoke Chroot failed - See logs for details.

@github-actions
Copy link
Contributor

github-actions bot commented Feb 10, 2026

💫 TO BE CONTINUED... Smoke Claude failed! Our hero faces unexpected challenges...

@github-actions
Copy link
Contributor

github-actions bot commented Feb 10, 2026

✅ Coverage Check Passed

Overall Coverage

Metric Base PR Delta
Lines 82.02% 82.14% 📈 +0.12%
Statements 82.06% 82.17% 📈 +0.11%
Functions 81.95% 82.05% 📈 +0.10%
Branches 74.41% 74.52% 📈 +0.11%
📁 Per-file Coverage Changes (1 files)
File Lines (Before → After) Statements (Before → After)
src/docker-manager.ts 82.6% → 83.0% (+0.46%) 81.9% → 82.4% (+0.48%)

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

@github-actions github-actions bot mentioned this pull request Feb 10, 2026
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This pull request implements defense-in-depth mitigations for two secret exposure vectors that bypass the LD_PRELOAD one-shot token library: direct reads of /proc/self/environ and reads of the generated docker-compose.yml file via the /host mount.

Changes:

  • Adds environment variable scrubbing in entrypoint.sh before exec to clean /proc/self/environ of sensitive tokens in both chroot and non-chroot execution paths
  • Adds redactComposeSecrets() function to replace sensitive environment values with **REDACTED** in docker-compose.yml immediately after docker compose up completes
  • Adds 9 unit tests covering redaction behavior, edge cases, and token list verification

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 1 comment.

File Description
src/docker-manager.ts Added SENSITIVE_ENV_NAMES constant and redactComposeSecrets() function to redact secrets from docker-compose.yml; integrated redaction after container startup
src/docker-manager.test.ts Added comprehensive test suite (9 tests) for redactComposeSecrets() and SENSITIVE_ENV_NAMES validation
containers/agent/entrypoint.sh Added DEFAULT_SENSITIVE_TOKENS constant and environment scrubbing logic before exec in both chroot and non-chroot paths to prevent /proc/self/environ exposure
containers/agent/one-shot-token/README.md Updated documentation to note that /proc/self/environ direct reads are now mitigated by AWF's entrypoint scrubbing

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +902 to +909
if (env && typeof env === 'object') {
for (const key of Object.keys(env)) {
if (SENSITIVE_ENV_NAMES.has(key) && env[key]) {
env[key] = '**REDACTED**';
redacted = true;
}
}
}
Copy link

Copilot AI Feb 10, 2026

Choose a reason for hiding this comment

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

The environment type check could be more specific to avoid silent failures. Docker Compose supports both object format (environment: {KEY: value}) and array format (environment: [KEY=value]). While this codebase only generates object format, a more defensive check would be if (env && typeof env === 'object' && !Array.isArray(env)) to explicitly skip array-formatted environments rather than silently processing them incorrectly. Currently, if env is an array, Object.keys(env) would return string indices ("0", "1", etc.) which wouldn't match SENSITIVE_ENV_NAMES, causing the redaction to silently skip those entries.

Copilot uses AI. Check for mistakes.
Mossaka and others added 2 commits February 10, 2026 20:57
- Remove shell-level `unset` of sensitive tokens from entrypoint.sh.
  The shell unset ran before exec, so tokens were gone from the
  environment before the one-shot-token library could cache them,
  breaking authentication (Copilot CLI could not find tokens).

- Add __attribute__((constructor)) to one-shot-token.c that eagerly
  caches all sensitive token values and calls unsetenv() at library
  load time (before main()). This closes the /proc/self/environ
  exposure window without breaking getenv() access.

- Write docker-compose.yml with mode 0600 to prevent the agent
  container (running as awfuser) from reading secrets via /host mount.
  This eliminates the TOCTOU race between container start and redaction.

- Fix CodeQL TOCTOU alert in redactComposeSecrets by replacing
  existsSync+readFileSync with a single readFileSync in try/catch.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@Mossaka
Copy link
Collaborator

Mossaka commented Feb 10, 2026

Security Review & Fixes

Reviewed this PR and addressed the following issues:

Issues Found & Fixed (in cf5fff4)

1. Shell-level unset breaks authentication (Critical)
The entrypoint.sh was unsetting sensitive env vars (e.g., COPILOT_GITHUB_TOKEN) before exec, so the one-shot-token LD_PRELOAD library never saw them. This caused all smoke tests to fail with "No authentication information found".

Fix: Removed the shell-level unset and moved /proc/self/environ scrubbing into the C library via __attribute__((constructor)). The constructor eagerly caches all sensitive token values and calls unsetenv() at library load time (before main()), so /proc/self/environ is clean from process start while getenv() still returns cached values.

2. CodeQL TOCTOU race in redactComposeSecrets (Security)
existsSync() followed by readFileSync() is a classic time-of-check-time-of-use race.

Fix: Replaced with a single readFileSync() inside a try/catch.

3. docker-compose.yml readable by agent container (Security)
The file was written with default permissions, allowing the agent (running as awfuser) to read plaintext tokens from /host/tmp/awf-*/docker-compose.yml between container start and redaction.

Fix: Write docker-compose.yml with 0o600 permissions (root-only readable). Since AWF runs as root and the agent runs as awfuser, the file is unreadable from inside the container. This eliminates the TOCTOU race window between docker compose up and redactComposeSecrets().

Also merged origin/main to pick up the C library caching changes from #640.

Remaining notes (not blocking)

  • --env-all / --env can pass custom secrets not in SENSITIVE_ENV_NAMES — those won't be redacted or scrubbed. Worth documenting.
  • AWF_ONE_SHOT_TOKENS is referenced in entrypoint.sh but never explicitly passed to the container env (only works with --env-all). Pre-existing issue.

@github-actions
Copy link
Contributor

github-actions bot commented Feb 10, 2026

📰 DEVELOPING STORY: Smoke Copilot reports failed. Our correspondents are investigating the incident...

@github-actions
Copy link
Contributor

github-actions bot commented Feb 10, 2026

Chroot tests failed Smoke Chroot failed - See logs for details.

@github-actions
Copy link
Contributor

github-actions bot commented Feb 10, 2026

💫 TO BE CONTINUED... Smoke Claude failed! Our hero faces unexpected challenges...

@github-actions
Copy link
Contributor

github-actions bot commented Feb 10, 2026

🌑 The shadows whisper... Smoke Codex failed. The oracle requires further meditation...

@Mossaka Mossaka closed this Feb 10, 2026
@github-actions
Copy link
Contributor

❌ Build Test: Java - FAILED

Status: Unable to execute tests due to environment issues.

Issue

Java/Maven commands cannot be executed in the current AWF agent environment (chroot mode). All Java commands return bash version information instead of executing:

$ java -version
GNU bash, version 5.2.21(1)-release (x86_64-pc-linux-gnu)
...

Environment Details

  • Chroot mode: Enabled (AWF_CHROOT_ENABLED=true)
  • Java available at: /opt/hostedtoolcache/Java_Temurin-Hotspot_jdk/21.0.10-7/x64
  • Maven available at: /usr/bin/mvn
  • All binaries are in PATH but cannot execute properly

Test Results

Project Compile Tests Status
gson BLOCKED
caffeine BLOCKED

Overall: FAILED - Cannot execute Java/Maven commands

Recommendation

This test should be run in a standard GitHub Actions environment with the actions/setup-java action, not through the AWF agent container in chroot mode.

AI generated by Build Test Java

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.

fix: restrict /proc/self/environ and docker-compose.yml secret exposure

2 participants