Skip to content

Latest commit

 

History

113 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

tollgate

A configurable checkpoint for sensitive operations in your dev environment. Stop. Confirm. Proceed.

CI License: MIT Go Report Card

tollgate is a small CLI tool that intercepts sensitive operations against external services and asks you to confirm them. It works by placing shim binaries named git and gh on your $PATH ahead of the real ones. When a watched command runs (git push, gh pr create, gh repo delete), the shim pauses, prompts you in the terminal, and only proceeds if you say yes.

It exists because agents now write and run code in your environment, and the operations that touch the outside world deserve a moment of human attention. tollgate is that moment.

$ git push origin main
tollgate: allow git [push origin main]? [y/n/a] y
[ ... real git push proceeds ... ]

Why this exists

Most agent-driven workflows lack human-in-the-loop checkpoints for destructive or external operations. Pushing to a remote, creating a pull request, deleting a repository, deploying to production: these are operations where a one-token mistake by an agent has real consequences.

tollgate gives you a configurable set of "ask first" gates. It's not a sandbox; it's a tap on the shoulder. The friction is the feature.

The design doesn't try to distinguish "agent" from "user." Both go through the same prompt. If you don't want a prompt for your own work, you have an environment variable kill switch. The model stays honest.


How it works

When installed, tollgate puts two shim binaries (git and gh) into ~/.tollgate/bin/. You add that directory to the front of your $PATH. From then on, any process that runs git or gh hits the shim first.

The shim's decision sequence:

  1. Check the TOLLGATE environment variable. If set to off, exec the real binary immediately and exit.
  2. Match the invocation against a configurable watchlist (git push, gh pr create, etc.). If unwatched, exec the real binary and exit.
  3. Consult current state: global on/off, session pause, prior session-allow rules.
  4. If a prompt is needed, write a question to /dev/tty and read the response.
  5. On allow, exec the real binary. On deny, exit non-zero.
  6. Log the decision to ~/.tollgate/audit.log.

Unwatched invocations (git status, gh pr list, etc.) pass through with negligible overhead. The shim uses syscall.Exec to replace itself with the real binary rather than forking and waiting on it.


When it works (and when it doesn't)

The shim approach relies on $PATH inheritance. When any process runs git or gh, the OS searches $PATH in order and runs the first match. The shim wins by being first.

Interception depends on two things being true at the same time:

  1. ~/.tollgate/bin is in $PATH
  2. The process running the command inherited that $PATH

The common case that works: You open a terminal, your shell sources .zshrc, and you launch Claude Code from that terminal. Every bash command Claude runs inherits that $PATH. Subagents, background tasks, parallel tool calls: all protected, because they are all subprocesses of the same Claude Code instance.

What won't be protected by default:

  • A Claude Code session that was open before you ran tollgate install and updated .zshrc. That session inherited the old $PATH. Open a new terminal to pick it up.
  • Cron jobs, launchd services, and CI runners. These typically don't source .zshrc and start with a minimal environment. They need explicit PATH setup in their own config.
  • Any process that calls git or gh using a full absolute path (e.g. /opt/homebrew/bin/git) instead of relying on $PATH lookup.

Quick check: Run which git after install. If it returns ~/.tollgate/bin/git, the current session is protected. If it returns anything else, run source ~/.zshrc or open a new terminal.


Install

From source

git clone https://github.com/rockwellwindsor/tollgate
cd tollgate
make build
./bin/tollgate install

Finishing the install

tollgate install writes the shim binaries into ~/.tollgate/bin/ and prints the one-line addition for your shell profile:

export PATH="$HOME/.tollgate/bin:$PATH"

After updating your shell config, reload it (source ~/.zshrc) or open a new terminal.

Verify the install:

which git
# /Users/you/.tollgate/bin/git    <- good, this is the shim

tollgate status
# tollgate: ENABLED
# audit: 0 entries

Usage

Commands

tollgate status              show current state, active rules, and audit log count
tollgate pause               disable prompts for this shell session
tollgate resume              re-enable prompts for this shell session
tollgate on                  enable globally
tollgate off                 disable globally (across all sessions)
tollgate clear-logs          delete the audit log (with confirmation)
tollgate install             write/update shim binaries and config

The prompt

When a watched command is intercepted:

tollgate: allow git [push origin main]? [y/n/a]
Key Meaning
y Allow this once
n Deny this once
a Allow this pattern for the rest of this shell session

The kill switch

Bypass tollgate for a single invocation:

TOLLGATE=off git push

Or for a whole session:

tollgate pause
# ...do stuff freely...
tollgate resume

Or persistently across all sessions:

tollgate off    # stays off until you run `tollgate on`

The three layers (env var, session pause, global toggle) are independent. Use whichever fits the situation.


Configuration

The config file lives at ~/.tollgate/config.json (override the directory with TOLLGATE_HOME).

Default watchlist

Out of the box, tollgate watches:

  • git push (any push to a remote)
  • git push --force / -f (separate pattern from plain push)
  • gh pr create
  • gh pr merge
  • gh repo create
  • gh repo delete
  • gh release create

Everything else passes through silently.

default_action

The default_action field controls what happens when no session rule applies:

Value Behavior
prompt Ask every time (default)
allow Log but never prompt, "soft off"
deny Block all watched ops without prompting
{
  "default_action": "prompt"
}

Toggling

Three layers of disable, in increasing scope:

Method Scope When to use
TOLLGATE=off cmd One invocation One-off bypass during manual work
tollgate pause / tollgate resume One shell session A chunk of manual work in one terminal
tollgate on / tollgate off All sessions, persistent Stepping away from agent work entirely

No time-bounded resume. Time-bounded toggles behave like a cache, and caches in dev tools tend to mislead you about current state. If tollgate is on, it's on. If you turned it off, it's off until you turn it back on.


Audit log

Every watched invocation writes a line to ~/.tollgate/audit.log as JSONL:

{"binary":"git","args":["push","origin","main"],"pattern":"git-push","decision":"allowed-once"}

Pass-through (unwatched) invocations are not logged. Only watched ones.

To clear the log:

tollgate clear-logs              # interactive confirmation
tollgate clear-logs --dry-run    # show what would be deleted, delete nothing
tollgate clear-logs --yes        # skip confirmation

clear-logs only ever touches the audit log. It will not remove your config, your shims, or any session state.


What I learned building this

  • Errors as values is verbose, but honest. Every place a thing can fail is visible in the code. After a while, I stopped wishing for try/catch.
  • Table-driven tests make TDD feel natural. A fast compile loop and a standard library testing package with no setup ceremony made test-first feel like the path of least resistance, not a discipline.
  • syscall.Exec is a magic word. The unwatched-path performance trick (replacing the shim process with the real binary rather than forking and waiting) is the kind of thing you can only do when the language exposes the OS directly. Worth knowing about.
  • Single-binary distribution is the underrated feature. No runtime, no node_modules, no virtualenv. The install story is make build and that's it.

A longer writeup of the design decisions and what changed about my thinking is at windsordevelopmentstudio.io/post/tollgate-confirmation-layer-for-ai-agents.


Roadmap

v1.x

  • Per-directory enable/disable (a .tollgate-off file walks up from cwd)
  • Optional desktop notifications alongside terminal prompts
  • Windows support (PATHEXT handling, signal semantics, cmd.exe quirks)
  • Configurable log rotation policy

v2

  • Vercel support with patterns for vercel deploy --prod, vercel env, vercel domains
  • Shims for npm publish, cargo publish, and similar package-publish operations

Open issues track all of the above. Contributions and design suggestions welcome.


Development

Built with Go 1.22+. Tests live alongside the code they test. End-to-end tests live in tests/e2e/ and spawn real shim binaries against a synthetic $PATH.

make test          # run all tests
make lint          # run golangci-lint
make build         # build all binaries to ./bin/
make test-coverage # generate HTML coverage report

License

MIT. See LICENSE.


Built by Rockwell Windsor Rice

About

Go CLI that intercepts sensitive shell commands (git push, vercel deploy, terraform apply) and requires explicit approval before they run. A confirmation layer for AI agents, and a safety net for humans too.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages