manifest.plugins list. Rate limiting and CSRF are always-on built-ins tuned by env and your manifest; SSRF protection is built into the runtime; and the auth hardening (TOTP, API keys, Argon2, session lifetime) lives in the auth layer.
rate_limit
Always-on, tiered per-window request budgets. Buckets authenticated callers by user id and anonymous callers by IP. Returns 429 RATE_LIMITED when the budget is exhausted. It’s tuned by env, not a per-plugin config block.
The window is 60s. In dev mode (
PYLON_DEV_MODE truthy) both tiers are effectively off (100k/min). The anonymous cap is kept tight on purpose — anonymous traffic against /api/auth/* is the brute-force surface.
Beyond this global limiter, the runtime applies additional budgets on hot paths automatically:
/api/ai/streamhas its own per-user (per-IP for anon) rate limiter.- Webhook and function invocations (
/api/webhooks/<name>,/api/fn/<name>) rate-limit unauthenticated callers by peer IP.
Unified trustedOrigins
Pylon has three browser-facing gates that each reject a request when the caller’s origin isn’t trusted:
The single declarative source for all three is
manifest.auth.trustedOrigins:
http://localhost, 127.0.0.1, [::1] — any port, http only) is always auto-trusted at every gate, so pylon dev works on whatever port you pick without manifest config. Bearer-authenticated requests (SPAs, native clients) don’t trigger the CSRF gate at all — it only fires when a session cookie is attached.
Per-gate overrides
Ops who need a different allowlist for one gate can split via env vars — each overrides its gate’s default:
If none of the env vars are set, all three read from
manifest.auth.trustedOrigins. In production with neither manifest entries nor env vars, the CORS gate fails to start with a clear error — there’s no implicit wildcard fallback. PYLON_DEV_MODE=true relaxes that to * for local-only development.
SSRF defense (built-in)
Pylon guards its own server-side HTTP requests (image optimizer, OIDC discovery, and other outbound fetches) against SSRF by refusing to connect to private / link-local IP ranges (loopback, RFC 1918,169.254.0.0/16 cloud metadata, and the IPv6 equivalents). This is built into the runtime — there is no net_guard plugin to enable and no per-app allow_hosts config. When you make outbound calls from your own server functions, validate user-supplied URLs before fetching them.
Auth hardening lives in the auth layer
The following are real framework features, configured throughauth({...}) and the /api/auth/* routes — not plugins:
- Password auth — Argon2 hashing behind
/api/auth/password/*. See Auth → Password. - TOTP / 2FA —
/api/auth/totp/*(enroll / verify / disable + backup codes). See Auth → TOTP. - API keys — long-lived bearer tokens via
/api/auth/api-keys. See Auth → API keys. - Session lifetime — tune via
auth({ session: { expiresIn } })(default 30 days):
Recommended production posture
- Set
manifest.auth.trustedOriginsto your real origins (fails closed in production if unset). - Lower
PYLON_RATE_LIMIT_MAX/PYLON_RATE_LIMIT_MAX_AUTHEDif your defaults are too generous for your traffic. - Add
totpfor sensitive accounts and shortensession.expiresInfor high-risk apps. - Keep untrusted, user-supplied URLs out of server-side fetches, and validate any you must make.