| Version | Supported |
|---|---|
main |
✅ |
| Latest tagged release | ✅ |
| Older tags | ❌ |
Please report suspected vulnerabilities privately by emailing security@idct.tech with:
- A description of the issue and its impact.
- Steps to reproduce (PoC where possible).
- Affected versions / commit SHAs.
- Any known mitigations.
We will acknowledge your report within 72 hours and aim to release a fix within 30 days for high-severity issues. Once a fix is available we will coordinate public disclosure and credit the reporter (unless anonymity is requested).
In scope:
- The
nutsCaddy HTTP handler module in this repository. - The published
idcttech/nutsDocker image. - Build tooling and GitHub Actions workflows in this repository.
Out of scope:
- Third-party dependencies (please report upstream instead; notify us if the issue manifests specifically through NUTS).
- Denial of service via resource exhaustion that requires administrative
misconfiguration (e.g.
max_connectionsunset in a public deployment).
Production deployments should:
- Enable NATS TLS via
nats_tls_ca/nats_tls_cert/nats_tls_key. - Protect the NUTS HTTP route with Caddy
basic_auth,forward_auth, a reverse proxy, application policy, orsubscriber_jwt_keywhen subscriber authentication or per-topic authorization is needed. NATS auth directives only authenticate NUTS to NATS; they do not authenticate browser subscribers. - Set
max_connectionsto a value appropriate for the host. - Set
max_event_sizeto bound memory per event. - Keep
write_timeoutenabled (30 s by default) so a client or proxy that stops reading cannot hold a connection, its JetStream consumer and its prefetched messages indefinitely. - Set
replay_max_messagesorreplay_windowwhen a large retained stream could make replay too expensive. - Restrict
allowed_originsto your trusted front-ends. - Run the container as a non-root user (already the default).
Without subscriber_jwt_key or an upstream auth layer, any client that can
reach a NUTS route can request any valid topic under that handler's
topic_prefix. CORS is not authorization; it only controls browser read access
by origin.
Use one or more of these controls when tenant or private-topic isolation matters:
subscriber_jwt_keywith a JWTsubscribeclaim for first-party topic authorization inside NUTS.- Caddy route policy,
forward_auth, a reverse proxy, or application cookies. - Separate NUTS route blocks with separate streams/prefixes per tenant.
NUTS subscriber JWTs use HMAC signatures (HS256, HS384, or HS512) and
must include a subscribe claim. Supported filters are exact topic names,
single-token wildcards such as orders.*, and tail wildcards such as
tenant-a.>. A bare > matches every topic on the route per standard NATS
semantics; a bare * is accepted as a NUTS-only alias with the same "every
topic" meaning, which is more permissive than NATS itself (where bare *
only matches single-token subjects). Missing, expired, badly signed, or
unauthorized tokens are rejected before an ephemeral JetStream consumer is
created. The exp and nbf time claims are optional; when present they are
enforced. Both must be integer epoch seconds — RFC 7519 §2 permits non-integer
NumericDate, but fractional values are rejected as malformed rather than
truncated; all major JWT issuers emit integer epoch seconds. For public or
browser-facing routes, issue short-lived tokens with exp. NUTS rejects compact JWTs over 8 KiB, decoded JWT segments over 6 KiB,
and subscribe claims with more than 128 filters.
Use an edge proxy, CDN, WAF, Caddy rate-limit plugin, or upstream gateway to limit request churn by IP, user, or tenant. Recommended buckets are:
- SSE connection attempts to the NUTS route.
- Replay-heavy requests with stale
last-idorLast-Event-IDvalues. - Repeated invalid-topic requests returning
400. - Repeated subscription failures or connection-cap rejections returning
503.
max_connections caps concurrent streams, but it is not a request-rate limit.
On retained streams with a large backlog, set replay_window and/or
replay_max_messages so one reconnect cannot force an unbounded
replay. The default 0 values preserve compatibility by allowing all retained
replay; production public routes should choose explicit bounds that match their
retention and client recovery budget.