Skip to content

feat(#791): inject pre-script outputs into sandbox environment - #818

Open
fullsend-ai-coder[bot] wants to merge 2 commits into
mainfrom
agent/791-prescript-output-flow
Open

feat(#791): inject pre-script outputs into sandbox environment#818
fullsend-ai-coder[bot] wants to merge 2 commits into
mainfrom
agent/791-prescript-output-flow

Conversation

@fullsend-ai-coder

Copy link
Copy Markdown

Summary

Adds pre-script to sandbox data flow: non-reserved key=value outputs written to $FULLSEND_PRESCRIPT_OUTPUT are now injected as sandbox environment variables before the agent starts.

Related Issue

Closes #791

Changes

  • Add prescript.SandboxEnv() function that filters pre-script outputs, excluding reserved protocol keys (skipped, reason)
  • In runAgent (step 4-post), merge pre-script sandbox outputs into h.Env.Sandbox after the skip check and before sandbox creation — buildSandboxEnvLines in bootstrapEnv picks them up naturally
  • Pre-script outputs override static env.sandbox entries on key collision (runtime-computed values take precedence over static config)
  • Hyphenated keys (valid in the prescript protocol) pass through but are silently skipped by buildSandboxEnvLines since they are not valid POSIX env var names
  • Update normative prescript-output/v1 doc to describe sandbox injection semantics

Testing

  • Unit tests for SandboxEnv (nil/empty/reserved-only/mixed outputs)
  • Integration tests verifying outputs flow through runPreScriptSandboxEnvbuildSandboxEnvLines
  • Test that pre-script outputs override static env.sandbox entries
  • Test that hyphenated keys are accepted by SandboxEnv but filtered by buildSandboxEnvLines
  • Integration test via runAgent proving outputs reach sandbox creation
  • go test ./internal/prescript/... ./internal/cli/... -race passes
  • go build ./... succeeds
  • go vet clean

Checklist

  • PR title follows Conventional Commits (correct type, ! for breaking changes)
  • Commits are signed off (DCO) — human and human-directed agent sessions only

Closes #791

Post-script verification

  • Branch is not main/master (agent/791-prescript-output-flow)
  • Secret scan passed (gitleaks — ba77dbdbbd17e9bb18c47bf6efa023c98b697158..HEAD)
  • PR body secret scan passed (gitleaks — no-git)
  • Pre-commit hooks passed (authoritative run on runner)
  • Tests ran inside sandbox

Pre-scripts can now pass computed values (tokens, URLs, pre-fetched
data) into the sandbox environment. Non-reserved outputs (everything
except skipped/reason) written to FULLSEND_PRESCRIPT_OUTPUT are merged
into env.sandbox after the pre-script completes, before sandbox
creation.

Add prescript.SandboxEnv() to filter outputs suitable for sandbox
injection. In runAgent, merge these outputs into h.Env.Sandbox after
the skip check (step 4-post), where buildSandboxEnvLines picks them
up during bootstrapEnv. Pre-script outputs override static env.sandbox
entries when keys collide, since the pre-script computes values from
runtime context.

Hyphenated keys (valid in the prescript protocol) pass through
SandboxEnv but are silently skipped by buildSandboxEnvLines since
they are not valid POSIX env var names.

Update the normative prescript-output/v1 doc to describe sandbox
injection semantics.

Note: pre-commit could not run (network unavailable in sandbox).

Closes #791
@fullsend-ai-review

fullsend-ai-review Bot commented Aug 2, 2026

Copy link
Copy Markdown

🤖 Finished Review · ✅ Success · Started 3:02 AM UTC · Completed 3:21 AM UTC
Commit: 0b92e88 · View workflow run →

@fullsend-ai-review

fullsend-ai-review Bot commented Aug 2, 2026

Copy link
Copy Markdown

Review

Findings

Medium

  • [defense-in-depth gap] internal/prescript/prescript.go:306SandboxEnv does not filter reservedSandboxKeys (PATH, HOME, LD_PRELOAD, BASH_ENV, etc.). A pre-script emitting one of these keys passes through SandboxEnv and gets inserted into h.Env.Sandbox, only to be silently dropped downstream by buildSandboxEnvLines. The "Injected N" log message overcounts because it includes keys that will not actually reach the sandbox. Defense-in-depth favors filtering at the source as well as the sink.
    Remediation: Have SandboxEnv also filter against the reserved sandbox key set, or adjust the log to count only keys that survive buildSandboxEnvLines.

  • [stale-doc] docs/guides/user/building-custom-agents.md:318 — The pre-script output section documents only skip-gating capability (skipped=true/reason). Users reading this guide to understand pre-script features will not discover the new sandbox env var injection.
    Remediation: Add a subsection documenting that valid POSIX identifiers written to FULLSEND_PRESCRIPT_OUTPUT are injected into env.sandbox.

Low

  • [test coverage gap] internal/cli/prescript_run_test.go:202 — No test covers a pre-script output that collides with a reservedSandboxKey (e.g., PATH). Adding one would document and lock in the expected filtering behavior.

  • [information disclosure] internal/cli/run.go — The override INFO log prints both old and new values (%q → %q). If a static env.sandbox value contained an expanded secret, it would appear on stderr.

  • [scope-creep] docs/normative/prescript-output/v1/README.md:46 — The spec's introductory purpose statement still describes the protocol solely for skip-gating. Consider updating to reflect the broader scope.

  • [code-organization] internal/cli/run.go:964 — Override log uses fmt.Fprintf(os.Stderr, ...) while adjacent messages use printer.StepDone. Consider using printer.StepInfo for consistency.

  • [stale-doc] docs/ADRs/0049-agent-configuration-env-var-convention.md:74 — Could reference the new pre-script output injection as an additional source for env.sandbox values.

  • [incomplete-doc] docs/guides/user/bring-your-own-agent.md:162 — Guide shows pre-script usage but does not mention dynamic env.sandbox population from pre-script outputs.

Previous run

Review

Findings

Medium

  • [key-format-mismatch] internal/prescript/prescript.goSandboxEnv returns all non-reserved keys including hyphenated ones (e.g., existing-pr), but buildSandboxEnvLines downstream rejects keys that are not valid POSIX identifiers. The StepDone log message "Injected N pre-script output(s) into sandbox env" may overstate the actual count, and SandboxEnv's contract implies sandbox-ready output when it includes keys that won't survive downstream filtering. Consider filtering non-POSIX keys in SandboxEnv itself so the filtering concern is centralized.

  • [privilege-escalation] internal/cli/run.go:973 — Pre-script outputs unconditionally override admin-declared env.sandbox values. The override semantics are intentional and documented in the normative spec, and pre-scripts are admin-authored with the same trust level as harness YAML. However, no log message is emitted when an override occurs, reducing visibility for harness authors troubleshooting unexpected values. Consider logging when a pre-script output shadows a harness-declared entry.

  • [insufficient-reserved-key-filtering] internal/prescript/prescript.goSandboxEnv filters only protocol-reserved keys (skipped, reason). Security-sensitive env var names (PATH, HOME, LD_PRELOAD, etc.) are correctly blocked downstream by reservedSandboxKeys in buildSandboxEnvLines, so no bypass exists today. Duplicating the check in SandboxEnv would provide defense-in-depth against future code paths that might consume its output without routing through buildSandboxEnvLines.

  • [stale-doc] docs/ADRs/0055-unified-env-var-delivery.md — ADR 0055 catalogs env var delivery mechanisms but doesn't mention that env.sandbox can now be dynamically populated by pre-script outputs. Pre-script outputs merge into env.sandbox (not a separate mechanism), so the ADR remains structurally accurate, but noting the dynamic source would improve its completeness as a reference.

Low

  • [maintainability] internal/prescript/prescript.go:291reservedOutputKeys and reservedKeys are independently maintained. A future key addition to one but not the other would leak a reserved key into sandbox env. Consider deriving one from the other.

  • [naming-convention] internal/prescript/prescript.go:292reservedOutputKeys is a map[string]bool while reservedKeys is a []string. Both types are correct for their respective uses (membership check vs. iteration), but the inconsistency is a minor readability concern.

  • [variable-placement] internal/prescript/prescript.go:292reservedOutputKeys is declared inline before SandboxEnv. The package convention places package-level variables near the top (lines 56–82).


Labels: PR modifies sandbox env var injection in the prescript/CLI subsystem and adds a new feature

fullsend-ai-review[bot]

This comment was marked as outdated.

@guyoron1

guyoron1 commented Aug 2, 2026

Copy link
Copy Markdown
Owner

/fs-fix

@fullsend-ai-coder

fullsend-ai-coder Bot commented Aug 2, 2026

Copy link
Copy Markdown
Author

🤖 Finished Fix · ✅ Success · Started 6:48 AM UTC · Completed 6:58 AM UTC
Commit: 0b92e88 · View workflow run →

- Filter non-POSIX keys (e.g. hyphenated) in SandboxEnv so injected
  count matches what buildSandboxEnvLines exports (key-format-mismatch)
- Log when pre-script output overrides a static env.sandbox entry
  (privilege-escalation visibility)
- Derive reservedOutputKeys from reservedKeys to keep sets in sync
  (maintainability) and move to package-level var section (variable-placement)
- Add note to ADR 0055 about pre-script dynamic population (stale-doc)
- Add TestSandboxEnv_FiltersHyphenatedKeys unit test

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

Copy link
Copy Markdown
Author

🔧 Fix agent — iteration 1 (human-triggered)

Addressed 6 of 7 review findings. Disagreed with 1 (insufficient-reserved-key-filtering) because implementing defense-in-depth would require either a circular dependency or a third independently maintained key set, both worse than the current architecture where buildSandboxEnvLines is the single enforcement point.

Fixed (6):

  1. key-format-mismatch: SandboxEnv returns hyphenated keys that buildSandboxEnvLines rejects (internal/prescript/prescript.go): Added validPosixKeyRe filter in SandboxEnv to exclude non-POSIX keys (e.g. hyphenated) so the injected count accurately reflects what reaches the sandbox. Updated integration test to match new filtering behavior.
  2. privilege-escalation: no log message when pre-script output overrides static env.sandbox entry (internal/cli/run.go): Added INFO log to stderr when a pre-script output key shadows a harness-declared env.sandbox entry, showing the old and new values for troubleshooting.
  3. stale-doc: ADR 0055 does not mention pre-script outputs as a dynamic source for env.sandbox (docs/ADRs/0055-unified-env-var-delivery.md): Added a Consequences bullet noting that env.sandbox can be dynamically populated by pre-script outputs, per ADR annotation policy (minor annotations to accepted ADRs are permitted).
  4. maintainability: reservedOutputKeys and reservedKeys independently maintained (internal/prescript/prescript.go): Replaced the hand-maintained reservedOutputKeys map with an init function that derives it from reservedKeys, ensuring future key additions are automatically reflected in both sets.
  5. naming-convention: reservedOutputKeys is map[string]bool while reservedKeys is []string (internal/prescript/prescript.go): Addressed by deriving reservedOutputKeys from reservedKeys — the map is now built programmatically from the slice, making the type difference a natural consequence of their different usage patterns (iteration vs membership check).
  6. variable-placement: reservedOutputKeys declared inline before SandboxEnv instead of package-level section (internal/prescript/prescript.go): Moved reservedOutputKeys and new validPosixKeyRe declarations to the package-level variable section (after reservedKeys at line 82), matching the existing convention.

Disagreed (1):

  1. insufficient-reserved-key-filtering: duplicate reservedSandboxKeys check in SandboxEnv for defense-in-depth: reservedSandboxKeys lives in internal/cli (the cli package) while SandboxEnv is in internal/prescript. Importing cli from prescript would create a circular dependency. Duplicating the list would create a third independently maintained set (contradicting the maintainability finding). The architectural constraint that all sandbox env flows through buildSandboxEnvLines ensures no bypass exists today or in the future without a deliberate code change that would itself need review.

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 7:01 AM UTC · Completed 7:18 AM UTC
Commit: 36541dd · View workflow run →

@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.

@@ -289,6 +305,33 @@ func Relay(res Result) (relayed bool, err error) {
return true, nil
}

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] defense-in-depth gap

SandboxEnv does not filter reservedSandboxKeys (PATH, HOME, LD_PRELOAD, BASH_ENV, etc.). A pre-script emitting one of these keys passes through SandboxEnv and gets inserted into h.Env.Sandbox, only to be silently dropped downstream by buildSandboxEnvLines. The 'Injected N' log message overcounts because it includes keys that will not actually reach the sandbox.

Suggested fix: Have SandboxEnv also filter against the reserved sandbox key set, or adjust the log to count only keys that survive buildSandboxEnvLines.

@@ -199,6 +200,133 @@ func TestRunPreScript_CleansUpOutputFile(t *testing.T) {
assert.Empty(t, entries)
}

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] test coverage gap

No test covers a pre-script output that collides with a reservedSandboxKey (e.g., PATH). Adding one would document and lock in the expected filtering behavior.

Suggested fix: Add a test that writes a reserved sandbox key via pre-script output and asserts it does not appear in buildSandboxEnvLines output.

owned by the protocol; a future CLI may interpret additional lowercase
single-word keys, so scripts should prefix their own outputs (`myagent_pr=123`)
to avoid colliding with a future directive.
Any other valid key is parsed, logged, relayed, and — when the key is a valid

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] scope-creep

The spec introductory purpose statement still describes the protocol solely for skip-gating. Consider updating to reflect the broader scope.

Comment thread internal/cli/run.go
return nil
}

// 4-post. Inject pre-script outputs into sandbox environment (#791).

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] code-organization

Override log uses fmt.Fprintf(os.Stderr, ...) while adjacent messages use printer.StepDone. Consider using printer.StepInfo for consistency.

@github-actions

github-actions Bot commented Sep 4, 2026

Copy link
Copy Markdown

This pull request has been automatically marked as stale because it has not had any activity in the last month. It will be closed in 2 weeks if no further activity occurs. Remove the stale label to reset the inactivity timer.

@github-actions github-actions Bot added the stale label Sep 4, 2026
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.

Support pre-script to sandbox data flow in the harness

1 participant