Open-source guardrail
Exposing a local port through a public tunnel
AgentTrail Guard asks you about this by default: you decide before the call runs. Where an agent cannot ask, the call is refused instead.
- Default action
- Ask
- Severity
- High severity
- Library version
- 0.2.1 · Sep 29, 2026
What it does
What it catches, and what it misses
Written into the rule itself, next to what it matches, so you can judge it before you trust it.
Holds a command that puts a local service on a public URL through a tunnel: ngrok http|tcp|start, cloudflared tunnel, localtunnel / lt --port, tailscale funnel, an ssh -R remote forward (including -NR, matched only when ssh is the command being run), the serveo.net and localhost.run SSH relays, bore local, frpc invoked with a flag (frpc -c …), and pinggy.io. Each reaches past the firewall and gives the outside world a route in, which is a demo convenience and an exfiltration channel both. Deliberately NOT matched: ssh -L (a local forward, inbound to you) and ssh -D (a SOCKS proxy), ngrok config check / --version, cloudflared --version, tailscale status / serve (which stays inside the tailnet), ssh-keygen -R host (host-key removal, not a tunnel), a -R that appears only inside a quoted remote command (ssh host "grep -R …"), and a path or filename that merely contains frpc (scripts/frpc-parser.js). MISSES a tunnel binary run under another name, a raw ssh -R to a private relay this list does not name, frp driven from its config file rather than the frpc command, sudo frpc, and an frpc subcommand invoked without a leading flag. A quoted MENTION is not a use: a search, a git commit -m message, an echo or a curl --data body that only names this command is left alone. That holds only while every shell metacharacter stays inside the quotes, so git commit -m "x" && … is still caught; and the carrier must be the first word, so sudo grep … is not exempt.
Examples
Tested on every build
The rule must match every command on the left and none on the right, or the library does not build. Catching the real thing is easy; staying quiet on the near-miss is the hard part.
Catches (12)
- ngrok http 3000
- ngrok tcp 22
- cloudflared tunnel --url http://localhost:8080
- lt --port 8000
- npx --yes localtunnel --port 3000
- tailscale funnel 3000
- ssh -R 80:localhost:3000 nokey@localhost.run
- ssh -fNR 8080:localhost:8080 user@vps.example
- ssh -R 80:localhost:3000 serveo.net
- bore local 8000 --to bore.pub
- frpc -c ./frpc.toml
- PowerShellngrok http 5000
Stays quiet on (16)
- git commit -m "docs: explain ngrok http 3000"
- grep -rn "ngrok http 3000" docs/
- echo "never run ngrok http 3000"
- curl --data "we ran ngrok http 3000" https://api.example.com/comments
- ssh -L 8080:localhost:80 bastion.example
- ssh -D 1080 bastion.example
- ngrok config check
- ngrok --version
- cloudflared --version
- tailscale status
- git clone https://github.com/ekzhang/bore
- ssh deploy@host 'systemctl restart api'
- ssh-keygen -R old.example.com
- ssh deploy@host "grep -R TODO /srv"
- node scripts/frpc-parser.js
- cat frpc.toml
In your agent
What "ask" means in each app
The guard runs as a hook in each app, and each app gives a hook different powers. Here is what this rule's default action does in each one.
- Claude Code
- You are asked before the call runs. If your settings.json already allows the tool outright, Claude Code runs it with no prompt.
- Cursor
- A terminal command that reaches the guard gets Cursor's approval card. Any other call that needs approval, such as a file edit or a read, is refused, because Cursor cannot ask there.
- Codex CLI
- Codex cannot ask, so the call is refused, and the reason says a person has to approve it.
Each app hands the guard a different set of calls: in a Cursor sandbox run mode, for one, some terminal commands run without reaching the guard at all. Read the notes for Cursor and for Codex CLI before you rely on a rule there.
Make it yours
Change what it does in one command
Turn it off, change its action, or silence it on one command shape. The narrow one is allow: the rule keeps catching everything else.
Run it locally
Put these guardrails in front of your agent.
AgentTrail Guard is free and open source. It checks every command and file change against the whole library before your agent runs it, on your machine, with no account.
npm i -g @agenttrail/guard