Skip to main content

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):

  1. Accepts TCP connection, validates HTTP/2 connection preface.
  2. 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)
  3. Validates JWT against configured OIDC issuer (Dex).
  4. Checks authorization against an in-memory policy map (AuthorizationService::with_test_policies).
  5. Constructs an AuthContext struct and calls inject_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.
  6. Enters tokio::io::copy_bidirectional for 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

  1. 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-supplied x-prism-* headers first. A client sending x-prism-user-id: admin@evil.com in 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.

  2. 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 Go AuthContext treats these headers as trusted truth.

  3. Unauthenticated passthrough: When auth is disabled (no DEX_ISSUER configured), the proxy creates an AuthContext::unauthenticated that sets user_id: "anonymous" and permission: Write. The Go side's HasPermission returns true for 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.

  4. Permission inference is coarse: determine_permission (main.rs:188-205) maps gRPC :path to 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.

  5. No shared proxy state: Each proxy instance has its own in-memory BackendRegistry and AuthorizationService. 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

  1. Define the proxy as the normative authN/authZ enforcement boundary for Prism-native traffic.
  2. Define the namespace as the normative isolation and authorization boundary.
  3. Specify the exact proxy-to-backend auth contract: what is advisory, what is cryptographically verifiable, and what backends must validate.
  4. Define how proxy instances share the state they need to operate as a coherent fleet (signing keys, routing tables, session data).
  5. Catalog all headers in use today and rationalize them into a clean set.
  6. 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:

  1. Parse HTTP/2 HEADERS frame to extract routing key and auth token.
  2. Authenticate the caller (JWT validation).
  3. Authorize the requested namespace operation (Topaz).
  4. Inject auth context into the forwarded HEADERS frame.
  5. 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 HeaderActionReason
x-prism-user-idRename to x-prism-subjectStable subject is not always a human user ID
x-prism-user-emailRemoveEmail is an attribute, not a trust-bearing claim; backends that need it can extract from the backend token
x-prism-scopesRemoveNot populated by proxy today; OAuth scopes belong in the backend token claims
x-prism-token (session middleware)Consolidate into x-prism-tokenSession middleware already reads this; align naming with the normative proof header
x-prism-service-typeRemoveRedundant with x-prism-service-account presence
x-prism-aws-account-idRemoveCloud-specific identity should flow through future federation model
x-prism-aws-role-nameRemoveSame 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 any x-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:

  1. Backend token signing keys: All proxy instances must sign with the same key so that any backend can validate tokens from any proxy.
  2. Routing tables: Namespace-to-backend-address mappings must be consistent across proxies.
  3. Session caching: Optional, for JWT validation result caching across instances.
  4. 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_system for 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 BackendRegistry populated 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:

ModeBackplane StoreUse Case
Single proxy, local devIn-memory (current behavior)Developer workstation
Single proxy, SQLiteInternal KeyValue backed by SQLiteSingle-node production
Multi-proxy, distributedInternal KeyValue backed by Redis/PostgresMulti-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:

  1. Proxy injects the 6 current headers with expected values for an authenticated request.
  2. Proxy injects x-prism-user-id: "anonymous" and x-prism-permission: "write" for unauthenticated requests.
  3. Backend ExtractAuthContext produces the correct struct from those headers.
  4. Backend HasPermission("write") returns true for unauthenticated context (documents current behavior).
  5. 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

  1. Add retain filter in inject_context_headers to strip all client-supplied x-prism-* headers before proxy injection.

  2. Rename AuthContext fields and to_headers output:

    • user_idsubject (output: x-prism-subject)
    • Remove user_email field and x-prism-user-email header
    • Remove scopes field and x-prism-scopes header
    • Add subject_type field ("user" or "service")
  3. Update from_claims to construct issuer-scoped subject identifiers (e.g., format!("oidc:dex|{}", claims.sub)).

  4. Update unauthenticated to set subject_type: "user" and permission: Read instead of Write.

Files changed: pkg/plugin/auth_context.go

  1. Rename header constants to match new names:

    • HeaderUserIDHeaderSubject (x-prism-subject)
    • Remove HeaderUserEmail, HeaderScopes
    • Add HeaderToken (x-prism-token)
    • Add HeaderSubjectType (x-prism-subject-type)
  2. Update AuthContext struct: remove UserEmail, Scopes; rename UserIDSubject; add SubjectType.

  3. Add BackendToken field. Update ExtractAuthContext to extract x-prism-token.

  4. Add ValidateBackendToken(backendToken string) (*TokenClaims, error) function that validates the backend token JWT signature and claims.

  5. Update HasPermission to require IsAuthenticated == true and a valid backend token for production mode. Remove the "proxy allowed it" short-circuit.

Files changed: pkg/plugin/session_middleware.go

  1. Update extractJWT to also check x-prism-token header (already does this, just ensure alignment with new naming).
  2. Add backend token validation step after JWT extraction.

Files changed: pkg/plugin/auth_interceptor.go

  1. 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

  1. Remove ServiceTypeHeader, AWSAccountIDHeader, AWSRoleNameHeader.
  2. Keep ServiceNameHeader, ServiceNamespaceHeader, ServiceClusterHeader, ServiceAccountHeader.
  3. Rename constants to use the x-prism-service-* prefix consistently (already correct).

Phase 2: Backend Token Issuance

Files changed: prism-proxy/src/main.rs

  1. 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
  2. Add mint_backend_token function that creates a signed JWT with the claims specified in the Backend Token Claims section.

  3. Add backend_token field to AuthContext. Set it in from_claims and unauthenticated.

  4. Update to_headers to include x-prism-token: Bearer <backend-token>.

New file: prism-proxy/src/backplane.rs

  1. BackplaneClient trait with methods:

    • get_signing_key() -> Ed25519KeyPair
    • get_routing_table() -> HashMap<String, String>
    • watch_routing_table() -> WatchStream
    • store_signing_key(key) -> Result<()>
  2. InMemoryBackplane implementation (current behavior, no external dependencies).

  3. KeyValueBackplane implementation (uses Prism KeyValue client against __prism_system namespace).

New file: prism-proxy/src/backend_token.rs

  1. BackendTokenClaims struct with iss, sub, aud, ns, act, typ, exp, iat, jti.
  2. mint_token(key, claims) -> String
  3. validate_token(public_key, token) -> Result<BackendTokenClaims>

Files changed: pkg/plugin/auth_context.go

  1. Add ValidateBackendToken function that validates Ed25519-signed JWTs.
  2. 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

  1. Test: proxy strips client-supplied x-prism-* headers.
  2. Test: proxy injects x-prism-token with valid backend token.
  3. Test: backend rejects request without x-prism-token.
  4. Test: backend rejects request with invalid backend token (wrong key, expired, wrong audience).
  5. Test: backend rejects request with spoofed x-prism-subject that doesn't match backend token claims.
  6. Test: unauthenticated context has subject_type: "user" and permission: "read".
  7. Test: authenticated context has issuer-scoped subject identifier.

Files changed: existing integration tests

  1. Update any tests that reference removed headers (x-prism-user-id, x-prism-user-email, x-prism-scopes).
  2. Update any tests that depend on HasPermission returning true for unauthenticated requests.

Phase 4: Proxy Backplane Integration

Files changed: prism-proxy/src/main.rs

  1. Replace BackendRegistry::new() with BackplaneClient::get_routing_table().
  2. Add startup sequence: connect to backplane → load signing key → load routing table → start accepting connections.
  3. Add routing table watch: update backend registry when backplane notifies of changes.

Files changed: prism-proxy/src/backplane.rs

  1. Implement KeyValueBackplane using Prism KeyValue client.
  2. 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

  1. 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?

  2. 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 aud claim is structured.

  3. 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.

  4. 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.

  5. Token audience granularity: Should aud be pattern-type/namespace (e.g., keyvalue/team-alpha) or just namespace (e.g., team-alpha)? Namespace-level is simpler but allows token reuse across pattern types within the same namespace.

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