Decision Gate Authentication, Policy, and Disclosure Architecture

Authentication, authorization, and disclosure policy.

On this page Current section: Table of Contents

Audience: Engineers implementing or reviewing MCP authentication, authorization, and error disclosure behavior.


Table of Contents

  1. Executive Overview
  2. Request Context and Identity
  3. Authentication Modes
  4. Tool Visibility and Call Policy
  5. Namespace Authorization (Pluggable)
  6. Usage Metering and Quotas (Pluggable)
  7. Auth Audit Events
  8. Disclosure Posture (JSON-RPC and HTTP)
  9. Rate Limiting and Overload Responses
  10. File-by-File Implementation Anchors

Executive Overview

Decision Gate MCP enforces strict, fail-closed authentication and separately owned authorization policy. Authentication is transport-aware (stdio, HTTP, SSE), configured via server.auth, and owned by RequestAuthenticator. RequestAuthenticator receives request identity only; it does not receive a tool action and cannot become a second tool-policy owner. ToolVisibilityResolver is the sole owner of static tool discovery and invocation policy, configured by server.tools. A separate, pluggable namespace authorization layer enforces namespace scoping before tool execution. Every supported semantic call passes its exact namespace into the authorization seam. The initial profile has no provider-scoped tool call or standalone evidence-query authority. Caller evidence enters only through the scenario precheck/evaluation carriers; source and assurance claims are constructed by the server, not accepted from caller fields. Named time, immutable environment, and rooted-document acquisition use operator-registered local authorities and the scenario-owned binding relation rather than provider-scoped RBAC. Auth decisions emit structured audit events, and request failures are mapped to stable JSON-RPC error codes and HTTP status codes for deterministic disclosure and metrics labeling. Internally, MCP now retains catalog-backed exact symbolic/numeric identity plus projection-safe public codes for audit and telemetry while keeping the external JSON-RPC envelope projection-first. The generated DG authority artifacts under Docs/generated/decision-gate/ freeze those OSS error and telemetry semantics in machine-readable form so private verification tooling consumes DG-owned contracts rather than inferring them ad hoc from implementation details. F:crates/decision-gate-mcp/src/auth.rs L293-L372 F:crates/decision-gate-mcp/src/tools/policy.rs L266-L281 F:crates/decision-gate-mcp/src/tools/policy.rs L36-L72 F:crates/decision-gate-mcp/src/server.rs L1984-L2017


Scope and Non-Goals

Scope is MCP ingress identity, authentication, authorization, namespace authorization hooks, disclosure, audit, and overload handling. This document does not define namespace placement, evaluator ownership, fencing, or replication; authorization scope is distinct from those future control-plane responsibilities.

Layer Responsibilities

  • Ingress normalizes untrusted transport identity and request metadata.
  • Authentication establishes the principal context.
  • ToolVisibilityResolver and namespace authorization decide whether a request may execute.
  • Disclosure and audit project the result without weakening the decision.

Request Context and Identity

Request Context

Incoming requests are normalized into a RequestContext that records transport, peer IP, auth header, and optional authoritative/caller request identity metadata. For HTTP/SSE transports, the sole application credential carrier is the Authorization header. Caller-asserted certificate subjects are not an admitted identity channel. Caller-provided provenance arrives via x-caller-request-id and is treated as unsafe input: it is strictly validated and rejected if invalid. The server always issues its own canonical UUIDv7 x-request-id and returns it on responses, providing a stable, auditable identifier even when caller provenance is missing. MCP additionally tracks the per-message JSON-RPC id as an internal request-context field, but that protocol identifier is kept separate from both caller provenance and the authoritative server request ID. It is not returned as an HTTP/SSE header and it does not replace audit or telemetry request identity channels. F:crates/decision-gate-mcp/src/auth.rs L82-L173 F:crates/decision-gate-mcp/src/server.rs L993-L1072 F:crates/decision-gate-mcp/src/server.rs L1648-L1734

Principal Identity

AuthContext is a sealed tagged principal carrier. Its variants bind a transport-derived local subject or canonical bearer-token digest directly to the matching authentication method; callers cannot construct mismatched option fields or an anonymous/malformed “authenticated” context. Local admission assigns the exact stdio or loopback subject from transport. Bearer admission stores the typed sha256 ContentDigest, and ACL/usage identity is projected only from that sealed variant. There is no fabricated fallback principal for invalid state. F:crates/decision-gate-mcp/src/auth.rs L181-L216 F:crates/decision-gate-mcp/src/auth.rs L503-L517


Authentication Modes

Auth mode is configured via server.auth.mode:

  • local_only: stdio is allowed; HTTP/SSE are only allowed for loopback IPs.
  • bearer_token: bearer token verifier material must resolve from every configured server.auth.bearer_tokens secret reference before publication.

Configuration surface:

Implementation details:

  • Local-only rejects non-loopback HTTP/SSE.
  • Bearer secret references resolve atomically at startup; raw material is bounded, validated, hashed into verifier digests, and dropped. Request credentials are parsed with size and scheme validation and compared against every verifier digest without an early-exit match oracle.
  • Server TLS protects the transport. It does not mint application identity. F:crates/decision-gate-mcp/src/auth.rs L479-L552

Tool Visibility and Call Policy

ToolVisibilityResolver is the sole static tool-policy owner. The server.tools.allowlist and server.tools.denylist sets govern both tools/list and direct invocation. A tool that is not callable is projected as UnknownTool, preventing discovery through different call/list disclosure. Unknown tool names and oversized policy sets fail configuration admission. The removed server.auth.allowed_tools surface has no parser alias or runtime bridge; its presence is an unknown-field error. F:crates/decision-gate-mcp/src/tools/visibility.rs F:crates/decision-gate-config/src/config.rs

Authentication results are emitted by the tool router before tool policy:

Primary Flow/Sequence Description

  1. The server normalizes request context and rejects malformed identity input.
  2. Authentication establishes a principal or rejects the request.
  3. Tool policy, namespace authorization, and applicable quota checks run before the tool’s side effect.
  4. The result is audited and projected through the JSON-RPC/HTTP disclosure policy.

Namespace Authorization (Pluggable)

Namespace authorization is enforced by a pluggable NamespaceAuthorizer hook. The standalone implementation permits only local authentication carrying a durable authentication-governance receipt and requires namespace context for every namespace-bearing tool. Enterprise deployments supply an authorizer that binds principals to namespace scopes.

Policy allow and deny are disjoint NamespaceAuthzDecision variants. Failure to obtain an authoritative decision is a separate NamespaceAuthorizationError that preserves the exact source chain; the router records a fail-closed denial event and returns the authority failure rather than mislabeling it as a policy deny. If that required denial audit also fails, both failures are preserved in a composite tool error. Namespace authorization runs after static tool-policy checks and before tool execution. All outcomes emit dedicated audit events (namespace_authz).

Implementation references:


Usage Metering and Quotas (Pluggable)

Usage metering and quota checks are enforced by a pluggable UsageMeter hook. The admitted OSS composition uses the same durable governance sink as request audit; explicit no-op markers reject every operation and cannot satisfy durable startup wiring. Enterprise deployments supply the platform-quota adapter. Usage reservations run before tool execution; denials emit usage_audit events. A sealed admission retains the authenticated principal. Resolution rejects principal substitution, commits a distinct resolution_attempt, asks the quota authority for the idempotent transition, and emits resolution_finalized only after authority confirmation. The usage seam receives three distinct identity inputs: caller provenance (caller_request_id), authoritative server request identity (request_id), and a dedicated tool-call idempotency_key. Decision Gate derives that idempotency key from the normalized JSON-RPC id when available, falling back to the server request_id only when the protocol message does not carry a usable JSON-RPC identifier. Safe JSON-RPC identifiers pass through directly; unsafe identifiers are deterministically transformed into jsonrpc-sha256:<hex> to preserve fail-closed enterprise quota/idempotency behavior without leaking JSON-RPC identity into transport headers, audit request IDs, or telemetry request IDs.

Implementation references:


Auth Audit Events

Authentication decisions emit structured mcp_request_authentication audit events with action context, transport, subject, method, and failure details. The action identifies the attempted operation in evidence; it is not an input to authentication policy. The default audit sink logs JSON lines to stderr; tests can use a no-op sink. F:crates/decision-gate-mcp/src/auth.rs L379-L445


Disclosure Posture (JSON-RPC and HTTP)

Stage Evaluation Disclosure

scenario_evaluate_stage returns the accepted run view, not raw observation values. Attempt/evidence material remains in accepted history and runpack families according to evidence disclosure policy. scenario_precheck_stage returns only semantic outcome and trace for caller-submitted or explicit local evidence and makes no accepted-progress claim. The removed cursor feedback configuration has no compatibility alias.

JSON-RPC Error Envelope

The MCP server responds using JSON-RPC error codes and structured metadata (kind, retryable, request_id, optional retry_after_ms). Error kinds are stable labels used for metrics and audit categorization. Internally, the server also retains a projection-safe public code, optional reason code, and canonical exact identity on the error object for audit/telemetry sinks, but those fields are not serialized onto the public JSON-RPC wire by default. The public MCP projection remains Docs/generated/decision-gate/mcp_errors.json, while the richer canonical OSS completeness source is now Docs/generated/decision-gate/error_catalog.json. F:crates/decision-gate-mcp/src/server.rs L1268-L1283 F:crates/decision-gate-mcp/src/server.rs L1672-L1707 F:crates/decision-gate-mcp/src/server.rs L2140-L2198 F:crates/decision-gate-mcp/src/audit.rs L48-L78 F:crates/decision-gate-mcp/src/telemetry.rs L102-L120

Telemetry Authority Artifacts

Decision Gate publishes generated telemetry event and operator-seam authority artifacts:

  • Docs/generated/decision-gate/telemetry_event_catalog.json
  • Docs/generated/decision-gate/telemetry_operator_seams.json

These artifacts are generated from DG-owned source manifests in crates/decision-gate-contract/catalogs/ and freeze event codes and high-risk operator seams that observability governance is expected to protect. Metric family identity, labels, help text, units, kind, cardinality, and histogram bucket profiles are owned by a platform telemetry catalog supplied by the integrating workspace; that external authority is not a Decision Gate repository path, and DG must not carry a row-bearing metric catalog mirror.

Error Mapping (Tool Errors)

Tool errors are mapped to HTTP status + JSON-RPC error codes:

ToolErrorHTTPJSON-RPC CodeMessage
Unauthenticated401-32001unauthenticated
Unauthorized403-32003unauthorized
InvalidParams400-32602provided message
CapabilityViolation400-32602code: message
UnknownTool400-32601unknown tool
ResponseTooLarge200-32070provided message
RateLimited200-32071provided message
NotFound200-32004provided message
Conflict200-32009provided message
Evidence200-32020provided message
ControlPlane200-32030provided message
Runpack200-32040provided message
RateLimitAuthority200-32050rate-limit authority failed
Internal200-32050provided message
Serialization200-32060serialization failed

These mappings are implemented in jsonrpc_error. F:crates/decision-gate-mcp/src/server.rs L1984-L2015

Exact Identity Backing

The JSON-RPC mapping remains the stable public contract, but it is now backed by catalog-owned exact identities. Routed ToolError variants and server ingress rejections resolve to canonical OSS symbolic/numeric identities first and only then project onto the public JSON-RPC taxonomy. F:crates/decision-gate-mcp/src/tools/error.rs L126-L189 F:crates/decision-gate-mcp/src/server.rs L2270-L2307

Auth Challenge Header (RFC 6750)

HTTP/SSE responses for unauthenticated requests include a WWW-Authenticate header with a Bearer realm when bearer token auth is enabled. This aligns with RFC 6750 and keeps auth challenges explicit without leaking token validation details. F:crates/decision-gate-mcp/src/auth.rs L46-L75 F:crates/decision-gate-mcp/src/server.rs L1706-L1718

Request-Identity Headers

HTTP/SSE responses always include a server-issued canonical lowercase hyphenated UUIDv7 in x-request-id. If the caller supplied a valid x-caller-request-id, it is echoed back as caller provenance, but it never replaces the authoritative server request identifier. Invalid caller request IDs are rejected before request parsing and are not echoed. The rejection uses HTTP 400 with JSON-RPC error code -32073 (invalid_caller_request_id). F:crates/decision-gate-mcp/src/server.rs L993-L1072 F:crates/decision-gate-mcp/src/server.rs L1648-L1749

Request Parsing Failures

Invalid JSON-RPC versions, unknown methods, and malformed request bodies are rejected with standard JSON-RPC error codes and HTTP 400. F:crates/decision-gate-mcp/src/server.rs L1505-L1583


Rate Limiting and Overload Responses

decision-gate-mcp::rate_limit is the sole Decision Gate owner of the in-process fixed-window algorithm. MCP ingress and account-scoped enterprise docs supply different key and outward-policy types, but they do not own copies of the clock, bucket, eviction, counter, or publication relation. The admitted policy seals non-zero request count, window, and key capacity and proves the two-window retention horizon representable before a limiter can exist.

For a key k, policy (m, w, c), serialized monotonic sample t, and retained state S, admission is a partial deterministic transition step(S, k, t) -> Result<(S', Allow | Limited(retry)), E>. A successful transition publishes the complete next state and advances one global monotonic watermark. Synchronization failure, clock regression, capacity exhaustion, counter failure, or retry projection failure publishes no state. Any positive sub-millisecond retry remainder projects to the named one-millisecond public floor; it cannot become a zero retry hint.

The server enforces:

  • Inflight request limits (reject with 503 and -32072, kind inflight_limit, reason code dg.server.inflight_limit_exhausted).
  • Rate-window limiting (reject with 429 and -32071, kind rate_limited, reason code dg.server.rate_limit_window_exhausted, including retry-after hints).
  • Rate-limiter capacity saturation (reject with 503 and -32074, kind rate_limiter_capacity, reason code dg.server.rate_limiter_capacity_exhausted).
  • Payload size limits (reject with 413 and -32070).

The fixed-window authority owns a bounded map plus one globally published monotonic watermark. It samples only after serializing access, validates clock order and every fallible calculation before publication, and leaves the exact prior state unchanged on clock regression, capacity exhaustion, arithmetic failure, or poisoned synchronization. Ordinary scheduler reordering therefore cannot be misclassified as clock regression, while switching attribution keys cannot hide a real regression.

Local acquisition has its own validated operation and concurrency budget. The initial profile contains no outbound network acquisition path.

These failures are reported with structured JSON-RPC error metadata and are marked retryable when appropriate. F:crates/decision-gate-mcp/src/rate_limit.rs F:crates/decision-gate-mcp/src/server.rs


Invariants

  • Untrusted request identity never becomes authoritative without validation.
  • Missing or denied authentication/authorization fails closed before tool work.
  • Namespace authorization is an access decision, not a placement decision.
  • Disclosure preserves stable public errors without exposing sensitive detail.

Failure Modes and Recovery Matrix

Failure modeFail-closed behaviorRecovery
Invalid request identityReject before authorization/tool execution.Correct caller input.
Authentication unavailable or deniedReject request.Restore auth configuration/backend or credentials.
Namespace authorization unavailableDeny namespace-scoped request.Restore authority and retry.
Rate/usage limit exceededReturn structured retryable response where applicable.Wait or restore capacity/quota.

File-by-File Implementation Anchors

AreaFileNotes
Auth config surfacecrates/decision-gate-config/src/config.rsAuth modes, token/subject allowlists, tool allowlist.
Auth policy enginecrates/decision-gate-mcp/src/auth.rsDefaultRequestAuthenticator, auth modes, audit events, token parsing.
Tool auth integrationcrates/decision-gate-mcp/src/tools/router.rsPer-call authorization + audit emission.
Namespace authz interfacecrates/decision-gate-mcp/src/namespace_authz.rsPluggable namespace authorization seam.
Usage metering interfacecrates/decision-gate-mcp/src/usage.rsPluggable usage metering + quota enforcement seam.
Fixed-window request authoritycrates/decision-gate-mcp/src/rate_limit.rsSealed policy, monotonic state, bounded retention, and atomic publication.
JSON-RPC disclosurecrates/decision-gate-mcp/src/server.rsError mapping and response codes.

Test and Contract Traceability

  • Authentication, namespace authorization, disclosure, and server tests under crates/decision-gate-mcp/ cover the current ingress contract.
  • Generated contract artifacts freeze public error and telemetry projections.

Operational Readiness Hooks

  • Monitor authentication denials, namespace authorization denials, quota rejections, and disclosure-safe error categories.
  • A healthy authorization path does not prove evaluator placement or writer ownership; those require separate future namespace-cell evidence.

Maintenance Rules

  • Preserve the distinction between authorization scope and placement authority.
  • Update the generated contract artifacts whenever public disclosure text or schema changes.

Threat Model Delta Statement

Threat Model Delta: none for this terminology correction; no ingress policy or runtime authorization behavior changed.