fix(hooks): validate on_create hook commands against shell injection#85
fix(hooks): validate on_create hook commands against shell injection#85JanTvrdik wants to merge 1 commit intocontember:mainfrom
Conversation
Add validate_hook_command() that rejects dangerous shell metacharacters (;, &&, ||, |, backticks, $(), >, <) in on_create hook commands from project-level settings. Untrusted repos could exploit format!()-based interpolation to chain malicious commands. apply_on_create() now returns Option<ShellType> and logs a warning when validation fails instead of executing the command. Closes contember#70 Co-Authored-By: Claude Code
There was a problem hiding this comment.
Pull request overview
This PR mitigates shell command injection risk in the terminal.on_create hook by validating hook commands for dangerous shell metacharacters and skipping unsafe hooks instead of blindly interpolating them into a shell script.
Changes:
- Added
validate_hook_command()and a denylist of dangerous shell metacharacters forterminal.on_create. - Updated
apply_on_create()to returnOption<ShellType>and skip execution (with a warning) when validation fails. - Updated call sites and expanded tests to cover the new validation behavior.
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 3 comments.
| File | Description |
|---|---|
crates/okena-workspace/src/hooks.rs |
Adds validation + changes apply_on_create to return Option, plus new unit/integration tests. |
src/workspace/actions/execute.rs |
Updates terminal spawn path to handle apply_on_create() returning None. |
src/views/root/terminal_actions.rs |
Updates shell-switch action to handle apply_on_create() returning None. |
crates/okena-views-terminal/src/layout/terminal_pane/mod.rs |
Updates terminal pane creation to handle apply_on_create() returning None. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| const DANGEROUS_SHELL_PATTERNS: &[&str] = &[";", "&&", "||", "|", "`", "$(", ">", "<"]; | ||
|
|
||
| /// Validate that a hook command does not contain shell injection metacharacters. | ||
| /// Project-level hooks from untrusted repos could abuse these to run arbitrary commands. | ||
| /// Returns `Ok(())` if the command is safe, or `Err` with a description of what was rejected. | ||
| pub fn validate_hook_command(cmd: &str) -> Result<(), String> { | ||
| for pattern in DANGEROUS_SHELL_PATTERNS { | ||
| if cmd.contains(pattern) { | ||
| return Err(format!( | ||
| "hook command contains dangerous shell metacharacter '{}': {}", | ||
| pattern, cmd |
There was a problem hiding this comment.
validate_hook_command still allows command chaining via newline (\n/\r) and single & (background/command separator on POSIX; command separator on cmd.exe). Since apply_on_create interpolates on_create_cmd into a shell string, an attacker can still inject additional commands without using any of the currently blocked patterns. Consider rejecting &, \n, and \r (and add unit tests for these cases) so the validation actually closes the injection vector.
| return Err(format!( | ||
| "hook command contains dangerous shell metacharacter '{}': {}", | ||
| pattern, cmd | ||
| )); | ||
| } | ||
| } | ||
| Ok(()) | ||
| } | ||
|
|
||
| /// Apply the `terminal.on_create` command by wrapping the shell to run | ||
| /// the command first, then `exec` into the original shell. | ||
| /// Environment variables are exported so they persist in the shell session. | ||
| /// Produces: `sh -c 'export K=V; ...; <on_create_cmd>; exec <shell_cmd>'` | ||
| pub fn apply_on_create(shell: &ShellType, on_create_cmd: &str, env_vars: &HashMap<String, String>) -> ShellType { | ||
| /// | ||
| /// Returns `None` if the command fails validation (shell injection detected). | ||
| pub fn apply_on_create(shell: &ShellType, on_create_cmd: &str, env_vars: &HashMap<String, String>) -> Option<ShellType> { | ||
| if let Err(e) = validate_hook_command(on_create_cmd) { | ||
| log::warn!("Skipping terminal.on_create hook: {}", e); | ||
| return None; |
There was a problem hiding this comment.
The validation error string includes the full hook command, and this gets logged at warn level. Hook commands can contain secrets (tokens, passwords) and logging them may leak sensitive data into logs/telemetry. Consider logging only the rejected metacharacter (and perhaps the hook source), or truncating/redacting the command in the log message.
| /// We reject hook commands from project-level settings that contain these, | ||
| /// since they could chain or redirect arbitrary commands. | ||
| const DANGEROUS_SHELL_PATTERNS: &[&str] = &[";", "&&", "||", "|", "`", "$(", ">", "<"]; | ||
|
|
||
| /// Validate that a hook command does not contain shell injection metacharacters. | ||
| /// Project-level hooks from untrusted repos could abuse these to run arbitrary commands. |
There was a problem hiding this comment.
Docstrings for DANGEROUS_SHELL_PATTERNS/validate_hook_command say the rejection is for “project-level settings”, but apply_on_create calls validation for any resolved hook (project/parent/global). Either clarify the documentation to match current behavior, or plumb through whether the hook is project-scoped so global/user-configured hooks aren’t unexpectedly restricted.
| /// We reject hook commands from project-level settings that contain these, | |
| /// since they could chain or redirect arbitrary commands. | |
| const DANGEROUS_SHELL_PATTERNS: &[&str] = &[";", "&&", "||", "|", "`", "$(", ">", "<"]; | |
| /// Validate that a hook command does not contain shell injection metacharacters. | |
| /// Project-level hooks from untrusted repos could abuse these to run arbitrary commands. | |
| /// Hook commands from any scope (project/parent/global) that contain these | |
| /// are considered unsafe, since they could chain or redirect arbitrary commands. | |
| const DANGEROUS_SHELL_PATTERNS: &[&str] = &[";", "&&", "||", "|", "`", "$(", ">", "<"]; | |
| /// Validate that a hook command does not contain shell injection metacharacters. | |
| /// This is intended to protect against untrusted hook configurations (for example, | |
| /// commands defined in project repositories) but can be applied to any hook scope. |
matej21
left a comment
There was a problem hiding this comment.
Good approach with the metacharacter blocklist. A few gaps to close:
- Newline (
\n) is missing fromDANGEROUS_SHELL_PATTERNS— it's a valid command separator insh -cand bypasses all current checks. Please add it. - The validation applies to all config levels (global, parent, project). A legitimate global hook with pipes (e.g.
grep ... | head) will be silently rejected. Consider scoping validation to project-level hooks only, or at minimum documenting this restriction. - Silent failure (warn log only) — users won't notice their hook was skipped. Consider surfacing a visible warning in the terminal.
Summary
validate_hook_command()that rejects dangerous shell metacharacters (;,&&,||,|, backticks,$(,>,<) inon_createhook commandsapply_on_create()now returnsOption<ShellType>— logs a warning and skips the hook when validation failsOptionreturn type gracefullyCloses #70
Test plan
validate_hook_commandcovering simple commands (pass) and all injection vectors (reject)apply_on_createreturnsNonefor malicious inputapply_on_create_with_env_varstest updated and passingcargo buildandcargo testpassCo-Authored-By: Claude Code