Drop-in Rust edge proxy: auth, rate limiting, WAF, hardened headers — zero app changes.
2.0K
mancube/eggrd)A drop-in Rust edge proxy: authentication, rate limiting, hardened response headers, and TLS — with zero code changes to your app.
EdgeGuard is the missing front door for any HTTP app (including the ones generated/vibe-coded without one). It owns the request path (auth, rate-limit, validation, WAF-lite) and the response path (CSP/HSTS/cookie hardening, leaky-header stripping) in a single static binary.
mancube/eggrd — static musl binary on distroless/static, runs as nonroot, no shell, CA roots included.linux/amd64, linux/arm64 (both built natively; measured on 0.3.0)/usr/local/bin/edgeguard (entrypoint) · Exposes: 8080/etc/edgeguard/edgeguard.tomlEdgeGuard is the secure front door: one hop that sits between your clients and the app, so traffic reaches the app only after it clears auth, rate limiting, and validation. Your app stops listening on the public port — EdgeGuard does — and is forwarded to unchanged. One upstream, one hop; it is not a CDN, an API gateway, or an identity provider.
CLIENTS EDGEGUARD UPSTREAMS
(public internet) (this image) (what it fronts)
┌───────────────┐
│ Browsers │ ─┐
│ API callers │ │ ┌────────────┐ plain HTTP ┌───────────────┐
│ LLM / SSE apps│ ├ TLS·auth▶│ edgeguard │ ─────────▶ │ your app │
│ bots·scanners │ │ rate-lim │ req path │ (localhost) │ (unchanged) │
└───────────────┘ │ WAF·DLP │ ────────── │ ├───────────────┤
┌───────────────┐ │ │ resp path │ ─────────▶ │ LLM provider │
│ CDN / LB / DNS│ ─┘ :443/8080│ CSP · HSTS │ hardened │ static site │
│ (optional) │ │ cookies │ response └───────────────┘
└───────────────┘ └─────┬──────┘
│
┌─────────────────────────┘
├──▶ Redis — one global rate-limit shared across replicas
├──▶ control plane — policy pull · usage · quota (managed mode)
└──▶ Prometheus — scrapes /__edgeguard/metrics
APP_PORT or an external UPSTREAM URL: your
own app, a streaming LLM backend, or a static frontend + /api backend split via
[[upstreams]]. The app is forwarded to unchanged — zero code changes./__edgeguard/metrics.| Tag | Notes |
|---|---|
latest | newest release (= 0.4.0) |
0.4.0 | Self-signed TLS + :80→HTTPS redirect + edgeguard cert; access-log query redaction, now on by default (see the upgrade note); aws-lc-sys dropped — image ~9 MiB smaller |
0.3.1 | --version added; unknown arguments now rejected instead of silently ignored |
0.3.0 | ACME issuance fixed (0.7.2 could not read Let's Encrypt's current authorization payload); linux/arm64 restored |
0.2.2 | doc-comment changes only — amd64 only, arm64 was not published for this tag |
0.2.1 | cookie-hardening opt-out (httponly_cookie_exempt) for JS-readable / double-submit CSRF cookies |
0.2.0 | per-path upstreams, request IDs, gzip, WebSocket passthrough, IP access lists |
Pin a version in production: mancube/eggrd:0.4.0.
Upgrading to 0.4.0 changes what lands in your access logs.
[log] querynow defaults toredact: query-string values that look like a credential (by name —token,key,secret,password,auth,code,state,[log] redact_params; or by shape — a JWT, a long high-entropy token) are written as<redacted>. This is the log only — the upstream, WAF, rate limiting and DLP all still see the full request byte-for-byte. If a dashboard or SIEM rule depends on the full query string being logged, set[log] query = "full"to keep the previous behaviour.Upgrading to 0.3.1 changes how bad command lines are treated. Arguments the proxy does not recognise are now an error at startup instead of being silently discarded. If a container currently passes a flag that was being ignored — including a typo like
--confg— it will now fail to start rather than run with settings it was never given. Check yourcommand:/args:before rolling this out broadly.If you are on
0.2.xand use[tls] acme = true, upgrade. ACME issuance was broken in those images — the proxy started and then never obtained a certificate, failing withmissing field `token`. Fixed in 0.3.0 and verified against Let's Encrypt staging.If you are on
arm64, avoid0.2.2— that tag was publishedamd64only.0.3.0builds both architectures natively again.
Front-proxy mode — put EdgeGuard in front of an existing upstream (the natural mode for this image):
docker run -p 8080:8080 \
-e UPSTREAM=http://app.internal:3000 \
mancube/eggrd:0.4.0 --config /etc/edgeguard/edgeguard.toml
Bring your own config (overrides the baked-in default):
docker run -p 8080:8080 \
-e UPSTREAM=http://app.internal:3000 \
-v "$PWD/edgeguard.toml:/etc/edgeguard/edgeguard.toml:ro" \
mancube/eggrd:0.4.0 --config /etc/edgeguard/edgeguard.toml
⚠️ The shipped config's
usersvalue is a non-working placeholder — set a real credential before exposing anything (see Auth).
Co-process mode (EdgeGuard supervises your app as PID 1) needs your app in the same image. Copy the binary into your app's image instead of running this one directly:
COPY --from=mancube/eggrd:0.4.0 /usr/local/bin/edgeguard /usr/local/bin/edgeguard
ENTRYPOINT ["/usr/local/bin/edgeguard", "--config", "/etc/edgeguard/edgeguard.toml", "--wrap", "node server.js"]
EdgeGuard binds the platform's $PORT and runs your app on APP_PORT. (Full process-group signaling on Unix; Windows is best-effort — prefer front-proxy mode there.)
All config is optional — secure defaults ship in the baked-in edgeguard.toml. Common env overrides:
| Env | Meaning | Default |
|---|---|---|
PORT | public listen port | 8080 |
APP_PORT | internal port for the wrapped app | 3000 |
UPSTREAM | external upstream URL (front-proxy mode) | derived from APP_PORT |
ADMIN_PORT | private listener for the ops endpoints | 0 (off) |
EDGEGUARD_CONFIG | config path (alternative to --config) | — |
EDGEGUARD_JWT_SECRET | HS* JWT secret (overrides config) | — |
EDGEGUARD_API_KEYS | API keys, comma-separated | — |
EDGEGUARD_REDIS_URL | shared-store rate-limiter URL | — |
RUST_LOG | log filter, e.g. info, edgeguard=debug | info |
Auth, rate limits, TLS/ACME, CSP, WAF-lite, and size/method limits are set in the TOML file. Editing it while running hot-reloads the policy in place (port/TLS changes still need a restart).
[[upstreams]], single upstream by default): route /api to a backend and everything else to a static frontend (longest-prefix wins) — a static-frontend + API-backend split in one proxy.text/event-stream) forwarded unbuffered, frame-by-frame (fronts streaming LLM backends without collapsing time-to-first-byte); optional WebSocket / Upgrade tunneling.X-Request-Id): reuse a well-formed inbound id or mint a UUID v4, forward it upstream, echo it on every response, tag the access log — one id correlates client, proxy, and app.[access], allow-all by default): coarse CIDR allow/deny lists evaluated by client IP before auth and rate limiting.validation.compress_responses, off by default): gzip for clients that ask, skipping already-compressed types and SSE.auth.mode): HTTP Basic ($argon2 PHC hashes), static API key / bearer (constant-time), or JWT (HS/RS/ES/PS/EdDSA, static key or cached JWKS; configured alg pinned).429): per-IP, optional per-route overrides, optional per-key; in-process governor or shared Redis store for multi-replica global limits.X-Content-Type-Options, X-Frame-Options, Referrer-Policy, Permissions-Policy; adds Secure; HttpOnly; SameSite to cookies — with a per-cookie httponly_cookie_exempt opt-out so a JS-readable double-submit CSRF cookie stays readable; strips Server / X-Powered-By.413), header-size (431), method allowlist (405).The reserved /__edgeguard/* namespace is never forwarded upstream:
| Endpoint | Purpose |
|---|---|
/__edgeguard/health | liveness (always 200) |
/__edgeguard/ready | readiness — 200 only when upstream accepts a connection, else 503 |
/__edgeguard/metrics | Prometheus metrics |
/__edgeguard/csp-report | CSP violation report sink |
Use ADMIN_PORT to move these onto a private listener and keep them off the public port.
Hash a password with the built-in helper (distroless has no shell — pass --hash as an arg, feed the password on stdin):
echo -n 'your-password' | docker run -i --rm mancube/eggrd:0.4.0 --hash
# paste the $argon2id$... string as the user's value in edgeguard.toml
A plaintext users value is dev-only (compared in constant time, never stored hashed). Always use an $argon2 hash for anything reachable.
Apache-2.0. Codename "EdgeGuard" is a working title.
Content type
Image
Digest
sha256:69356cedf…
Size
7 MB
Last updated
6 days ago
docker pull mancube/eggrd