Crux Daemon · 7. Auth and scopes

The route-auth middleware defaults to shadow mode: contract violations are logged and never blocked. Only the per-handler scope checks bite out of the box. Everything else in this chapter is detail on top of that fact.

This chapter is reference. For the daemon's overall shape see chapter 1; for identity and capability tokens see chapter 14.

7.0 In plain English

Two separate questions get asked of every request that reaches the daemon, and it helps a great deal to keep them apart. The first is who are you, which is authentication, and it is answered by CORECRUXD_AUTH_MODE: either a JWT proves the caller's identity, or, under off and dev_scopes, nothing does. The second is are you allowed to do this particular thing, which is authorisation, and it is answered by scopes: short strings like a read or admin permission that the caller must be carrying for the handler to proceed.

Scopes are checked at two independent layers, and the difference between them is the single most important thing in this chapter. The layer that actually enforces is the per-handler check: each handler calls a require_http_* function directly and returns 403 if the caller lacks the scope. The other layer is route-auth middleware, which classifies every route against a hand-maintained contract of what it should require, and compares that with what the caller presented. In its default posture that middleware is a camera, not a lock. It observes the mismatch, records it, and lets the request through.

That is a defensible design, because a contract table maintained by hand will disagree with reality while it is being built, and a lock built on a wrong table locks out legitimate traffic. It is only dangerous when it is misread. If you look at the route-auth contract, see that a route is classified as admin-write, and conclude the route is therefore protected, you have concluded something the default configuration does not do. The protection on that route is whatever require_http_* call the handler itself makes, and nothing else.

You will come here at two moments. When you first move the daemon off loopback, because that is when authentication stops being theoretical, and §7.1 plus §7.5 tell you what is exposed and what is public by design. And when a call returns 403 and you need to know which scope it wanted, which is §7.2 for the vocabulary and §7.3 for the failure shapes, including the missingScopes array in the response body that names exactly what was absent.

The trap worth stating twice, because it is not obvious from reading the code: there is no axum extractor doing scope checks for you. Handlers call the check imperatively, so a handler that forgets the call has no compile-time backstop. Nothing will fail to build. Under CORECRUXD_AUTH_MODE=off, separately, the scope primitives return success immediately without checking anything at all, which is correct for local development and is exactly why §7.9 gathers the sharp edges in one place.

7.1 CORECRUXD_AUTH_MODE: the four values

There is no default. The daemon refuses to start without it, see chapter 4 §4.3, conditions 1 and 2. It can be supplied either as the environment variable or as daemon.auth_mode in the YAML config file.

AuthMode::parse (auth.rs:58-69) matches on s.trim() and is case-sensitive per arm, each casing is enumerated literally, so mixed casing such as Off or Jwt_Hs256 does not parse and aborts startup.

Accepted literalsResolves toDefined at
"" (empty), off, OFFOffauth.rs:60
dev, DEV, dev_scopes, DEV_SCOPES, devscopes, DEVSCOPES, dev-scopes, DEV-SCOPESDevScopesauth.rs:61
jwt, JWT, jwt_hs256, JWT_HS256, jwt-hs256, JWT-HS256JwtHs256auth.rs:64
jwt_jwks, JWT_JWKS, jwt-jwks, JWT-JWKS, jwks, JWKS, oidc, OIDC, jwt_oidc, JWT_OIDC, jwt-oidc, JWT-OIDCJwtJwksauth.rs:65
anything elseinvalid, startup abortsauth.rs:67

Canonical serialisation, used in evidence and /v1/version, is off, dev_scopes, jwt_hs256, jwt_jwks (auth.rs:71).

What each mode does

ModeCredentialBehaviour
Offnonehttp_ctx returns an empty-scope context with TenantAllow::Any; every require_http_* and require_grpc_* returns Ok(()) immediately; scope_bypass = true, so has_scope() is true for every string, including scopes that do not exist (auth.rs:849, auth.rs:1096)
DevScopesX-Corecrux-Scopes header, or Authorization: Bearer <space- or comma-separated scopes>, the bearer is the scope list, unsignedScopes are taken verbatim from the request. No identity, no tenant claim. A request with neither input gets a 401 (auth.rs:385)
JwtHs256Authorization: Bearer <HS256 JWT>Verifies the HS256 signature with CORECRUXD_JWT_HS256_SECRET. exp and nbf validated with 30s leeway; optional iss and aud pinning (auth.rs:514)
JwtJwksAuthorization: Bearer <JWT>Verifies against a JWKS, inline JSON, file, URL or OIDC discovery. Algorithm allowlist defaults to RS256; kid lookup with rate-limited refresh-on-miss (auth.rs:698)

Both JWT modes may additionally accept a registered MCP agent token as an HTTP fallback, see §7.6.

DevScopes is unauthenticated self-assertion. The bearer token is the scope list. Its only guard is the loopback-bind rail, which CORECRUXD_ALLOW_INSECURE_DEV_AUTH_BIND=1 disables. Do not run it on a reachable interface.

Per-mode environment requirements

  • Off, none.
  • DevScopes, none.
  • JwtHs256, required CORECRUXD_JWT_HS256_SECRET, raw bytes or base64:-prefixed, at least 32 bytes unless CORECRUXD_ALLOW_WEAK_HS256_SECRET is set. Optional CORECRUXD_JWT_ISS, CORECRUXD_JWT_AUD (auth.rs:237).
  • JwtJwks, one of CORECRUXD_JWT_JWKS_JSON, _PATH, _URL, or CORECRUXD_JWT_OIDC_DISCOVERY_URL, each with a legacy CORECRUXD_JWKS_* alias. Optional CORECRUXD_JWT_ISS, _AUD, _ALGS (default RS256), _JWKS_MIN_REFRESH_SECONDS (default 30) (auth.rs:254).

The startup posture gates

GateRuleLocation
Dev-auth network bindOff and DevScopes may not bind non-loopback HTTP or gRPC without CORECRUXD_ALLOW_INSECURE_DEV_AUTH_BIND=1main.rs:2030
Replicated commit plus JWTA JWT mode with CommitLevel::ReplicatedCommit requires CORECRUXD_REPLICATION_AUTH_BEARERmain.rs:2039
MCP bindMCP on a non-loopback address with an empty agent registry aborts without the same overridemain.rs:2050
Agent-token strengthAn agent-token variable present but invalid aborts unless CRUX_MCP_ALLOW_EMPTY_AGENT_REGISTRY=1main.rs:1967
HS256 secret strengthThe secret must decode to at least 32 bytes unless CORECRUXD_ALLOW_WEAK_HS256_SECRET=1auth.rs:348

One sharp edge for anyone embedding the config loader: config.auth_mode silently falls back to DevScopes when parsing fails (config.rs:886). That is safe only because main aborts first. Any other caller of load_config() that does not replicate the two guards inherits a fail-open default.

7.2 The complete scope list

Scopes are plain strings, not an enum. There is no central registry. They are declared inline at route contracts, at handler require_http_* calls, and in a handful of named constant arrays. The list below is the complete set that any authorisation check compares against.

Core HTTP and gRPC scopes

ScopeGrantsCanonical definition
admin:readUniversal read override. Satisfies every read-class route and bypasses the tenant check on the any-of variant. Also the console read plane and /v1/admin/* GETroute_auth.rs:123; check auth.rs:1379
admin:writeUniversal write override: /v1/admin/* mutations, POST /v1/passports, /v1/local/ingest, /v1/credits/*, /v1/legal-holds, device-grant approve. Also grants the passport-impersonation overrideroute_auth.rs:122; override auth.rs:1217
facts:read/v1/features/capabilities GET, /v1/sync/tenants/* GET, /v1/orchestrators, /v1/punchcards, /v1/activity, /v1/cost, /v1/observe/*route_auth.rs:239
facts:writeFact, entity, edge, kind and session mutations; /v1/cases; /v1/memory/import; /v1/append; /v1/studio/library/* install; /v1/work writes; /v1/coord/* writesroute_auth.rs:227; handler facts.rs:139
query:readThe general read scope: /v1/query/*, /v1/projections/entity/*, /v1/receipts/*, /v1/replay/*, /v1/events/*, /v1/observations/*, /v1/ops/*, /v1/bootstrap/*, /v1/audit/*, /v1/studio/*, /v1/cases/retrieve, /v1/context, /v1/quotaroute_auth.rs:134
sessions:readSession reads; part of the broad read union on /v1/work, /v1/passports, /v1/agents/*; /v1/coord/* GETroute_auth.rs:405
sessions:writeSession mutations, /v1/coord/* writes, the OpenAI shimroute_auth.rs:227
receipts:readReceipt reads; part of the read union on /v1/receipts/, /v1/replay/, /v1/events/, /v1/observations/, /v1/ops/, /v1/bootstrap/, /v1/audit/route_auth.rs:194
exports:readExport reads; the same union, plus /v1/incidents/*/exportroute_auth.rs:474
replication:writeThe only scope for /v1/internal/replication/*, the leader-to-follower segment pushroute_auth.rs:103
events:readgRPC only: ReadStream, tenant-checkedgrpc.rs:777
events:writegRPC only: AppendBatch, tenant-checkedgrpc.rs:764
compute:embedPOST /v1/compute/embed. The only accepted scope, no admin overridecompute.rs:29
provenance:write/v1/provenance/*, alongside admin:writeprovenance.rs:622
integrations:installInstall an integration pack. Treated as a write scope by the route-auth invariants testintegrations_github.rs:44
integrations:grantGrant an installed packconsole.rs:2251
integrations:disableDisable a packintegrations_github.rs:92
passport:impersonatePermits X-Corecrux-Passport-Id to differ from the verified token identity. The only alternative to admin:write for that overrideauth.rs:1216
tenant:chunks:readConsole per-tenant chunk listing, tenant-checkedconsole.rs:3037
tenant:content:previewConsole chunk content preview, tenant-checked. Classified read-only by the route-auth invariantsconsole.rs:3107
enrichers:first_partyPOST /v1/actions/enrich, an alternative to admin:writeactions.rs:16

Entitlement-shaped scopes

These are capability claims (product.rs:78) that are also accepted as bearer scopes on their own surface, checked with an any-of against the claim plus a fallback.

ScopeSurfaceDefinition
agent_brief:pro/v1/workbench/briefworkbench.rs:48
context_pack:budgeted/v1/workbench/context-packworkbench.rs:49
impact:preflight/v1/workbench/impact-preflightworkbench.rs:50
ledger:history/v1/workbench/command-ledgerworkbench.rs:51
audit:triage/v1/workbench/audit-triageworkbench.rs:52
reasoning:timeline/v1/workbench/reasoning-timelineworkbench.rs:53
handoff:v2/v1/workbench/handoffworkbench.rs:54
route_probe:lab/v1/workbench/route-probeworkbench.rs:55
api_drift:check/v1/workbench/api-driftworkbench.rs:56
policy:simulate/v1/workbench/policy-simulationworkbench.rs:57
gpu1:answer, gpu1:rerank, gpu1:enrich, gpu1:coverage, gpu1:developer/v1/gpu1/*, tenant-checked with an admin:write fallback. Only present with --features hosted-surfacesgpu1.rs:48
replay:answerThe replay-answer laneproduct.rs:88
console:workbenchThe console workbench entitlementproduct.rs:108

The full claim catalogues are entitlement claims, not bearer scopes, except for the subset above. Do not document the whole catalogue as an auth scope list.

Two namespaces that are not HTTP scopes

  • The MCP capability ladder, tool:list, tool:invoke:read, tool:invoke:metered, tool:invoke:side_effect, tool:invoke:destructive. Derived from a passport's reputation tier and returned by resolve_principal (principal.rs:72). Federation caps this to tool:list and tool:invoke:read (policy.rs:60).
  • Sync-peer capabilities, the pull and push capabilities from the capability-token crate, used only by the mutual-auth sync plane (sync.rs:53).

Scope-string semantics

  • Parsing splits on comma or ASCII whitespace, trims, drops empties, and collects into a BTreeSet (auth.rs:363).
  • Comparison is exact string equality. There is no prefix or wildcard matching (auth.rs:804).
  • The one prefix rule anywhere: matched_scope.starts_with("admin:") skips the tenant check (auth.rs:1379).
  • Claim sources for scopes in JWT modes: scope (string), scp (string and array), scopes (array), permissions (array), the union of all four (auth.rs:422).

7.3 How scopes are checked

There are two independent layers, and the first never replaces the second, the module doc says so explicitly (route_auth.rs:22).

  1. Route-auth middleware, coarse, contract-driven, and in shadow mode by default. See §7.4.
  2. Handler-level checks, direct require_http_* calls inside each handler. This is the layer that actually enforces.

The primitives

FunctionSemanticsLocation
require_http_scopes(auth, headers, &[..])all-of; returns Ok immediately when the mode is Offauth.rs:1320
require_http_any_scope(auth, headers, &[..])any-of; Ok when the mode is Offauth.rs:1340
require_http_any_scope_for_tenant(…, tenant_id)any-of plus a tenant check, skipped for any admin:* matchauth.rs:1359
require_http_scopes_for_tenant(…, tenant_id)all-of plus an unconditional tenant checkauth.rs:1388
require_grpc_scopesall-of: dead code, no live callerauth.rs:1413
require_grpc_scopes_for_tenantall-of plus a tenant check; the only live gRPC gateauth.rs:1434

There is no axum extractor. Handlers take headers: HeaderMap and call these functions imperatively. That is a deliberate design, and it means a handler that forgets the call has no compile-time backstop.

Failure shapes: an all-of failure is a 403 carrying code: "MISSING_SCOPE" and a missingScopes array; an any-of failure carries missingAnyScope. See chapter 8.

7.4 The route-auth middleware, and its shadow default

classify_route(method, path) (route_auth.rs:75) is a hand-maintained ordered if-chain over axum route templates, first match wins. It returns a RouteAuthContract of { class, scopes, feature_gate }, where scopes is an any-of accepted set.

RouteAuthClass is one of Public, Read, Write, AdminRead, AdminWrite, InternalReplication, FeatureGated (route_auth.rs:34).

The enforcement posture comes from CORECRUXD_ROUTE_AUTH, read once at router build time (route_auth.rs:564):

ValueBehaviour
offThe middleware is pass-through (route_auth.rs:612)
enforceClassified routes require their scopes. Unclassified routes and requests with no matched template fail closed with 403 (route_auth.rs:621)
anything else, including unsetshadow: the default. The contract is evaluated, a route_auth_shadow_mismatch line is logged, and the request continues (route_auth.rs:586, route_auth.rs:685)

Say it plainly: out of the box, the route-auth layer blocks nothing. It is an observation tool. Set CORECRUXD_ROUTE_AUTH=enforce to make it an enforcement tool, and expect the console asset routes to start returning 403 when you do, see §7.5.

One more thing that is documentation rather than enforcement: feature_gate on a contract is never read by the middleware (route_auth.rs:50). Flag gating lives in the handler.

The middleware's decision order (route_auth.rs:607):

  1. Mode off, pass.
  2. Read the method and matched path; a missing template is 403 in enforce, a warning in shadow.
  3. No classification is 403 in enforce, a warning in shadow.
  4. Sync mutual-auth bypass: if sync_mutual_auth is on and the path is one of the five peer routes, the middleware defers entirely; those routes are authorised cryptographically in the handler (route_auth.rs:664). That bypass is an exact-path list; a new /v1/sync/tenants/... route will not inherit it.
  5. Public passes with no auth in every mode.
  6. Otherwise, an any-of scope check against the contract.

7.5 The public routes

Exhaustive, from classify_route (route_auth.rs:79).

Exact-match public: GET /healthz, GET /readyz, GET /metrics, /session, /invocation/verify, GET /v1/openapi.json, GET /v1/version, GET /v1/witness/smoke, POST /v1/sync/handshake/nonce.

Prefix-public: everything under /v1/auth/ (route_auth.rs:94), the seven registered rails: GET /v1/auth/whoami, POST /v1/auth/tailscale/token, and the five device-grant routes start, token, approve, refresh, revoke.

That prefix has no per-route narrowing. Any future /v1/auth/* route is public by default. device/approve is the sensitive one, and it is gated inside the handler by require_http_scopes(&["admin:write"]) (auth_device.rs:292), not by its route class.

The console HTML routes are outside the auth matrix entirely. /, /console, /console-assets/{name}, /console-v2/{name}, /console-3d/{*path} and /activate are registered at console.rs:294 and merged after .with_state, so they are absent from classify_route. The completeness test does not parse console.rs. Consequence: they are served with no auth under off and shadow, and would return 403 under enforce. They are gated only by CORECRUXD_CONSOLE_ENABLED.

The OpenAPI route table carries a parallel, purely documentary auth column (openapi.rs:118): 14 public, 77 read, 60 write, 51 admin-read, 26 read-write, 24 admin-write, 43 feature-gated, 5 admin, 1 internal. It is drift-tested against the router but not against classify_route.

7.6 Token formats accepted

The daemon terminates plain HTTP/1.1 and HTTP/2 over TCP (main.rs:2217). There is no TLS or mTLS anywhere in corecruxd. TLS is an upstream-proxy concern, and every credential below rides plaintext until one is in front.

FormatModeDetail
Dev scope bearerDevScopesX-Corecrux-Scopes: receipts:read,exports:read or Authorization: Bearer receipts:read exports:read. The header wins over the bearer. No signature, no identity, no expiry. Both Bearer and bearer casings accepted (auth.rs:371)
HS256 JWTJwtHs256HS256 only. exp and nbf required-checked with 30s leeway; iss pinned only if CORECRUXD_JWT_ISS is set; aud likewise (auth.rs:514)
JWKS or OIDC JWTJwtJwksAlgorithms from CORECRUXD_JWT_ALGS, default RS256. Supported: RS256/384/512, ES256/384, PS256/384/512. HS256 is not accepted in this mode. The header alg must be in the allowlist before decode. Key sources, first non-empty wins: inline JSON, file, OIDC discovery, URL. JWK types honoured are RSA and EC; use must be sig if present; kid is required or the key is skipped. A single-key JWKS resolves a kid-less token; multi-key without a kid is rejected (auth.rs:698)
MCP agent tokenMCP plane, and HTTP as an opt-in fallbackOpaque, 32 to 256 bytes, charset [A-Za-z0-9._~-]. Stored as BLAKE3 hashes only; lookup is constant-time (agent.rs:178)
OAuth bearerMCP planeHosted-client bearer validated by RFC 7662 introspection, when configured. Grants read-only; writes are rejected before dispatch by a method allowlist (server.rs:173)
Device grant/v1/auth/device/*RFC 8628. Codes TTL 600s, poll interval 5s, user-code alphabet excludes 0, O, 1 and I. Tenant and scopes come from the approver, never the polling client (auth_device.rs:31)
Tailscale identity/v1/auth/whoami, /v1/auth/tailscale/tokenThe Tailscale-User-Login header is trusted only when the peer is loopback or inside CORECRUXD_TS_TRUSTED_PROXY_CIDRS. The allowlist maps the identity to a tenant and scopes, and the mapped tenant is authoritative (auth_rails.rs:47)
Sync peer capability token/v1/sync/* when mutual auth is onFour headers: x-crux-peer-token (base64 capability token, canonical round-trip enforced, 16 KiB cap), x-crux-peer-pubkey, x-crux-peer-nonce (32 bytes, issued by the public nonce route), x-crux-peer-sig. Not TLS (sync.rs:60)
Replication bearerleader to followerCORECRUXD_REPLICATION_AUTH_BEARER, a client-side credential the leader attaches, auto-prefixed with Bearer if absent (grpc.rs:860). The server side of /v1/internal/replication/* is authorised by the ordinary replication:write scope

Tokens the daemon mints: mint_scoped_jwt_from_env signs HS256 with the same secret (loopback_auth.rs:179), with nbf backdated 30 seconds. Issuance rails cap the TTL at 300 seconds (auth_rails.rs:49).

The agent-token HTTP fallback, and its default

Under a JWT mode, if JWT verification fails, the token is retried against the agent registry when CORECRUXD_HTTP_ACCEPT_AGENT_TOKENS is truthy (auth.rs:146). Agent tokens carry no claims, so every accepted token maps to one operator-configured scope and tenant pair:

  • Scopes from CORECRUXD_AGENT_TOKEN_HTTP_SCOPES, default {admin:read, admin:write, facts:write, query:read, sessions:read, sessions:write}, the default includes admin:write.
  • Tenant from CORECRUXD_AGENT_TOKEN_HTTP_TENANT, default *, meaning every tenant.
  • Identity is stamped agent:<name> for both subject and passport id.

Enabling this flag without also setting the scopes and tenant grants a claimless opaque token full admin write across every tenant. The fallback is not wired into the gRPC path.

Not implemented

  • mTLS and client certificates, none. There is no TLS at all in corecruxd.
  • An X-API-Key header, not implemented in this daemon.
  • Cookies, deliberately excluded. The console CORS layer does not allow credentials, because the JWT rides an Authorization header (console.rs:285).

7.7 Headers the daemon reads

HeaderPurpose
Authorization: Bearer <token>A token, or in DevScopes a literal scope list (auth.rs:373)
X-Corecrux-ScopesScope list, comma- or whitespace-separated (auth.rs:363)
X-Corecrux-Passport-IdThe acting passport. See the binding rules below
X-Corecrux-Tenant-IdTenant selector for writes (auth.rs:1151)
x-request-id, traceparentCorrelation, sanitised at ingress

Passport binding rules (auth.rs:1186):

ModeToken claimHeader present?Result
Off or DevScopes-anyHeader taken verbatim, unverified, any caller may assert any passport id
JWTanyabsentThe claim wins
JWTpresent and equalpresentAccepted
JWTanypresent, differs, caller has passport:impersonate or admin:writeThe header wins
JWTabsentpresent403 PASSPORT_HEADER_UNBOUND
JWTpresent, differs, no override scopepresent403 PASSPORT_HEADER_MISMATCH

The header is shape-validated at ingress: 1 to 128 ASCII characters from [A-Za-z0-9._:-], else 400 (ingress.rs:207).

canonical_passport_claim_verified is true only when the passport_id claim was present and non-empty on a cryptographically verified JWT. Legacy aliases and sub are valid identity for ordinary routes but cannot authorise four-eyes boundaries (auth.rs:405).

7.8 Tenant isolation

Tenant authority has exactly one source: the bearer token's claims (auth.rs:442). Never a request body. Off and DevScopes both yield TenantAllow::Any; there is no tenant isolation whatsoever in the dev modes.

The enforcement primitive, require_tenant_allowed (auth.rs:822):

  • Any allows.
  • Only(set) allows only if the set contains the tenant, else 403 TENANT_FORBIDDEN with a tenantId extension.
  • Missing gives 403 TENANT_CLAIM_MISSING.

Any admin:* scope match short-circuits the tenant check entirely on the any-of variant (auth.rs:1379). An admin:read token is cross-tenant by construction.

Write stamping is dark by default

TenantStampMode from CORECRUXD_TENANT_WRITE_STAMP (auth.rs:964): 1, true, on or enforce mean on; shadow or audit mean shadow; anything else, including unset, means off.

ModeWrites stampReads resolve
Off (default)always defaultalways default
Shadowalways default, but logs what On would have donedefault
Onthe resolved tenanta single-tenant token resolves to that tenant, else default

Under On: Missing resolves to default; Any uses the x-corecrux-tenant-id selector or default; a single-tenant token uses that tenant; a multi-tenant token without a selector gets 403 TENANT_SELECTOR_REQUIRED; a selector outside the set gets 403 TENANT_FORBIDDEN.

Blast radius: resolve_write_tenant and resolve_read_tenant have exactly two call sites in the whole daemon, facts.rs:179 and facts.rs:187. Every other write plane stamps default unconditionally. The in-source note at facts.rs:161 states that the MCP write plane has no per-token tenant claim and uniformly stamps default; MCP-plane multi-tenancy is an open follow-up.

Other tenant surfaces: gRPC AppendBatch and ReadStream check req.tenant_id; sync routes check the path segment and reject a / in the tenant id; the console chunk and preview routes are tenant-checked; POST /v1/passports is hard-coded to tenant default (passports.rs:152).

Agent-private fact visibility is a separate mechanism: the __agent::<owner>:: entity prefix, visible to the owning identity only (scope.rs:12). Group-shared private visibility is explicitly not implemented (scope.rs:49).

7.9 The sharp edges, gathered

Everything in this list is stated elsewhere in the chapter. It is repeated here because an operator reviewing a deployment needs one place to look.

#FindingEvidence
1Route-auth is shadow-only by default. Contract violations are logged, never blockedroute_auth.rs:578
2Tenant write-stamping is off by default and reaches only two call sites. Every write lands on tenant defaultauth.rs:986
3/v1/auth/ is public by prefix. Any route added under it is unauthenticated by defaultroute_auth.rs:94
4Console HTML routes are outside the auth matrix, open in shadow, 403 in enforceconsole.rs:294
5require_grpc_scopes is dead code. Only two gRPC RPCs are scope-gated at all; the rest have no scope checkauth.rs:1413
6The agent-token HTTP fallback defaults to admin:write across all tenantsauth.rs:173
7admin:* bypasses tenant checks via the only prefix match in the scope systemauth.rs:1379
8DevScopes is unauthenticated self-assertionauth.rs:857
9An empty MCP agent registry serves the MCP plane anonymouslyserver.rs:337
10Device-grant state is process-local. A restart invalidates every pending grant and refresh credential; there is no external revocationauth_device.rs:29
11feature_gate on a route contract is documentation onlyroute_auth.rs:50
12config.auth_mode silently defaults to DevScopes when parsing fails, safe only because main aborts firstconfig.rs:886
13In Off mode has_scope() returns true for every string, including scopes that do not existauth.rs:1096
14No TLS or mTLS. Every credential rides plaintext unless a proxy terminates TLSmain.rs:2217
15The passport header is unverified in Off and DevScopesauth.rs:1191
16Group-shared private-fact visibility is not implemented, deferred and documentedscope.rs:49
17The sync mutual-auth deferral is an exact-path list. A new sync route will not inherit itroute_auth.rs:551
18CRUX_PASSPORT_REVOCATION fails open on any set value other than 1 or truedispatch.rs:115
19The RCX router never checks revocation. A revoked-but-unexpired token is still authorisedcrux-router/src/lib.rs:120

Sources