Skip to content

Security: harden the exec/FS trust boundary in server_actions#59

Merged
hyperpolymath merged 1 commit into
mainfrom
claude/project-scope-planning-v2pndp
Jul 2, 2026
Merged

Security: harden the exec/FS trust boundary in server_actions#59
hyperpolymath merged 1 commit into
mainfrom
claude/project-scope-planning-v2pndp

Conversation

@hyperpolymath

Copy link
Copy Markdown
Owner

Summary

Closes three injection classes reachable from GUI-supplied values through src/interface/ffi/src/server_actions.zig. Part of the v1.0 hardening roadmap (follows #57/#58).

Changes

  • Remote command injection (primary). The SSH branches in executeAction/streamLogs/getServerStatus built ssh <host> podman … <container_name> with no protection. SSH concatenates its post-target args into one string that the remote login shell re-parses, so a container_name like foo; rm -rf / executed the rm. The pre-existing executeSSH helper was unused and its -- does not stop remote-shell reparsing (its doc comment claimed otherwise — corrected).
  • SSH option injection. An unvalidated host such as -oProxyCommand=evil is read by ssh as an option.
  • Path traversal / XML injection. run_script accepted scripts/../../etc/x (prefix check only); write_server_config interpolated a raw profile_id into a filesystem path and wrote Settings.xml element text with no escaping (operator attributes escaped only ").

Fixes:

  • Allowlist validators isSafeContainerName / isSafeHost / isSafeProfileId reject every shell metacharacter and path-traversal character at the boundary.
  • Single SSH path: callers build a bare command and route through dispatch()executeSSH, which now POSIX-single-quotes every remote arg (shellQuoteInto) as defence-in-depth.
  • run_script also rejects .. components and absolute paths; write_server_config validates profile_id and XML-escapes all five predefined entities in every interpolated value.
  • parseAndDispatch converts validation errors into a structured failure result instead of propagating.
  • Also adds timeout-minutes to push-email-notify.yml (clears the Hypatia workflow_audit finding).

RSR Quality Checklist

Required

  • Tests pass (zig build test{,-integration,-smoke,-property} — 150 green: 92 unit / 39 integration / 5 smoke / 14 property)
  • No banned language patterns (Zig only)
  • No banned functions introduced
  • SPDX license headers present on modified source files
  • No secrets, credentials, or .env files included

As Applicable

  • ABI/FFI changes validated (no ABI surface change; abi-contract.yml gates unaffected)
  • Documentation updated for user-facing changes (n/a — internal hardening)

Testing

10 new adversarial unit tests in server_actions.zig: injection payloads (;, backticks, $(…), pipes) rejected by the validators; -oProxyCommand host rejected; scripts/../../etc/passwd traversal rejected; shellQuoteInto/xmlEscapeInto output asserted; executeAction/run_script/write_server_config reject malicious inputs at the boundary. Grep-gate: no inline ssh arg-building remains.

🤖 Generated with Claude Code

https://claude.ai/code/session_01TaWWedv6VQqeZaPvc94keN


Generated by Claude Code

Three injection classes were reachable from GUI-supplied values:

1. Remote command injection. The SSH paths in executeAction/streamLogs/
   getServerStatus built 'ssh <host> podman ... <container_name>' with no
   protection; ssh concatenates its remote args into one string the remote
   login shell re-parses, so a container_name like 'foo; rm -rf /' ran the
   rm. (executeSSH existed but was unused AND its own '--' does not stop
   remote-shell reparsing — its doc comment was wrong.)
2. SSH option injection. An unvalidated host such as '-oProxyCommand=evil'
   is read by ssh as an option.
3. Path traversal. run_script accepted 'scripts/../../etc/x' (prefix check
   only); write_server_config interpolated a raw profile_id into a path; and
   Settings.xml element text was interpolated with no escaping (operator
   attrs escaped only '"').

Fixes:
- Allowlist validators isSafeContainerName / isSafeHost / isSafeProfileId
  reject every shell metacharacter and path-traversal char at the boundary.
- Single SSH path: the three callers build a bare command and route through
  dispatch() -> executeSSH, which now POSIX-single-quotes every remote arg
  (shellQuoteInto) as defence-in-depth; false doc comment corrected.
- run_script also rejects '..' components and absolute paths.
- write_server_config validates profile_id and XML-escapes all five entities
  in every interpolated value (xmlEscapeInto/Alloc).
- parseAndDispatch converts validation errors into a structured failure
  result instead of propagating.
- 10 adversarial unit tests (injection payloads, traversal, quoting,
  escaping). Grep-gate: no inline 'ssh' arg-building remains. 150 tests green.

Also adds timeout-minutes to push-email-notify.yml (Hypatia workflow_audit).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TaWWedv6VQqeZaPvc94keN
@sonarqubecloud

sonarqubecloud Bot commented Jul 2, 2026

Copy link
Copy Markdown

@github-actions

github-actions Bot commented Jul 2, 2026

Copy link
Copy Markdown

🔍 Hypatia Security Scan

Findings: 2 issues detected

Severity Count
🔴 Critical 0
🟠 High 0
🟡 Medium 2
View findings
[
  {
    "reason": "Repository has 21 non-main remote branch(es). Policy: single main branch only.",
    "type": "GS007",
    "file": ".",
    "action": "delete_remote_branches",
    "rule_module": "git_state",
    "severity": "medium"
  },
  {
    "reason": "Code scanning (Hypatia): hypatia/workflow_audit/missing_timeout_minutes -- Hypatia workflow_audit: missing_timeout_minutes -- 7 day(s) old",
    "type": "CSA001",
    "file": "push-email-notify.yml",
    "action": "review",
    "rule_module": "code_scanning_alerts",
    "severity": "medium"
  }
]

Powered by Hypatia Neurosymbolic CI/CD Intelligence

@hyperpolymath
hyperpolymath marked this pull request as ready for review July 2, 2026 15:38
@hyperpolymath
hyperpolymath merged commit ae0cbcd into main Jul 2, 2026
27 checks passed
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.

2 participants