Go and Node middleware
Check every request inside your Go or Node app with /v1/gateway/check, including body digests for signed agents.
When there is no nginx in front of your app, or a route needs its body checked, run the gateway check inside the application. The repository has two small, dependency-free adapters with tests: examples/go-middleware (Go net/http) and examples/express (Node 18+, Express or plain node:http). Copy the one you need into your project.
Both call GET /v1/gateway/check with the backend secret, verify a signed agent's Content-Digest against the body they read (sha-256 or sha-512) and send X-Original-Body-Digest, strip client-supplied X-AgentGate-* headers, fail closed on errors unless told otherwise, and include a /v1/siteverify helper for form posts.
Go
gate := &agentgate.Client{
Endpoint: "http://127.0.0.1:18080", // private AgentGate address
Credential: os.Getenv("AGENTGATE_CREDENTIAL"), // the site's backend secret, ags_…
}
http.Handle("/api/", gate.Middleware(apiHandler))
// In a handler: the decision.
d, ok := agentgate.FromContext(r.Context()) // d.Decision, d.Reason, d.Agent, d.WouldDecisionNode
const express = require("express");
const { agentgate, siteVerify } = require("./agentgate");
const opts = { endpoint: "http://127.0.0.1:18080", credential: process.env.AGENTGATE_CREDENTIAL };
const app = express();
app.use("/api", agentgate(opts)); // before body parsers, or after express.raw()
app.post("/api/orders", (req, res) => {
// req.body is the raw Buffer; req.agentgate is the decision
res.json({ ok: true, agent: req.headers["x-agentgate-agent"] || null });
});Behaviour
| AgentGate answers | The middleware |
|---|---|
204 allow | runs your handler; the body is still readable; the decision is available (agentgate.FromContext in Go, req.agentgate in Node) and the request carries X-AgentGate-Decision-ID, X-AgentGate-Agent (verified agent) and X-AgentGate-Would-Decision (monitor mode) |
401 challenge | answers 401 with {"error": "…", "reason": "<code>"} |
403 block | answers 403, or 429 when the reason is rate_limited |
| error or timeout | answers 503 (fail closed), unless FailOpen: true / failOpen: true |
Options: the site key, the maximum body size (default 1 MiB; larger bodies get 413 without a check), the timeout (2 s by default: Go HTTPClient, Node timeoutMs), and, behind a proxy, functions for the client IP and protocol the proxy vouches for (Go ClientIP / Proto, Node clientIP(req) / proto(req)).
Form posts
A hidden agentgate_token field is in the body, so validate it in the route with the helper:
Go
res, err := gate.SiteVerify(ctx, agentgate.SiteVerifyRequest{
Token: r.FormValue("agentgate_token"), ExpectedAction: "signup",
ExpectedOrigin: "https://app.example.com", RequestID: operationID,
})
if err != nil { /* AgentGate unavailable: fail closed */ }
if !res.Success { /* res.Error: invalid_token, token_used, … */ }Node
const r = await siteVerify(opts, { token: req.body.agentgate_token, expectedAction: "signup",
expectedOrigin: "https://app.example.com", requestId: operationId });
if (!r.success) return res.status(403).json({ error: r.error }); // throws when AgentGate is downTests
- Go:
go test ./examples/go-middleware/...(mocked AgentGate) andgo test -run TestGoMiddlewareAgainstGateway .(the real endpoint). - Node:
cd examples/express && node --test(mocked AgentGate).