fix(P99): P0 heredoc command injection + ROADMAP/REQUIREMENTS reconciliation

P0 fix (final review T1): internal/sshpush/idempotency.go heredoc
command injection via fixed EOF delimiter. Replaced with per-write random
delimiter verified absent from content (strings.Contains check). Fake SSH
server updated to parse the delimiter dynamically from the command. This
prevents command injection via crafted file content in multi-tenant
namespaces.

ROADMAP reconciliation (final review T2.1): updated v0.9 phase list to
reflect actual execution — 14 tagged phases (P03/P04/P08 combined,
P07a/b/c combined), tags v0.8.1..v0.8.14. Milestone marked COMPLETE.
Phase checkboxes marked [x] with actual REQs covered.

REQUIREMENTS reconciliation: 21 v0.9-scoped REQs marked Complete
(062,063,064,067,068,069,070,071,072,073,074,076,077,078,081,082,
083,085,088,089,090). 9 v0.10-deferred REQs (061,065,066,075,079,
080,084,086,087) Phase columns fixed to reference only v0.10 (not v0.9/v0.8)
so verify-reqs doesn't flag them as belonging to completed milestones.

Final review: P0 fixed. P1 warnings logged for post-hoc v0.10: fuzz in CI,
podman command quoting, scheduler O(n^2), ProcessRuntime stdout leak,
host-key verification path gap. 12/19 grill gates cleared; 7 deferred to
v0.10 (C-08,C-09,C-11,C-12,C-13,C-19).

26 packages pass, 20 bats pass, gofmt clean, verify-reqs 90 consistent.

---ci---
project: orca
phase: 99
milestone: v0.9
status: execute
---/ci---
This commit is contained in:
Jon Chery
2026-08-05 19:02:54 +00:00
parent 7b2f6719bb
commit c51eba5e84
4 changed files with 64 additions and 59 deletions
+6 -4
View File
@@ -48,10 +48,12 @@ func (t *Transport) writeFile(ctx context.Context, peer string, path string, con
}
// Build the remote command: mkdir -p <dir> && cat > <tmp> <<'EOF'
// ... EOF && chmod <mode> <tmp> && mv <tmp> <path>. The heredoc
// delimiter is chosen to not appear in the content (we use a fixed
// marker; content with the marker would break, but the marker is
// sufficiently unusual).
const eof = "ORCA_PUSH_EOF_a1b2c3"
// delimiter is per-write random and verified absent from content
// to prevent command injection via crafted file content.
eof := "ORCA_PUSH_EOF_" + randomToken(16)
for strings.Contains(string(content), eof) {
eof = "ORCA_PUSH_EOF_" + randomToken(16)
}
modeStr := fmt.Sprintf("%04o", uint32(mode.Perm()))
cmd := fmt.Sprintf(
"mkdir -p %s && cat > %s <<'%s'\n%s\n%s\nchmod %s %s && mv -f %s %s",
+12 -6
View File
@@ -180,19 +180,25 @@ func (s *fakeSSHServer) runCommand(cmd string) ([]byte, int) {
// handleWrite parses the heredoc write command produced by writeFile.
// Command format:
//
// mkdir -p '<dir>' && cat > '<tmp>' <<'ORCA_PUSH_EOF_a1b2c3'
// mkdir -p '<dir>' && cat > '<tmp>' <<'ORCA_PUSH_EOF_<random>'
// <content>
// ORCA_PUSH_EOF_a1b2c3
// ORCA_PUSH_EOF_<random>
// chmod <mode> '<tmp>' && mv -f '<tmp>' '<path>'
func (s *fakeSSHServer) handleWrite(cmd string) ([]byte, int) {
const eof = "ORCA_PUSH_EOF_a1b2c3"
// Find the opening heredoc line: ... <<'EOF'\n
openerIdx := strings.Index(cmd, "<<'"+eof+"'")
// Find the opening heredoc line: ... <<'ORCA_PUSH_EOF_<random>'\n
// The delimiter is per-write random (P0 fix); extract it from the command.
openerIdx := strings.Index(cmd, "<<'")
if openerIdx < 0 {
return []byte("sh: no heredoc opener\n"), 1
}
eofStart := openerIdx + len("<<'")
eofEnd := strings.Index(cmd[eofStart:], "'")
if eofEnd < 0 {
return []byte("sh: no heredoc closer quote\n"), 1
}
eof := cmd[eofStart : eofStart+eofEnd]
// Body starts after the opener line's newline.
rest := cmd[openerIdx+len("<<'"+eof+"'"):]
rest := cmd[eofStart+eofEnd+1:]
nl := strings.Index(rest, "\n")
if nl < 0 {
return []byte("sh: no body start\n"), 1