Security
knext's security invariants — authed mutating endpoints, network isolation, digest pinning, supply-chain signing, and runtime hardening.
Security in knext is not a milestone to defer — these controls run through every phase. The non-negotiable rule: no unauthenticated mutating endpoints, ever. Several controls are enforced automatically; the rest are documented guarantees knext holds to.
No unauthenticated mutating endpoints
Any route that changes state requires auth. Two app routes mutate cache state, and both require a Bearer token:
| Endpoint | Method | Auth |
|---|---|---|
/api/cache/invalidate | POST | Bearer CACHE_INVALIDATE_TOKEN, fail-closed |
/api/cache/events | DELETE | same Bearer token |
The token check is:
- Fail-closed — if
CACHE_INVALIDATE_TOKENis unset or empty, nothing is authorized. An unconfigured deployment rejects all invalidations rather than leaving the endpoint open. - Constant-time — comparison uses
timingSafeEqualso the token cannot be recovered via response timing (length is checked first, which does not leak the secret).
The token is provisioned from a Kubernetes Secret into the CACHE_INVALIDATE_TOKEN env var — never
hardcoded.
There is intentionally no GET /api/cache/invalidate handler. A mutating GET is
prefetchable/link-triggerable and would leak the Bearer token into URLs and logs. App Router
returns 405 for the unexported GET; POST is the only invalidation entrypoint.
The browser RUM beacon POST /api/rum is a deliberate, justified exception — a bounded metrics
aggregator, not a write primitive. See Observability → RUM
for the full rationale.
Network isolation
Auth is one factor; network isolation is the second. knext reconciles a default-on Kubernetes
NetworkPolicy for your app automatically.
- Object: a
NetworkPolicynamed<app>-allow-ingress, tied to your app's lifecycle (garbage-collected on delete). - Targets: the app's serving pods (selected by
serving.knative.dev/service: <app>). - Allows ingress from: the
knative-servingandkourier-systemnamespaces (so scale-from-zero traffic keeps flowing) plus same-namespace pods. Everything else — arbitrary cross-namespace and external pod-direct traffic — is denied. - Toggle:
spec.security.networkPolicy(*bool).nil(unset) ortruereconciles the policy (default-on);falseskips it and deletes any existing one on the next reconcile.
Honest scope: L3/L4, not L7. A NetworkPolicy filters by source pod/namespace at the network
layer — it cannot target an HTTP path. It does not isolate /api/cache/invalidate per se; it
makes the whole pod unreachable for disallowed direct traffic (a leaked token is useless to an
attacker who cannot route to the pod). It is also only enforced if your cluster CNI supports
NetworkPolicy (Calico, Cilium, etc.) — on a non-enforcing CNI the policy is a no-op.
Digest pinning
knext rejects mutable image tags everywhere.
:latestis rejected. An app whoseimageis:latest, tag-only, or untagged is markedDegraded(reasonInvalidImage) and never deployed — a ref is accepted only if it is digest-pinned (@sha256:).- knext's own images are digest-pinned too. A build-time check fails if any internal deployment manifest references a non-pinned tag, so knext never ships a mutable image reference.
See Operator → Security defaults.
Supply chain
Both the app image and the operator image go through the same supply-chain pipeline:
- SBOM per image — generated with syft (SPDX-JSON), published as an artifact.
- Vulnerability scan — Trivy scanning for
HIGH,CRITICAL, failing the build on released images. - Signing + attestation — cosign keyless signing (OIDC / Sigstore) on releases, plus an
SBOM attestation (
cosign attest --type spdxjson) bound to the image digest.
Runtime hardening
- Distroless, non-root. The operator image is
gcr.io/distroless/static:nonrootrunning as UID65532; the app image runs as a non-rootnodeuser. - No token automount. knext sets
AutomountServiceAccountToken: falseon the app's ServiceAccount. - Graceful shutdown. On
SIGTERMthe server forwards the signal to Next.js so it drains in-flight requests and runsafter()callbacks before exit, then closes the metrics server — no dropped requests on scale-down. The drain has a hard cap kept below the pod'sterminationGracePeriodSeconds.
Secrets
Secrets live in Kubernetes Secrets / env only — never in config files, source, container
images, or URLs. knext provisions them (via spec.secrets — envFrom / envMap) and the app
reads them from the environment.
Honest scope — not yet shipped. Service-to-service mTLS/authz between the gateway and backends, and the cluster-local gRPC backend services with no public ingress, are designed but not yet shipped. Today's enforced boundary is endpoint auth plus the network policy above.
See also: Operator & the NextApp CRD · Observability · Scale-to-zero.