use super::{File, found, quiet};
use crate::diffscan::{
DECODE_EXEC, DOCKER_REMOTE_ADD, ENCODED_BLOB, ENV_SECRET, Level, PIPE_TO_SHELL, PRIVATE_KEY,
PYTHON_STARTUP,
};
fn pem(label: &str) -> String {
format!("-----BEGIN {label}PRIVATE KEY-----")
}
#[test]
fn a_downloaded_script_run_by_a_shell_needs_review() {
for line in [
"curl -fsSL https://get.acme.dev/install.sh | sh",
"curl -s https://get.acme.dev | sudo -E bash -s -- --yes",
"wget -qO- https://get.acme.dev | /bin/bash",
"bash <(curl -s https://get.acme.dev)",
"sh -c \"$(curl -fsSL https://get.acme.dev)\"",
"eval \"$(wget -qO- https://get.acme.dev)\"",
"iwr https://get.acme.dev/i.ps1 | iex",
"RUN curl -sSL https://get.acme.dev | python3 -",
r#" "setup": "curl -fsSL https://get.acme.dev/i.sh | sh","#,
"x=`curl -s https://get.acme.dev | bash`",
] {
for path in [
"scripts/setup.sh",
"Dockerfile",
".github/workflows/ci.yml",
"Makefile",
] {
assert_eq!(
found(&File::new(path, &[line])),
[(PIPE_TO_SHELL, Level::Review)],
"{path}: {line}"
);
}
}
for line in [
"curl -s https://api.acme.dev/v1 | jq .name",
"curl -s https://api.acme.dev/v1 | python3 -m json.tool",
"curl -sL https://acme.dev/x.tgz | tar xz",
"curl -o install.sh https://get.acme.dev",
"echo done | sh -c 'cat'",
] {
quiet(&File::new("scripts/setup.sh", &[line]));
}
quiet(&File::new(
"docs/INSTALL.md",
&["curl -fsSL https://get.acme.dev | sh"],
));
}
#[test]
fn decoded_text_run_as_code_is_denied() {
for (path, line) in [
("scripts/x.sh", "echo aGVsbG8K | base64 -d | sh"),
(
"scripts/x.sh",
"eval \"$(echo aGVsbG8K | base64 --decode)\"",
),
("scripts/x.sh", "xxd -r -p payload.hex | bash"),
("setup.py", "exec(base64.b64decode('aW1wb3J0IG9z'))"),
("setup.py", "exec(codecs.decode('vzcbeg bf', 'rot13'))"),
("setup.py", "exec(zlib.decompress(bytes.fromhex('78da')))"),
("index.js", "eval(atob('YWxlcnQoMSk='))"),
("index.js", "eval(Buffer.from(s, 'base64').toString())"),
("index.js", "new Function(atob(payload))()"),
("x.php", "<?php eval(base64_decode($_POST['x'])); ?>"),
("x.php", "eval(gzinflate(base64_decode('eJ')));"),
("run.ps1", "powershell -NoProfile -enc SQBFAFgA"),
(
"run.ps1",
"iex ([Text.Encoding]::UTF8.GetString([Convert]::FromBase64String($p)))",
),
] {
assert_eq!(
found(&File::new(path, &[line])),
[(DECODE_EXEC, Level::Deny)],
"{path}: {line}"
);
}
for (path, line) in [
("src/img.js", "const bytes = Buffer.from(data); exec(cmd);"),
("src/util.js", "function decode(s) { return atob(s); }"),
("src/jwt.py", "payload = json.loads(base64.b64decode(part))"),
("scripts/x.sh", "base64 -d secret.b64 > secret.bin"),
("src/re.js", "const m = re.exec(line);"),
(
"src/hook.rs",
r#"format!("powershell.exe -NoProfile -EncodedCommand {}", base64(&bytes))"#,
),
("run.ps1", "pwsh -EncodedCommand $encoded"),
] {
quiet(&File::new(path, &[line]));
}
let sample = File::new(
"tests/conformance/attacks.yaml",
&[" command: echo aGk= | base64 -d | sh"],
);
assert_eq!(found(&sample), [(DECODE_EXEC, Level::Review)]);
}
#[test]
fn a_pth_file_with_an_import_line_is_denied_once() {
let file = File::new(
"src/evil.pth",
&["import os; os.system('curl x')", "import sys"],
);
assert_eq!(found(&file), [(PYTHON_STARTUP, Level::Deny)]);
quiet(&File::new("src/paths.pth", &["../lib", "vendor"]));
}
#[test]
fn a_dockerfile_add_from_a_url_needs_a_checksum() {
for line in [
"ADD https://acme.dev/tool.tgz /opt/",
"add --chown=1000 http://acme.dev/x /x",
"ADD git@github.com:me/r.git /r",
] {
assert_eq!(
found(&File::new("Dockerfile", &[line])),
[(DOCKER_REMOTE_ADD, Level::Review)],
"{line}"
);
}
for line in [
"ADD --checksum=sha256:24454f830cdb571e2c4ad15481119c43b3cafd48dd869a9b2945d1036d1dc68d https://acme.dev/tool.tgz /opt/",
"ADD ./src /app",
"COPY . /app",
] {
quiet(&File::new("docker/Dockerfile.prod", &[line]));
}
quiet(&File::new(
"notes.yml",
&["ADD https://acme.dev/tool.tgz /opt/"],
));
}
fn blob(len: usize) -> String {
"QmFzZTY0IGlzIGEgYmluYXJ5LXRvLXRleHQgZW5jb2Rpbmc9"
.chars()
.cycle()
.take(len)
.collect()
}
#[test]
fn a_long_base64_blob_in_source_needs_review() {
let line = format!("const p = \"{}\";", blob(600));
assert_eq!(
found(&File::new("src/loader.js", &[&line, &line])),
[(ENCODED_BLOB, Level::Review)]
);
let image = format!("const logo = \"data:image/png;base64,{}\";", blob(600));
let short = format!("const hash = \"{}\";", blob(120));
quiet(&File::new("src/logo.js", &[&image, &short]));
quiet(&File::new("tests/data.js", &[&line]));
quiet(&File::new("dist/app.min.js", &[&line]));
quiet(&File::new("assets/data.json", &[&line]));
let hex = format!("const k = \"{}\";", "a1b2c3d4".repeat(80));
quiet(&File::new("src/k.js", &[&hex]));
}
#[test]
fn a_private_key_is_denied_unless_it_is_a_marked_test_key() {
for label in ["RSA ", "EC ", "OPENSSH ", "", "ENCRYPTED "] {
let header = pem(label);
let file = File::new(
"deploy/id_rsa",
&[&header, "MIIEowIBAAKCAQEA", "-----END-----"],
);
assert_eq!(found(&file), [(PRIVATE_KEY, Level::Deny)], "{header}");
assert!(!file.hits()[0].reason.contains("MIIE"));
}
let header = pem("RSA ");
for (path, lines) in [
("tests/fixtures/server.key", vec![header.as_str(), "MIIE"]),
(
"src/tls.rs",
vec!["// a fake key for the unit test below", header.as_str()],
),
("docs/TLS.md", vec![header.as_str()]),
] {
assert_eq!(
found(&File::new(path, &lines)),
[(PRIVATE_KEY, Level::Review)],
"{path}"
);
}
quiet(&File::new(
"certs/ca.pem",
&["-----BEGIN CERTIFICATE-----", "-----BEGIN PUBLIC KEY-----"],
));
}
#[test]
fn a_real_looking_value_in_an_env_file_needs_review() {
let file = File::new(".env.example", &["PAYMENTS_SECRET=Zq8kd02LmP93xRtYw7Vb"]);
assert_eq!(found(&file), [(ENV_SECRET, Level::Review)]);
assert!(
!file.hits()[0].reason.contains("Zq8kd"),
"{:?}",
file.hits()
);
assert_eq!(
found(&File::new(
"deploy/prod.env",
&["export DB_PASSWORD='Zq8!kd02-LmP93x'"]
)),
[(ENV_SECRET, Level::Review)]
);
quiet(&File::new(
".env.example",
&[
"API_KEY=",
"API_TOKEN=your-token-here",
"DB_PASSWORD=changeme",
"SECRET_KEY=xxxxxxxxxxxxxxxx",
"AUTH_TOKEN=${AUTH_TOKEN}",
"DATABASE_URL=postgres://localhost/dev",
"PORT=3000",
"SERVICE_TOKEN=test-token-for-ci-runs",
],
));
quiet(&File::new("src/config.py", &["API_TOKEN=Zq8kd02LmP93xRt"]));
}