use std::path::PathBuf;
const ALLOWED: &[&str] = &[
"anyhow!",
"arg_err",
"bad_arg",
"E_",
"unsupported operator",
"unknown ",
"invalid ",
"no such ",
"does not exist",
"not provided",
"requires ",
"Cannot convert",
"failed to",
"outside the workspace",
"no backend found",
"cannot be",
"expected ",
"[SECURITY]",
];
const SHELL_MARKERS: &[&str] = &[
"Get-",
"Set-",
"New-",
"Remove-",
"Start-",
"Stop-",
"Restart-",
"Add-",
"Invoke-",
"Compress-",
"Expand-",
"Where-Object",
"Select-Object",
"ConvertTo-",
"ConvertFrom-",
"Read-Host",
"Write-Output",
"FindWindow",
"System.",
"display notification",
"display dialog",
"do shell script",
"$_.",
"$env:",
];
fn source(rel: &str) -> String {
let path = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join(rel);
std::fs::read_to_string(&path).unwrap_or_else(|e| panic!("read {}: {e}", path.display()))
}
fn placeholder_embedded_in_single_quotes(line: &str) -> bool {
let mut search_from = 0;
while let Some(rel) = line[search_from..].find("{}") {
let at = search_from + rel;
let left = line[..at].rfind('\'');
let right = line[at + 2..].find('\'').map(|r| at + 2 + r);
if let (Some(l), Some(r)) = (left, right) {
let before = &line[l + 1..at];
let after = &line[at + 2..r];
let crosses = |s: &str| s.contains(',') || s.contains('"') || s.contains('{');
if !crosses(before) && !crosses(after) {
return true;
}
}
search_from = at + 2;
}
false
}
fn is_suspect(line: &str) -> bool {
let trimmed = line.trim();
if trimmed.starts_with("//") || trimmed.starts_with("///") || trimmed.starts_with("*") {
return false;
}
let has_quoted_placeholder = trimmed.contains("'{}'")
|| trimmed.contains("\\\"{}\\\"")
|| trimmed.contains("\"{}\"")
|| placeholder_embedded_in_single_quotes(trimmed);
if !has_quoted_placeholder {
return false;
}
if ALLOWED.iter().any(|a| trimmed.contains(a)) {
return false;
}
SHELL_MARKERS.iter().any(|m| trimmed.contains(m))
}
#[test]
fn no_shell_command_interpolates_a_value_without_quoting_it() {
let mut offenders = Vec::new();
for file in ["src/builtins.rs", "src/safety.rs", "src/os_tools.rs"] {
for (i, line) in source(file).lines().enumerate() {
if is_suspect(line) {
offenders.push(format!(" {}:{}\n {}", file, i + 1, line.trim()));
}
}
}
assert!(
offenders.is_empty(),
"shell command(s) interpolate a value directly into a quoted literal — a value \
containing a quote (PowerShell/AppleScript) or `$(…)` (double-quoted PowerShell) \
executes.\n\nUse `safety::ps_quote(&v)` or `safety::applescript_quote(&v)` and \
interpolate with `{{}}` rather than `'{{}}'`, so the helper supplies the quotes.\n\n\
If the line is genuinely not a shell string, add a distinguishing substring to \
ALLOWED in this test.\n\n{}",
offenders.join("\n")
);
}
fn enclosing_macro(lines: &[&str], line_idx: usize) -> Option<&'static str> {
for l in lines[..=line_idx].iter().rev().take(40) {
if l.contains("ps_script!(") {
return Some("ps_script!");
}
if l.contains("applescript!(") {
return Some("applescript!");
}
if l.contains("format!(") {
return Some("format!");
}
}
None
}
#[test]
fn powershell_commands_with_values_use_the_checked_macro() {
let mut offenders = Vec::new();
for file in ["src/builtins.rs", "src/os_tools.rs"] {
let text = source(file);
let lines: Vec<&str> = text.lines().collect();
for (i, line) in lines.iter().enumerate() {
let t = line.trim();
if t.starts_with("//") || t.starts_with("*") {
continue;
}
if !t.contains("{}") || !SHELL_MARKERS.iter().any(|m| t.contains(m)) {
continue;
}
if ALLOWED.iter().any(|a| t.contains(a)) {
continue;
}
if enclosing_macro(&lines, i) == Some("format!") {
offenders.push(format!(" {}:{}\n {}", file, i + 1, t));
}
}
}
assert!(
offenders.is_empty(),
"PowerShell/AppleScript command(s) built with `format!`, which accepts any \
`Display` value — including a caller-supplied `String`.\n\nUse \
`crate::ps_script!` (or `crate::applescript!`), which accepts only \
pre-escaped literals, integers, and `&'static str`, so an unescaped value \
is a compile error.\n\nFor a value that must be interpolated *unquoted* \
(a size or a port), validate it with `safety::ps_bare_number`.\n\n{}",
offenders.join("\n")
);
}
#[test]
fn the_guard_detects_the_shape_it_is_meant_to_catch() {
assert!(
is_suspect(r#" .args(["-Command", &format!("Start-Service '{}'", name)])"#),
"the exact pre-fix shape of finding 10a must be flagged"
);
assert!(
is_suspect(r#"$bytes = [System.Text.Encoding]::UTF8.GetBytes("{}")"#),
"the pre-fix shape of finding 10c must be flagged"
);
assert!(
is_suspect(r#""display notification \"{}\" with title \"{}\"","#),
"the AppleScript shape must be flagged"
);
assert!(
is_suspect(
r#"format!("Get-NetIPAddress | Where-Object {{ $_.InterfaceAlias -like '*{}*' }}", iface)"#
),
"a placeholder embedded in a single-quoted -like pattern must be flagged"
);
assert!(
is_suspect(
r#"format!("$p = Start-Process -FilePath cmd -ArgumentList '/C {}' -PassThru", command)"#
),
"a placeholder embedded in a single-quoted -ArgumentList must be flagged"
);
assert!(!is_suspect(
r#" &format!("Get-Process -Id {} -ErrorAction SilentlyContinue", pid),"#
));
assert!(!is_suspect(
r#" let cmd = format!("(Get-Process -Id {}).PriorityClass", pid);"#
));
assert!(!is_suspect(
r#" .args(["-Command", &format!("Start-Service {}", crate::safety::ps_quote(&name))])"#
));
assert!(!is_suspect(
r#"$bytes = [System.Text.Encoding]::UTF8.GetBytes({})"#
));
assert!(!is_suspect(
r#"return Err(anyhow!("sess_eval: no such session '{}'", id));"#
));
}