From ea648b7dffc62e3e1ea81e756b0cbaff145b0f8c Mon Sep 17 00:00:00 2001 From: t Date: Tue, 14 Jul 2026 12:56:22 -0700 Subject: [PATCH] fix(careful): fail closed for compound recursive deletes --- careful/bin/check-careful.sh | 28 ++++++++-------------------- test/hook-scripts.test.ts | 15 +++++++++++++++ 2 files changed, 23 insertions(+), 20 deletions(-) diff --git a/careful/bin/check-careful.sh b/careful/bin/check-careful.sh index d9c39e48c..22bf8b922 100755 --- a/careful/bin/check-careful.sh +++ b/careful/bin/check-careful.sh @@ -25,26 +25,14 @@ fi # Normalize: lowercase for case-insensitive SQL matching CMD_LOWER=$(printf '%s' "$CMD" | tr '[:upper:]' '[:lower:]') -# --- Check for safe exceptions (rm -rf of build artifacts) --- -if printf '%s' "$CMD" | grep -qE 'rm\s+(-[a-zA-Z]*r[a-zA-Z]*\s+|--recursive\s+)' 2>/dev/null; then - SAFE_ONLY=true - RM_ARGS=$(printf '%s' "$CMD" | sed -E 's/.*rm[[:space:]]+(-[a-zA-Z]+[[:space:]]+)*//;s/--recursive[[:space:]]*//') - for target in $RM_ARGS; do - case "$target" in - */node_modules|node_modules|*/\.next|\.next|*/dist|dist|*/__pycache__|__pycache__|*/\.cache|\.cache|*/build|build|*/\.turbo|\.turbo|*/coverage|coverage) - ;; # safe target - -*) - ;; # flag, skip - *) - SAFE_ONLY=false - break - ;; - esac - done - if [ "$SAFE_ONLY" = true ]; then - echo '{}' - exit 0 - fi +# --- Check for safe exceptions (one standalone rm of build artifacts) --- +# Match the complete command. Parsing only the last rm is unsafe because shell +# syntax or comments can hide an earlier destructive command, for example: +# rm -rf / # rm -rf node_modules +# Unknown syntax fails closed and falls through to the destructive checks. +if printf '%s' "$CMD" | grep -qE '^[[:space:]]*rm[[:space:]]+(-[a-zA-Z]*r[a-zA-Z]*[[:space:]]+|--recursive[[:space:]]+)(([^[:space:];&|#]*/)?(node_modules|\.next|dist|__pycache__|\.cache|build|\.turbo|coverage)[[:space:]]*)+$' 2>/dev/null; then + echo '{}' + exit 0 fi # --- Destructive pattern checks --- diff --git a/test/hook-scripts.test.ts b/test/hook-scripts.test.ts index f1ffe1239..db2e7629f 100644 --- a/test/hook-scripts.test.ts +++ b/test/hook-scripts.test.ts @@ -96,6 +96,21 @@ describe('check-careful.sh', () => { expect(output.permissionDecision).toBe('ask'); expect(output.message).toContain('recursive delete'); }); + + test.each([ + 'rm -rf /; rm -rf node_modules', + 'rm -rf / && rm -rf node_modules', + 'rm -rf / # rm -rf node_modules', + 'rm -rf node_modules; rm -rf /', + 'rm -rf node_modules || rm -rf /', + 'echo ok && rm -rf /', + 'rm -rf node_modules\nrm -rf /', + ])('never lets a safe-looking target hide a destructive command: %s', (command) => { + const { exitCode, output } = runHook(CAREFUL_SCRIPT, carefulInput(command)); + expect(exitCode).toBe(0); + expect(output.permissionDecision).toBe('ask'); + expect(output.message).toContain('recursive delete'); + }); }); // --- SQL destructive commands ---