Skip to content

Commit 61814aa

Browse files
Robert E. Leeruvnet
andcommitted
fix: Address 3 MEDIUM security audit findings
- Fix UTF-8 byte-slicing panic in parse_cc_command (use chars().skip()) - Add 1MB stdin read limit in hook processor (prevent OOM) - Add -l (literal) flag to send_slash_command (prevent tmux key injection) - Validate slash commands against safe character pattern - Add warning log on datetime parse failures Security audit: 0 CRITICAL, 0 HIGH, 0 remaining MEDIUM findings. Co-Authored-By: claude-flow <ruv@ruv.net>
1 parent 49f1fb0 commit 61814aa

File tree

4 files changed

+28
-6
lines changed

4 files changed

+28
-6
lines changed

src/bot.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -282,7 +282,9 @@ pub fn is_kill_command(text: &str) -> bool {
282282
pub fn parse_cc_command(text: &str) -> Option<String> {
283283
let lower = text.to_lowercase();
284284
if lower.starts_with("cc ") {
285-
Some(format!("/{}", text[3..].trim()))
285+
// UTF-8 safe: skip 3 chars (not bytes) to avoid panicking on multi-byte input
286+
let rest: String = text.chars().skip(3).collect();
287+
Some(format!("/{}", rest.trim()))
286288
} else {
287289
None
288290
}

src/hook.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,9 @@ use tokio::net::UnixStream;
1111
/// It reads JSON from stdin, forwards to the bridge daemon via unix socket,
1212
/// and optionally returns hookSpecificOutput to Claude Code on stdout.
1313
pub async fn process_hook(socket_path: &std::path::Path) -> Result<()> {
14-
// Read all of stdin
14+
// Read stdin with 1 MB limit to prevent unbounded memory allocation
1515
let mut input = String::new();
16-
io::stdin().read_to_string(&mut input)?;
16+
io::stdin().take(1_048_576).read_to_string(&mut input)?;
1717

1818
let input = input.trim();
1919
if input.is_empty() {

src/injector.rs

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -176,12 +176,29 @@ impl InputInjector {
176176
None => return Ok(false),
177177
};
178178

179-
// Send command text (without -l, so tmux interprets special keys)
179+
// Validate command matches safe pattern: /word (alphanumeric + hyphens only)
180+
let cmd_body = command.strip_prefix('/').unwrap_or(command);
181+
if !cmd_body
182+
.chars()
183+
.all(|c| c.is_alphanumeric() || c == '-' || c == '_' || c == ' ')
184+
{
185+
tracing::warn!(
186+
command,
187+
"Slash command rejected: contains unsafe characters"
188+
);
189+
return Ok(false);
190+
}
191+
192+
// Send command text with -l (literal mode) to prevent tmux key interpretation
180193
let mut cmd = Command::new("tmux");
181194
if let Some(socket) = &self.tmux_socket {
182195
cmd.arg("-S").arg(socket);
183196
}
184-
cmd.arg("send-keys").arg("-t").arg(target).arg(command);
197+
cmd.arg("send-keys")
198+
.arg("-t")
199+
.arg(target)
200+
.arg("-l")
201+
.arg(command);
185202

186203
let output = cmd.output()?;
187204
if !output.status.success() {

src/session.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -403,7 +403,10 @@ impl SessionManager {
403403
fn parse_datetime(s: &str) -> DateTime<Utc> {
404404
DateTime::parse_from_rfc3339(s)
405405
.map(|dt| dt.with_timezone(&Utc))
406-
.unwrap_or_else(|_| Utc::now())
406+
.unwrap_or_else(|e| {
407+
tracing::warn!(input = %s, error = %e, "Failed to parse datetime, falling back to now");
408+
Utc::now()
409+
})
407410
}
408411

409412
#[cfg(test)]

0 commit comments

Comments
 (0)