fix(server): allow newlines in exec command arguments#1965
Conversation
Single-quoted POSIX strings treat newlines as literal characters, not command separators. The newline rejection prevented multi-line scripts passed via sandbox exec (e.g. python3 -c with a multi-line body). Null byte rejection is preserved since null bytes truncate C-level shell parsing. Closes NVIDIA#1963 Signed-off-by: Adel Zaalouk <azaalouk@redhat.com>
Add reject_null_bytes() for exec command argument validation, replacing reject_control_chars() which also rejected newlines. Multi-line scripts passed to bash -c or python3 -c are legitimate and safe because shell_escape() wraps them in POSIX single quotes where newlines are literal. Newline rejection is preserved for workdir (no legitimate path contains newlines) and environment values. Closes NVIDIA#1963 Signed-off-by: Adel Zaalouk <azaalouk@redhat.com>
Split reject_control_chars into reject_null_char and reject_newline_chars so callers can compose the checks they need. reject_control_chars now delegates to both. Exec command arguments call reject_null_char directly. Addresses review feedback from @elezar on PR NVIDIA#1965. Signed-off-by: Adel Zaalouk <azaalouk@redhat.com>
| command: vec![ | ||
| "python3".to_string(), | ||
| "-c".to_string(), | ||
| "def f():\n return 1\nprint(f())".to_string(), |
There was a problem hiding this comment.
This test should include both CR/LF and a single quote in the same command argument. The risky interaction here is not just that newlines are accepted, but that multiline script text still composes correctly with POSIX single-quote escaping.
For example, something like print('one')\r\nprint('two') would give us regression coverage for the exact case this helper has to keep safe.
| }; | ||
| assert!(build_remote_exec_command(&req).is_err()); | ||
| // Validation layer rejects newlines in workdir | ||
| assert!(validate_exec_request_fields(&req).is_err()); |
There was a problem hiding this comment.
nit: This test name still says build_remote_exec_command, but the assertion now exercises validate_exec_request_fields(). Could we rename it to validate_exec_rejects_newlines_in_workdir, or drop it since validation.rs already has this coverage?
| ))); | ||
| } | ||
| reject_control_chars(arg, &format!("command argument {i}"))?; | ||
| reject_null_char(arg, &format!("command argument {i}"))?; |
There was a problem hiding this comment.
nit: The function comment above still says this validates control characters generally, but command args now intentionally allow CR/LF and only reject NUL. Maybe reword it to something like: Validate exec request size limits and field-specific character constraints.
| if value.bytes().any(|b| b == b'\n' || b == b'\r') { | ||
| return Err("value contains newline or carriage return".to_string()); | ||
| } | ||
| if value.is_empty() { |
There was a problem hiding this comment.
Since this helper now allows \n/\r, the downstream command_preview fields in the exec logging paths should not log the assembled command raw. In line-oriented log sinks, user-controlled CR/LF can split records or create forged-looking continuation lines. Can we escape the preview before logging, and reuse the same helper for both non-interactive and interactive exec?
let command_preview: String = command
.chars()
.take(120)
.flat_map(char::escape_default)
.collect();|
nit: |
Summary
Allow newline characters in
sandbox execcommand arguments so multi-line scripts (python3 -c "def f():\n ...",bash -c "echo 'line1\nline2'") work without base64 workarounds.Related Issue
Fixes #1963
Changes
shell_escape()(sandbox.rs): Remove newline rejection. Newlines inside POSIX single quotes are literal characters, not command separators. Null byte rejection preserved.validate_exec_request_fields()(validation.rs): Addreject_null_bytes()for exec command arguments instead ofreject_control_chars(). Newline rejection preserved for workdir and environment values.Testing
Verified end-to-end against a live gateway with podman driver on macOS:
cargo test --package openshell-server— 857 tests pass, 0 failuressandbox exec— workssandbox exec— worksChecklist