Capability allowlist
The agent has a closed set of capabilities — functions in code-reviewed Go. Anything not on the list cannot run, period. It never accepts arbitrary shell. Adding a capability is a change to the signed binary.
Handing an LLM a shell is reckless. Infraa is built the opposite way: the AI can read and propose, but every state-changing action passes through your approval and a single-use signed token the agent verifies on the server. Trust is earned, not assumed.
Those are three separate parties, and no one of them can do the others' job. The LLM has read-only tools and a way to draft a plan — but no way to run it. Approval is a human action in the UI. Execution happens only on the server, only for the exact capability you signed off on.
If you're not comfortable with what's about to happen, you read the exact commands before they run.
Defense in depth means no single failure hands over your server. If one layer were bypassed, the next still holds.
The agent has a closed set of capabilities — functions in code-reviewed Go. Anything not on the list cannot run, period. It never accepts arbitrary shell. Adding a capability is a change to the signed binary.
Every write call carries a JWT signed by the gateway's private key. The agent verifies signature, server, capability, arg-hash, and expiry on each call. Five-minute TTL, single-use via a persisted nonce store.
The agent runs as a non-root infraa user. A tight /etc/sudoers.d/infraa grants NOPASSWD only for the specific binaries write capabilities call — not a general sudo grant. Even a compromised agent can't run arbitrary root commands.
The model has two tool surfaces: read tools that return data, and propose tools that write a Proposal row and return its id. There is no execute_ tool. No path exists from an LLM message to a write capability that skips your approval.
Playbook templates are rendered in the gateway before anything reaches the agent, which has no template engine at all. A malicious catalog module can't inject runtime expressions, and the arg-hash is computed over the rendered args.
When you approve, the gateway mints one JWT and dispatches one capability call. Every claim narrows what that token can do — and the agent checks all of them before it acts.
The arg-hash is a SHA-256 over the canonical JSON of the rendered arguments, so the args can't be swapped after you've signed off.
Say a token leaks. What can an attacker actually do with it? The answer is deliberately, provably small.
Exactly one action — the one that was approved.
server_id binds it. Useless anywhere else.
Past exp, the agent rejects it outright.
The jti is spent on first use and remembered.
No escalation path, no arbitrary shell, no second use. The token is the least-privilege unit of the whole system.
Everything above protects your server from the AI. This layer protects your apps from the internet. The agent classifies traffic on each app's managed nginx vhost hour by hour, right on the box — raw IPs and User-Agents never leave your server. When more than 30% of a day's traffic looks automated on real volume (500+ requests), Infraa flags the app and recommends enabling protection.
Detection is a User-Agent heuristic, and we're honest about its limit: a bot spoofing a real browser UA is invisible in a log line. Bot counts are a floor, not a total — which is why the rate limit below applies to every client, spoofed or not.
34% of requests to blog.example.com look automated. Enable rate limiting + scraper blocking on this app?
Per-IP rate limiting — 30 requests a second, bursting to 60 — plus optional blocking of known crawler and scanner User-Agents, written into the app's nginx vhost on your server. Takes effect on the next deploy.
Reads the app vhost logs and bans IPs that keep hitting 429, 403, or 444 — the repeat offenders the rate limit is already turning away. The SSH jail is on by default.
Crowd-sourced defense: IPs flagged across the CrowdSec network are dropped at the firewall by an nftables bouncer, before a request ever reaches nginx.
For domains running on Cloudflare nameservers — Infraa checks that for you — Bot Fight Mode can be switched on at the edge through the same propose → approve flow as any other change.
No black boxes — the per-app guard is plain nginx config you can read on your own box, and nothing here installs or switches on without your say-so.
Tokens, installed modules, and audit history all live in one managed Postgres database with continuous point-in-time recovery — no dump file to locate or replay.
Agent tokens, module state, and audit log in one place.
Restore to any moment inside the recovery window.
Inside the PITR window — no external dump pipeline needed.
A restore operation, not a manual replay.
See what the agent does with all of this in place, or connect a server and approve your first action.