Skip to content

Security: exclude all secret-bearing env vars from agent container via AWF --exclude-env#23360

Merged
lpcox merged 4 commits intomainfrom
copilot/fix-env-all-exposes-secrets
Mar 28, 2026
Merged

Security: exclude all secret-bearing env vars from agent container via AWF --exclude-env#23360
lpcox merged 4 commits intomainfrom
copilot/fix-env-all-exposes-secrets

Conversation

Copy link
Copy Markdown
Contributor

Copilot AI commented Mar 28, 2026

When --env-all passes the full runner environment into the AWF container, sensitive tokens are visible to any bash tool the agent runs — creating a prompt-injection exfiltration vector in pull_request_target workflows. AWF v0.26.0 adds --exclude-env to strip specific vars from the container's visible environment while the API proxy continues to handle auth transparently.

Changes

  • pkg/constants/constants.go: Bump DefaultFirewallVersionv0.26.0; add AWFExcludeEnvMinVersion = "v0.26.0" constant marking the minimum AWF version that supports --exclude-env
  • pkg/workflow/awf_helpers.go:
    • Add ExcludeEnvVarNames []string field to AWFCommandConfig
    • Add ComputeAWFExcludeEnvVarNames(workflowData, coreSecretVarNames) helper that dynamically computes the exact exclude list per workflow by scanning for actual ${{ secrets.* }} references across: engine core tokens, MCP gateway key, GitHub MCP server token, HTTP MCP header secrets, mcp-scripts env vars, engine.env, and agent.env
    • Add awfSupportsExcludeEnv(firewallConfig) helper that gates --exclude-env emission on the effective AWF version being ≥ v0.26.0 (latest and default/unset are treated as ≥ the minimum)
    • In BuildAWFArgs, emit sorted --exclude-env flags only when the effective AWF version supports it — workflows pinning an older version (e.g. v0.25.0) compile without the flag to avoid runtime failures
  • Engines (Copilot, Claude, Codex, Gemini): each calls ComputeAWFExcludeEnvVarNames with its core secret var names so the exclude list is workflow-specific and complete
  • Tests / golden files: Updated to verify per-engine dynamic exclude lists; new integration test cases assert GITHUB_MCP_SERVER_TOKEN is excluded when tools.github is configured, and that --exclude-env is absent when an old AWF version is pinned; TestAWFSupportsExcludeEnv unit tests cover all version edge cases
  • Lock files: All 178 workflows recompiled with engine-specific --exclude-env lists, e.g.:
# Copilot workflow with GitHub tool + MCP servers:
sudo -E awf ... --env-all \
  --exclude-env COPILOT_GITHUB_TOKEN \
  --exclude-env GITHUB_MCP_SERVER_TOKEN \
  --exclude-env MCP_GATEWAY_API_KEY \
  --enable-api-proxy ...

# Codex workflow with shared/gh.md (mcp-scripts secret):
sudo -E awf ... --env-all \
  --exclude-env CODEX_API_KEY \
  --exclude-env GH_AW_GH_TOKEN \
  --exclude-env OPENAI_API_KEY \
  --enable-api-proxy ...

Non-secret mcp-scripts vars (e.g. GH_DEBUG: "1") are not excluded — only vars whose configured values contain a ${{ secrets.* }} reference are excluded.

Requires the companion AWF change (gh-aw-firewall#1482) to land before this takes effect at runtime.

[!WARNING]


🔒 GitHub Advanced Security automatically protects Copilot coding agent pull requests. You can protect all pull requests by enabling Advanced Security for your repositories. Learn more about Advanced Security.

…om agent container env via AWF --exclude-env flag

Agent-Logs-Url: https://github.com/github/gh-aw/sessions/f77df111-2010-4960-b0c4-805f269dc54c

Co-authored-by: lpcox <15877973+lpcox@users.noreply.github.com>
Copilot AI changed the title [WIP] Fix env-all exposing secrets in pull_request_target workflows Security: exclude COPILOT_GITHUB_TOKEN and GITHUB_MCP_SERVER_TOKEN from agent container env via AWF --exclude-env Mar 28, 2026
Copilot AI requested a review from lpcox March 28, 2026 16:59
@github-actions
Copy link
Copy Markdown
Contributor

Hey @Copilot 👋 — great work on this security hardening! Excluding COPILOT_GITHUB_TOKEN and GITHUB_MCP_SERVER_TOKEN from the agent container's visible environment is a meaningful defence-in-depth improvement against prompt-injection exfiltration attacks, and the PR is clean and well-structured.

A few small observations worth addressing before this is promoted out of draft:

  • Integration test coverage is asymmetricfirewall_args_integration_test.go only asserts --exclude-env COPILOT_GITHUB_TOKEN in all three test cases, but never asserts --exclude-env GITHUB_MCP_SERVER_TOKEN. The unit test in gh_cli_mount_test.go covers both, so the gap is in the integration tests. Adding the second assertion would make the coverage symmetric and guard against a future regression where only one token is excluded.
  • Draft status — the PR is still in draft. Once the companion firewall change (gh-aw-firewall#1482) has landed or is confirmed ready, mark this ready for review.
In pkg/workflow/firewall_args_integration_test.go, for every existing assertion that checks
  strings.Contains(lockYAML, "--exclude-env COPILOT_GITHUB_TOKEN")
add an immediately-following assertion that checks
  strings.Contains(lockYAML, "--exclude-env GITHUB_MCP_SERVER_TOKEN")
with an appropriate t.Error message (e.g. "Compiled workflow should exclude GITHUB_MCP_SERVER_TOKEN via --exclude-env").
Apply this to all three test cases in the file (TestFirewallArgs with custom AWF args, without custom AWF args, and with SSL bump).

Generated by Contribution Check ·

@lpcox
Copy link
Copy Markdown
Collaborator

lpcox commented Mar 28, 2026

@copilot make sure that the compiler is very strict about which environment variables are passed to awf

@lpcox lpcox marked this pull request as ready for review March 28, 2026 17:22
Copilot AI review requested due to automatic review settings March 28, 2026 17:22
@lpcox lpcox added the security:medium-severity A security issue that should be addressed within one week. label Mar 28, 2026
Copy link
Copy Markdown
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 PR hardens AWF container execution by ensuring sensitive GitHub tokens are not exposed to the agent container environment when --env-all is used.

Changes:

  • Bump default AWF version to v0.26.0 (to pick up --exclude-env support).
  • Add an exclusion list for sensitive env vars and emit --exclude-env flags after --env-all in AWF command construction.
  • Update tests and golden/lock outputs to assert the new flags are present.

Reviewed changes

Copilot reviewed 116 out of 186 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
pkg/constants/constants.go Bumps default AWF version and introduces the excluded env var list.
pkg/workflow/awf_helpers.go Appends --exclude-env flags to AWF args generation.
pkg/workflow/gh_cli_mount_test.go Adds assertions that both sensitive env vars are excluded.
pkg/workflow/firewall_args_integration_test.go Adds assertions that compiled workflows include --exclude-env (currently only for one token).
pkg/workflow/testdata/wasm_golden/** Updates wasm golden outputs for AWF version and new flags.
.github/workflows/*.lock.yml Recompiled workflows reflecting AWF version bump and --exclude-env additions.
.changeset/patch-exclude-secret-env-vars-from-agent-container.md Adds release note for the security change and version bump.

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

Comment on lines +148 to +157
// Pass all environment variables to the container, but exclude sensitive tokens.
// AWF's API proxy (--enable-api-proxy) handles authentication for COPILOT_GITHUB_TOKEN
// and GITHUB_MCP_SERVER_TOKEN transparently, so the container does not need the raw
// values. Excluding them via --exclude-env prevents a prompt-injected agent from
// exfiltrating tokens through bash tools such as `env` or `printenv`.
// Requires AWF v0.26.0+ for --exclude-env support.
awfArgs = append(awfArgs, "--env-all")
for _, excludedVar := range constants.AWFExcludedEnvVars {
awfArgs = append(awfArgs, "--exclude-env", excludedVar)
}
Copy link

Copilot AI Mar 28, 2026

Choose a reason for hiding this comment

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

BuildAWFArgs always appends --exclude-env flags, but the AWF version can be overridden via network.firewall.version (and the install step uses that version). If a workflow pins an older AWF (< v0.26.0), the compiled command will include an unsupported flag and fail at runtime. Consider gating --exclude-env emission on the effective AWF version (e.g., default or firewallConfig.Version), only adding it for v0.26.0+ (and possibly latest).

Copilot uses AI. Check for mistakes.
Comment on lines +82 to +86
// Verify sensitive token vars are excluded
if !strings.Contains(lockYAML, "--exclude-env COPILOT_GITHUB_TOKEN") {
t.Error("Compiled workflow should exclude COPILOT_GITHUB_TOKEN via --exclude-env")
}

Copy link

Copilot AI Mar 28, 2026

Choose a reason for hiding this comment

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

This test asserts --exclude-env COPILOT_GITHUB_TOKEN but does not assert --exclude-env GITHUB_MCP_SERVER_TOKEN, even though the implementation adds both exclusions. Adding the second assertion would prevent regressions where only one token remains excluded.

This issue also appears in the following locations of the same file:

  • line 148
  • line 242

Copilot uses AI. Check for mistakes.
Comment on lines +373 to +383
// AWFExcludedEnvVars lists the environment variable names that must be excluded from the
// agent container's visible environment via AWF's --exclude-env flag (requires AWF v0.26.0+).
//
// These are sensitive GitHub tokens that the agent process should never be able to read
// directly. AWF's API proxy handles authentication for these tokens transparently, so the
// container does not need the raw values. Excluding them prevents a prompt-injection attack
// from exfiltrating tokens via bash tools such as `env` or `printenv`.
var AWFExcludedEnvVars = []string{
"COPILOT_GITHUB_TOKEN",
"GITHUB_MCP_SERVER_TOKEN",
}
Copy link

Copilot AI Mar 28, 2026

Choose a reason for hiding this comment

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

AWFExcludedEnvVars is an exported, mutable slice. Any importing package could append/modify it at runtime, which could inadvertently weaken the intended security behavior. Consider making it unexported and exposing a function that returns a copy, or otherwise ensuring callers can’t mutate the underlying slice.

Copilot uses AI. Check for mistakes.
@lpcox
Copy link
Copy Markdown
Collaborator

lpcox commented Mar 28, 2026

@copilot apply changes based on the comments in this thread

Copy link
Copy Markdown
Contributor Author

Copilot AI commented Mar 28, 2026

@copilot make sure that the compiler is very strict about which environment variables are passed to awf

Done in 922df6f. The compiler now dynamically computes the exact --exclude-env list per workflow via ComputeAWFExcludeEnvVarNames():

  • Engine-specific core tokens (e.g. COPILOT_GITHUB_TOKEN, ANTHROPIC_API_KEY, CODEX_API_KEY/OPENAI_API_KEY, GEMINI_API_KEY)
  • MCP_GATEWAY_API_KEY — only when MCP servers are configured
  • GITHUB_MCP_SERVER_TOKEN — only when the GitHub tool is configured
  • HTTP MCP header secrets — all vars using ${{ secrets.* }} references
  • mcp-scripts env vars — only those with secret references (non-secret vars like GH_DEBUG: "1" are not excluded)
  • engine.env / agent.env vars — any custom var whose value references a secret

The hardcoded AWFExcludedEnvVars constant is removed. Each engine calls ComputeAWFExcludeEnvVarNames(workflowData, []string{"ENGINE_TOKEN"}) so coverage is automatic for any workflow configuration.

Warning

Firewall rules blocked me from connecting to one or more addresses (expand for details)

I tried to connect to the following addresses, but was blocked by firewall rules:

  • https://api.github.com/graphql
    • Triggering command: /usr/bin/gh /usr/bin/gh api graphql -f query=query($owner: String!, $name: String!) { repository(owner: $owner, name: $name) { hasDiscussionsEnabled } } -f owner=github -f name=gh-aw (http block)
    • Triggering command: /usr/bin/gh /usr/bin/gh api graphql -f query=query($owner: String!, $name: String!) { repository(owner: $owner, name: $name) { hasDiscussionsEnabled } } -f owner=github -f name=gh-aw ota=5% yHigh=170M 64/pkg/tool/linu../../../**/*.json conf�� w/js/**/*.json&#39; --ignore-path credential.helpe../../../.prettierignore 64/pkg/tool/linux_amd64/vet (http block)
    • Triggering command: /usr/bin/gh /usr/bin/gh api graphql -f query=query($owner: String!, $name: String!) { repository(owner: $owner, name: $name) { hasDiscussionsEnabled } } -f owner=github -f name=gh-aw rt/yaml (http block)
  • https://api.github.com/repos/astral-sh/setup-uv/git/ref/tags/eac588ad8def6316056a12d4907a9d4d84ff7a3b
    • Triggering command: /usr/bin/gh gh api /repos/astral-sh/setup-uv/git/ref/tags/eac588ad8def6316056a12d4907a9d4d84ff7a3b --jq .object.sha h ../../../.pret.prettierignore --local x_amd64/vet committer.email (http block)
    • Triggering command: /usr/bin/gh gh api /repos/astral-sh/setup-uv/git/ref/tags/eac588ad8def6316056a12d4907a9d4d84ff7a3b --jq .object.sha h ../../../.prettierignore ace-editor.lock.yml agent-performance-analyzer.lock.yml agent-persona-exnode agentic-observab/home/REDACTED/work/gh-aw/gh-aw/actions/setup/js/node_modules/.bin/pre�� ai-moderator.loc--write archie.lock.yml arti�� w/js/**/*.json&#39; --ignore-path auto-triage-issu../../../.prettierignore blog-auditor.lock.yml bot-detection.lodocker brave.lock.yml breaking-change-inspect changeset.lock.yghcr.io/github/serena-mcp-server:latest (http block)
  • https://api.github.com/repos/github/gh-aw
    • Triggering command: /usr/bin/gh gh api /repos/github/gh-aw --jq .visibility (http block)
    • Triggering command: /usr/bin/gh gh api /repos/github/gh-aw --jq .visibility --write ../../../**/*.json er: String!, $name: String!) { repository(owner: $owner, name:-f --ignore-path ../../../.pretti-C x_amd64/vet git -C /home/REDACTED/work/gh-aw/gh-aw/.github/workflows rev-parse r: $owner, name: $name) { hasDiscussionsEnabled } } --get-regexp --local x_amd64/vet git (http block)
  • https://api.github.com/repos/github/gh-aw-actions/git/ref/tags/v0
    • Triggering command: /usr/bin/gh gh api /repos/github/gh-aw-actions/git/ref/tags/v0 --jq .object.sha h ../../../.pret.prettierignore --local /node gpg.program (http block)
    • Triggering command: /usr/bin/gh gh api /repos/github/gh-aw-actions/git/ref/tags/v0 --jq .object.sha h ../../../.pret.prettierignore ace-editor.lock.--log-level=error agent-performance-analyzer.lock.yml agent-persona-exmake agentic-observabbuild ai-moderator.lock.yml archie.lock.yml arti�� w/js/**/*.json&#39; --ignore-path ../../../.prettierignore auto-triage-issues.lock.yml blog-auditor.lock.yml bot-detection.logit brave.lock.yml breaking-change-/home/REDACTED/work/gh-aw/gh-aw/.github/workflows changeset.lock.yconfig (http block)
  • https://api.github.com/repos/githubnext/agentics/git/ref/tags/
    • Triggering command: /usr/bin/gh gh api /repos/githubnext/agentics/git/ref/tags/# --jq .object.sha --local credential.username $name) { hasDiscussionsEnabled } } (http block)
    • Triggering command: /usr/bin/gh gh api /repos/githubnext/agentics/git/ref/tags/# --jq .object.sha ithub/workflows (http block)

If you need me to access, download, or install something from one of these locations, you can either:

Copilot AI changed the title Security: exclude COPILOT_GITHUB_TOKEN and GITHUB_MCP_SERVER_TOKEN from agent container env via AWF --exclude-env Security: exclude all secret-bearing env vars from agent container via AWF --exclude-env Mar 28, 2026
…R_TOKEN assertions

Agent-Logs-Url: https://github.com/github/gh-aw/sessions/ce30b6f6-2d89-4cd0-b64d-2a863bdd9854

Co-authored-by: lpcox <15877973+lpcox@users.noreply.github.com>
Copy link
Copy Markdown
Contributor Author

Copilot AI commented Mar 28, 2026

@copilot apply changes based on the comments in this thread

Done in c63ab88. Applied all three review changes:

  1. Version gating (awf_helpers.go:148-157): Added AWFExcludeEnvMinVersion = "v0.26.0" constant and awfSupportsExcludeEnv(firewallConfig) helper. BuildAWFArgs now only emits --exclude-env when the effective AWF version is ≥ v0.26.0. Workflows pinning e.g. v0.25.0 compile without the flag.

  2. Missing GITHUB_MCP_SERVER_TOKEN assertions (firewall_args_integration_test.go:82-86, 148, 242): Added two new integration test cases — one with tools: github: asserting both COPILOT_GITHUB_TOKEN and GITHUB_MCP_SERVER_TOKEN are excluded, and one pinning v0.25.0 asserting --exclude-env is absent. Also added TestAWFSupportsExcludeEnv unit tests covering nil/empty/latest/v0.26.0/v0.27.0/v0.25.0.

  3. Exported mutable slice (constants.go:373-383): Already addressed in the previous commit (922df6f) — the AWFExcludedEnvVars slice was removed entirely, replaced by ComputeAWFExcludeEnvVarNames() which returns a fresh slice each call.

Warning

Firewall rules blocked me from connecting to one or more addresses (expand for details)

I tried to connect to the following addresses, but was blocked by firewall rules:

  • https://api.github.com/graphql
    • Triggering command: /usr/bin/gh /usr/bin/gh api graphql -f query=query($owner: String!, $name: String!) { repository(owner: $owner, name: $name) { hasDiscussionsEnabled } } -f owner=github -f name=gh-aw (http block)
    • Triggering command: /usr/bin/gh /usr/bin/gh api graphql -f query=query($owner: String!, $name: String!) { repository(owner: $owner, name: $name) { hasDiscussionsEnabled } } -f owner=github -f name=gh-aw ire (http block)
    • Triggering command: /usr/bin/gh /usr/bin/gh api graphql -f query=query($owner: String!, $name: String!) { repository(owner: $owner, name: $name) { hasDiscussionsEnabled } } -f owner=github -f name=gh-aw ota=5% yHigh=170M 64/pkg/tool/linu-f conf�� ../pkg/workflow/-f gpg.program r: $owner, name:-f (http block)
  • https://api.github.com/repos/astral-sh/setup-uv/git/ref/tags/eac588ad8def6316056a12d4907a9d4d84ff7a3b
    • Triggering command: /usr/bin/gh gh api /repos/astral-sh/setup-uv/git/ref/tags/eac588ad8def6316056a12d4907a9d4d84ff7a3b --jq .object.sha h ../../../.prettierignore (http block)
  • https://api.github.com/repos/github/gh-aw
    • Triggering command: /usr/bin/gh gh api /repos/github/gh-aw --jq .visibility get --local 64/pkg/tool/linux_amd64/vet commit.gpgsign (http block)
  • https://api.github.com/repos/github/gh-aw-actions/git/ref/tags/v0
    • Triggering command: /usr/bin/gh gh api /repos/github/gh-aw-actions/git/ref/tags/v0 --jq .object.sha h ../../../.pret.prettierignore (http block)
  • https://api.github.com/repos/githubnext/agentics/git/ref/tags/
    • Triggering command: /usr/bin/gh gh api /repos/githubnext/agentics/git/ref/tags/# --jq .object.sha (http block)

If you need me to access, download, or install something from one of these locations, you can either:

@lpcox lpcox merged commit 531594a into main Mar 28, 2026
82 checks passed
@lpcox lpcox deleted the copilot/fix-env-all-exposes-secrets branch March 28, 2026 17:45
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

security:medium-severity A security issue that should be addressed within one week.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Security: --env-all exposes secrets to prompt-injected agent in pull_request_target workflows

3 participants