mirror of
https://github.com/0xMassi/webclaw.git
synced 2026-05-30 20:55:12 +02:00
fix(cli): close --on-change command injection via sh -c (P0) (#20)
* fix(cli): close --on-change command injection via sh -c (P0)
The --on-change flag on `webclaw watch` (single-URL, line 1588) and
`webclaw watch` multi-URL mode (line 1738) previously handed the entire
user-supplied string to `tokio::process::Command::new("sh").arg("-c").arg(cmd)`.
Any path that can influence that string — a malicious config file, an MCP
client driven by an LLM with prompt-injection exposure, an untrusted
environment variable substitution — gets arbitrary shell execution.
The command is now tokenized with `shlex::split` (POSIX-ish quoting rules)
and executed directly via `Command::new(prog).args(args)`. Metacharacters
like `;`, `&&`, `|`, `$()`, `<(...)`, env expansion, and globbing no longer
fire.
An explicit opt-in escape hatch is available for users who genuinely need
a shell pipeline: `WEBCLAW_ALLOW_SHELL=1` preserves the old `sh -c` path
and logs a warning on every invocation so it can't slip in silently.
Both call sites now route through a shared `spawn_on_change()` helper.
Adds `shlex = "1"` to webclaw-cli dependencies.
Version: 0.3.13 -> 0.3.14
CHANGELOG updated.
Surfaced by the 2026-04-16 workspace audit.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* chore(brand): fix clippy 1.95 unnecessary_sort_by errors
Pre-existing sort_by calls in brand.rs became hard errors under clippy
1.95. Switch to sort_by_key with std::cmp::Reverse. Pure refactor — same
ordering, no behavior change. Bundled here so CI goes green on the P0
command-injection fix.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
6316b1a6e7
commit
1352f48e05
6 changed files with 73 additions and 39 deletions
|
|
@ -427,7 +427,7 @@ fn extract_colors(decls: &[CssDecl]) -> Vec<BrandColor> {
|
|||
.collect();
|
||||
|
||||
// Sort by frequency (descending)
|
||||
colors.sort_by(|a, b| b.count.cmp(&a.count));
|
||||
colors.sort_by_key(|c| std::cmp::Reverse(c.count));
|
||||
|
||||
// Promote top non-white/black to Primary/Secondary if they're still Unknown
|
||||
let mut assigned_primary = colors.iter().any(|c| c.usage == ColorUsage::Primary);
|
||||
|
|
@ -615,7 +615,7 @@ fn extract_fonts(decls: &[CssDecl]) -> Vec<String> {
|
|||
}
|
||||
|
||||
let mut fonts: Vec<(String, usize)> = freq.into_iter().collect();
|
||||
fonts.sort_by(|a, b| b.1.cmp(&a.1));
|
||||
fonts.sort_by_key(|f| std::cmp::Reverse(f.1));
|
||||
fonts.into_iter().map(|(name, _)| name).collect()
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue