Skip to content

fix(#333): unify env var blocklist for sandbox and credentials - #401

Closed
fullsend-ai-coder[bot] wants to merge 1 commit into
mainfrom
agent/333-unify-env-blocklist
Closed

fix(#333): unify env var blocklist for sandbox and credentials#401
fullsend-ai-coder[bot] wants to merge 1 commit into
mainfrom
agent/333-unify-env-blocklist

Conversation

@fullsend-ai-coder

Copy link
Copy Markdown

Extract a single shared ReservedEnvKeys set in internal/sandbox/reserved.go referenced by both the provider credential validation (EnsureProvider) and the sandbox environment validation (bootstrapEnv). This eliminates drift between the two paths — a variable that is unsafe as a provider credential key is equally unsafe when injected via runner_env.

The unified blocklist covers:

  • Infrastructure (PATH, HOME, SHELL)
  • Dynamic linker injection (LD_PRELOAD, LD_LIBRARY_PATH)
  • Shell injection (BASH_ENV, ENV, PROMPT_COMMAND)
  • Proxy/network (HTTP(S)_PROXY, ALL_PROXY, etc.)
  • TLS trust chain (SSL_CERT_*, *_CA_BUNDLE, etc.)
  • Git config (GIT_CONFIG_GLOBAL, GIT_CONFIG_SYSTEM, etc.)
  • Runtime injection (NODE_OPTIONS, PYTHONPATH, etc.)
  • FULLSEND_* prefix

EnsureProvider now rejects reserved credential key names before building openshell args. bootstrapEnv rejects reserved runner_env key names before writing the sandbox .env file.

Note: pre-commit could not run in-sandbox (shellcheck-py download blocked by network policy, exit 3). The post-script runs authoritative pre-commit on the runner.


Closes #333

Post-script verification

  • Branch is not main/master (agent/333-unify-env-blocklist)
  • Secret scan passed (gitleaks — a2afa8b179a351845733c680b839a39a82304db6..HEAD)
  • Pre-commit hooks passed (authoritative run on runner)
  • Tests ran inside sandbox

Extract a single shared ReservedEnvKeys set in
internal/sandbox/reserved.go referenced by both the
provider credential validation (EnsureProvider) and the
sandbox environment validation (bootstrapEnv). This
eliminates drift between the two paths — a variable that
is unsafe as a provider credential key is equally unsafe
when injected via runner_env.

The unified blocklist covers:
- Infrastructure (PATH, HOME, SHELL)
- Dynamic linker injection (LD_PRELOAD, LD_LIBRARY_PATH)
- Shell injection (BASH_ENV, ENV, PROMPT_COMMAND)
- Proxy/network (HTTP(S)_PROXY, ALL_PROXY, etc.)
- TLS trust chain (SSL_CERT_*, *_CA_BUNDLE, etc.)
- Git config (GIT_CONFIG_GLOBAL, GIT_CONFIG_SYSTEM, etc.)
- Runtime injection (NODE_OPTIONS, PYTHONPATH, etc.)
- FULLSEND_* prefix

EnsureProvider now rejects reserved credential key names
before building openshell args. bootstrapEnv rejects
reserved runner_env key names before writing the sandbox
.env file.

Note: pre-commit could not run in-sandbox (shellcheck-py
download blocked by network policy, exit 3). The
post-script runs authoritative pre-commit on the runner.

Closes #333
@github-actions

Copy link
Copy Markdown

E2E tests did not run

E2E tests run automatically for org/repo members and collaborators on pull requests.

For other contributors, a maintainer must add the ok-to-test label after the latest push.

See E2E testing guide for details.

@fullsend-ai-review

fullsend-ai-review Bot commented Jul 12, 2026

Copy link
Copy Markdown

🤖 Finished Review · ✅ Success · Started 7:30 AM UTC · Completed 7:41 AM UTC
Commit: a2afa8b · View workflow run →

@fullsend-ai-review

Copy link
Copy Markdown

Review — request-changes

PR: fix(#333): unify env var blocklist for sandbox and credentials
Reviewed at: dbbc9fb628fb07f62b130ff7c9ae47e7986ef3ff

Overview

This PR adds a unified ReservedEnvKeys blocklist in internal/sandbox/reserved.go and hooks validation into both EnsureProvider() (credential keys) and bootstrapEnv() (runner_env keys). The blocklist covers ~45 security-sensitive environment variables across 7 categories (infrastructure, dynamic linker, shell injection, proxy, TLS, git config, runtime injection) plus the FULLSEND_ prefix.

The security intent is sound — blocking dangerous env vars from entering the sandbox process is defense-in-depth. However, there is a critical correctness bug that would break all production harness configurations.


Critical

FULLSEND_ prefix reservation breaks all existing harness configs (internal/sandbox/reserved.go:89, internal/cli/run.go:1144)

The IsReservedEnvKey function rejects any key with the FULLSEND_ prefix. However, all production harness templates set FULLSEND_OUTPUT_SCHEMA and FULLSEND_OUTPUT_FILE in runner_env:

# internal/scaffold/fullsend-repo/harness/code.yaml:55-58
runner_env:
  FULLSEND_OUTPUT_SCHEMA: ${FULLSEND_DIR}/schemas/code-result.schema.json
  FULLSEND_OUTPUT_FILE: code-result.json

The same pattern appears in review.yaml, triage.yaml, fix.yaml, retro.yaml, and prioritize.yaml. The new ValidateEnvKeys(h.RunnerEnv) call at the top of bootstrapEnv would reject these keys before bootstrapEnv can read them (lines 1163–1179), causing every agent run to fail with:

validating runner_env: environment key "FULLSEND_OUTPUT_SCHEMA" is a reserved variable and cannot be set in sandbox env

Remediation: Exempt specific platform-managed FULLSEND_ keys (FULLSEND_OUTPUT_SCHEMA, FULLSEND_OUTPUT_FILE) from the blocklist, or move the validation to check only the keys that are directly written into the sandbox .env file rather than the full h.RunnerEnv map (which contains runner-side metadata that never enters the sandbox directly).


Medium

Mutable exported blocklist map (internal/sandbox/reserved.go:23)

ReservedEnvKeys is declared as var ReservedEnvKeys = map[string]bool{...} — an exported mutable map. Any code in-process can bypass the blocklist at runtime:

sandbox.ReservedEnvKeys["LD_PRELOAD"] = false
// or
delete(sandbox.ReservedEnvKeys, "PATH")
// or
sandbox.ReservedEnvKeys = nil

For a security-critical blocklist, the map should be unexported with access only through IsReservedEnvKey(), or replaced with a function that checks against a compile-time constant set.

Documentation gaps for new validation behavior (docs/ADRs/0024-harness-definitions.md, docs/guides/user/building-custom-agents.md)

ADR-0024 documents runner_env without mentioning key restrictions. The user guides for building custom agents and customizing agents show runner_env examples with no warnings about reserved keys. Users will encounter cryptic validation errors without documentation explaining which keys are prohibited and why.


Low

Non-deterministic error reporting (internal/sandbox/reserved.go:101)

ValidateCredentialKeys and ValidateEnvKeys iterate a map and return on the first reserved key found. Since Go map iteration is non-deterministic, when multiple reserved keys are present, the error message names a different key on each run, making debugging harder.

Missing regression test for platform keys (internal/cli/run_test.go)

The test suite covers rejection of FULLSEND_OUTPUT_DIR but does not include a test verifying that FULLSEND_OUTPUT_SCHEMA (used by production harnesses) passes or fails validation. An integration-style test loading a real harness config would have caught the critical regression.


Positive observations

  • Comprehensive test coverage for the new validation functions (212 lines of tests in reserved_test.go)
  • Both credential and runner_env paths are covered
  • Proxy variable case variants (HTTP_PROXY/http_proxy) are correctly handled
  • IsReservedEnvKey correctly handles nil maps (Go map lookups on nil return zero value)
  • The FULLSEND_ prefix check is independent of the map, providing defense-in-depth
  • Validation is placed before the security-sensitive operations (fail-fast pattern)

Labels: PR modifies sandbox security validation in internal/sandbox/ and is labeled security on the linked issue

@fullsend-ai-review fullsend-ai-review Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

See the review comment for full details.

// name. It checks both the exact key against [ReservedEnvKeys] and the
// FULLSEND_ prefix.
func IsReservedEnvKey(key string) bool {
if ReservedEnvKeys[key] {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[critical] logic-error

The FULLSEND_ prefix reservation in IsReservedEnvKey breaks all existing harness configurations. Production harness templates (code.yaml, review.yaml, triage.yaml, fix.yaml, retro.yaml, prioritize.yaml) set FULLSEND_OUTPUT_SCHEMA and FULLSEND_OUTPUT_FILE in runner_env. The new ValidateEnvKeys(h.RunnerEnv) call at the top of bootstrapEnv rejects these keys before bootstrapEnv can read them (lines 1163-1179), causing every agent run to fail.

Suggested fix: Exempt platform-managed FULLSEND_ keys (FULLSEND_OUTPUT_SCHEMA, FULLSEND_OUTPUT_FILE) from the blocklist, or validate only the keys that are directly written into the sandbox .env file rather than the full h.RunnerEnv map.

// - Git config: git configuration injection vectors
// - Runtime injection: language-specific startup/path injection
//
// The FULLSEND_ prefix is also reserved (checked by [IsReservedEnvKey]).

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[medium] mutable-security-control

ReservedEnvKeys is declared as an exported var map[string]bool. Any code in-process can mutate or nil-out the map to bypass the blocklist (e.g., sandbox.ReservedEnvKeys["LD_PRELOAD"] = false). For a security-critical blocklist, this should be unexported with access only through IsReservedEnvKey().

Suggested fix: Change to unexported var reservedEnvKeys and remove external direct map access. If read access is needed, expose via a function returning a copy.

func ValidateCredentialKeys(credentials map[string]string) error {
for key := range credentials {
if IsReservedEnvKey(key) {
return fmt.Errorf("credential key %q is a reserved environment variable and cannot be used", key)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[low] error-handling

ValidateCredentialKeys and ValidateEnvKeys iterate a map and return on the first reserved key found. Go map iteration is non-deterministic, so when multiple reserved keys are present, the error message names a different key on each run.

Suggested fix: Sort keys before iterating to produce deterministic error messages, or collect all reserved keys into a single error.

@guyoron1

Copy link
Copy Markdown
Owner

/fs-fix

@fullsend-ai-coder

fullsend-ai-coder Bot commented Jul 12, 2026

Copy link
Copy Markdown
Author

🤖 Finished Fix · ❌ Failure · Started 7:50 AM UTC · Completed 8:05 AM UTC
Commit: a2afa8b · View workflow run →

@guyoron1 guyoron1 closed this Jul 14, 2026
@guyoron1
guyoron1 deleted the agent/333-unify-env-blocklist branch July 29, 2026 15:22
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.

[ARCHIVED] Benchmark issue 333

1 participant