mirror of
https://github.com/affaan-m/ECC.git
synced 2026-09-20 16:47:59 +02:00
fix(gateguard): ignore heredoc prose for tee and path-qualified sinks (#2886)
Expand proven-passive heredoc recognition beyond bare `cat` so documentation writes via `tee`, `/bin/cat`, and `command cat` no longer trip the destructive command detector on body text, while still failing closed for shells and pipes.
This commit is contained in:
@@ -3,16 +3,23 @@
|
||||
const { extractCommandSubstitutions } = require('../lib/shell-substitution');
|
||||
|
||||
/**
|
||||
* Recognize the deliberately narrow passive sink supported by this parser.
|
||||
* Shell operators and substitutions make the payload's destination ambiguous,
|
||||
* so every other form retains the original input for fail-closed checks.
|
||||
* Recognize proven-passive sinks whose heredoc payload is data, not a command
|
||||
* stream. `cat` and `tee` (optionally path-qualified, or wrapped in
|
||||
* `command`/`builtin`/`env`) only write stdin; they do not execute the body.
|
||||
* Shell operators or substitution markers make the destination ambiguous, so
|
||||
* every other form retains the original input for fail-closed checks.
|
||||
*
|
||||
* @param {string} line
|
||||
* @returns {boolean}
|
||||
*/
|
||||
function isProvenPassiveHeredocLine(line) {
|
||||
const trimmed = line.trim();
|
||||
return /^cat(?=\s|[<>])/.test(trimmed) && !/[;&|()`]/.test(trimmed);
|
||||
// Fail closed on control operators / grouping / command substitutions.
|
||||
if (/[;&|()`]/.test(trimmed)) return false;
|
||||
// Optional wrapper + optional path prefix + cat|tee, then args or redirect.
|
||||
return /^(?:(?:command|builtin|env)\s+)?(?:(?:\.\/|\/(?:[\w.+-]+\/)*)?(?:cat|tee))(?=\s|[<>])/.test(
|
||||
trimmed
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1858,6 +1858,87 @@ function runTests() {
|
||||
passed++;
|
||||
else failed++;
|
||||
|
||||
if (
|
||||
test('allows #2886 migration-doc heredoc repro with DROP TABLE prose', () => {
|
||||
expectAllow(
|
||||
[
|
||||
"cat > migration-notes.md <<'EOF'",
|
||||
"This migration will DROP TABLE old_sessions once we've verified nothing reads from it anymore.",
|
||||
'EOF'
|
||||
].join('\n'),
|
||||
'issue #2886 cat heredoc repro'
|
||||
);
|
||||
})
|
||||
)
|
||||
passed++;
|
||||
else failed++;
|
||||
|
||||
if (
|
||||
test('allows destructive SQL prose inside a tee heredoc', () => {
|
||||
expectAllow(
|
||||
[
|
||||
"tee migration-notes.md <<'EOF'",
|
||||
'This migration will DROP TABLE old_sessions after verification.',
|
||||
'EOF'
|
||||
].join('\n'),
|
||||
'tee heredoc SQL prose'
|
||||
);
|
||||
})
|
||||
)
|
||||
passed++;
|
||||
else failed++;
|
||||
|
||||
if (
|
||||
test('allows destructive rm prose inside a path-qualified cat heredoc', () => {
|
||||
expectAllow(
|
||||
[
|
||||
"/bin/cat > notes.md <<'EOF'",
|
||||
'Cleanup steps mention rm -rf old-cache; do not run yet.',
|
||||
'EOF'
|
||||
].join('\n'),
|
||||
'path-qualified cat heredoc prose'
|
||||
);
|
||||
})
|
||||
)
|
||||
passed++;
|
||||
else failed++;
|
||||
|
||||
if (
|
||||
test('allows destructive prose inside a command-wrapped cat heredoc', () => {
|
||||
expectAllow(
|
||||
[
|
||||
"command cat > notes.md <<'EOF'",
|
||||
'Notes: DELETE FROM sessions; truncate staging.',
|
||||
'EOF'
|
||||
].join('\n'),
|
||||
'command-wrapped cat heredoc prose'
|
||||
);
|
||||
})
|
||||
)
|
||||
passed++;
|
||||
else failed++;
|
||||
|
||||
if (
|
||||
test('still denies real destructive commands (not heredoc prose)', () => {
|
||||
expectDestructiveDeny('rm -rf /tmp/real-destructive-target', 'real rm -rf');
|
||||
expectDestructiveDeny('git reset --hard', 'real git reset --hard');
|
||||
expectDestructiveDeny('drop table old_sessions', 'real drop table command text');
|
||||
})
|
||||
)
|
||||
passed++;
|
||||
else failed++;
|
||||
|
||||
if (
|
||||
test('fails closed when tee pipes heredoc payload into a shell', () => {
|
||||
expectDestructiveDeny(
|
||||
['tee notes.md <<EOF | bash', 'rm -rf /tmp/tee-piped-shell-target', 'EOF'].join('\n'),
|
||||
'tee piped to shell'
|
||||
);
|
||||
})
|
||||
)
|
||||
passed++;
|
||||
else failed++;
|
||||
|
||||
if (
|
||||
test('denies substitutions inside literal quote characters in an unquoted heredoc', () => {
|
||||
for (const payload of [
|
||||
|
||||
Reference in New Issue
Block a user