Skip to content

feat(gateguard): add env knobs for routine bash gate + extra destructive patterns#2161

Merged
affaan-m merged 2 commits into
affaan-m:mainfrom
gaurav0107:fix/2078-surface-gateguard-fact-force-bash-routin
Jun 7, 2026
Merged

feat(gateguard): add env knobs for routine bash gate + extra destructive patterns#2161
affaan-m merged 2 commits into
affaan-m:mainfrom
gaurav0107:fix/2078-surface-gateguard-fact-force-bash-routin

Conversation

@gaurav0107

@gaurav0107 gaurav0107 commented Jun 5, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Adds GATEGUARD_BASH_ROUTINE_DISABLED env var: truthy values (1, true, on, yes, enabled) skip the once-per-session routine bash gate in scripts/hooks/gateguard-fact-force.js. Destructive gate is unaffected.
  • Adds GATEGUARD_BASH_EXTRA_DESTRUCTIVE env var: regex source string for operator-supplied additional destructive patterns. Matches against the same quote-stripped, subshell-flattened command that the built-in DESTRUCTIVE_SQL_DD regex sees, so a phrase inside $(...) or backticks is also caught.
  • Defaults preserve current behavior (both env vars unset → identical to current code path).
  • Malformed GATEGUARD_BASH_EXTRA_DESTRUCTIVE regex is logged once to stderr with a [gateguard-fact-force] prefix and treated as not configured — hooks must never crash tool execution because of operator config errors.
  • Implements Option 2 from Feature request: surface gateguard fact_force_bash_routine / destructive_bash_extra config knobs in the JS port #2078 (the env-var sketch). Option 1 (.gateguard.yml reading) is deliberately out of scope for this PR.

Verification

  • node tests/run-all.js2619/2619 passed, 0 failed (12 new tests added in tests/hooks/gateguard-fact-force.test.js)
  • npx eslint scripts/**/*.js tests/**/*.jsclean
  • New test coverage:
    • GATEGUARD_BASH_ROUTINE_DISABLED=1 skips routine bash gate
    • Truthy aliases (true, on, yes, enabled, mixed case) all enable opt-out
    • Unset preserves baseline (denies first routine bash)
    • Falsy values (0, false, off, empty, unknown string) keep current behavior
    • Routine opt-out does NOT disable destructive bash gate (rm -rf still gated)
    • GATEGUARD_BASH_EXTRA_DESTRUCTIVE custom phrase fires destructive gate
    • Alternation second member also fires
    • Invalid regex degrades to baseline (no crash)
    • Unset extra-regex preserves baseline (no false positives)
    • Custom phrase inside $(...) is also caught

Fixes #2078


Summary by cubic

Adds env options to the Bash gateguard to disable the once-per-session routine gate and to add custom destructive regex patterns. Defaults are unchanged; destructive protections remain on. Fixes #2078.

  • New Features

    • GATEGUARD_BASH_ROUTINE_DISABLED: truthy values (1, true, on, yes, enabled) skip the routine Bash gate; destructive gate still runs.
    • GATEGUARD_BASH_EXTRA_DESTRUCTIVE: regex source for extra destructive phrases; matched against quote-stripped, subshell-flattened commands. Malformed regex is logged and ignored (no crash).
  • Bug Fixes

    • Reset warning when GATEGUARD_BASH_EXTRA_DESTRUCTIVE changes so each distinct invalid regex logs once, avoiding silent failures in long-running processes.

Written for commit 9e5a59b. Summary will update on new commits.

Review in cubic

Summary by CodeRabbit

  • New Features

    • Operators can define custom destructive command patterns via an environment variable to extend destructive-command detection.
    • Added a configuration toggle to disable routine Bash gating while keeping destructive-command blocking active.
  • Bug Fixes

    • Malformed custom patterns no longer crash the gate; invalid patterns are treated as unset and an error is logged only once per distinct value.

…ive patterns

The JS port of gateguard-fact-force has two bash gates: a destructive
gate (rm -rf, drop table, git push --force, etc.) that operators want
to keep, and a once-per-session routine gate that fires on the very
first bash invocation regardless of intent. Operators on hosts where
the routine gate is friction without signal (Cursor, OpenCode, etc.)
have been maintaining local patches that get clobbered on every plugin
update; the Python upstream gateguard-ai already exposes equivalent
config via .gateguard.yml.

Adds two env vars, both off-by-default so existing behavior is
preserved:

- GATEGUARD_BASH_ROUTINE_DISABLED — truthy values (1, true, on, yes,
  enabled) skip the routine bash gate. Destructive gate is unaffected.
- GATEGUARD_BASH_EXTRA_DESTRUCTIVE — regex source string for additional
  destructive patterns. Matches against the same quote-stripped,
  subshell-flattened command the built-in DESTRUCTIVE_SQL_DD regex sees,
  so a custom phrase inside $(...) or backticks is also caught. A
  malformed regex is logged once to stderr and treated as not configured
  rather than crashing the hook (hooks must never block tool execution
  unexpectedly).

Twelve new tests pin both env vars (truthy aliases, falsy values, unset
baseline, destructive-gate-still-fires, alternation members, malformed
regex degrades safely, custom phrase inside command substitution).
Existing 2619/2619 tests still pass; eslint clean.

Fixes affaan-m#2078
@coderabbitai

coderabbitai Bot commented Jun 5, 2026

Copy link
Copy Markdown
Contributor

Need the big picture first? Review this PR in Change Stack to see what changed before going file by file.

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: f3aaafee-f792-447c-911a-45e2fc85be5a

📥 Commits

Reviewing files that changed from the base of the PR and between a3f795e and 9e5a59b.

📒 Files selected for processing (2)
  • scripts/hooks/gateguard-fact-force.js
  • tests/hooks/gateguard-fact-force.test.js
🚧 Files skipped from review as they are similar to previous changes (2)
  • tests/hooks/gateguard-fact-force.test.js
  • scripts/hooks/gateguard-fact-force.js

📝 Walkthrough

Walkthrough

The hook adds two env controls: GATEGUARD_BASH_ROUTINE_DISABLED skips the once-per-session routine-Bash gate, and GATEGUARD_BASH_EXTRA_DESTRUCTIVE supplies operator regex for extra destructive detections. Extra regex is lazily compiled and memoized, invalid patterns log once to stderr and are treated as unconfigured, and destructive gating remains independent.

Changes

Bash gate environment configuration controls

Layer / File(s) Summary
Environment configuration helpers
scripts/hooks/gateguard-fact-force.js
Adds getExtraDestructiveRegex() and isRoutineBashGateDisabled() helper functions to parse and memoize env-var-based configuration, with safe handling of invalid regex patterns (one-time stderr logging, graceful fallback to unconfigured state).
Extra destructive pattern detection
scripts/hooks/gateguard-fact-force.js
Extends isDestructiveBash() to test the operator-supplied extra destructive regex against the flattened normalized command and each stripped command segment, returning early on match.
Routine gate skip integration
scripts/hooks/gateguard-fact-force.js
Adds conditional early-return in the Bash tool path to skip the once-per-session routine-bash gate when GATEGUARD_BASH_ROUTINE_DISABLED is enabled, leaving the destructive gate intact above.
Test coverage for env-based controls
tests/hooks/gateguard-fact-force.test.js
Validates GATEGUARD_BASH_ROUTINE_DISABLED with truthy aliases and baseline preservation, confirms destructive gate is unaffected by routine disable, verifies GATEGUARD_BASH_EXTRA_DESTRUCTIVE pattern matching (including alternation and command substitution), and tests graceful degradation on invalid regex without crashing; also asserts one-time-per-distinct-invalid-pattern stderr warnings.

Sequence Diagram

sequenceDiagram
  participant Caller
  participant run as run(rawInput)
  participant BashCheck as tool === 'Bash'
  participant Destructive as isDestructiveBash()
  participant RoutineDisabled as isRoutineBashGateDisabled()
  participant Session as ROUTINE_BASH_SESSION_KEY

  Caller->>run: invoke hook
  run->>BashCheck: check tool name
  alt tool is Bash
    run->>Destructive: test built-in destructive patterns
    Destructive->>Destructive: apply extra destructive regex (if configured)
    alt destructive match
      run->>Caller: deny (destructive gate)
    else no destructive match
      run->>RoutineDisabled: check GATEGUARD_BASH_ROUTINE_DISABLED
      alt routine disabled
        run->>Caller: allow (routine gate skipped)
      else routine enabled
        run->>Session: check once-per-session key
        alt first Bash of session
          Session->>Session: mark checked
          run->>Caller: deny (routine gate)
        else already checked
          run->>Caller: allow
        end
      end
    end
  else not Bash
    run->>Caller: allow (no gate)
  end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐰 A tiny hook nibbles the env seed,

Destructive scents caught in a regex weed.
Routine shies back when the flag is high,
Bad patterns warn once — no crash, just a sigh.
Hooray, operators: fewer patches to feed.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 20.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and specifically describes the two main features added: environment knobs for controlling the routine bash gate and configurable extra destructive patterns.
Linked Issues check ✅ Passed The PR implements Option 2 from issue #2078: adds GATEGUARD_BASH_ROUTINE_DISABLED to skip routine gating while preserving destructive checks, and GATEGUARD_BASH_EXTRA_DESTRUCTIVE for operator-supplied patterns with proper error handling and regex memoization.
Out of Scope Changes check ✅ Passed All changes are directly related to issue #2078 requirements: hook implementation in gateguard-fact-force.js and comprehensive test coverage in gateguard-fact-force.test.js with no unrelated modifications.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 ESLint

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

ESLint install failed. For unrecoverable errors, disable the tool in CodeRabbit configuration.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@gaurav0107 gaurav0107 marked this pull request as ready for review June 5, 2026 19:47
@gaurav0107 gaurav0107 requested a review from affaan-m as a code owner June 5, 2026 19:47

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@scripts/hooks/gateguard-fact-force.js`:
- Around line 79-92: The warning flag extraDestructiveWarnLogged is never reset
when extraDestructiveCacheKey (the env raw) changes, so subsequent distinct
invalid regex values are not reported; update the logic where
extraDestructiveCacheKey is assigned to reset extraDestructiveWarnLogged (or
track warnings per-key) whenever raw differs from the cached key so each bad
pattern logs once, and add a same-process regression test exercising
loadDirectHook() that sets multiple different invalid
GATEGUARD_BASH_EXTRA_DESTRUCTIVE values to verify a warning is emitted for each
distinct invalid value.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 91fb3ddb-cb3f-4282-8f54-62aa57ddd991

📥 Commits

Reviewing files that changed from the base of the PR and between bc8e12b and a3f795e.

📒 Files selected for processing (2)
  • scripts/hooks/gateguard-fact-force.js
  • tests/hooks/gateguard-fact-force.test.js

Comment thread scripts/hooks/gateguard-fact-force.js

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1 issue found across 2 files

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

Comment thread scripts/hooks/gateguard-fact-force.js
… changes

Both reviewers (CodeRabbit + cubic) flagged that
extraDestructiveWarnLogged was never reset when GATEGUARD_BASH_EXTRA_DESTRUCTIVE
flipped from one invalid regex to a different invalid regex. The
sticky boolean meant a long-running process saw bad-pattern-a's
warning then silently swallowed bad-pattern-b's parse failure.

Fix: clear extraDestructiveWarnLogged whenever the cache key changes
(i.e. before the regex compile attempt). The warn-once-per-distinct-
pattern invariant now matches the per-key cache invariant.

Adds a same-process regression test via loadDirectHook() that spies on
process.stderr.write and asserts: same bad pattern warns once across
multiple invocations; switching to a different bad pattern emits a
second warning; switching to a valid regex emits zero warnings.
@gaurav0107

Copy link
Copy Markdown
Contributor Author

Thanks @coderabbitai and @cubic-dev-ai — both findings were the same valid bug, fixed in 9e5a59b.

extraDestructiveWarnLogged is now reset whenever the cache key changes (right after the new raw is assigned, before the try/RegExp block), so a process that flips through bad-pattern-abad-pattern-b reports both. A same-process regression test was added that spies on process.stderr.write via loadDirectHook() and asserts:

  • same invalid pattern warns once (sanity baseline)
  • switching to a different invalid pattern emits a second warning (the bug being fixed)
  • switching to a valid regex emits zero warnings (no regression on the happy path)

node tests/hooks/gateguard-fact-force.test.js → 102/102 passing.

@cubic-dev-ai

cubic-dev-ai Bot commented Jun 5, 2026

Copy link
Copy Markdown
Contributor

Thanks @coderabbitai and @cubic-dev-ai — both findings were the same valid bug, fixed in 9e5a59b.

extraDestructiveWarnLogged is now reset whenever the cache key changes (right after the new raw is assigned, before the try/RegExp block), so a process that flips through bad-pattern-abad-pattern-b reports both. A same-process regression test was added that spies on process.stderr.write via loadDirectHook() and asserts:

  • same invalid pattern warns once (sanity baseline)
    ...

@gaurav0107 Looks good — the fix addresses exactly what both reviewers flagged. The key change in getExtraDestructiveRegex():

extraDestructiveCacheKey = raw;
extraDestructiveWarnLogged = false;      // <— the fix

That reset right before the try block means each distinct invalid pattern gets its own warning, rather than the first bad pattern consuming the slot forever. The test covering the three scenarios (same warn once, different warn again, valid no warn) pins that behavior properly.

The rest of the diff is clean too — ECC_ENABLE_VALUES for the routine opt-out, the getExtraDestructiveRegex() integration into isDestructiveBash(), and the graceful degradation on malformed regex all match what was described in the original sketch.

102/102 passing with no eslint issues is exactly the bar this team expects before merging. No further concerns on my side.

Tip: get faster answers by chatting with cubic’s review copilot in the review UI

@coderabbitai

coderabbitai Bot commented Jun 5, 2026

Copy link
Copy Markdown
Contributor

Tip

For best results, initiate chat on the files or code changes.

@gaurav0107 The fix is clean and well-targeted — resetting extraDestructiveWarnLogged right after the new raw is assigned (before the try/RegExp block) is exactly the right insertion point, since it ensures the flag is always tied to the current cache key's lifecycle rather than the module's lifetime. The three-assertion regression test covers the important bases neatly: baseline deduplication, the actual bug scenario (different-pattern second warning), and no false warnings on a valid regex.

🐇🎉

@affaan-m affaan-m merged commit 4afdb90 into affaan-m:main Jun 7, 2026
40 checks passed
syarfandi pushed a commit to syarfandi/ECC that referenced this pull request Jun 9, 2026
…ive patterns (affaan-m#2161)

* feat(gateguard): add env knobs for routine bash gate + extra destructive patterns

The JS port of gateguard-fact-force has two bash gates: a destructive
gate (rm -rf, drop table, git push --force, etc.) that operators want
to keep, and a once-per-session routine gate that fires on the very
first bash invocation regardless of intent. Operators on hosts where
the routine gate is friction without signal (Cursor, OpenCode, etc.)
have been maintaining local patches that get clobbered on every plugin
update; the Python upstream gateguard-ai already exposes equivalent
config via .gateguard.yml.

Adds two env vars, both off-by-default so existing behavior is
preserved:

- GATEGUARD_BASH_ROUTINE_DISABLED — truthy values (1, true, on, yes,
  enabled) skip the routine bash gate. Destructive gate is unaffected.
- GATEGUARD_BASH_EXTRA_DESTRUCTIVE — regex source string for additional
  destructive patterns. Matches against the same quote-stripped,
  subshell-flattened command the built-in DESTRUCTIVE_SQL_DD regex sees,
  so a custom phrase inside $(...) or backticks is also caught. A
  malformed regex is logged once to stderr and treated as not configured
  rather than crashing the hook (hooks must never block tool execution
  unexpectedly).

Twelve new tests pin both env vars (truthy aliases, falsy values, unset
baseline, destructive-gate-still-fires, alternation members, malformed
regex degrades safely, custom phrase inside command substitution).
Existing 2619/2619 tests still pass; eslint clean.

Fixes affaan-m#2078

* fix(gateguard): reset extra-destructive warn-once gate when env value changes

Both reviewers (CodeRabbit + cubic) flagged that
extraDestructiveWarnLogged was never reset when GATEGUARD_BASH_EXTRA_DESTRUCTIVE
flipped from one invalid regex to a different invalid regex. The
sticky boolean meant a long-running process saw bad-pattern-a's
warning then silently swallowed bad-pattern-b's parse failure.

Fix: clear extraDestructiveWarnLogged whenever the cache key changes
(i.e. before the regex compile attempt). The warn-once-per-distinct-
pattern invariant now matches the per-key cache invariant.

Adds a same-process regression test via loadDirectHook() that spies on
process.stderr.write and asserts: same bad pattern warns once across
multiple invocations; switching to a different bad pattern emits a
second warning; switching to a valid regex emits zero warnings.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Feature request: surface gateguard fact_force_bash_routine / destructive_bash_extra config knobs in the JS port

2 participants