// bash-normalize.mjs — Normalize bash parameter expansion evasion techniques. // // Attackers can evade command-name matching by inserting shell metacharacters // that are transparent to bash but break regex patterns. // // This module strips these constructs from command names so that downstream // pattern matching sees the canonical form. // // Exported as a shared module — used by pre-bash-destructive.mjs and // pre-install-supply-chain.mjs. /** * Normalize bash parameter expansion and quoting evasion in a command string. * * Strips: * - Empty single quotes: '' (e.g., w''get -> wget) * - Empty double quotes: "" (e.g., r""m -> rm) * - Single-char parameter expansion: ${x} -> x (evasion: attacker sets x=x) * - Multi-char parameter expansion: ${ANYTHING} -> '' (unknown value) * - Backslash escapes between word chars, iteratively (c\u\r\l -> curl) * - Backtick subshell with empty/whitespace content * * Does NOT strip: * - Quotes around arguments (only targets empty quotes that split command names) * - $VAR without braces (not an evasion pattern) * - Backslashes before non-word chars (\n, \t, etc.) * * @param {string} cmd - Raw command string * @returns {string} Normalized command string */ export function normalizeBashExpansion(cmd) { if (!cmd || typeof cmd !== 'string') return cmd || ''; let result = cmd // Strip empty single quotes: w''get -> wget .replace(/''/g, '') // Strip empty double quotes: r""m -> rm .replace(/""/g, '') // Single-char ${x} -> x (evasion: c${u}rl -> curl, assumes x=x) .replace(/\$\{(\w)\}/g, '$1') // Multi-char ${ANYTHING} -> '' (unknown value, strip entirely) .replace(/\$\{[^}]*\}/g, '') // Strip backtick subshell with empty/whitespace content .replace(/`\s*`/g, ''); // Iteratively strip backslash between word chars (c\u\r\l needs 2 passes) let prev; do { prev = result; result = result.replace(/(\w)\\(\w)/g, '$1$2'); } while (result !== prev); return result; }