Skip to content

fix(#5832): strip OIDC credentials from user-controlled environments - #5837

Merged
ifireball merged 3 commits into
mainfrom
agent/5832-block-mint-oidc-leak
Aug 3, 2026
Merged

fix(#5832): strip OIDC credentials from user-controlled environments#5837
ifireball merged 3 commits into
mainfrom
agent/5832-block-mint-oidc-leak

Conversation

@fullsend-ai-coder

Copy link
Copy Markdown
Contributor

Summary

Strip OIDC credential env vars from all user-controlled and sandbox-visible contexts to prevent user-authored scripts and LLM sessions from minting their own tokens. The parent harness process retains credentials for mintAgentToken and OIDC token refresh.

Changes

  • Add oidcDenyKeys denylist for 4 OIDC env vars (ACTIONS_ID_TOKEN_REQUEST_URL, ACTIONS_ID_TOKEN_REQUEST_TOKEN, FULLSEND_GCP_OIDC_URL, FULLSEND_GCP_OIDC_AUTH_FILE)
  • childScriptEnv: strip OIDC vars from pre/post script environments
  • shellSafeExpandEnv: refuse OIDC vars in host_files ${VAR} expansion
  • reservedSandboxKeys: block OIDC vars from env.sandbox injection
  • Harness expander/lookup closures: refuse OIDC vars in runner_env/env expansion
  • Preflight check: use childScriptEnv instead of raw os.Environ()
  • Validation scripts: use new scrubOIDCEnv helper to filter os.Environ()

Testing

  • Unit tests for childScriptEnv stripping OIDC vars (from process env and RunnerEnv)
  • Unit test for shellSafeExpandEnv refusing OIDC vars
  • Unit test for reservedSandboxKeys including OIDC vars
  • Unit test for buildSandboxEnvLines rejecting OIDC vars
  • Unit test for scrubOIDCEnv helper
  • Unit test for oidcDenyKeys completeness
  • All existing childScriptEnv, shellSafeExpandEnv, and buildSandboxEnvLines tests pass
  • go vet ./internal/cli/... passes
  • Secret scan passes

Closes #5832

Post-script verification

  • Branch is not main/master (agent/5832-block-mint-oidc-leak)
  • Secret scan passed (gitleaks — 93bd394a743d4db23c088b016423657c18f85058..HEAD)
  • PR body secret scan passed (gitleaks — no-git)

Prevent user-authored pre/post scripts, validation commands, and LLM
sessions from minting their own tokens by removing OIDC credential
env vars (ACTIONS_ID_TOKEN_REQUEST_URL, ACTIONS_ID_TOKEN_REQUEST_TOKEN,
FULLSEND_GCP_OIDC_URL, FULLSEND_GCP_OIDC_AUTH_FILE) from all
user-controlled and sandbox-visible contexts.

The parent harness process retains OIDC credentials for
mintAgentToken and background token refresh.

Controls implemented:
- childScriptEnv: strips OIDC vars from pre/post script environments
- shellSafeExpandEnv: refuses OIDC vars in host_files expansion
- reservedSandboxKeys: blocks OIDC vars from env.sandbox injection
- Harness expander/lookup: refuses OIDC vars in ${VAR} expansion
- Preflight check: uses childScriptEnv instead of raw os.Environ()
- Validation scripts: scrubs OIDC vars from os.Environ()

Closes #5832

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@fullsend-ai-coder
fullsend-ai-coder Bot requested a review from a team as a code owner August 2, 2026 12:11
@fullsend-ai-coder fullsend-ai-coder Bot added the ready-for-review Agent PR ready for human review label Aug 2, 2026
@fullsend-ai-review

fullsend-ai-review Bot commented Aug 2, 2026

Copy link
Copy Markdown

🤖 Finished Review · ✅ Success · Started 12:12 PM UTC · Completed 12:28 PM UTC
Commit: aa44151 · View workflow run →

@codecov

codecov Bot commented Aug 2, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@fullsend-ai-review

fullsend-ai-review Bot commented Aug 2, 2026

Copy link
Copy Markdown

Review

Findings

Low

  • [behavioral-inconsistency] internal/cli/run.go:530 — The expander closure returns "" for OIDC keys (silent empty expansion), while the lookup closure returns ("", false) (causes validation to fail). The different semantics are intentional and well-documented — lookup returning false causes ValidateRunnerEnvWith to treat the reference as unresolvable and fail-closed, which is the safer behavior. Minor maintainability concern only.

  • [deny-list-completeness] internal/cli/run.go:1899 — The oidcDenyKeys list remains a static set of four known OIDC credential variables. This deny list would not automatically catch new OIDC-related environment variables introduced by future mint infrastructure changes. A prefix-based approach (stripping all vars matching ACTIONS_ID_TOKEN_* or FULLSEND_GCP_OIDC_*) would be more resilient. The code includes a MAINTENANCE comment documenting the convention and noting that a single addition propagates to all deny checks.

Previous run

Review

Findings

Medium

  • [incomplete-credential-stripping] internal/cli/run.go:2059 — The host_files src path expansion uses os.ExpandEnv(hf.Src), which bypasses the oidcDenyKeys deny check applied at all other expansion sites in this PR. If a harness YAML declared host_files with src: '${FULLSEND_GCP_OIDC_AUTH_FILE}', the path would resolve to the actual OIDC auth file location and its contents could be uploaded into the sandbox. While host_files configuration is authored by trusted harness maintainers (limiting practical exploitability), this is an inconsistency in the defense-in-depth strategy — every other ${VAR} expansion path now checks oidcDenyKeys.
    Remediation: Replace os.ExpandEnv(hf.Src) with os.Expand(hf.Src, expander) using the same oidcDenyKeys-aware expander closure, or create a standalone wrapper function for reuse across all expansion sites.

Low

  • [behavioral-inconsistency] internal/cli/run.go:530 — The expander closure returns "" for OIDC keys (silent empty expansion), while the lookup closure returns ("", false) (causes validation to fail). The different semantics are intentional and well-documented — lookup returning false causes ValidateRunnerEnvWith to treat the reference as unresolvable and fail-closed, which is the safer behavior. Minor maintainability concern only.

  • [deny-list-completeness] internal/cli/run.go:1899 — The oidcDenyKeys list covers the four known OIDC credential variables. The deny list is static and would not catch new OIDC-related environment variables added in future mint infrastructure changes. Consider a prefix-based approach (e.g., ACTIONS_ID_TOKEN_*, FULLSEND_GCP_OIDC_*) or at minimum a doc comment noting that new credential variables must be added here.

Previous run (2)

Review

Findings

Medium

  • [incomplete-scrubbing-path] internal/cli/run.go:1623 — The validation script env is composed as append(scrubOIDCEnv(os.Environ()), validationEnv(h, ..., ...)...). The validationEnv function calls envToList(h.RunnerEnv) which appends RunnerEnv entries after scrubbing. If h.RunnerEnv contained an OIDC key (e.g., a harness YAML declaring runner_env: { ACTIONS_ID_TOKEN_REQUEST_URL: "literal-value" }), the key would survive scrubbing and appear in the validation script's environment. In contrast, childScriptEnv strips OIDC keys from the merged env (os.Environ + RunnerEnv). The same pattern applies at the post-loop sweep (line 2193). Practical risk is low — exploitation requires a deliberately malicious harness YAML — but this is a defense-in-depth gap worth closing.
    Remediation: Apply scrubOIDCEnv to the full composed slice, or switch to childScriptEnv for validation scripts.

  • [duplication-dry] internal/cli/run.go — The four OIDC key strings are duplicated between oidcDenyKeys and reservedSandboxKeys with no programmatic link. A future addition to oidcDenyKeys could silently miss the sandbox path.
    Remediation: Add OIDC keys to reservedSandboxKeys programmatically via init() or a shared slice constant.

Low

  • [behavioral-inconsistency] internal/cli/run.go:530 — The expander closure returns "" for OIDC keys (silent empty expansion), while the lookup closure returns ("", false) (causes validation to fail). The different semantics are intentional — fail-closed validation is the safer choice — but the divergence could trip a future maintainer.

  • [naming-consistency] internal/cli/run.go — The function is named scrubOIDCEnv but inline comments and the PR title use "strip." Picking one verb consistently would aid discoverability.


Labels: PR is a security hardening fix affecting the harness, sandbox, and mint components — matches the linked issue's component labels

fullsend-ai-review[bot]

This comment was marked as outdated.

@fullsend-ai-review fullsend-ai-review Bot added requires-manual-review Review requires human judgment security Security threat model and related concerns component/harness Agent harness, config, and skills loading component/sandbox OpenShell sandbox environment component/mint Token mint and cross-boundary credentials labels Aug 2, 2026
@ifireball

Copy link
Copy Markdown
Member

/fs-fix

@fullsend-ai-coder

fullsend-ai-coder Bot commented Aug 2, 2026

Copy link
Copy Markdown
Contributor Author

🤖 Finished Fix · ✅ Success · Started 12:58 PM UTC · Completed 1:06 PM UTC
Commit: aa44151 · View workflow run →

- Close incomplete scrubbing path: apply stripOIDCEnv to the full composed
  env slice (os.Environ + validationEnv) so OIDC keys from h.RunnerEnv
  cannot survive scrubbing in validation and post-loop sweep contexts.
- Eliminate DRY violation: replace hardcoded OIDC entries in
  reservedSandboxKeys with init() that merges from oidcDenyKeys, so
  future additions automatically block sandbox injection.
- Document intentional behavioral divergence between expander (silent
  empty expansion) and lookup (fail-closed validation) for OIDC keys.
- Rename scrubOIDCEnv → stripOIDCEnv for naming consistency with
  comments and PR title.

Addresses review feedback on #5837
@fullsend-ai-coder

Copy link
Copy Markdown
Contributor Author

🔧 Fix agent — iteration 1 (human-triggered)

Addressed all 4 review findings: closed the incomplete scrubbing path for validation envs, eliminated the DRY violation between oidcDenyKeys and reservedSandboxKeys via init(), documented the intentional expander/lookup behavioral divergence, and renamed scrubOIDCEnv to stripOIDCEnv for naming consistency.

Fixed (4):

  1. incomplete-scrubbing-path: validation script env composed with scrubOIDCEnv only on os.Environ(), leaving RunnerEnv OIDC keys unscrubbed (internal/cli/run.go): Moved stripOIDCEnv to wrap the full composed slice (os.Environ + validationEnv) at both the inline validation (line 1627) and post-loop sweep (line 2193) call sites, so OIDC keys injected via h.RunnerEnv are also stripped.
  2. duplication-dry: OIDC key strings duplicated between oidcDenyKeys and reservedSandboxKeys with no programmatic link (internal/cli/run.go): Replaced hardcoded OIDC entries in reservedSandboxKeys with an init() function that merges keys from oidcDenyKeys. Future additions to oidcDenyKeys automatically propagate to sandbox injection blocking.
  3. behavioral-inconsistency: expander returns empty string for OIDC keys while lookup returns (empty, false) with no documentation of the intentional divergence (internal/cli/run.go): Added a 3-line comment to the lookup closure explaining that it intentionally returns false (unlike expander's silent empty expansion) so ValidateRunnerEnvWith treats the reference as unresolvable and fails validation.
  4. naming-consistency: function named scrubOIDCEnv but comments and PR title use strip (internal/cli/run.go): Renamed scrubOIDCEnv to stripOIDCEnv and updated all call sites, doc comments, and test names to use strip consistently.

Tests: passed

Next steps:

  • /fs-review — request a re-review of the changes
  • /fs-fix <your instruction> — run another fix pass with specific guidance
  • Push commits directly — review re-runs automatically on push
    Updated by fullsend fix agent

@fullsend-ai-review

fullsend-ai-review Bot commented Aug 2, 2026

Copy link
Copy Markdown

🤖 Finished Review · ✅ Success · Started 1:07 PM UTC · Completed 1:22 PM UTC
Commit: 846df0f · View workflow run →

fullsend-ai-review[bot]

This comment was marked as outdated.

@ifireball

Copy link
Copy Markdown
Member

/fs-fix the following:

  • internal/cli/run.go (file-level): Line 2059 · [medium] incomplete-credential-stripping

    The host_files src path expansion uses os.ExpandEnv(hf.Src), which bypasses the oidcDenyKeys deny check applied at all other expansion sites. If a harness YAML declared host_files with src: '${FULLSEND_GCP_OIDC_AUTH_FILE}', the path would resolve to the actual OIDC auth file location and its contents could be uploaded into the sandbox. While host_files configuration is authored by trusted harness maintainers, this is an inconsistency in the defense-in-depth strategy.

    Suggested fix: Replace os.ExpandEnv(hf.Src) with os.Expand(hf.Src, expander) using the same oidcDenyKeys-aware expander closure, or create a standalone wrapper function for reuse across all expansion sites.

  • internal/cli/run.go:530: [low] behavioral-inconsistency

    The expander closure returns '' for OIDC keys (silent empty expansion), while the lookup closure returns ('', false) (causes validation to fail). The different semantics are intentional and well-documented. Minor maintainability concern only.

  • internal/cli/run.go:1899: [low] deny-list-completeness

    The oidcDenyKeys list covers four known OIDC credential variables. The deny list is static and would not catch new OIDC-related environment variables added in future mint infrastructure changes.

    Suggested fix: Consider adding a doc comment noting that new credential variables must be added to oidcDenyKeys, or switch to prefix matching for ACTIONS_ID_TOKEN_ and FULLSEND_GCP_OIDC_ prefixes.

@fullsend-ai-coder

fullsend-ai-coder Bot commented Aug 3, 2026

Copy link
Copy Markdown
Contributor Author

🤖 Finished Fix · ✅ Success · Started 10:04 AM UTC · Completed 10:13 AM UTC
Commit: 846df0f · View workflow run →

Replace os.ExpandEnv(hf.Src) in host_files src path expansion with a
new safeExpandEnv helper that consults oidcDenyKeys, closing the last
expansion site that could leak OIDC credential paths into the sandbox.

Also add a MAINTENANCE doc comment to oidcDenyKeys noting the prefix
convention (ACTIONS_ID_TOKEN_ / FULLSEND_GCP_OIDC_) so future credential
vars are added to the deny list.

Addresses review feedback on #5837
@fullsend-ai-coder

Copy link
Copy Markdown
Contributor Author

🔧 Fix agent — iteration 2 (human-triggered)

Addressed 2 of 3 review findings. Fixed the host_files src expansion gap by creating a safeExpandEnv helper that consults oidcDenyKeys. Added maintenance documentation to oidcDenyKeys noting prefix conventions. Disagreed with finding 2 (behavioral-inconsistency) as the different semantics are intentional and well-documented.

Fixed (2):

  1. incomplete-credential-stripping in host_files src expansion (internal/cli/run.go): Replaced os.ExpandEnv(hf.Src) with new safeExpandEnv(hf.Src) helper that consults oidcDenyKeys, closing the last expansion site that bypassed the OIDC credential deny check. Added test TestSafeExpandEnv_RefusesOIDCVars to verify the fix.
  2. deny-list-completeness documentation for oidcDenyKeys (internal/cli/run.go): Added MAINTENANCE doc comment to oidcDenyKeys noting that new OIDC credential vars must be added to the map, documenting the ACTIONS_ID_TOKEN_ and FULLSEND_GCP_OIDC_ prefix conventions, and clarifying that all expansion sites consult this single map.

Disagreed (1):

  1. behavioral-inconsistency between expander and lookup closures: The different semantics (expander returns empty string for silent expansion vs lookup returns false to fail validation) are intentional and already well-documented in the code comments. The reviewer themselves noted this is a 'Minor maintainability concern only' with no actionable change needed.

Tests: passed

Decision points
  • Created standalone safeExpandEnv helper rather than inlining the deny check (alternatives: Inline os.Expand with oidcDenyKeys check at the call site; rationale: A standalone function is reusable at any future expansion site and matches the pattern of shellSafeExpandEnv. The function is placed adjacent to shellSafeExpandEnv for discoverability.)
  • Used doc comment for deny-list maintenance guidance rather than prefix matching (alternatives: Switch to prefix-based matching for ACTIONS_ID_TOKEN_ and FULLSEND_GCP_OIDC_; rationale: Prefix matching would change the security boundary from explicit enumeration to pattern-based, which is a larger scope change than what the review requested. A doc comment is the minimal, safe improvement.)

Next steps:

  • /fs-review — request a re-review of the changes
  • /fs-fix <your instruction> — run another fix pass with specific guidance
  • Push commits directly — review re-runs automatically on push
    Updated by fullsend fix agent

@fullsend-ai-review

fullsend-ai-review Bot commented Aug 3, 2026

Copy link
Copy Markdown

🤖 Finished Review · ✅ Success · Started 10:14 AM UTC · Completed 10:31 AM UTC
Commit: 79017d8 · View workflow run →

Comment thread internal/cli/run.go
Comment thread internal/cli/run.go
@fullsend-ai-review fullsend-ai-review Bot added ready-for-merge All reviewers approved — ready to merge and removed requires-manual-review Review requires human judgment labels Aug 3, 2026
@ifireball
ifireball added this pull request to the merge queue Aug 3, 2026
@github-merge-queue
github-merge-queue Bot removed this pull request from the merge queue due to failed status checks Aug 3, 2026
@ifireball
ifireball added this pull request to the merge queue Aug 3, 2026
Merged via the queue into main with commit 8c50dd4 Aug 3, 2026
23 checks passed
@ifireball
ifireball deleted the agent/5832-block-mint-oidc-leak branch August 3, 2026 11:50
@fullsend-ai-retro

fullsend-ai-retro Bot commented Aug 3, 2026

Copy link
Copy Markdown

🤖 Finished Retro · ✅ Success · Started 11:53 AM UTC · Completed 12:09 PM UTC
Commit: 79017d8 · View workflow run →

@fullsend-ai-retro

Copy link
Copy Markdown

Retro: PR #5837 — strip OIDC credentials from user-controlled environments

Timeline

  1. Aug 2, 12:11 — Code agent opened PR with initial implementation (commit aa44151)
  2. Aug 2, 12:28 — Review agent posted 2 medium + 2 low findings (all true positives)
  3. Aug 2, 12:57 — Human (ifireball) triggered /fs-fix
  4. Aug 2, 13:06 — Fix agent addressed all 4 findings (commit 846df0f)
  5. Aug 2, 13:22 — Re-review found 1 new medium + 2 low findings
  6. Aug 3, 10:03 — Human triggered second /fs-fix (~21h later)
  7. Aug 3, 10:13 — Fix agent addressed 2 of 3, reasonably disagreed with 1 (commit 79017d8)
  8. Aug 3, 10:31 — Review agent approved (only 2 low findings remaining)
  9. Aug 3, 11:02 — Human approved; merged at 11:50

Workflow quality

Review quality was excellent. The review agent produced 9/9 true positives and 0 false positives across 3 iterations. Its two most impactful findings were genuine security gaps: (1) OIDC keys from RunnerEnv surviving stripping due to env composition order (scrubOIDCEnv applied before validationEnv was appended), and (2) os.ExpandEnv(hf.Src) in host_files processing bypassing the oidcDenyKeys deny-list entirely. Both were real defense-in-depth gaps in a credential-isolation PR.

Fix agent quality was high. Both fix commits were well-scoped, correctly targeted, and included tests. The fix agent's disagreement with the behavioral-inconsistency finding was sound — the expander and lookup closures serve different purposes and the divergence was intentional.

Token waste: minimal. Concurrency deduplication worked correctly. One early review run was cancelled by cancel-in-progress within 6 seconds. The 5 review runs visible in the dispatch repo at commit 3 time were for 5 different PRs, not duplicates. Source-repo dispatch triggers that didn't match any stage resolved in ~3 seconds.

Functional test flake: Test 002-needs-info-vague-crash failed on commit 2 (run 30749220000) with a 1-second crash ($0.00 cost), passed on commits 1 and 3. This was an infrastructure-level failure (instant crash during execution phase, not scoring) unrelated to the OIDC changes. Single data point — not filing a proposal, but noting it here.

Evidence for existing issues (not filing new proposals)

No new proposals

All candidate improvements identified in this retro are already tracked by existing open issues. The workflow performed well — the review agent caught real security gaps, the fix agent resolved them correctly, and the pipeline converged in 3 iterations with no wasted work.

waynesun09 added a commit that referenced this pull request Aug 3, 2026
Pre-scripts can no longer mint their own tokens since #5837 stripped the
OIDC mint credentials from the pre-script environment (#5832, enforcing
ADR 0073). Record it as a consequence annotation so the open forge-call
ownership question reads as token possession, not minting capability.

Assisted-by: Claude
Signed-off-by: Wayne Sun <gsun@redhat.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

component/harness Agent harness, config, and skills loading component/mint Token mint and cross-boundary credentials component/sandbox OpenShell sandbox environment ready-for-merge All reviewers approved — ready to merge ready-for-review Agent PR ready for human review security Security threat model and related concerns

Projects

None yet

Development

Successfully merging this pull request may close these issues.

mint: block token minting from user-controlled scripts and LLM sessions

1 participant