Security: harden the exec/FS trust boundary in server_actions#59
Merged
Conversation
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
|
🔍 Hypatia Security ScanFindings: 2 issues detected
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
marked this pull request as ready for review
July 2, 2026 15:38
This was referenced Jul 2, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.



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
executeAction/streamLogs/getServerStatusbuiltssh <host> podman … <container_name>with no protection. SSH concatenates its post-target args into one string that the remote login shell re-parses, so acontainer_namelikefoo; rm -rf /executed therm. The pre-existingexecuteSSHhelper was unused and its--does not stop remote-shell reparsing (its doc comment claimed otherwise — corrected).-oProxyCommand=evilis read by ssh as an option.run_scriptacceptedscripts/../../etc/x(prefix check only);write_server_configinterpolated a rawprofile_idinto a filesystem path and wroteSettings.xmlelement text with no escaping (operator attributes escaped only").Fixes:
isSafeContainerName/isSafeHost/isSafeProfileIdreject every shell metacharacter and path-traversal character at the boundary.dispatch()→executeSSH, which now POSIX-single-quotes every remote arg (shellQuoteInto) as defence-in-depth.run_scriptalso rejects..components and absolute paths;write_server_configvalidatesprofile_idand XML-escapes all five predefined entities in every interpolated value.parseAndDispatchconverts validation errors into a structured failure result instead of propagating.timeout-minutestopush-email-notify.yml(clears the Hypatiaworkflow_auditfinding).RSR Quality Checklist
Required
zig build test{,-integration,-smoke,-property}— 150 green: 92 unit / 39 integration / 5 smoke / 14 property).envfiles includedAs Applicable
abi-contract.ymlgates unaffected)Testing
10 new adversarial unit tests in
server_actions.zig: injection payloads (;, backticks,$(…), pipes) rejected by the validators;-oProxyCommandhost rejected;scripts/../../etc/passwdtraversal rejected;shellQuoteInto/xmlEscapeIntooutput asserted;executeAction/run_script/write_server_configreject malicious inputs at the boundary. Grep-gate: no inlinessharg-building remains.🤖 Generated with Claude Code
https://claude.ai/code/session_01TaWWedv6VQqeZaPvc94keN
Generated by Claude Code