Current Security Behavior¶
Authority¶
API authority derives from the actual live virtual key/credential, exact user membership or ordinary service account, Team, catalog ∩ Team allowance ∩ key policy, and budget admission. Human administration uses eight presets and bounded custom Organization/Team roles. Managed Agent content has no principal or reusable key. Caller headers and telemetry labels cannot select Organization, Team, owner, payer, namespace, capture or permission.
Team metadata, captured content and export use separate permissions: observability.read, observability.content.read, observability.export. A key with telemetry.ingest can export standalone spans; no Agent registration or prior inference is required.
Human sign-in¶
Human access is invitation-first, and every credential is checked against the same eligibility rule: an existing account, an unexpired pending invitation, or the bootstrap owner. Magic-link requests for any other address return success without sending mail, so the endpoint never reveals who is invited. Optional GitHub sign-in is a credential for an already-eligible address, never a way to provision one: the console rejects a GitHub account whose primary address is unverified on GitHub or fails the eligibility rule, at account creation, account linking, and every later sign-in. GitHub is not a trusted linking provider, so attaching it to an existing person still requires an address the console verified itself. The console keeps no OAuth token from GitHub or the enterprise identity provider after sign-in, because nothing calls those providers afterwards, and the database clears any GitHub token written to an account row by any path; any token another sign-in provider stored would be encrypted at rest with a key derived from BETTER_AUTH_SECRET. Authentication grants no membership; Organization membership comes from supported invitation, bootstrap or directory provisioning; Team roles and inference policy require their separate grants.
While enterprise sign-in is configured, an account its directory provisioned signs in only through the identity provider, so the provider's own controls such as MFA cannot be sidestepped. Magic-link requests for such an address return success without sending mail, like any ineligible address, and GitHub sign-in is refused with a message pointing to single sign-on, both for such an address and for a GitHub account already linked to such an account, whatever address GitHub reports now. The database migrations that introduced this rule end the sessions such accounts held, including ones a magic link or GitHub started, and remove GitHub accounts linked to them. Local accounts, including the retained recovery owner, keep magic-link and GitHub sign-in. Until its directory deletes it, a directory-provisioned account belongs to its directory's Organization: its address was asserted by that directory rather than proven by the person, so it cannot read, accept or reject another Organization's invitations, and the directory cannot change the address of an account that also belongs to another Organization.
Role grants never exceed the granter's own authority at the grant's scope, apart from content-read permissions in Team presets given to other people by a Team's member managers; see Grants stay within your own authority.
Keys and internal signatures¶
Virtual-key secrets are revealed once and stored as peppered digests. Rotation changes credential version without splitting logical key/producer/counters. Current expiry, key/credential/owner/Team status and exact membership are rechecked. Terminal revocation does not reactivate; rejoining does not revive old user keys.
Management tokens (mcm_…) attenuate a real human's current permissions and explicit scopes. They cannot perform inference, mint another token, or impersonate a personal Connector owner. Mixed credential families fail.
Four distinct secrets separate Console/Collector internal transport, runtime inbound, runtime heartbeat and gateway-only proof derivation. Runtime never receives INTERNAL_SERVICE_SECRET, caller API keys or MANAGED_EXECUTION_AUTH_MASTER. Console signatures bind serialized path + query exactly, including order and encoded representation, and the exact body digest, method, service, timestamp, nonce and real session claims. Do not sort/decode query parameters before signing. Duplicated decoded management query names are rejected. Raw body reading is bounded before signature validation; malformed UTF-8 is rejected. Managed runtime requests reject queries.
Listener isolation¶
Inference exposes only /v1/models, /v1/chat/completions, /v1/responses, /v1/embeddings, /v1/rerank, /mcp and /v1/deployments/{deploymentId}:invoke, plus public OAuth metadata. /management/v1, health/readiness and internal infrastructure use a separate control listener. Reverse proxies must not combine them.
MCP transport boundary¶
The public MCP endpoint and every configured Connector client are pinned to revision 2026-07-28. Mission Control does not accept or initiate initialize, mint protocol session IDs, negotiate a legacy revision, or silently fall back. Every inbound POST must carry the matching protocol version and client capabilities in request _meta; standard MCP-Protocol-Version, Mcp-Method, and Mcp-Name fields are checked before authentication. After caller resolution supplies the visible tool schema, recognized Mcp-Param-* values are checked before the invocation kernel or upstream dispatch. Client/server identity in MCP _meta is self-reported display/debug metadata and never authority.
All incoming requests with Origin are exact-matched against MCP_ALLOWED_ORIGINS before method routing, including unsupported GET, DELETE, PUT, PATCH, and HEAD requests. Browser preflight succeeds only for POST and the documented bounded header set, including syntactically valid Mcp-Param-*; missing/unconfigured origins or extra headers fail closed without an allow-origin response. Non-browser clients may omit Origin. Authorization, Team/owner lifecycle, allowance, key policy, schema eligibility, limits, Trace, and evidence are still re-evaluated independently of MCP metadata. Public result identity is always Mission Control's; upstream reserved server identity and unexpected exception detail cannot cross this boundary.
The gateway holds one immutable configuration view per process, including prepared outbound credentials. File and static-secret changes take effect after restart; drain in-flight requests before restarting. This view grants no authority: key policy and Team allowance remain live at authorization, and product limits remain live at admission. Only the non-secret normalized configuration hash enters new decisions and spans; legacy revision attribution remains historical. The view and secrets never enter managed runtime envelopes.
Organization YAML reaches only Organization-owned environment variables. One gateway serves every Organization, its environment also holds the platform's settings and secrets, and a resolved reference is sent to a URL the file chooses. A reference to a platform setting, a cloud SDK identity variable or a platform namespace is therefore refused when the file loads, and the gateway's secret resolver refuses it again before reading the environment. See Names Organization YAML cannot reference.
Connector static-header authentication resolves only explicit credential: env/NAME fields. Literal value text is never interpreted as a reference. Header names are case-folded once and cannot replace routing/framing, proxy, browser-security, MCP protocol, W3C Trace/baggage, or X-MC-*/X-Mission-Control-* fields. Literal Authorization is rejected; a secret reference may supply its complete value. All references and byte limits are checked before startup, nested entries are frozen in the compiled view, and upstream resource requests reject redirects. The caller's Mission Control Authorization is never copied into this prepared set. Stored redacted configuration contains reference metadata and administrator-visible literals, never resolved secret values.
Scheduled connector checks and tool sets¶
The gateway's single non-overlapping scheduler refreshes MCP connectivity and schemas using configured static/shared credentials or per-user Connector discoveryAuth. It never uses a person's grant, starts consent, or calls a business tool. Scheduled rows identify system:scheduler; manual refresh keeps audit evidence. Current status includes checked time; historical checks do not masquerade as current-process results. Last-successful schemas survive only a matching non-secret connection target key, including after unchanged restart. Failures preserve those schemas. Health is diagnostic and never grants or blocks authorization by itself.
A tool set's membership comes from startup-loaded catalog configuration. Team allowance and selected key tools may explicitly follow its ID; current membership expands only within catalog/Team/key intersection and eligibility. An empty, removed or foreign set grants nothing. Adding a resource to the catalog alone grants no Team or key access.
Governed tools on model calls¶
A model call that opts in receives the caller's eligible tools as function tools; the provider sees names and input schemas, never a Connector credential, and a request that does not opt in is unchanged. Automatic execution is a name list, never a classification: the gateway runs a call only when the request named the tool (or asked for every automatic tool), the YAML declaration says autoExecute: true, and the caller may see the tool. Everything else comes back to the caller pending. The loop is bounded to at most 20 provider rounds per request.
Every automatic hop goes through the same tool kernel as a /mcp call, with the already-verified caller and the model decision's Trace as origin: model_loop: it is authorized, checked against Team allowance, key policy, schema hash and budgets, prepared, admitted and recorded as its own mcp decision under the model request's span, and it makes exactly one upstream dispatch. A loop origin never binds a new Trace. A refused or failed hop is fed back to the model as fixed text; the model decision does not fail, and the reason lives on the hop's decision. Per-user Connectors keep their rules: a person's model call uses the person's own connection, and nothing falls back to a company or discovery credential. Tool output reaching the provider is bounded text; arguments and results enter neither logs nor the governance record, and a function tool of the caller's own cannot take a governed tool's name.
Rules and approvals¶
Rules are part of the reviewed configuration, compiled per tool in a fixed order (organization, Connector, tool) and carried by the runtime snapshot the invocation already pinned; nothing is read at call time from anywhere else. Argument rules run after schema validation and before any credential is prepared, so a blocked call reaches no credential and no upstream; result rules run on the returned text before the caller, the model or the capture sees it, so a redacted value is the only value that exists outside the gateway. The public answer is fixed (Tool invocation blocked by policy, rule_blocked); rule ids are safe identifiers, and the matched content, the pattern and its position never enter logs, decisions, error envelopes or the model context. Built-in secret detection is a fixed list of shapes. Rule patterns and secret shapes run in RE2, never in JavaScript's backtracking engine: configuration refuses look-around and backreferences, each pattern is compiled once, and matching time grows linearly with the inspected text, so crafted arguments or upstream results cannot stall the shared gateway process. Each decision's regex work is bounded, and work beyond the bound fails closed: the rule counts as matching. Evaluation assertion patterns, which Team members write, run in their own RE2 instance, so they never compile in the memory that tool rules, model guardrails and the built-in shapes use. An RE2 instance that runs out of memory is replaced: only the call that ran out fails closed, and the next call uses a fresh instance. See Regular expressions.
Administrator tool approval retains validated rule-redacted arguments, schema and exact requester owner/key provenance. A person with approvals.decide in the Team may decide; a user requester cannot approve their own call. Approval itself dispatches nothing. The exact retry rechecks live key/credential/owner/Team/policy, rules, schema and funding, then consumes approval atomically with the physical claim so exactly one dispatch follows an approval. Pending/approved rows expire after 24 hours. No secret/header/upstream response enters the record.
Managed conversation consent is a separate durable mechanism bound to exact execution/source-turn/interrupt/tool-call/schema/argument hash. Resume preserves original funding and rechecks the actual browser session or key credential version. Backend consent is not represented as verified human consent. Preparation failure does not consume dispatch authority; possible dispatch never restores it.
Shared MCP OAuth¶
Grants bind organization, connector, issuer, resource, client, grant, scopes and generation. Client material, interactive tokens and PKCE verifiers use AES-256-GCM with identity/version-bound associated data; keys stay outside the database. Machine access tokens are memory-only. Tokens never enter ordinary configuration, browser responses, traces or Agent envelopes. A dynamically registered client identifier and any issued secret are encrypted with the same keyring, bound to the issuer and credential generation, cleared by a generation change, and never appear in configuration responses or the Console.
Authorization-code flows use SDK discovery, PKCE and issuer validation with single-use ten-minute state bound to the initiating administrator. Callback routing joins the stored state digest to the session's same-tenant human membership; active-organization hints grant no authority. Current organization.settings.manage, generation and version are checked before tokens persist. Callback queries do not enter sign-in return URLs.
Refresh uses database fencing leases/version checks. Stale writes cannot revive disconnected grants or overwrite newer tokens. Uncertain rotating-token outcomes require reauthorization; proven failure before redemption preserves the grant. Cancelling one waiter does not discard another caller's shared refresh.
Credentials are prepared after local authorization and before admission, preserving existing enforcement points. Token-only transports have no unauthorized retry hook; insufficient scopes cause no automatic expansion or business replay. Disconnect invalidates local generation before best-effort provider revocation. Prepared invocations may finish; new preparation must match the current enabled generation.
OAuth fetching validates configured origins and connection destinations, uses HTTPS, bounds bodies/deadlines and rejects credential redirects. Private origins need connector and deployment approval. Mission Control keys and attributed end-user strings never become upstream tokens. Per-user grants follow the rules below; delegated token exchange is not implemented.
Per-user connector credentials¶
A per-user Connector never has a company credential for tool calls. Every stored grant, token, pending flow and lease carries an owner: the company (shared) or one person. Personal rows are encrypted with the same keyring and associated data that also binds the owner, so a personal token cannot be presented as a shared one or as another person's; shared rows keep their existing associated data. Personal header values are encrypted under a fingerprint of the declared header names, so a configuration change hides stale values rather than sending them.
Before preparation the gateway resolves the actual person from the user key or admitted managed execution. A saved per-Connector Person's connection requirement uses that person's exact Organization membership grant. Company connection uses an exact configured Connector binding. A service account or customer label cannot borrow an employee's grant. Missing/unusable personal credentials return an in-band connect request before dispatch; there is no company/discovery/anonymous fallback.
Discovery credentials are only for Refresh connection. Personal setup requires a real user session and current eligibility through an allowed user key, Team development path, or available Chat deployment requirement. Consumer Chat needs no technical Team membership. Losing eligibility orphans the grant without deleting it. Personal status invalidation and refresh preserve exact owner columns; encrypted grants cannot be relabeled as shared. Management tokens cannot perform personal setup.
Client connections¶
An AI client signs in through the Console as an OAuth 2.1 public client. Consent binds the person, Organization, Team, exact membership and client; PKCE S256 and an exact resource naming the gateway MCP endpoint are mandatory; authorization codes are stored as digests, are single-use, expire after ten minutes, and any failed exchange burns them. Access tokens live one hour; refresh tokens rotate on every use, and reuse of a rotated token or presentation by another client ends the connection. Registration accepts public clients only, with HTTPS or loopback HTTP redirect URIs (loopback ports may vary) and custom schemes only from OAUTH_CLIENT_REDIRECT_SCHEMES; client ID metadata documents are fetched over HTTPS with private address ranges blocked, validated and cached for a bounded time. Consent explicitly selects tools for a dedicated logical user key, with none selected by default and no implicit future Team additions. Only peppered digests of tokens are stored. Revocation from Virtual keys, suspension of the person or Team denies the next request, and each Request records the logical key and its credential version.
Trace trust boundary¶
W3C traceparent, X-Mission-Control-Trace-Id, Session ID, AG-UI runId, and correlation IDs are correlation, not authority. Mission Control creates or validates context once at ingress, records every downstream request under the Trace, and derives state from evidence. It exposes no Trace/Session open/close API.
The governance record (inference_decision) stores the decision each request received and its outcome. For an Organization that enables telemetry.standardOtlp, standard OTLP spans and the gateway's own request spans are stored in the span store; with it off, neither is. A shared Trace ID correlates the two; it never turns a span into a decision.
Observability non-disclosure¶
Readable scope is resolved before rows, counts, neighbors, cursors, search or span detail. Unknown/foreign identifiers share a generic unavailable/no-access state. Trace identity is Organization + Team + Trace ID; Session identity is namespace + Session ID. Span identity includes authenticated source/producer/span. Own-key Request access does not grant the shared Trace. Content names/errors/events/previews/search are protected even where structural metadata is readable; export is separately authorized.
Managed application content and private history¶
Public API entry requires actual key deployment.invoke and an approved owner. Private Console Chat/Preview/production tests use signed actual sessions at fixed-purpose control routes. Managed access is secretless, funded by the publisher Team and pinned to immutable content. A Preview does not certify Production; rollback retains current grants/policy/caps.
Threads/messages/instructions remain in the application tables of the PostgreSQL database. Private history requires the exact user and current Organization membership and is independent of Trace/Session grouping. Revoking audience or losing runtime availability can make history read-only without erasing it. Customer labels never transfer ownership. New invocation/resume rechecks current permission/audience/key. Stop aborts the stream; uncertain upstream completion retains truthful state and reservations.
Telemetry content¶
Capture intersects Organization maximum with Team, applicable key and deployment narrowing. All free-form names/text/errors/events/attributes/URLs are content. Metadata drops content; full capture retains bounded allowed content while always excluding credentials, signing/capability material, hidden reasoning and unrelated memory/files. Server retention and permanent heads/tombstones prevent replay extending expiry or restoring deleted spans.
The Collector synchronously authenticates and forwards through mc_producer/mc_writer to the signed gateway writer. No persisted credential queue or ClickHouse secret enters the Collector. Dedicated managed OTLP capabilities authenticate a producer, not body integrity or anti-replay. Control/storage outages are retryable; permanent invalid input/authority fails. Logical Trace deletion requires exact team.manage, durable audit intent and current recheck; physical payload cleanup is eventual. Usage facts remain independent.
Budgets and accounting¶
One physical model/provider retry or business tool dispatch has one ledger fact. Governing Organization/Team/member/service/key/customer meters may all reflect it without multiplying billed cost. Managed wrappers add no duplicate financial attempt. Session totals use only explicitly assigned facts. Finite spending caps need priced bounded liability; unknown is not zero.
Active work renews its existing lease without extra request or spend admission. Unknown termination retains debit and capacity until confirmed or the actual lease expires/recovery settles it. Errors never disclose hidden numeric ancestor constraints. Key-local cap authority does not grant ancestor budget access.
Readiness¶
Startup must complete strict file/secret preparation, and the PostgreSQL database must be reachable with every bundled migration applied (STORAGE_MIGRATION_REQUIRED otherwise). Readiness performs no upstream calls and never waits for model/MCP health, OAuth consent or discovery.
Agent runtime, Collector, and Console heartbeat at startup/every 30 seconds. Missing, mismatched, identity-conflicting, or older-than-120-second components make private readiness 503. The query service is in-process with Console.
Database connection¶
DATABASE_URL is a secret: its user part carries the database password. Keep it in the protected service environment (shared.env, mode 600, on the existing VPS) and never in Git, shell history, documentation or client bundles. Artifact deployment backups copy shared.env into the backup directory with mode 600; protect backup directories accordingly.
The deployment helper reads the URL only from shared.env on the host and never places it on the SSH command line. For psql, pg_dump and pg_restore it passes the password in PGPASSWORD and the rest of the URL as the connection argument, so the password never appears in a process list; a password= query parameter is refused. The release start guard reports a missing or non-Postgres DATABASE_URL without printing it. For manual psql, pg_dump or pg_restore commands, a URL with a password on the command line is visible to other local users in the process list while the command runs; on a shared host, keep the password in the operator's ~/.pgpass (mode 600) and use a URL without it.
Use a dedicated login role that owns only the Mission Control database; it needs no superuser, createdb or createrole privilege. Keep the Postgres port reachable only from the service hosts. When the database runs on another host, require verified TLS in the URL with sslmode=verify-full and sslrootcert=<path of the CA certificate>; the services (node-postgres) and the deployment helper's psql, pg_dump and pg_restore (libpq) all read both. Avoid sslmode=require: node-postgres treats it as verify-full and prints a security warning, while libpq only encrypts. A certificate the services reject can then pass the helper's preflight and backup and fail at its migration step, with the services stopped.
Every process sets application_name, so pg_stat_activity shows which component (mission-control-gateway, mission-control-console, mission-control-migrate and others) holds each connection. The storage layer logs no statements; errors from its facade carry the database message without the statement's parameters. The database's error detail can still repeat key or row values (Key (…)=(…) already exists, Failing row contains (…)), so keep logs that print whole error objects access-controlled.
Writes are serialized by one database-wide write lock, as they were on SQLite, so admission, budget reservation and audit keep their transactional behavior. Management audit events are append-only and hash-chained per Organization by the database itself, under the same lock, so no insert path can skip or fork the chain; an export verifies with audit:verify (Audit). Nothing migrates or resets data at startup. The six checks that read inside a JSON document (the models, tools and deployments of a Team allowance, key policy, inference funding and the experiment manifest) refuse a document containing a \u0000 escape or a lone surrogate, which SQLite accepted. Other JSON columns store such documents as before. The one-time SQLite import refuses them in every JSON column.
Related links¶
Enterprise identity and model policy boundaries¶
Named model guardrail policies are scoped to the authenticated Organization. Inline rules and named Organization baseline assignments are mandatory; exact virtual-model assignments only add rules. Resolution uses the authorized resource and pinned configuration, never caller-selected policy IDs, provider routing labels or telemetry. Provider fallback keeps the same effective policy. Deterministic matches run before any redaction; combined redaction precedes external classification. Classifiers evaluate sanitized content and do not promise identical verdicts after redaction changes. Policy assignment grants no model or human administrative authority. Safe sources and rule IDs are saved in Request evidence; full definitions remain behind organization.settings.manage. See model guardrails.
Enterprise OIDC binds one configured issuer, client and organization. SCIM provisions eligible identities and groups; arbitrary OIDC group claims grant no permission. Signed ID tokens are mandatory, including issuer/audience/expiry/nonce checks. Verified email proof or Entra's pre-provisioned immutable object ID establishes the first association. Subject bindings are immutable, cross-organization account adoption is refused, and directory mappings cannot grant organization.owner. Local owners remain available for recovery. Deprovisioning or manual suspension deletes Console sessions, ends directory-derived membership authority, revokes that membership's user keys, their client connections and its management tokens, and deletes the person's own Connector connections and header values; reactivation restores none of them. Service accounts and historical evidence remain. Directory reactivation cannot override manual suspension. See Enterprise identity.
Native Anthropic, AWS, Azure and Vertex adapters keep credentials in the gateway and use official SDK authentication. Automatic SDK retries are disabled so retries remain governed. Cloud credential chains are distinct from Mission Control-issued workload credentials or external secret-manager integration.
modelGuardrails inspects canonical text before dispatch and before output release. Its regex, secrets and pii matchers use the same bounded, fail-closed RE2 evaluation as tool rules. Deterministic redaction precedes external classification; classifier credentials and raw verdict bodies stay out of public errors. Output policy buffers streamed content until inspection, and local policy failures never select another provider. Provider usage remains billable when output is blocked. Embedding inputs and reranking queries/documents use the same input policy; vectors are not generated text. The policy does not inspect image pixels or guarantee detection of all sensitive content or attacks. See Model guardrails.