{"slug": "show-hn-nimblegate-git-push-guardrails-for-ai-agents", "title": "Show HN: Nimblegate – Git push guardrails for AI agents", "summary": "Nimblegate launched a self-hosted gateway that sits between an AI agent and a real git host, checking every push against enabled rules and forwarding clean pushes upstream in under a second while holding unsafe ones with a report. The tool, in production since early 2026, ships as a single Docker container with a docker compose plugin, a dashboard on port 7900, and a core rule kit of 18 of 51 frames that applies automatically at repo registration. Repos are registered with an HTTPS upstream URL and a write-scoped Personal Access Token, and the gateway relays over HTTPS only, shipping without an SSH client by design.", "body_md": "Git push guardrails for AI agents: block unsafe pushes consistently, forward safe ones, record every decision.\n\n**Status:**  [· used in production since early 2026](https://github.com/nimblegate/nimblegate/releases/latest)\n\nnimblegate sits **between your AI agent and your real git host**. Every push your\nagent makes is checked against the rules you turned on; clean pushes forward to\nyour upstream in under a second, unsafe ones are held with a clear report. Same\ninput, same answer, every time.\n\n**→ Try the [live demo](https://demo.nimblegate.com): click through a real dashboard over sample data, nothing to install.**\n\n**→ New here? The [Getting Started guide](https://github.com/nimblegate/nimblegate/blob/main/docs/getting-started.md) walks you from\ninstall to your first guarded push, step by step, no git expertise assumed.**\n\nSelf-hosted in one container. You need **Docker** with the **`docker compose`** plugin on the\nmachine that runs the gateway.\n\n**1. Start the gateway.**\n\n```\ncurl -O https://raw.githubusercontent.com/nimblegate/nimblegate/main/compose.yaml\ndocker compose up -d\n```\n\n**Deploying to a fresh cloud VPS?** One file does the whole box:\n[`deploy/cloud-init.yaml`](https://github.com/nimblegate/nimblegate/blob/main/deploy/cloud-init.yaml) - paste it as user data at\nserver creation and it boots hardened (firewall, key-only SSH) with the\ngateway running.\n\n**2. Claim the admin account.** Grab the one-time setup token, then set your password:\n\n```\ndocker logs nimblegate | grep nbg-setup     # prints the setup token\n```\n\nOpen **[http://localhost:7900/setup](http://localhost:7900/setup)**, paste the token, choose your admin password.\n\n**Remote/headless host?** The dashboard binds the host's loopback (it's the admin surface). Tunnel to it:\n\n```\nssh -L 7900:127.0.0.1:7900 <user>@<gateway-host>   # then open http://localhost:7900\n```\n\nUse `127.0.0.1`, **not** `localhost` (it's published on IPv4). For a trusted LAN instead, set `NIMBLEGATE_DASHBOARD_HOST=0.0.0.0`. → [dashboard access options](https://github.com/nimblegate/nimblegate/blob/main/docs/getting-started.md#dashboard-access)\n\n**3. Register the repo to guard.** In the dashboard: **Repos → Add a repo**, then fill in:\n\n- \n**Name** - lowercase letters, numbers, dots, hyphens, underscores (e.g.`my-app` ). This is the name you'll push to.\n- \n**Upstream URL** - your real repo as**HTTPS** :`https://github.com/you/my-app.git` (not the`git@…` SSH form). The container relays over**HTTPS only** - it ships without an SSH client by design - and a PAT authenticates**both public and private** repos.\n- \n**Upstream credential** - a**Personal Access Token** from your git host, scoped to**write that repo** :\n  - GitHub **fine-grained** : repository = that repo,**Contents → Read and write**\n  - GitHub **classic** : the`repo` scope\n  - GitLab / Gitea: a token with write/push on the repo\n *(Auto-PR's fix-loop needs a little more - see [Getting Started](https://github.com/nimblegate/nimblegate/blob/main/docs/getting-started.md#step-4-register-the-repo-to-guard).)*\n- GitHub \n\nClick **Register**. If the upstream **already has commits**, the gateway mirrors its history down automatically at registration, so your existing clones push cleanly. If that seed couldn't run (a missing or wrong credential, say), the repo row shows a one-click **Sync from upstream** - fix the credential, click it, done. The **core** rule kit (18 of the 51 frames, the catastrophic-prevention set) applies automatically; widen or narrow it any time on the repo's Policy page. → [other git hosts, scoped access](https://github.com/nimblegate/nimblegate/blob/main/docs/getting-started.md)\n\n**4. Authorize your push key.** On your dev machine, print your SSH **public** key:\n\n```\ncat ~/.ssh/id_ed25519.pub\n```\n\nCopy the whole line, then in the dashboard go to **Keys → Add a key**, paste it, and save. The gateway only ever sees your **public** key - never the private one.\n\n**5. Point your computer at the gateway and push.** Add the gateway as your remote and push to it instead of your real host.\n\n**This hop is SSH, not HTTPS.** You push to the gateway over **SSH on port 2222** - that's *not* the HTTPS upstream URL from step 3. The two hops use different protocols on purpose: **your machine → gateway = SSH**, **gateway → your real host = HTTPS**.\n\n```\ngit remote set-url origin ssh://git@<gateway-host>:2222/~/my-app.git   # my-app = the name from step 3\ngit push\n```\n\n**Note the `~/` in the URL** - it's required, not a typo. On the gateway the SSH user is locked to **git-shell** (it can run git push/clone and *nothing* else - no shell, no commands), which resolves repo paths relative to its home. So the path is `~/<repo>.git`, not a bare `/<repo>.git`. This restriction is a security feature: a key can only move git data through the gate, never run commands or read the gateway's upstream token - so a compromised dev key can't bypass the gate or steal your credential.\n\n**6. See it work.** A clean push is accepted and forwarded to your upstream; a push that trips a rule is blocked and never reaches the real host. Watch it live on the dashboard **Feed**.\n\nBare-metal install, SSH-key upstreams, multi-dev scoped access, or public TLS? → **[Getting Started](https://github.com/nimblegate/nimblegate/blob/main/docs/getting-started.md)** - full walkthrough + troubleshooting.\n\nThere are **three places**, and keeping them straight is the whole game:\n\n```\n   YOUR COMPUTER                THE GATEWAY                   THE UPSTREAM\n   (you / your agent            (nimblegate)                  (GitHub / Gitea / GitLab:\n    write + git push)                                          your real repo)\n\n   git push ──────────────────► checks your rules ─forwards─► stores the code\n   git clone ◄───────────────── serves the code\n```\n\n- **Your computer only talks to the gateway:** you push to it and clone from it,\nnever the upstream directly (that would skip the checks).\n- **Only the gateway talks to the upstream:** it holds the credential and\nforwards clean pushes for you.\n\nYour commits arrive at the upstream **byte-for-byte unchanged**: same SHA, same\nauthor, same signature. The gateway checks and forwards; it never rewrites. You\nsee every push and decision live on the dashboard, and you can change the rules\nanytime; the next push uses the new set.\n\n**What that gets you:**\n\n- **Hours back on review.** The gate is the filter between an agent's output and\nyour real repo, so you're not eye-scanning every generated diff yourself.\n- **Caught at push time, not deploy time.** Leaked credentials, force-pushes to\nmain, schema drift: held at the gateway, never in your upstream history.\n- **A reviewer the fix-loop converges against.** \"Push → fail → fix → pass\" only\nworks when the reviewer answers the same way every time. Pattern checks do; AI\nreviewers don't.\n- **Proof of what it stopped.** A live feed of every decision, plus a stats page\nestimating the hours saved.\n- **Invisible when clean.** A clean push forwards in under a second; the gate\nspeaks up only when there's something to see.\n\nFull walkthrough with the mental model spelled out: **[Getting\nStarted](https://github.com/nimblegate/nimblegate/blob/main/docs/getting-started.md)**.\n\nBecause each of those either lives somewhere the agent can reach, or fires after the code has already landed.\n\n- **A pre-commit hook runs on the machine the agent controls.** It can be\nbypassed (`git push --no-verify` ), edited, or simply never installed in a fresh\nworktree. nimblegate runs on a separate box the agent has no shell on, and it\nholds the only credential to your real host - so there's nothing to skip and\nnowhere to go around.\n- **Branch protection and CODEOWNERS gate the *merge*, not the *push*.** By the\ntime they fire, a leaked key is already an object in your upstream's history;\ndeleting the branch doesn't unleak it, and rotating the credential is your only\nreal remedy. The gate rejects the push, so the object never reaches your host\nat all.\n- **CI runs after the push landed** - and its config lives in the repo the agent\nis editing. Same exposure, one step later.\n- **The fix-loop needs a reviewer that answers the same way every time.** \"Push →\nfail → fix → pass\" only converges against a deterministic gate. Pattern checks\nare; an AI reviewer isn't.\n\nNone of these replace each other - keep your hooks and your branch protection. The gate is the one layer that sits outside the machine writing the code.\n\nDay to day, the gate fits the normal feature-branch flow:\n\n1. **Your agent works on a feature branch** - coding agents often use a git\nworktree per task, so several branches are in flight at once (one task, one\nbranch) - and pushes it**to the gateway** .\n2. The gate checks that push. **Clean** → forwarded to your upstream, where you\nor the agent open a PR (`feature → main` ), as usual.**Finding** →**rejected** , and the bad commit never reaches the upstream.\n3. On a rejection with an open PR, the gate posts the finding as a PR comment (see Auto-PR below) - the agent reads it, fixes, and re-pushes until it passes. (The gate comments on a PR; it doesn't open one - your git host does that.)\n4. **You review the PR and merge it to `main`** on your git host, as usual.\n\nBecause rejected commits bounce at the gate, the branch on your upstream only\never holds gated-clean code - so by the time you review a PR, it has already\npassed the automated checks; you spend review time on judgment, not hunting for\nleaked keys or `rm -rf`. A new repo's **protected refs default to `refs/heads/*`**,\nso the agent's *feature* branches are gated, not just `main` - including nested\nnames like `agent/task-1` or `feature/login`, since a trailing `/*` matches at any\ndepth. Leave it there unless you deliberately want `main`-only checks.\n\nWhen a push is rejected, the gateway posts the findings as a **structured comment\non the upstream PR** and fires a **webhook** with the same JSON, so an agent\n(Claude Code, Cursor, Copilot, custom CI) reads the rejection and fixes itself.\nThe sticky comment updates in place, tracking **attempt N/M** and @-mentioning the\nagent; when a later push passes, it flips to **✅ All findings resolved** and the\nloop closes. The `/auto-pr` dashboard surfaces it as Dashboard / Repos / Activity\n/ Setup tabs.\n\nMinimum config (per repo, `/auto-pr` → Setup or `gateway.toml`): set\n`[notification] enabled = true` and a mention handle and/or webhook URL. The\nupstream token needs **comment** permission (separate from the relay's push\npermission): **GitHub** - classic `repo`, or fine-grained **Issues: Read and\nwrite** + **Pull requests: Read** (PR comments use the Issues API); **Gitea** -\n`write:issue`; **GitLab** - the **` api`** scope (no narrower one allows MR\ncomments). If deliveries fail, the **Auto-PR → Repos** tab shows the error +\na **Retry now** button. Full setup + receiver examples:\n**[notifications.md](https://github.com/nimblegate/nimblegate/blob/main/docs/notifications.md)**.\n\nnimblegate ships **51 rules (\"frames\")** for the mistakes agents make most, grouped\ninto one-click **kits**:\n\n- **`core`***(every repo)* : hardcoded credentials, committed private keys,\nforce-push to protected branches,`--no-verify` bypass,`rm -rf` of protected\npaths,`curl | sh` patterns, DB schema drift, non-idempotent migrations.\n- **`web-app`** /**` cf-pages-project`** /**` cf-workers-project`** : stack-shaped\nbundles (HTML/SEO, Cloudflare Pages + D1, Workers).\n- **`security-strict`** /**` encoding-strict`** : the invisible-Unicode attack\nclass, checksum-validated personal data in fixtures (payment cards, IBANs,\nSSNs), and paste-corruption that silently breaks parsers. Stackable on any kit.\n- **Supply-chain** :`commands/approved-registries-only` flags dependency\nsources (Maven/Gradle repositories, npm registries, pip index URLs) that\nbypass your approved registry mirror - declare your\nhosts once, catch the agent-added bypass at push time.\n\nPlus your own **regex rules**, authored from the dashboard with a live \"would have\nflagged N files\" preview: no code, no rebuild. Choosing and authoring policy:\n**[policy-authoring.md](https://github.com/nimblegate/nimblegate/blob/main/docs/policy-authoring.md)**. Full catalog:\n**[frames.md](https://github.com/nimblegate/nimblegate/blob/main/docs/frames.md)**.\n\nEvery repo then runs those rules in one of two modes, set per repo:\n\n|  | Enforce (default) | Observe ( `observe = true` ) | \n|---|---|---|\n| **BLOCK finding** | push **rejected** | recorded, **relayed** | \n| **Pusher / agent sees** | findings + `push rejected` | **nothing** , silent | \n| **Operator sees** | dashboard + audit log | dashboard + audit log (⚠ banner) | \n| **Acts as a guardrail?** | **yes** | **no, advisory only** | \n| **Use it for** | day-to-day protection | measuring/benchmarking agents | \n\nObserve mode is **deliberately silent** so an agent can't adapt to the gate; it\nmeasures behavior, it is **not** a safety net (a credential or `curl | sh` is\nlogged and let through). Switching modes takes effect on the next push.\n\nThe gateway exposes a read-only analytics API (MCP + REST) so an agent can query\nthe decision log directly: *\"which repo had the biggest bounce-back this month?\"*,\n*\"how much debugging time has the gate prevented?\"*, *\"what changed this week?\"*,\nanswered from SQL over the validated log. Mint a bearer token\n(`nimblegate gateway token new <label>`) and point your MCP client at\n`http://<host>:7900/mcp`. No agent? The same reports are one-click buttons on the\ndashboard **Reports** page. Reference: **[agent-api.md](https://github.com/nimblegate/nimblegate/blob/main/docs/agent-api.md)**.\n\n| Guide | What's in it | \n|---|---|\n| **[Getting started](https://github.com/nimblegate/nimblegate/blob/main/docs/getting-started.md)** | Install → first guarded push, step by step, with a troubleshooting quick-check. Start here. | \n| **[Policy authoring](https://github.com/nimblegate/nimblegate/blob/main/docs/policy-authoring.md)** | Choosing frame kits, writing custom regex rules, fitting alongside your linters + CI. | \n| **[Frame catalog](https://github.com/nimblegate/nimblegate/blob/main/docs/frames.md)** | Every built-in frame: what each category catches, severity, tiers. | \n| **[Auto-PR / notifications](https://github.com/nimblegate/nimblegate/blob/main/docs/notifications.md)** | The fix-loop rail: config, webhook receivers, troubleshooting. | \n| **[Agent API](https://github.com/nimblegate/nimblegate/blob/main/docs/agent-api.md)** | MCP + REST analytics surface for agents. | \n| **[Operations](https://github.com/nimblegate/nimblegate/blob/main/docs/operations.md)** | Update, backup/recovery, monitoring, password reset. | \n| **[Dev-machine setup](https://github.com/nimblegate/nimblegate/blob/main/docs/server/DEV-MACHINE-SETUP.md)** | Making the gateway a real boundary (close the bypass paths). | \n| **[Troubleshooting](https://github.com/nimblegate/nimblegate/blob/main/docs/troubleshooting.md)** | Operator gotchas beyond the getting-started table. | \n| **[Security model](https://github.com/nimblegate/nimblegate/blob/main/docs/security-model.md)** | What's hardened, the trust boundaries, the threat model. | \n\nnimblegate is honest about its scope so it stays trustworthy:\n\n- **The rules you enable are exactly what gets caught.** A rule that isn't on\ncatches nothing; a pattern outside the rule set flows straight through. The tool\nis*secure as configured* : coverage is whatever you turn on.\n- **It is not a substitute for human review of human work.** The audience is\nagent pushes, code generated faster than anyone reads every line.\n- **It is not a vulnerability scanner or an AI reviewer.** It runs the same\npattern checks the same way every time. That's the value, and the limit.\n\nThe *tool itself* is hardened: separate machine your agent can't reach, holds the\nonly upstream credential, single-admin auth, append-only audit log. But **you're\nthe operator; the coverage is yours to decide.** Full version:\n[security-model.md](https://github.com/nimblegate/nimblegate/blob/main/docs/security-model.md).\n\n**Operator security - this part matters:**\n\n- **Use a separate, minimal-scope upstream token per repo, and rotate it\nregularly.** A fine-grained PAT scoped to*Contents: write on that one repo* (nothing wider) means a leaked token can push only to that repo, not your whole\naccount - and rotation bounds how long any leak stays useful. Encrypting the\ntoken at rest does**not** protect a*running* gateway (it must decrypt the\ntoken to use it); minimal scope + rotation are what actually limit the blast\nradius.\n- **The guarantee depends on correct deployment.** Run the gateway on a**dedicated box** your agent can't otherwise reach, keep the git SSH user on**git-shell** (push/clone only - no shell, no commands), don't hand out keys or\nexpose the box loosely, and never authorize a dev key directly on the upstream.**Set up improperly, it can be compromised:** an attacker who obtains a shell as\nthe git user could read the stored upstream token and push**directly to your\nreal repo, bypassing the gate entirely.** Treat the gateway as\nsecurity-sensitive infrastructure.\n\nNon-commercial use is free and unrestricted under [PolyForm Noncommercial 1.0.0](https://github.com/nimblegate/nimblegate/blob/main/LICENSE)\n\n- no time limit, no feature gating, today and for good.\n\n**Free, non-commercial** - for example:\n\n- Personal projects and your own learning\n- Research and teaching\n- Use by a non-profit / not-for-profit\n- Trying it out before a commercial decision\n\n**Needs a commercial license** (`contact@nimblegate.com`) - for example:\n\n- Use inside a for-profit business, including gating code that ships a paid product\n- Offering nimblegate, or a derivative, as a hosted service to others\n\nThe [LICENSE](https://github.com/nimblegate/nimblegate/blob/main/LICENSE) is authoritative; if your case is unclear, just ask.\n\nYou get:\n\n- **One combined container** (sshd + dashboard, s6-supervised, auto-restart).\n- **Single-admin auth** built in (bcrypt, server-side sessions, setup-token bootstrap).\n- **Same-rules-every-time gate** across pre-commit / pre-receive / post-receive.\n- **51 built-in frames** + your own regex linters.\n- **Auto-PR rail** (vendor-neutral): structured PR comment + webhook on every\nrejected push,`@bot` mention + multi-bot rotation, loop guardrails, fix-loop\nclosure, exponential-backoff delivery with deadletter. Gitea + GitHub adapters.\n- **Dashboard** : live feed, time-saved/recurring stats, policy + custom linters +\nwhitelist, repo lifecycle, frames catalog, Auto-PR tabs, health, SSH key\nmanagement, events log, per-page help.\n- **Upstream credential management** +**whitelist** (with required reason).\n\nUsing nimblegate in a for-profit setting requires a commercial license - a flat\n**$10/month or $99/year per company** (all your devs, the app as-is, best-effort\nemail support, no SLA). Terms and the buy links: **[commercial license](https://github.com/nimblegate/nimblegate/blob/main/COMMERCIAL.md)**.\n\nLarger organisations needing an SLA, signed terms, or bespoke features: email\n`contact@nimblegate.com`.\n\nYou define which rules fit your repo, stack, and threat model; you review the\ncoverage you enabled and test the gate in your environment; you keep the rest of\nyour security posture in place (nimblegate is **one layer**); and you keep the\ngateway machine itself secure. Found a bypass or coverage gap? Please report it\nvia [SECURITY.md](https://github.com/nimblegate/nimblegate/blob/main/SECURITY.md).\n\nNo telemetry. The binary phones home zero times. The gateway logs what *you push\nto it* and sends nothing anywhere except your configured upstream. See\n[PRIVACY.md](https://github.com/nimblegate/nimblegate/blob/main/PRIVACY.md).\n\nnimblegate is source-available under **[PolyForm Noncommercial 1.0.0](https://github.com/nimblegate/nimblegate/blob/main/LICENSE)**,\nprovided AS-IS without warranty. Non-commercial use is free and unrestricted,\ntoday and for good. **Commercial use requires a [commercial license](https://github.com/nimblegate/nimblegate/blob/main/COMMERCIAL.md)**\n($10/month or $99/year per company; email `contact@nimblegate.com` for larger orgs).\n\n- **Donations:**[GitHub Sponsors](https://github.com/sponsors/nimblegate) keep\nthe[demo gateway](https://demo.nimblegate.com) hosted and the project alive.\n- **Contributing:** PRs welcome for rules, docs, and fixes; see[CONTRIBUTING.md](https://github.com/nimblegate/nimblegate/blob/main/CONTRIBUTING.md) .\n- **Security:** don't open a public issue. Email`security@nimblegate.com` . See[SECURITY.md](https://github.com/nimblegate/nimblegate/blob/main/SECURITY.md) .", "url": "https://wpnews.pro/news/show-hn-nimblegate-git-push-guardrails-for-ai-agents", "canonical_source": "https://github.com/nimblegate/nimblegate", "published_at": "2026-09-22 12:08:11+00:00", "updated_at": "2026-09-22 12:25:21.193142+00:00", "lang": "en", "topics": ["ai-agents", "developer-tools", "ai-tools"], "entities": ["Nimblegate", "Docker", "GitHub", "GitLab", "Gitea"], "alternates": {"html": "https://wpnews.pro/news/show-hn-nimblegate-git-push-guardrails-for-ai-agents", "markdown": "https://wpnews.pro/news/show-hn-nimblegate-git-push-guardrails-for-ai-agents.md", "text": "https://wpnews.pro/news/show-hn-nimblegate-git-push-guardrails-for-ai-agents.txt", "jsonld": "https://wpnews.pro/news/show-hn-nimblegate-git-push-guardrails-for-ai-agents.jsonld"}}