RFC-063: Normative Proxy Auth Contract and Namespace Security Model
Summary
This RFC defines the normative authentication and authorization behavior of the Prism proxy, the namespace-level security contract that backends rely on, and the specific code changes required to get there from the current implementation.
The prior drafts bundled federation, provisioning, and service vendoring into one proposal. Those topics are deferred to follow-on RFCs (064, 065, 066). This RFC stays focused on the platform invariant: Prism is a namespace-oriented data access gateway whose proxy is the authN/authZ enforcement point and whose backend contract is explicit, minimal, and verifiable.
Current State
What Exists Today
The proxy and backend have an informal auth context propagation mechanism. Understanding it is prerequisite to defining the target state.
Rust proxy (prism-proxy/src/main.rs):
- Accepts TCP connection, validates HTTP/2 connection preface.
- Decodes the first HEADERS frame via HPACK to extract:
x-prism-namespace(required, used for routing)authorization(optional, Bearer JWT):path(optional, used to infer read vs write permission)
- Validates JWT against configured OIDC issuer (Dex).
- Checks authorization against an in-memory policy map (
AuthorizationService::with_test_policies). - Constructs an
AuthContextstruct and callsinject_context_headers, which decodes the original HEADERS frame, adds proxy-injected headers to the header map, re-encodes via HPACK, and writes the modified frame to the backend TCP stream. - Enters
tokio::io::copy_bidirectionalfor zero-copy forwarding of all subsequent frames.
Proxy-injected headers (from AuthContext::to_headers at main.rs:90-113):
x-prism-trace-id (UUID per connection)
x-prism-user-id (JWT sub claim, or "anonymous")
x-prism-user-email (JWT email claim, if present)
x-prism-namespace (from client request)
x-prism-permission ("read" or "write", inferred from :path)
x-prism-scopes (comma-separated, currently always empty)
Go backend (pkg/plugin/auth_context.go):
ExtractAuthContext reads the same 6 headers from gRPC metadata. Pattern handlers call it to get an AuthContext struct with UserID, UserEmail, Namespace, Permission, Scopes, TraceID, and IsAuthenticated.
Service identity (pkg/authz/service_identity.go):
A parallel set of 7 x-prism-service-* headers for workload identity (Kubernetes, AWS, static). These are read by ExtractServiceIdentity but are not injected by the Rust proxy today. They appear to be intended for service-to-service calls where a Go service is the caller rather than a human user.
Session middleware (pkg/plugin/session_middleware.go):
SessionMiddleware reads JWT from either authorization: Bearer <token> or x-prism-token: <token>, caches sessions in memory, and extracts user identity from the JWT claims directly (not from the proxy-injected headers).
Security Gaps in the Current Implementation
-
No header stripping:
inject_context_headers(main.rs:210-258) decodes the client's HEADERS frame, adds proxy headers to the map, and re-encodes. It does NOT remove client-suppliedx-prism-*headers first. A client sendingx-prism-user-id: admin@evil.comin its original request will have that value survive into the re-encoded frame, where it will be overwritten only if the proxy also injects a header with the same key. The behavior depends on insertion order and HPACK encoder state, making it unreliable and insecure. -
No backend token: The proxy injects advisory headers but provides no cryptographically verifiable proof. Any system that can reach the backend directly can forge
x-prism-*headers. The GoAuthContexttreats these headers as trusted truth. -
Unauthenticated passthrough: When auth is disabled (no
DEX_ISSUERconfigured), the proxy creates anAuthContext::unauthenticatedthat setsuser_id: "anonymous"andpermission: Write. The Go side'sHasPermissionreturnstruefor unauthenticated requests because "if we got here, proxy allowed it" (auth_context.go:121-124). This is a reasonable local-dev shortcut but must be locked down for production. -
Permission inference is coarse:
determine_permission(main.rs:188-205) maps gRPC:pathto read or write based on method name substring matching. This is the maximum granularity available to the transparent proxy without decoding protobuf, and it is sufficient for the namespace-level read/write/admin model. -
No shared proxy state: Each proxy instance has its own in-memory
BackendRegistryandAuthorizationService. There is no mechanism for proxy instances to share routing tables, signing keys, session state, or policy updates. All state is configured at startup from environment variables or hardcoded test policies.
Project Anchors
- Prism is a data access gateway with stable client APIs and namespace-based isolation (
MISSION.md). - The Rust proxy is a transparent HTTP/2 proxy for Prism-native traffic: it parses HEADERS for routing and auth, then forwards raw frames (ADR-059).
- Authorization is Topaz-based and namespace-oriented (ADR-050).
- The proxy is the primary fast-rejection point for authN/authZ. Backend layers may perform additional verification for defense in depth (RFC-062).
Goals
- Define the proxy as the normative authN/authZ enforcement boundary for Prism-native traffic.
- Define the namespace as the normative isolation and authorization boundary.
- Specify the exact proxy-to-backend auth contract: what is advisory, what is cryptographically verifiable, and what backends must validate.
- Define how proxy instances share the state they need to operate as a coherent fleet (signing keys, routing tables, session data).
- Catalog all headers in use today and rationalize them into a clean set.
- Identify the specific code changes required, grounded in the existing implementation.
Non-Goals
- Redesigning Prism into a generic API gateway.
- Superseding ADR-059.
- Defining SAML, SCIM, or self-service vendoring (see RFC-064, RFC-065, RFC-066).
Decision
1. Proxy Remains Transparent for Prism-Native Traffic
The proxy continues to operate per ADR-059:
- Parse HTTP/2 HEADERS frame to extract routing key and auth token.
- Authenticate the caller (JWT validation).
- Authorize the requested namespace operation (Topaz).
- Inject auth context into the forwarded HEADERS frame.
- Forward all subsequent frames as raw bytes.
This RFC does not authorize path-based REST routing or protocol-aware dispatch in the proxy data plane.
2. Header Set Rationalization
The current codebase has two independent header sets that serve different identity types. This RFC consolidates them into a single normative set.
Normative Header Set
All headers use the x-prism- prefix. The proxy MUST strip any client-supplied header matching this prefix before injection.
Proof-bearing (backends MUST validate the backend token and reject requests without it):
x-prism-token Bearer <backend-token>
Advisory context (ergonomic copies of claims from the backend token; backends MUST NOT trust these without validating x-prism-token):
x-prism-trace-id UUID per connection
x-prism-subject Stable Prism subject identifier (e.g., "oidc:auth0|abc123")
x-prism-namespace Namespace from client request
x-prism-permission "read" or "write" (inferred from :path by proxy)
x-prism-subject-type "user" or "service"
Service identity (advisory, present only for service-to-service calls):
x-prism-service-name Calling service name
x-prism-service-ns Calling service namespace
x-prism-service-cluster Cluster or environment
x-prism-service-account K8s ServiceAccount (if applicable)
Headers Removed or Renamed
| Current Header | Action | Reason |
|---|---|---|
x-prism-user-id | Rename to x-prism-subject | Stable subject is not always a human user ID |
x-prism-user-email | Remove | Email is an attribute, not a trust-bearing claim; backends that need it can extract from the backend token |
x-prism-scopes | Remove | Not populated by proxy today; OAuth scopes belong in the backend token claims |
x-prism-token (session middleware) | Consolidate into x-prism-token | Session middleware already reads this; align naming with the normative proof header |
x-prism-service-type | Remove | Redundant with x-prism-service-account presence |
x-prism-aws-account-id | Remove | Cloud-specific identity should flow through future federation model |
x-prism-aws-role-name | Remove | Same as above |
Resulting Header Surface
x-prism-token proof (backend token)
x-prism-trace-id advisory
x-prism-subject advisory
x-prism-namespace advisory
x-prism-permission advisory
x-prism-subject-type advisory
x-prism-service-name advisory (service callers only)
x-prism-service-ns advisory (service callers only)
x-prism-service-cluster advisory (service callers only)
x-prism-service-account advisory (service callers only)
10 headers total. 1 proof-bearing. 9 advisory. Zero ambiguity about what to trust.
3. Proxy-to-Backend Contract
The contract is simple: the backend token is the proof-bearing artifact. Everything else is advisory.
Normative Backend Trust Rules
The proxy:
- MUST strip all client-supplied
x-prism-*headers from the HEADERS frame before injecting its own. - MUST inject its own canonical auth context after successful authentication and authorization.
- MUST mint a short-lived backend token and inject it as
x-prism-token. - MUST present a proxy identity over mTLS to the backend (future; today the backend is on localhost).
Backends:
- MUST validate the Prism-issued backend token (
x-prism-token) before trusting anyx-prism-*claim. - MUST reject requests that lack a valid backend token.
- MUST treat all other
x-prism-*headers as untrusted advisory data. - MUST reject direct client access that bypasses the proxy.
Backend Token Claims
iss "prism-proxy/<instance-id>"
sub stable Prism subject (e.g., "oidc:auth0|abc123", "svc:k8s:payments/api")
aud backend audience (e.g., "keyvalue/default" or "pubsub/events")
ns namespace
act "read" or "write"
typ "user" or "service"
exp expiry (recommended 60s)
iat issued-at
jti unique token identifier
The act claim is coarse-grained (read or write) because that is the maximum the transparent proxy can infer from the gRPC :path without protocol-aware decoding. This is intentional. Fine-grained action authorization is the backend's responsibility.
The aud claim is scoped to the namespace and backend type (e.g., keyvalue/team-alpha). This restricts token reuse across backends.
Authorization Granularity and Transparency
RFC-063 is intentionally compatible with ADR-059's transparent proxy model.
For Prism-native traffic:
- authentication happens at session establishment
- authorization should happen at session establishment, stream establishment, or from stable request metadata the proxy already has
- the proxy MUST NOT decode arbitrary pattern payloads just to satisfy this RFC
If a future feature requires payload-aware authorization or per-message mediation, that feature MUST explicitly document how it narrows or supersedes ADR-059 before implementation.
Header Stripping Implementation
The current inject_context_headers function (main.rs:210-258) does not strip client headers. The fix:
fn inject_context_headers(original_frame, context):
headers = hpack_decode(original_frame.payload)
// Strip ALL x-prism-* headers from client input
headers.retain(|name, _value| !name.starts_with("x-prism-"))
// Inject proxy-owned headers
for (key, value) in context.to_headers():
headers.insert(key, value)
// Inject backend token
headers.insert("x-prism-token", format!("Bearer {}", context.backend_token))
encoded = hpack_encode(headers)
return new_headers_frame(encoded)
This is a small change to the existing function. The header map is already being decoded and re-encoded. Adding the retain filter before injection closes the spoofing vulnerability.
4. Stable Subject Identifiers
Prism subject identifiers MUST be issuer-scoped and stable:
oidc:<issuer-identifier>|<user-id> e.g., "oidc:auth0|abc123"
oidc:dex|<dex-user-id> e.g., "oidc:dex|alice"
saml:<idp-slug>|<name-id> e.g., "saml:okta|00u123..."
svc:<type>:<namespace>/<name> e.g., "svc:k8s:payments/order-api"
Email MAY appear as an attribute in the backend token claims but MUST NOT be the canonical subject key. Cross-provider identity linking is deferred to RFC-064.
5. Proxy Shared State via Prism Data Plane
Proxy instances need shared state for:
- Backend token signing keys: All proxy instances must sign with the same key so that any backend can validate tokens from any proxy.
- Routing tables: Namespace-to-backend-address mappings must be consistent across proxies.
- Session caching: Optional, for JWT validation result caching across instances.
- Policy state: Topaz policy snapshots or authorization decision caches.
Rather than introducing a separate infrastructure dependency (Redis, etcd, database), the proxy fleet can use Prism's own data serving architecture. Each proxy instance is both a client of and a participant in the Prism data plane.
Proxy Backplane
The proxy backplane is a dedicated internal namespace (e.g., __prism_system) that stores shared proxy state using the KeyValue pattern backed by an internal store (SQLite for single-node, Redis or similar for distributed deployments).
┌─────────────────────────────────────────────────────────┐
│ Proxy Fleet │
│ │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ Proxy 1 │ │ Proxy 2 │ │ Proxy 3 │ │
│ │ │ │ │ │ │ │
│ │ Backplane│ │ Backplane│ │ Backplane│ │
│ │ Client │ │ Client │ │ Client │ │
│ └────┬─────┘ └────┬─────┘ └────┬─────┘ │
│ │ │ │ │
│ └──────────────┼──────────────┘ │
│ │ │
│ ┌───────▼────────┐ │
│ │ __prism_system│ │
│ │ namespace │ │
│ │ │ │
│ │ KeyValue: │ │
│ │ ┌───────────┐ │ │
│ │ │signing-key│ │ ← Ed25519 key pair │
│ │ │routing/* │ │ ← ns → backend addr │
│ │ │sessions/* │ │ ← JWT validation cache │
│ │ │policies/* │ │ ← Topaz policy refs │
│ │ └───────────┘ │ │
│ └────────────────┘ │
└─────────────────────────────────────────────────────────┘
Key management:
- On first startup, a proxy instance checks
__prism_systemfor an existing signing key. - If none exists and the instance is configured as primary, it generates an Ed25519 key pair, stores the private key in the system namespace, and publishes the public key for backends to retrieve.
- If a key exists, the proxy retrieves and uses it.
- Key rotation: a new key is generated, both keys are valid during a grace period, old key is deprecated after TTL expiry. Backends fetch the public key set on startup and refresh periodically.
Routing table:
- Currently each proxy has an in-memory
BackendRegistrypopulated at startup (main.rs:647-672). - The backplane stores namespace-to-address mappings in the system namespace.
- Admin API writes propagate to the system namespace, and proxies watch for changes.
- Single-node deployments continue to use the in-memory registry (no external dependency).
Deployment modes:
| Mode | Backplane Store | Use Case |
|---|---|---|
| Single proxy, local dev | In-memory (current behavior) | Developer workstation |
| Single proxy, SQLite | Internal KeyValue backed by SQLite | Single-node production |
| Multi-proxy, distributed | Internal KeyValue backed by Redis/Postgres | Multi-node production |
The backplane is an optimization of the existing architecture, not a new infrastructure requirement. Single-proxy deployments work exactly as they do today.
6. Namespace Security Model
Each namespace defines:
- owning principals
- permitted reader/writer/admin relationships (via Topaz)
- allowed backend or adapter audiences
- audit requirements
The namespace is the security tenancy boundary and the unit of policy attachment. A future integration is valid only if its access can be expressed as: "subject X may perform action Y within namespace Z against integration surface W."
7. Launcher Role in the Trust Chain
The data flow is: Client → Proxy → [Launcher →] Pattern Process.
In the current implementation, the proxy connects directly to the pattern process backend via TCP (main.rs:472). The launcher manages pattern process lifecycles but is not in the data path for proxied traffic.
This RFC assumes the proxy-to-backend connection is direct (proxy to pattern process). If a future architecture introduces the launcher as a data-plane intermediary, the launcher must either:
- Pass through the backend token unchanged, OR
- Validate the proxy's backend token, re-mint its own token with the same claims, and forward to the pattern process.
The launcher is not in the data plane today. If it enters the data plane, its trust role must be specified in a separate decision.
Preparatory Work
The specific code changes in Phases 1-4 are the main implementation. Before starting those, there are four independently shippable preparatory changes that de-risk the larger effort. Each has zero external dependencies, can land as its own PR, and does not change behavior.
Prep 1: Header Stripping (Security Fix)
The inject_context_headers function in prism-proxy/src/main.rs:210-258 already decodes and re-encodes HPACK headers. Adding a retain filter before injection closes a real header-spoofing vulnerability.
Touches: 1 file, 1 function. No Go-side changes needed. Existing tests pass because they do not send spoofed headers.
Risk: Minimal. The function already intercepts and modifies the header map. Adding a filter before insertion is additive.
PR scope: prism-proxy/src/main.rs only.
Prep 2: Rust Header Constants and AuthContext Refactor
The Rust side has header names scattered as string literals across main.rs, transparent_proxy_v2.rs, and proxy_integration_runner.rs. Extract them into a constants module (prism-proxy/src/headers.rs). Move AuthContext, from_claims, unauthenticated, and to_headers out of main.rs into prism-proxy/src/auth_context.rs.
This is a mechanical refactoring that makes the header rename in Phase 1 a single-point-of-truth change instead of a find-and-replace across 7 files.
Touches: 2 new files (headers.rs, auth_context.rs), 3 modified files (main.rs, transparent_proxy_v2.rs, proxy_integration_runner.rs). Zero behavior change. Tests pass unchanged.
Risk: Minimal. Pure code movement with no logic changes.
PR scope: prism-proxy/ only.
Prep 3: Backend Token and Backplane Modules (Library Code)
Create prism-proxy/src/backend_token.rs with mint_token, validate_token, and BackendTokenClaims. Create prism-proxy/src/backplane.rs with the BackplaneClient trait and InMemoryBackplane wrapping the existing BackendRegistry.
Neither module is wired into the data plane yet. They are pure library code with unit tests. The InMemoryBackplane implementation is a thin wrapper around the existing Arc<RwLock<HashMap<String, String>>> pattern.
This also establishes the __prism_system namespace concept as a module boundary, even though the KeyValue-backed implementation comes in Phase 4.
Touches: 2 new files. No existing files changed. Zero behavior change.
Risk: Minimal. New code that is not yet called from the data plane. Tests are self-contained.
PR scope: prism-proxy/ only.
Prep 4: Auth Contract Conformance Tests
Write integration tests that document what the proxy injects today before changing any of it. These tests will fail immediately once the header rename in Phase 1 starts, making that change self-verifying.
Key test cases:
- Proxy injects the 6 current headers with expected values for an authenticated request.
- Proxy injects
x-prism-user-id: "anonymous"andx-prism-permission: "write"for unauthenticated requests. - Backend
ExtractAuthContextproduces the correct struct from those headers. - Backend
HasPermission("write")returns true for unauthenticated context (documents current behavior). - Proxy does NOT strip client-supplied
x-prism-*headers (documents the current vulnerability; this test will flip to assert stripping once Prep 1 lands).
Touches: New test file tests/integration/auth_contract_test.go. No existing files changed.
Risk: Minimal. New tests that document current behavior. Test 5 is intentionally failing until Prep 1 lands, or can be written to pass by asserting current (insecure) behavior and updated in Prep 1.
PR scope: tests/ only.
Prep Work Sequencing
Prep 1 (header stripping) ← standalone security fix, ships immediately
│
├── Prep 2 (Rust constants) ← can run in parallel with Prep 3
├── Prep 3 (token modules) ← can run in parallel with Prep 2
└── Prep 4 (conformance) ← can start immediately, gates Phase 1
│
▼
Phase 1-4 (RFC-063 main implementation)
Prep 1 is the most urgent because it fixes a real vulnerability. Preps 2 and 3 can be done in parallel since they touch different files. Prep 4 documents current behavior and provides a safety net for the Phase 1 header rename. After all four land, the actual Phase 1-4 implementation becomes tightly scoped integration work instead of a cross-cutting refactor.
Specific Code Changes
Phase 1: Secure the Header Channel
Files changed: prism-proxy/src/main.rs
-
Add
retainfilter ininject_context_headersto strip all client-suppliedx-prism-*headers before proxy injection. -
Rename
AuthContextfields andto_headersoutput:user_id→subject(output:x-prism-subject)- Remove
user_emailfield andx-prism-user-emailheader - Remove
scopesfield andx-prism-scopesheader - Add
subject_typefield ("user"or"service")
-
Update
from_claimsto construct issuer-scoped subject identifiers (e.g.,format!("oidc:dex|{}", claims.sub)). -
Update
unauthenticatedto setsubject_type: "user"andpermission: Readinstead ofWrite.
Files changed: pkg/plugin/auth_context.go
-
Rename header constants to match new names:
HeaderUserID→HeaderSubject(x-prism-subject)- Remove
HeaderUserEmail,HeaderScopes - Add
HeaderToken(x-prism-token) - Add
HeaderSubjectType(x-prism-subject-type)
-
Update
AuthContextstruct: removeUserEmail,Scopes; renameUserID→Subject; addSubjectType. -
Add
BackendTokenfield. UpdateExtractAuthContextto extractx-prism-token. -
Add
ValidateBackendToken(backendToken string) (*TokenClaims, error)function that validates the backend token JWT signature and claims. -
Update
HasPermissionto requireIsAuthenticated == trueand a valid backend token for production mode. Remove the "proxy allowed it" short-circuit.
Files changed: pkg/plugin/session_middleware.go
- Update
extractJWTto also checkx-prism-tokenheader (already does this, just ensure alignment with new naming). - Add backend token validation step after JWT extraction.
Files changed: pkg/plugin/auth_interceptor.go
- Add backend token validation in the unary and stream interceptors. Reject requests with invalid or missing backend tokens.
Files changed: pkg/authz/service_identity.go
- Remove
ServiceTypeHeader,AWSAccountIDHeader,AWSRoleNameHeader. - Keep
ServiceNameHeader,ServiceNamespaceHeader,ServiceClusterHeader,ServiceAccountHeader. - Rename constants to use the
x-prism-service-*prefix consistently (already correct).
Phase 2: Backend Token Issuance
Files changed: prism-proxy/src/main.rs
-
Add Ed25519 key pair loading/generation to proxy startup. Key source:
- File path from environment variable (
PRISM_SIGNING_KEY_PATH) - In-memory generation if no path configured (single-proxy dev mode)
- Future: backplane system namespace
- File path from environment variable (
-
Add
mint_backend_tokenfunction that creates a signed JWT with the claims specified in the Backend Token Claims section. -
Add
backend_tokenfield toAuthContext. Set it infrom_claimsandunauthenticated. -
Update
to_headersto includex-prism-token: Bearer <backend-token>.
New file: prism-proxy/src/backplane.rs
-
BackplaneClienttrait with methods:get_signing_key() -> Ed25519KeyPairget_routing_table() -> HashMap<String, String>watch_routing_table() -> WatchStreamstore_signing_key(key) -> Result<()>
-
InMemoryBackplaneimplementation (current behavior, no external dependencies). -
KeyValueBackplaneimplementation (uses Prism KeyValue client against__prism_systemnamespace).
New file: prism-proxy/src/backend_token.rs
BackendTokenClaimsstruct withiss,sub,aud,ns,act,typ,exp,iat,jti.mint_token(key, claims) -> Stringvalidate_token(public_key, token) -> Result<BackendTokenClaims>
Files changed: pkg/plugin/auth_context.go
- Add
ValidateBackendTokenfunction that validates Ed25519-signed JWTs. - Public key source: file path from environment variable (
PRISM_VERIFY_KEY_PATH), with future backplane support.
Phase 3: Conformance Tests
New file: tests/integration/backend_token_test.go
- Test: proxy strips client-supplied
x-prism-*headers. - Test: proxy injects
x-prism-tokenwith valid backend token. - Test: backend rejects request without
x-prism-token. - Test: backend rejects request with invalid backend token (wrong key, expired, wrong audience).
- Test: backend rejects request with spoofed
x-prism-subjectthat doesn't match backend token claims. - Test: unauthenticated context has
subject_type: "user"andpermission: "read". - Test: authenticated context has issuer-scoped subject identifier.
Files changed: existing integration tests
- Update any tests that reference removed headers (
x-prism-user-id,x-prism-user-email,x-prism-scopes). - Update any tests that depend on
HasPermissionreturning true for unauthenticated requests.
Phase 4: Proxy Backplane Integration
Files changed: prism-proxy/src/main.rs
- Replace
BackendRegistry::new()withBackplaneClient::get_routing_table(). - Add startup sequence: connect to backplane → load signing key → load routing table → start accepting connections.
- Add routing table watch: update backend registry when backplane notifies of changes.
Files changed: prism-proxy/src/backplane.rs
- Implement
KeyValueBackplaneusing Prism KeyValue client. - Add key rotation logic: generate new key, publish both during grace period, retire old key.
Deferred Work and RFC Split
RFC-063 (This RFC)
- Normative proxy behavior
- Proxy-to-backend auth contract
- Header set rationalization
- Backend token issuance and validation
- Proxy shared state (backplane)
- Namespace security model
RFC-064: Federation Profile
- OIDC provider model (Auth0, Okta, Azure AD, Google, Dex)
- SAML 2.0 support
- Session bridging rules
- Multi-IdP selection
- Account-linking rules based on stable subject identifiers
Key rule: must build on RFC-063's stable subject model. Must not use email equality as the trust basis.
RFC-065: Provisioning and Directory Sync
- SCIM profile
- Provider-scoped directory objects
- Namespace binding model
- Conflict resolution and idempotency
Key rule: SCIM must not directly mutate shared authorization state without provider and namespace scoping.
RFC-066: Integrated Services and Adapters
- Digital twins and external API integrations
- Integration class taxonomy (
pattern-backed,adapter-backed,edge-integrated) - Registration, ownership proof, health semantics
- SSRF, egress, and network isolation controls
Key rule: must define approved integration classes. No arbitrary tenant-supplied endpoints.
Alternatives Considered
Alternative A: Use Existing x-prism-* Headers as Trusted
Rejected. Client header spoofing is a demonstrated vulnerability in the current code. Advisory headers without cryptographic proof are insufficient for production.
Alternative B: mTLS-Only Trust (No Backend Token)
Rejected. mTLS authenticates the proxy-to-backend channel but does not carry per-request authorization context (who the user is, what namespace, what permission). A backend token is needed to convey that context verifiably.
Alternative C: External State Store (Redis, etcd) for Proxy Backplane
Rejected as default. Introduces an infrastructure dependency that single-node deployments should not need. Using Prism's own KeyValue pattern for the system namespace keeps the dependency optional and self-hosted.
Alternative D: Generic API Gateway Approach
Rejected. Prism's transparent proxy model (ADR-059) is a core architectural advantage. Protocol-aware routing in the proxy would sacrifice zero-copy forwarding and protocol decoupling for limited benefit.
Security Considerations
Header Spoofing
Client-supplied x-prism-* headers are a spoofing vector. Phase 1 closes this by stripping all x-prism-* headers before proxy injection.
Backend Token Replay
Short-lived tokens (60s TTL) plus audience restriction limit replay. mTLS on the proxy-to-backend channel provides additional channel binding. Tokens are not revocable within their TTL; if instant revocation is needed, future work can add a token deny-list in the backplane.
Signing Key Compromise
If a signing key is compromised, an attacker can mint valid backend tokens. Mitigation: keys are stored in the backplane (not on disk in multi-node), key rotation is supported, and the grace period limits exposure. Key material never leaves the Prism trust boundary.
Unauthenticated Mode
Today, AuthContext::unauthenticated grants Write permission. Phase 1 changes this to Read and the Go side's HasPermission will require a valid backend token for write operations. Unauthenticated mode is for local development only and should be disabled in production via configuration.
Identity Linking
Cross-IdP identity linking is deferred to RFC-064. The stable subject format (oidc:<issuer>|<id>) prevents accidental unification.
Open Questions
-
Backend token format: Ed25519-signed JWT is proposed for compact size and fast verification. Are there reasons to prefer HMAC-SHA256 with a shared secret instead?
-
Launcher data plane role: If the launcher enters the data path (proxy → launcher → pattern), does it validate and re-mint tokens, or pass through? This is a future decision but affects how the backend token
audclaim is structured. -
Service identity injection: Who injects service identity headers (
x-prism-service-*) today? The Rust proxy does not inject them. Are they intended for sidecar-to-sidecar calls? This should be clarified before Phase 1. -
Backplane bootstrapping: When multiple proxies start simultaneously against an empty system namespace, how is the initial signing key created without a race? Options: designated primary, distributed lock via KeyValue, or manual key seed.
-
Token audience granularity: Should
audbepattern-type/namespace(e.g.,keyvalue/team-alpha) or justnamespace(e.g.,team-alpha)? Namespace-level is simpler but allows token reuse across pattern types within the same namespace.
Related Documents
MISSION.md- ADR-007: Authentication and Authorization
- ADR-050: Topaz for Policy-Based Authorization
- ADR-059: Transparent HTTP/2 Proxy
- RFC-019: Plugin SDK Authorization Layer
- RFC-056: Unified Configuration Model
- RFC-062: Unified Authentication and Session Management
- RFC-064: Federation Profile (proposed)
- RFC-065: SCIM Provisioning (proposed)
- RFC-066: Integrated Services (proposed)
Revision History
- 2026-04-13: Initial draft
- 2026-04-13: Adversarial-review rewrite narrowing to normative proxy and namespace contract
- 2026-04-13: Ground in existing code, rationalize header set, add proxy backplane, specify concrete code changes by file
- 2026-04-13: Add preparatory work section (4 independently shippable PRs), update implementation plan sequencing