Daedro / Handbook
Chapters

Shared MCP Hub: identity, isolation and internal secrets requirements

Status: architecture requirements for the publication specification; not an implementation or a security certification. Updated November 4, 2026.

Product boundary

V1 is a shared MCP service hub. Users have accounts, profiles and connections. Shared compute and a common catalog do not imply shared credentials, user data, conversations, job results or permissions. Daedro Secret is an independently deployable module with its own API and authority. It serves the hub internally; users do not need to learn OpenBao operations. Dedicated customer infrastructure is a later phase and is not required to achieve V1 data isolation.

Only operator-reviewed adapters belong in the V1 shared pool. Uploading arbitrary MCP executables into a communal worker is out of scope. A read-only tool can still exfiltrate data; a read-only flag is not an isolation boundary.

Required identity model

Use distinct immutable identifiers, not names, email addresses, MAC addresses or hashes of API tokens:

Entity Meaning and boundary
Account/subject Human identity, keyed by verified issuer + subject
Tenant/workspace Authorization boundary; create a personal workspace by default
User profile User-owned settings inside a workspace; not a service deployment
Catalog service/version Public operator-approved MCP implementation and declared tools
Connection A subject/profile's provider account, secret references and allowed operations
Job/run Immutable owner tuple, selected connection/version and audit correlation
Worker deployment Shared execution capacity; never the source of user authorization

Every connection, upload, job, result, secret reference, log view, export, pagination cursor and idempotency key must be scoped to the authoritative owner tuple (tenant_id, subject_id, profile_id, connection_id) as applicable. Where a resource belongs to a workspace rather than a person, the ACL must explicitly say so. Merely joining the same deployment never grants access to another user's data.

Membership and permissions are resolved on the server from a verified session. Client-supplied owner IDs are filters at most, never proof of authority. Enforce ownership on every read/write/list/search/download/stream route and worker retry. A random UUID prevents guessing, not unauthorized access to a known UUID. Return consistent non-disclosing missing/foreign-resource errors.

Human access and onboarding

The user-facing flow is browser-only: sign in, create/use a passkey, enroll Google Authenticator with a QR code, confirm the code, then connect an MCP. OpenBao root, unseal shares, local scripts and PEM passphrases do not appear in this workflow.

Prefer a maintained IdP (Keycloak is the current candidate) over a new bespoke WebAuthn/TOTP/account-recovery state machine. The hub consumes verified OIDC with PKCE, state and nonce. Enforce passkey user verification and TOTP as explicit requirements; do not assume a default IdP flow enforces both. Configure and test strong-authentication claims, recent-authentication requirements and step-up for adding credentials, changing factors, exporting data and privileged actions.

The first owner must be bound to an already verified identity or a narrowly scoped, one-use bootstrap grant. Do not let the first visitor to an empty public site claim administration. Later registrations receive ordinary user permissions; only an explicit workspace grant permits sharing. Bootstrap administrative credentials are operator machinery, revoked after configuration.

Passkeys authenticate possession of a credential, not a Mac serial number. Apple or Google password managers may synchronize passkeys. Label the interaction "Use your passkey" or "Continue with Touch ID" where actually available; do not claim hardware binding unless credential properties and the supported device policy establish it. A stable domain must be chosen before enrollment; changing the RP ID later is an account-migration event. IP-address RP IDs are unsuitable.

Recovery has two independent layers: recovering a human account and recovering the secrets service. They must not be the same forgotten password. Offer an additional passkey and a deliberate recovery method in the account UI. Losing both factors must not silently fall back to a weaker public password/reset flow. The exact recovery policy must be approved in the publication spec; a single laptop Keychain is not sufficient organization-wide disaster recovery.

Trusted request context across shared services

The gateway creates an authenticated internal context with stable owner IDs, connection ID, approved service/tool version, operation scope, audience, expiry, job ID and attempt ID. Bind it to authenticated service-to-service transport. Consumers verify provenance and audience and derive ownership from the verified context. Reject unsigned client X-User, X-Tenant or equivalent headers.

MCP transport sessions, SSE streams and resumption identifiers are scoped to the same authenticated principal. Never treat an MCP session ID as authentication, and never reuse a principal's provider client/session for a different connection. Connection pooling is allowed only where per-request identity/credentials remain correctly separated and tested. Global caches must include the complete owner and connection scope; provider data cannot be cached solely by tool arguments.

Jobs record their owner before enqueueing. Workers cannot select a different owner or provider connection from untrusted job contents. Attempts are fenced; replayed/stale attempts cannot overwrite another attempt or another user. Stop new work when connection access is revoked and define how already-running work is cancelled. Revocation and expiry must also apply to result retrieval.

Data and secret boundaries

PostgreSQL stores metadata and authoritative ACLs. Application predicates are mandatory; row-level policies are useful defense in depth where supported. Object storage uses scoped object keys and short-lived download/upload grants; never accept an arbitrary bucket/key supplied by a user as an authorized object. Shared indexes, exports, temp files and backup restoration must preserve the same scope.

The hub stores connection metadata and opaque secret references only. Personal tokens, private contexts and secret values belong to separate private namespaces in Daedro Secret, never the shared catalog, YAML examples or the hub database. The browser sends secret material over authenticated TLS to the Secret API; an optional same-origin gateway must not persist, log or replay secret request bodies. Do not send provider keys through URL parameters, analytics or general chat/context storage. Personal context is user data with its own retention/ACL, not automatically shared merely because the backing service or model is communal.

Secrets are write-only from ordinary application views. Show connection metadata, last verification and rotation state, not stored key values. Scope an OpenBao reference to tenant + subject/profile + connection, with a pinned version for a run. The resolver must verify the full grant before reading a secret. User input cannot choose an arbitrary Vault path, AppRole, endpoint, executable or tool.

Separate gateway identity, credential-provisioning authority, workload access and operator recovery. A browser session never receives an OpenBao administrative credential. Runtime workers receive only the selected provider credential or a short-lived provider-native credential where available. Short-lived OpenBao tokens do not make a static provider API key short-lived. Revoking the OpenBao token cannot revoke a provider key already read.

Do not inject different users' secrets into a shared long-lived process environment. An adapter requiring environment secrets needs a fresh bounded child/container per execution with cleanup, or a reviewed request-scoped credential interface. Shared compute can allocate ephemeral execution contexts without selling dedicated customer infrastructure. Containers and encryption alone do not prove tenant isolation. Trusted host administrators can access runtime secrets; V1 is not a zero-knowledge product.

Provider responses and payloads remain untrusted. Prevent endpoint/redirect SSRF, credential forwarding to another origin and arbitrary tool execution. Define provider egress allowlists and separately authorize paid or destructive tools. Apply per-user/connection concurrency, storage and spend quotas to bound abuse and noisy-neighbor effects. Do not log secrets, OTP seeds, tokens, raw provider responses or other users' payloads in common operational logs.

Per-action Google Authenticator authorization

This is a hard product requirement, separate from sign-in MFA. Every human action that creates, imports, replaces, rotates, deletes, restores, exports, reveals (if supported), grants access to, or revokes access to a secret requires a fresh Google Authenticator authorization for that exact action. Changing authenticators or recovery policy requires equivalent explicit step-up. Entering the secrets metadata area also requires step-up, but a metadata-view lease grants no mutation or reveal authority. Do not silently fall back to login MFA when step-up fails.

The Secret module, not a client flag or generic hub session, owns this protocol:

  1. Authenticate the person and authorize the proposed target. Canonicalize the complete operation, including exact target IDs, expected versions, grant recipient, workload/tool scope, expiry and any batch items. Keep proposed secret values out of public metadata and logs; use an internal keyed digest when binding low-entropy sensitive input rather than exposing a plain hash.
  2. Create a random server-side challenge with subject, tenant, session binding, action, target, payload digest, creation/expiry, failure count and consumed status. Initial maximum lifetime: 90 seconds; never refresh by polling.
  3. Display the concrete action for review, then require a fresh TOTP code from the enrolled Google Authenticator. The code belongs only in the secure form. The six-digit code is not itself a signature over the action: the server must enforce the binding. Do not advertise transaction signing from TOTP alone.
  4. Verify the factor with the trusted identity system or the module's reviewed TOTP verifier. Consume the accepted authenticator time-step globally for that credential, and atomically consume the challenge with the operation/approval record. The same code must not approve a different challenge or replay an already-approved action. Initial failure limit: five attempts per challenge, with account and source rate limits and bounded backoff.
  5. Execute only the canonical action approved. Re-check ownership, grant policy and expected version immediately before commit. Reject payload substitution, stale target versions and concurrent reuse. Idempotency may return the original result; it must not repeat a mutation or transform approval into another action.
  6. Persist a metadata-only audit record with actor, target, action, factor event, outcome and correlation ID. Never record TOTP codes/seeds or secret contents. On unavailable MFA, policy service or required audit persistence, fail closed.

A success receipt is short-lived, single-use and audience-bound to the Secret module/action. It is not a general bearer permission to manipulate secrets for several minutes. Exact batch import may be one action only if the user sees the complete batch and its scope before approval; additions or changes invalidate it. If a TOTP period was already consumed, explain that the next code is needed.

If using OIDC step-up, bind state and nonce to the action intent and require a flow that demonstrably performs a fresh OTP challenge. A cached SSO session, a static amr claim or a prior auth_time is insufficient evidence of this action's OTP. The selected IdP integration must prove this behavior before publication; do not assume the provider exposes a direct OTP-validation API.

Automated MCP reads are different: a human first approves an explicit execution grant with TOTP. The machine can then obtain only its allowed secret version, for the approved subject/profile/connection, tool/workload and finite TTL or run budget. Each machine read has workload authentication, fresh grant checks and an audit event. It cannot create, rotate, reveal to a human, or broaden its grant. Grant expiry, revocation or verification outage prevents new reads. Background reads do not require a person to type an OTP for every request, and must not be misrepresented as an OTP-approved human action.

Availability and internal OpenBao operations

Choose automatic unsealing independent of a user's laptop. On GCP, a narrowly scoped VM identity plus a dedicated software Cloud KMS key is the preferred candidate. OpenBao 2.7 requires the external GCP KMS plugin; pin and verify it. Protect the KMS key from deletion and test restart and backup restoration. A retained encrypted old volume is not a usable rollback when its unseal material is inaccessible. Never claim otherwise.

Operator recovery material must be independently recoverable and tested. Do not leave a root token as the normal application's credential. Remove temporary bootstrap grants after testing scoped management and runtime identities. Avoid a Mac-only IAP tunnel and 24-hour VM stop policy as hidden dependencies of a public service. Budget alerts are not a spending cap; quotas and bounded execution must implement the intended limits.

Verified implementation gaps

These findings come from code inspection; this is not an exhaustive audit.

Location Current behavior Publication requirement
internal/secretsui/handler.go Static entity-to-workspace mapping; no self-service accounts/profiles Persistent identity, membership and profile/connection authorization
internal/secrets/resolver.go Grant key is (workspace, deployment) Add user/profile/connection scope; deployment is capacity, not owner
deploy/openbao/connect-existing.py Imports operator credentials into demo, passes all operations through that workspace Replace demo migration glue with authenticated per-user connection lifecycle
internal/secretmcp/http.go One operator-selected bearer per configured upstream operation Authenticated per-user context and connection-scoped upstream identity
Voice processor server.py:244 processor_owner = SHA256(bearer token) Stable owner independent of credential rotation; shared hub token must not collapse every user into one upstream owner
Voice processor repository.py Jobs/uploads are queried by owner, which is currently the token-derived identity Preserve predicates, replace owner derivation, test two-user cross-access
internal/secretmcp/runner.go Approved child/container, environment secrets, coarse completion result Cross-run cleanup, request-scoped secrets and observable per-user run/result contracts
internal/transport/fleet.go Shared/private deployment management exists, and creation defaults private V1 product contract must prioritize shared services and must not imply instance ownership solves data isolation
docs/connected-mcp-rollout.md Real S3 listing and audio/video capabilities only; no media workflow Test real upload → transcription → result and all corresponding owner checks

Mandatory public release gates

  1. Two independently authenticated users and at least two profiles/connections each can use the same service deployment without observing each other's connection metadata, secrets, uploads, jobs, results, logs, cache entries or streams. Test guessed and known IDs, cursors, stale sessions and direct API requests, not just the normal UI.
  2. Register, authenticate, revoke and recover accounts in a real browser. Test missing factor, replay, invalid origin/RP ID, CSRF and privileged-action step-up. Confirm passkey alone cannot satisfy a route requiring passkey+TOTP.
  3. For every human Secret API mutation, test missing OTP, wrong OTP, expired challenge, reused time-step, same/different-action replay, payload substitution, simultaneous submissions, changed ownership/version and MFA/audit outage. Existing login MFA alone must never authorize the operation. Verify that an explicitly approved workload grant permits only its exact automated reads.
  4. Authenticate every hop gateway → worker → provider; spoofed owner fields, reused session identifiers and cross-tenant job messages are rejected.
  5. Rotate a provider key without changing data ownership. Delete/revoke a connection, verify queued/running/read-result behavior, and verify no unrelated connection changes. Test missing/expired leases and provider errors safely.
  6. Restart the VM and OpenBao without owner intervention; restore a backup into an isolated environment and verify ownership and secret grants. Prove cleanup on crash/timeout and quota enforcement under parallel users.
  7. A real S3 read and real audio/video processing each succeed through the UI with the selected connection. Unsupported/offline adapters remain unavailable, not green. Provider charges and data retention are visible before submission.
  8. Publish the threat model, privacy/retention rules and actual acceptance evidence. Do not claim "no leaks", "hardware-bound", "production ready" or "zero knowledge" from a screenshot, capability probe or encrypted storage alone.

Architecture decision notes and sources

Keycloak supports passwordless/discoverable WebAuthn and OTP enrollment. Its default conditional flow can skip second-factor work after passkey authentication, so the required combined flow needs deliberate configuration and negative tests. See the Keycloak administration guide.

Keycloak's container guide recommends a 2 GB limit for small production deployments; 750 MB is only a comparison to its previous heap setting. Co-location with a 768 MiB OpenBao limit plus PostgreSQL and the hub on a 2 GB VM has insufficient credible headroom. Do not use development-mode H2 to hide that constraint. See Keycloak container memory guidance.

Cloud KMS software key versions are listed at $0.06/month, plus operations; this is a component estimate, not the whole deployment budget. See Google Cloud KMS pricing. OpenBao documents auto-unseal lifecycle dependencies and the GCP KMS plugin transition.

A Go implementation using an established WebAuthn verification library can reduce resident infrastructure, but it still owns enrollment, challenge/session state, TOTP storage/replay control, revocation and account recovery. That is a distinct security product scope, not a quick replacement of the login form. For public V1, prefer an existing maintained IdP unless the publication spec explicitly funds and tests that ownership. The existing Go OIDC dependency can support integration.