never-rest

Security boundaries

never-rest is a contract and admission layer. It is not an identity provider, a WAF, or a mesh. This page states the order a request walks, the controls the library owns, and the controls that stay with the deployment.

Admission order

HTTP:

flowchart TD
  req[Request] --> anon["anonymous admission"]
  anon --> match["route match, no body read"]
  match --> verify["verify principal from headers"]
  verify --> body["bounded body read"]
  body --> parse["validate declared sources"]
  parse --> authz["authorize principal plus parsed args"]
  authz --> cost["principal limiter and concurrency slot"]
  cost --> handler["handler with signal"]
  handler --> out["output validation and disclosed response"]

Local dispatch skips the network steps. The adapter supplies the principal; then validation, authorization, admission, and the handler run. A protected operation without a principal returns boundary.unauthenticated.

Unmatched paths still go through anonymous admission when it is configured, then return route_not_found without reading a body.

What never-rest owns

ControlWhere
Declared input and output schemasContractDef / parseRouteSources / parseOutput
Graded disclosuredisclose; message and issues[].message are always public
Mandatory operation policydefinePolicy — coverage checked at construction
Bounded JSON ingressServeOptions.limits (default 1 MiB, 30 s)
Streaming Node bridgetoNodeHandler({ maxBodyBytes, bodyTimeoutMs })
Typed boundary errorsreserved boundary.* codes; HTTP statuses owned by serve
Admission limiters and concurrency./limits; onStoreFailure is required
Cancellationsignal on handlers, local dispatch, and the client
Explicit retryisRetryable and withRetry({ idempotent })

Policy is a map, not middleware. See advanced-usage.md.

What the deployment owns

never-rest does not terminate TLS, parse X-Forwarded-For, issue tokens, or share limiter state across replicas.

ControlOwner
TLS, certificates, HSTSLoad balancer / platform
Firewall, WAF, bot scoringEdge
Client certificates / mTLSIngress
OAuth / OIDC issuer and user directoryIdentity provider
VPN / private networkPlatform
Trusted-proxy client IPIngress; pass a key you already verified
Node requestTimeout, headersTimeout, maxHeaderSizeNode process / http.createServer
Proxy and CDN body limitsIngress, before the process
Hosts that parse the body before serve sees itThat host (some frameworks buffer first)
Distributed rate-limit storeYour Redis / memorystore adapter
Worker-pool cancellation for CPU workApplication — see examples/cancellation

Trusted-proxy keys

never-rest will not read X-Forwarded-For. If anonymous admission needs a client key, take it from a header your proxy overwrites, or from a value the host already verified:

admission: {
  anonymous: {
    limiter,
    key: ({ request }) => request?.headers.get('x-edge-client-key') ?? 'anonymous',
  },
  onStoreFailure: 'reject',
}

A store failure never silently allows unlimited work. onStoreFailure is 'reject' (503 boundary.overloaded) or { fallback } to another Limiter.

createMemoryLimiter is single-process. State resets on restart and is not shared between replicas. Use fromRateLimiterFlexible when the store lives elsewhere.

Disclosure contract

message and issues[].message are public data. They are kept at every disclosure level. The nextStep regex is a defensive heuristic, not redaction. Secrets belong in cause or ctx.

createDispatcher defaults to public because it serves foreign transports. createLocalClient defaults to full because it is module-to-module. serve omitted disclosure is public. Do not raise disclosure from a caller header.

What never-rest does not ship

  • An OAuth issuer, user directory, VPN, TLS terminator, or firewall
  • A distributed limiter implementation
  • MCP authorization servers or transport security
  • A public-message template catalogue
  • Parsing of forwarding headers

Those stay in the host that mounts the contract.

On this page