feat(pre-bash-destructive): T8 — base64-pipe-shell idiom (E9)
Adds BLOCK_RULE for the malware-loader pattern: echo|cat|printf <base64-blob> | base64 -d | <shell> This is a common RCE delivery shape that bypasses static name-matching gates by encoding the destructive command as a base64 blob. The new rule fires only when the final pipe target is a shell interpreter (bash, sh, zsh, dash, ksh) — base64 decoded into jq or any non-shell consumer remains allowed. 5 new tests in pre-bash-destructive.test.mjs: - 3 BLOCK cases (echo|base64|bash, printf|base64|sh, cat|base64|zsh) - 2 FP probes (base64 -d -> jq passes; base64 -d alone passes) Closes E9 in critical-review-2026-04-20.md.
This commit is contained in:
parent
761e81309b
commit
336e4db1b8
2 changed files with 43 additions and 0 deletions
|
|
@ -79,6 +79,17 @@ const BLOCK_RULES = [
|
|||
'strings, which is a common code injection vector. Blocked. ' +
|
||||
'Refactor to use explicit commands instead.',
|
||||
},
|
||||
{
|
||||
name: 'T8 — base64-pipe-shell idiom (echo BLOB | base64 -d | sh)',
|
||||
// Matches: echo|cat|printf <base64-blob> | base64 -d | <shell>
|
||||
// Common malware loader pattern that bypasses static name-matching by
|
||||
// delivering the destructive command as encoded text.
|
||||
pattern: /\b(?:echo|cat|printf)\s+[A-Za-z0-9+/=]+\s*\|\s*base64\s+-d\s*\|\s*(?:bash|sh|zsh|dash|ksh)\b/i,
|
||||
description:
|
||||
'Decoding a base64 blob and piping it directly into a shell interpreter ' +
|
||||
'is a remote-code-execution loader pattern. Decode the blob first, ' +
|
||||
'inspect it, then execute explicitly if safe.',
|
||||
},
|
||||
// Policy-defined additional blocked patterns
|
||||
...getPolicyValue('destructive', 'additional_blocked', []).map(entry => ({
|
||||
name: entry.name || 'Custom blocked pattern',
|
||||
|
|
|
|||
|
|
@ -82,6 +82,38 @@ describe('pre-bash-destructive — BLOCK cases', () => {
|
|||
assert.match(result.stderr, /BLOCKED/);
|
||||
assert.match(result.stderr, /eval/i);
|
||||
});
|
||||
|
||||
it('blocks T8 — base64 blob piped into bash', async () => {
|
||||
const result = await runHook(SCRIPT, bashPayload('echo aGVsbG8K | base64 -d | bash'));
|
||||
assert.equal(result.code, 2);
|
||||
assert.match(result.stderr, /BLOCKED/);
|
||||
assert.match(result.stderr, /base64/i);
|
||||
});
|
||||
|
||||
it('blocks T8 — printf base64 blob piped into sh (no spaces around pipes)', async () => {
|
||||
const result = await runHook(SCRIPT, bashPayload('printf foo|base64 -d|sh'));
|
||||
assert.equal(result.code, 2);
|
||||
assert.match(result.stderr, /BLOCKED/);
|
||||
assert.match(result.stderr, /base64/i);
|
||||
});
|
||||
|
||||
it('blocks T8 — cat blob piped into zsh', async () => {
|
||||
const result = await runHook(SCRIPT, bashPayload('cat YWJjZGVm | base64 -d | zsh'));
|
||||
assert.equal(result.code, 2);
|
||||
assert.match(result.stderr, /BLOCKED/);
|
||||
});
|
||||
|
||||
it('T8 FP probe — base64 -d to jq is NOT blocked (no shell terminator)', async () => {
|
||||
// The pattern requires the final pipe target to be a shell interpreter.
|
||||
// Decoding base64 to feed a JSON parser is a legitimate workflow.
|
||||
const result = await runHook(SCRIPT, bashPayload('echo aGVsbG8K | base64 -d | jq .'));
|
||||
assert.equal(result.code, 0);
|
||||
});
|
||||
|
||||
it('T8 FP probe — base64 -d alone (no shell pipe) is NOT blocked', async () => {
|
||||
const result = await runHook(SCRIPT, bashPayload('echo aGVsbG8K | base64 -d'));
|
||||
assert.equal(result.code, 0);
|
||||
});
|
||||
});
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue