mirror of
https://github.com/affaan-m/ECC.git
synced 2026-09-29 04:55:12 +02:00
fix(gateguard): detect stdin dd writes and launcher-wrapped shells
This commit is contained in:
@@ -530,7 +530,7 @@ function wrapperValueOption(arg, valueFlags) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Explicit external-launcher argv grammars used only by the dd classifier.
|
||||
// Explicit external-launcher argv grammars for dd and shell-wrapper discovery.
|
||||
// Unknown flags do not justify guessing which later argument executes.
|
||||
const DD_LAUNCHER_OPTIONS = {
|
||||
xargs: {
|
||||
@@ -735,7 +735,7 @@ function isDestructiveQuoteAware(raw, depth = 0) {
|
||||
if (isDestructiveDd(tokens)) return true;
|
||||
if (isDestructiveSqlClient(tokens)) return true;
|
||||
if (isDestructiveFindExec(tokens)) return true;
|
||||
const argv = unwrapLeadWrappers(tokens);
|
||||
const argv = unwrapLeadWrappers(tokens, true, true);
|
||||
if (SHELL_WRAPPERS.has(commandBasename(argv[0]))) {
|
||||
const ci = argv.indexOf('-c', 1);
|
||||
if (ci !== -1 && argv[ci + 1] && isDestructiveQuoteAware(argv[ci + 1], depth + 1)) {
|
||||
@@ -763,7 +763,8 @@ function commandBasename(token) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Detect a `dd` invocation carrying an `if=` operand.
|
||||
* Detect a `dd` invocation carrying an `if=` or `of=` operand.
|
||||
* Keep the existing input-file gate and include output-only writes from stdin.
|
||||
*
|
||||
* Token-based rather than a regex arm because the verdict has to depend on
|
||||
* `dd` being the command, not on `dd if=` appearing anywhere in the line:
|
||||
@@ -779,7 +780,7 @@ function commandBasename(token) {
|
||||
*/
|
||||
function isDestructiveDd(tokens, allowShellBuiltins = true) {
|
||||
const argv = unwrapLeadWrappers(tokens, allowShellBuiltins, true);
|
||||
return commandBasename(argv[0]) === 'dd' && argv.slice(1).some(operand => /^if=/i.test(operand));
|
||||
return commandBasename(argv[0]) === 'dd' && argv.slice(1).some(operand => /^(?:if|of)=/i.test(operand));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -185,6 +185,18 @@ function runDdRegressionTests() {
|
||||
};
|
||||
const destructive = [
|
||||
'dd if=/dev/zero of=/dev/sda',
|
||||
'dd of=/dev/sda bs=1M',
|
||||
'cat /dev/zero | dd of=/dev/sda',
|
||||
'sudo dd of=/dev/sda < /dev/zero',
|
||||
'dd bs=1M of="./output"',
|
||||
"timeout 60 bash -c 'dd if=/dev/zero of=/dev/sda'",
|
||||
"nohup sh -c 'dd if=input'",
|
||||
"nice -n 5 sh -c 'dd if=input'",
|
||||
"xargs sh -c 'dd if=input'",
|
||||
"timeout 2 sh -c 'dd of=output'",
|
||||
"nohup sh -c 'echo $(dd of=output)'",
|
||||
"nice -n 5 sh -c 'cat <<EOF\n$(dd of=output)\nEOF'",
|
||||
'find . -exec dd of=output \\;',
|
||||
'dd if=./image of=./out',
|
||||
'dd of=./out bs=1M if="./image"',
|
||||
"'/bin/dd' if=input of=output",
|
||||
@@ -295,6 +307,16 @@ function runDdRegressionTests() {
|
||||
];
|
||||
const passive = [
|
||||
'echo dd if=input',
|
||||
'echo dd of=/dev/sda',
|
||||
'grep dd of=output file',
|
||||
"printf '%s' 'dd of=output'",
|
||||
'dd count=0',
|
||||
"timeout 2 echo 'sh -c dd of=output'",
|
||||
"timeout 2 sh -c 'echo \"dd of=output\"'",
|
||||
"nohup sh -c 'cat <<EOF\ndd of=output\nEOF'",
|
||||
"nice -n 5 echo 'dd of=output'",
|
||||
"xargs echo 'sh -c dd of=output'",
|
||||
'echo "note; find . -exec dd of=output \\;"',
|
||||
'command -v dd',
|
||||
'command -v dd if=input',
|
||||
'command -V dd if=input',
|
||||
@@ -410,7 +432,9 @@ function runDdRegressionTests() {
|
||||
['sudo -u dd echo if=input', false],
|
||||
["env -S 'echo ok; dd if=input'", false],
|
||||
['find . -exec echo {} \\; -exec dd if=input \\;', true],
|
||||
['command -pv dd', false]
|
||||
['command -pv dd', false],
|
||||
["timeout 2 sh -c 'dd if=input'", true],
|
||||
['cat /dev/zero | dd of=/dev/sda', true]
|
||||
]) {
|
||||
check(`dd hook-input contract: ${command}`, () => {
|
||||
fs.rmSync(stateDir, { recursive: true, force: true });
|
||||
@@ -617,6 +641,7 @@ function runTests() {
|
||||
if (
|
||||
test(`denies dd whose input path is not word-initial: ${command}`, () => {
|
||||
const result = runBashHook({ tool_name: 'Bash', tool_input: { command } });
|
||||
assert.strictEqual(result.code, 0, `hook should exit successfully for ${command}`);
|
||||
const output = parseOutput(result.stdout);
|
||||
assert.ok(output, 'hook should produce JSON output');
|
||||
assert.ok(output.hookSpecificOutput, 'hook should return a permission decision');
|
||||
|
||||
Reference in New Issue
Block a user