Skip to content

fix(sandbox): scrub dynamic credential env vars#682

Open
PierrunoYT wants to merge 3 commits into
Gitlawb:mainfrom
PierrunoYT:agent/scrub-dynamic-secret-env
Open

fix(sandbox): scrub dynamic credential env vars#682
PierrunoYT wants to merge 3 commits into
Gitlawb:mainfrom
PierrunoYT:agent/scrub-dynamic-secret-env

Conversation

@PierrunoYT

@PierrunoYT PierrunoYT commented Jul 14, 2026

Copy link
Copy Markdown
Contributor

Summary

  • pass every resolved provider profile's custom apiKeyEnv into the sandbox engine
  • scrub those config-derived names in Linux, macOS, and Windows sandbox command plans
  • scrub case-insensitive ZERO_OAUTH_<NAME>_CLIENT_SECRET variables for dynamically named OAuth providers
  • keep the existing catalog-derived and static secret scrubbing behavior centralized

Root cause

The sandbox environment scrubber knew only a static secret list and the built-in provider catalog. Custom provider profiles can name arbitrary credential variables through apiKeyEnv, and OAuth provider names create client-secret variables dynamically, so neither category was represented in that fixed set.

Sandboxed commands could therefore inherit those credentials even though built-in provider secrets were removed.

Fixes #676

Validation

  • go fmt ./...
  • go vet ./...
  • go test ./internal/sandbox ./internal/cli -count=1
  • repository-wide go test ./... -count=1 completed successfully for the changed packages and all but unrelated Windows process-lifecycle tests
  • go run golang.org/x/vuln/cmd/govulncheck@v1.3.0 ./...: no vulnerabilities found
  • pinned golangci-lint reported the same 35 pre-existing findings tracked by Pinned lint and vulnerability commands cannot analyze the Go 1.26 module #680 and none in this patch

Known baseline/environment limitations

  • TestLoadProviderCommandTimeout takes about 10 seconds instead of its expected 5 seconds on this host.
  • TestManagerCheckRealGopls fails because the installed gopls process exits with EOF.
  • Several internal/tools process-termination and temporary-directory cleanup tests fail on Windows because child processes retain file handles.
  • make lint uses a Unix shell assignment in fmt-check that does not run under this Windows shell.

Summary by CodeRabbit

  • Security Improvements

    • Sandbox commands now scrub additional provider credential environment variables using resolved configuration, consistently across Linux, macOS, and Windows.
    • Improved handling of dynamically named OAuth client-secret environment variables (case-insensitive ZERO_OAUTH_*_CLIENT_SECRET pattern).
  • Bug Fixes

    • Fixed cases where sensitive configured keys were not removed in all execution paths, including degraded fallback scenarios.
  • Tests

    • Added regression coverage to verify configured and pattern-based sensitive environment scrubbing behavior.

@coderabbitai

coderabbitai Bot commented Jul 14, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: d9a25532-0261-4ced-87a3-0a58086003d2

📥 Commits

Reviewing files that changed from the base of the PR and between b0086c4 and 7b59fc8.

📒 Files selected for processing (2)
  • internal/sandbox/runner.go
  • internal/sandbox/runner_test.go
🚧 Files skipped from review as they are similar to previous changes (1)
  • internal/sandbox/runner.go

Walkthrough

The change passes provider-configured API key environment names into sandbox engines, propagates them through command plans, and scrubs them alongside dynamically named ZERO_OAUTH_*_CLIENT_SECRET variables across supported platforms.

Changes

Sensitive environment scrubbing

Layer / File(s) Summary
Engine sensitive-key contract
internal/sandbox/engine.go, internal/sandbox/runner.go
EngineOptions accepts sensitive environment keys, which are normalized, stored, and propagated into command specifications.
CLI key collection and platform wiring
internal/cli/app.go, internal/cli/exec.go, internal/cli/sandbox_sensitive_env_test.go, internal/sandbox/runner.go, internal/sandbox/windows_runner.go
Provider profile and active-provider APIKeyEnv values are passed through interactive and exec sandbox setup into Linux, macOS, Windows, and direct command environment construction.
Environment scrubbing and validation
internal/sandbox/runner.go, internal/sandbox/runner_test.go
Configured keys are normalized and removed from command environments; dynamic OAuth client-secret names are also detected, with tests covering fallback plans and safe-variable preservation.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Sequence Diagram(s)

sequenceDiagram
  participant CLI as CLI sandbox setup
  participant Engine as Sandbox Engine
  participant Plan as Command Plan
  participant Scrubber as Environment Scrubber
  CLI->>Engine: Pass provider-sensitive environment keys
  Engine->>Plan: Propagate normalized keys
  Plan->>Scrubber: Build and scrub command environment
  Scrubber-->>Plan: Return filtered environment
Loading

Possibly related PRs

  • Gitlawb/zero#660: Both changes modify sandbox environment scrubbing and command-plan environment filtering.
  • Gitlawb/zero#685: Both changes modify secret-related environment filtering in internal/sandbox/runner.go.

Suggested reviewers: kevincodex1, vasanthdev2004, euxaristia

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title matches the main change: sandbox environment scrubbing for dynamic credential variables.
Linked Issues check ✅ Passed The PR addresses #676 by passing custom apiKeyEnv values into sandboxing, scrubbing dynamic ZERO_OAUTH_*_CLIENT_SECRET names, and adding regression tests.
Out of Scope Changes check ✅ Passed The changes stay focused on sandbox env scrubbing and related command-plan plumbing, with no clear unrelated additions.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@PierrunoYT PierrunoYT marked this pull request as ready for review July 14, 2026 21:05
Copilot AI review requested due to automatic review settings July 14, 2026 21:05

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

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 closes a security gap in the sandbox runner by expanding secret environment-variable scrubbing to include configuration-derived provider credential env var names (apiKeyEnv) and dynamically named OAuth client-secret variables (ZERO_OAUTH_<NAME>_CLIENT_SECRET) across Linux/macOS/Windows sandbox command plans.

Changes:

  • Thread config-derived sensitive env key names from the CLI’s resolved config into sandbox.Engine, then into per-command planning.
  • Extend scrubSensitiveEnv to accept additional sensitive keys and to drop dynamically patterned OAuth client-secret variables case-insensitively.
  • Add tests covering both direct scrubbing behavior and engine propagation of configured sensitive env keys.

Reviewed changes

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

Show a summary per file
File Description
internal/sandbox/windows_runner.go Uses sensitive-env-aware sandbox environment construction for Windows restricted token plans.
internal/sandbox/runner.go Plumbs sensitive keys through command planning and expands scrubbing to include additional keys + dynamic OAuth patterns.
internal/sandbox/runner_test.go Extends scrub tests and adds an engine-level test ensuring configured sensitive keys are removed from plans.
internal/sandbox/engine.go Adds SensitiveEnvKeys support to engine options and normalizes keys on engine creation.
internal/cli/exec.go Passes config-derived provider apiKeyEnv names into sandbox engine construction.
internal/cli/app.go Passes config-derived provider apiKeyEnv names into the interactive app’s sandbox engine creation path.
internal/cli/sandbox_sensitive_env_test.go Adds a unit test for collecting provider-sensitive env keys from resolved config.

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

Comment on lines +1034 to +1045
for _, key := range keys {
key = strings.TrimSpace(key)
folded := strings.ToUpper(key)
if key == "" {
continue
}
if _, ok := seen[folded]; ok {
continue
}
seen[folded] = struct{}{}
out = append(out, key)
}
A config value mistakenly given as a full assignment (e.g.
"COMPANY_LLM_SECRET=...") would never match the real env key during
scrubbing, silently re-exposing the credential to sandboxed commands.
normalizeSensitiveEnvKeys now keeps only the name part before '=' and
re-trims it, per PR review.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

@jatmn jatmn left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I found issues that need to be addressed before this is ready.

Findings

  • [P1] Scrub credentials on degraded command plans too
    internal/sandbox/runner.go:169
    BuildCommandPlan attaches the configured keys to spec, but the supported unavailable-backend fallback returns directCommandPlan, which simply leaves Env nil. The normal bash tool creates a spec with nil Env, so exec.Cmd inherits COMPANY_LLM_SECRET and ZERO_OAUTH_<NAME>_CLIENT_SECRET unchanged whenever the native backend is unavailable (the intended EnforcementDegraded path covered by TestBuildCommandPlanDegradesUnavailableFallback and TestUnavailableBackendsDegradeForTargetPlatforms). That leaves the reported credential-exfiltration scenario open on fallback hosts; construct a scrubbed environment for direct/degraded plans as well and add an inherited-environment regression test.

directCommandPlan (used when the native sandbox backend is unavailable,
disabled, or not required, including the EnforcementDegraded fallback)
left Env nil whenever spec.Env was nil, so exec.Cmd inherited the full
caller environment unscrubbed. Configured apiKeyEnv credentials and
dynamically named ZERO_OAUTH_<NAME>_CLIENT_SECRET variables leaked
through that path even though the wrapped sandbox plans already
scrubbed them.

Route direct plans through scrubSensitiveEnv the same way wrapped
plans do, snapshotting os.Environ() when spec.Env is nil so scrubbing
still applies. Adds a regression test covering the degraded-fallback
path with both a configured and a dynamically named secret.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

@anandh8x anandh8x left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Re-reviewing against commit 7b59fc83 (head). The nil spec.Envsnapshot fix (patch 3/3) closes a real leak: direct/unwrapped command plans were inheriting the parent env unscrubbed, defeating the whole point of the scrubber — a sandboxed command could seeOPENAI_API_KEYetc. by going through the direct plan path. That was the P1 and the fix is correct (snapshotos.Environ()then runscrubSensitiveEnvbefore forking). The configurableSensitiveEnvKeysplumbing from CLI → engine is clean, and the case-insensitiveZERO_OAUTH_*_CLIENT_SECRETpattern viastrings.ToUpper` is the right shape for env vars.

LGTM. Minor follow-up (not blocking): the isDynamicSensitiveEnvKey length check rejects the empty-provider case ZERO_OAUTH__CLIENT_SECRET (double underscore) — a one-line test for that edge case would be cheap. Also worth documenting the dynamic-pattern list near the function so a future maintainer knows where to add a second pattern.

Cross-PR note: #682 sits between #681 and #685. Recommend rebasing #682 onto #681, then #685 onto #682, to resolve the credentialDenyReadPaths / scrubSensitiveEnv signature overlap cleanly.

@gnanam1990 gnanam1990 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Local review: built and ran go test ./internal/cli ./internal/sandbox on darwin/arm64 (cli 94s); all pass. The direct/degraded-plan env scrubbing (including the spec.Env == nil -> os.Environ() snapshot) is a genuine security fix. One integration note.

}

func scrubSensitiveEnv(env []string) []string {
func scrubSensitiveEnv(env []string, additionalKeys ...string) []string {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[P2] Sibling-PR merge conflict on scrubSensitiveEnv in runner.go

This PR changes the signature to scrubSensitiveEnv(env, additionalKeys ...string) and adds normalizeSensitiveEnvKeys. #685 adds a hardcoded ZERO_DAEMON_REMOTE_TOKEN_FILE to the same function's literal list, and both touch runner_test.go's TestScrubSensitiveEnv. Each merges into main cleanly on its own, but they conflict with each other; #681 also rewrites credentialDenyReadPathsIn in profile.go. Sequence the three sandbox credential PRs and rebase as needed.

@jatmn jatmn left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

LGTM

@Vasanthdev2004 Vasanthdev2004 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Looks good to me. This closes a real leak: config-derived apiKeyEnv names and dynamic ZERO_OAUTH_*CLIENT_SECRET vars are now scrubbed on every plan path (linux/seatbelt/windows wrapped plus the degraded direct fallback that previously inherited the caller env unscrubbed). I checked out the branch and confirmed go build, go vet, and gofmt are clean on the touched files, and the new tests (TestScrubSensitiveEnv, TestEngineScrubsConfiguredSensitiveEnvKeys, TestBuildCommandPlanDegradedFallbackScrubsInheritedEnv, TestProviderSensitiveEnvKeys) pass. The bare ZERO_OAUTH_CLIENT_SECRET (no provider segment) the reviewer noted is preserved by the prefix/suffix length guard, but that's fine in practice: Zero's envKey always emits ZERO_OAUTH_CLIENT_SECRET with a name segment, so that exact bare name is never a credential Zero sets.

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.

Sandbox env scrub misses custom apiKeyEnv and dynamic OAuth client secrets

6 participants