#![allow(clippy::unwrap_used, clippy::expect_used, clippy::panic)]
mod common;
use std::sync::LazyLock;
use proptest::prelude::*;
use rippy_cli::allowlists;
use rippy_cli::verdict::Decision;
static SAFE_CMDS: LazyLock<Vec<&'static str>> = LazyLock::new(allowlists::all_simple_safe);
static WRAPPERS: LazyLock<Vec<&'static str>> = LazyLock::new(allowlists::all_wrappers);
const DANGEROUS_COMMANDS: &[&str] = &[
"rm -rf /",
"dd if=/dev/zero of=/dev/sda",
"chmod 777 /",
"shred /dev/sda",
];
const INJECTION_VECTORS: &[&str] = &["; ", " && ", " || "];
const SAFE_ARGS: &[&str] = &["", "-la", "-v", "file.txt", "/tmp/foo", "-r .", "-n 10"];
const SAFE_INNER_COMMANDS: &[&str] = &[
"ls -la",
"echo hello",
"cat file.txt",
"grep pattern file",
"wc -l file",
"head -10 file",
"tail -5 file",
"pwd",
"whoami",
"date",
];
const RECURSIVE_TEMPLATES: &[&str] = &[
"bash -c '{CMD}'",
"sh -c '{CMD}'",
"docker exec container {CMD}",
"env FOO=bar {CMD}",
"xargs {CMD}",
"time {CMD}",
"nice {CMD}",
];
proptest! {
#![proptest_config(ProptestConfig {
cases: 512,
max_shrink_iters: 256,
.. ProptestConfig::default()
})]
#[test]
fn safe_command_passthrough(
cmd_idx in 0..SAFE_CMDS.len(),
arg_idx in 0..SAFE_ARGS.len(),
) {
let cmd = SAFE_CMDS[cmd_idx];
let arg = SAFE_ARGS[arg_idx];
let full = if arg.is_empty() {
cmd.to_string()
} else {
format!("{cmd} {arg}")
};
let mut analyzer = common::isolated_analyzer();
let verdict = analyzer.analyze(&full).expect("analyze succeeds");
let msg = format!(
"SIMPLE_SAFE command {:?} was not allowed: {:?}",
full, verdict.reason,
);
prop_assert!(verdict.decision == Decision::Allow, "{}", msg);
}
#[test]
fn injection_detection(
safe_idx in 0..SAFE_INNER_COMMANDS.len(),
danger_idx in 0..DANGEROUS_COMMANDS.len(),
vector_idx in 0..INJECTION_VECTORS.len(),
) {
let safe = SAFE_INNER_COMMANDS[safe_idx];
let danger = DANGEROUS_COMMANDS[danger_idx];
let vector = INJECTION_VECTORS[vector_idx];
let full = format!("{safe}{vector}{danger}");
let mut analyzer = common::isolated_analyzer();
let verdict = analyzer.analyze(&full).expect("analyze succeeds");
let msg = format!(
"Injection not caught: {:?} => {:?} ({:?})",
full, verdict.decision, verdict.reason,
);
prop_assert!(verdict.decision >= Decision::Ask, "{}", msg);
}
#[test]
fn contrast_safe_inner(
template_idx in 0..RECURSIVE_TEMPLATES.len(),
inner_idx in 0..SAFE_INNER_COMMANDS.len(),
) {
let template = RECURSIVE_TEMPLATES[template_idx];
let inner = SAFE_INNER_COMMANDS[inner_idx];
let cmd = template.replace("{CMD}", inner);
let mut analyzer = common::isolated_analyzer();
let verdict = analyzer.analyze(&cmd).expect("analyze succeeds");
let msg = format!(
"Safe inner blocked: {:?} => {:?}",
cmd, verdict.reason,
);
prop_assert!(verdict.decision == Decision::Allow, "{}", msg);
}
#[test]
fn contrast_dangerous_inner(
template_idx in 0..RECURSIVE_TEMPLATES.len(),
danger_idx in 0..DANGEROUS_COMMANDS.len(),
) {
let template = RECURSIVE_TEMPLATES[template_idx];
let danger = DANGEROUS_COMMANDS[danger_idx];
let cmd = template.replace("{CMD}", danger);
let mut analyzer = common::isolated_analyzer();
let verdict = analyzer.analyze(&cmd).expect("analyze succeeds");
let msg = format!(
"Dangerous inner allowed: {:?} => {:?}",
cmd, verdict.reason,
);
prop_assert!(verdict.decision >= Decision::Ask, "{}", msg);
}
#[test]
fn wrapper_safe_passthrough(
wrapper_idx in 0..WRAPPERS.len(),
inner_idx in 0..SAFE_INNER_COMMANDS.len(),
) {
let wrapper = WRAPPERS[wrapper_idx];
let inner = SAFE_INNER_COMMANDS[inner_idx];
let cmd = format!("{wrapper} {inner}");
let mut analyzer = common::isolated_analyzer();
let verdict = analyzer.analyze(&cmd).expect("analyze succeeds");
let msg = format!(
"Wrapper {} blocked safe inner: {:?} => {:?}",
wrapper, cmd, verdict.reason,
);
prop_assert!(verdict.decision == Decision::Allow, "{}", msg);
}
#[test]
fn wrapper_dangerous_detected(
wrapper_idx in 0..WRAPPERS.len(),
danger_idx in 0..DANGEROUS_COMMANDS.len(),
) {
let wrapper = WRAPPERS[wrapper_idx];
let danger = DANGEROUS_COMMANDS[danger_idx];
let cmd = format!("{wrapper} {danger}");
let mut analyzer = common::isolated_analyzer();
let verdict = analyzer.analyze(&cmd).expect("analyze succeeds");
let msg = format!(
"Wrapper {} missed dangerous: {:?} => {:?}",
wrapper, cmd, verdict.reason,
);
prop_assert!(verdict.decision >= Decision::Ask, "{}", msg);
}
}