fix(sandbox): avoid shell in FsBridge writes#1155
Conversation
Write sandbox files through tee with the target path passed as a discrete argv entry, so shell metacharacters in filenames are not evaluated by sh -c. Adds regression coverage for command-injection-style filenames and append mode.
🔒 Security Review Requested (P1)This PR fixes a command injection vulnerability in Scope: 2 files, +122/-6 lines, includes regression tests. Assigning to the Bridge Crew security pipeline:
|
🛡️ Security Review — PR #1155: FsBridge Command Injection FixReviewer: Worf, Chefe de Segurança — Bridge Crew 1. Vulnerability SummaryOriginal Vulnerability (CWE-78)
b.dockerExec(ctx, []byte(content), "sh", "-c", fmt.Sprintf("cat %s %q", redir, resolved))Go's Exploitation: A path like → Bash executes Impact: Arbitrary command execution as root inside the sandbox container. CVSS 3.1: AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H = 9.8 (Critical). Fix AppliedThe args := []string{"tee"}
if appendMode { args = append(args, "-a") }
args = append(args, "--", resolved)
_, stderr, exitCode, err := b.dockerExec(ctx, []byte(content), args...)This eliminates the shell interpretation layer — 2. Remaining Injection Vectors — NEGATIVE FINDINGS ✅2.1 Shell metacharacters in filename ✅ SAFEAll shell metacharacters are preserved as literal filename characters:
2.2 Option injection via
|
| Test Case | Severity | Rationale |
|---|---|---|
Option injection: filename -o/dev/null |
Low | -- delimiter already protects this, but test would prevent regression |
| Append mode + malicious path | Low | Current append test only uses safe path append.txt |
| Filename with newlines | Low | Verify tee handles newlines in filenames correctly via argv |
| Filename with backticks | Low | Verify no shell evaluation via tee |
5. Code Quality Observations
5.1 Comment quality ✅
// Write content via stdin without invoking a shell. Passing the resolved path
// as a discrete argv entry prevents shell metacharacters in filenames from
// being interpreted as commands inside the sandbox container.Clear, concise, explains WHY the change was made. Good for future maintainers.
5.2 Consistency with ReadFile ✅
ReadFile already used cat -- <path> without shell invocation. This fix brings WriteFile to parity.
5.3 Error handling ✅
Non-zero exit codes from tee are properly checked and returned as errors.
6. Verdict: ✅ APPROVE
The fix correctly and completely addresses the CWE-78 command injection vulnerability in FsBridge.WriteFile. The sh -c invocation is removed entirely, and filename arguments are passed as discrete argv entries to tee. The -- delimiter prevents option injection. Test coverage is adequate with strong regression protection.
Recommendations (non-blocking, for follow-up PR)
- Add option injection test: Test a filename like
-o/dev/nullto verify--delimiter protection (tee -- /workspace/-o/dev/null). - Add append + malicious path test: Test append mode with
$(command)in filename to verify consistency with non-append mode. - Comment on
teestdout: Note thatteewrites content to stdout in addition to the file, and that the stdout buffer is intentionally discarded.
Threat Model Validation
| Vector | Before | After | Risk |
|---|---|---|---|
$(cmd) in filename |
❌ EXECUTED | ✅ LITERAL | Eliminated |
| Backticks in filename | ❌ EXECUTED | ✅ LITERAL | Eliminated |
;, |, & in filename |
❌ PARSED | ✅ LITERAL | Eliminated |
Option injection (--help) |
N/A via shell | ✅ Blocked by -- |
Protected |
| Content via stdin | ✅ Safe | ✅ Safe | Unchanged |
| Path traversal | ✅ Container-scoped | ✅ Container-scoped | Unchanged |
Qapla'. The fix is clean, minimal, and correct. Merge approved.
Reviewed by: 🛡️ Worf — Bridge Crew Security
Reference: CWE-78, CWE-88
Summary
FsBridge.WriteFilecommand injection by removing thesh -cwrite path.teeinstead of interpolating it into a shell command string.Security impact
A path such as
notes/$(touch /tmp/pwned).txtis now treated as a literal filename argument instead of being evaluated by the shell.Test plan
gofmt -w internal/sandbox/fsbridge.go internal/sandbox/fsbridge_test.gogo test ./internal/sandbox -count=1git diff --cached --checkCloses #1121