Private Secrets pilot
What works in code
The secrets command connects these steps without accepting secret paths or
workspace IDs from the browser:
- Sign in using an administrator-provisioned OpenBao userpass account.
- Complete the configured OpenBao TOTP enforcement challenge.
- Resolve the authenticated entity ID to its configured workspace.
- Click Run approved MCP. The broker issues a fresh one-use SecretID (at most 60 seconds), logs in with its deployment AppRole and reads one pinned KV v2 version. The service token lifetime cannot exceed five minutes.
- Revoke the OpenBao token before starting the MCP. On any failure, deny execution. Pass only the needed key to the reviewed MCP process.
- Show a run reference, duration and success/failure. Raw MCP content and secret values are not returned to the browser.
The server uses HTTPS on loopback, secure HttpOnly SameSite cookies, session rotation after MFA, CSRF tokens and exact origin checks. Sign-in/verification is limited to ten attempts per minute across this small private installation. Sessions last at most five minutes; logout and shutdown attempt revocation.
Operator configuration
Build with make build. Store configuration and the scoped provisioner token
outside Git in owner-only files (mode 0600). The TLS private key must also be
owner-only. Use a trusted certificate for the browser; never disable TLS checks.
{
"endpoint": "https://127.0.0.1:8820",
"ca_file": "/private/daedro/bao-ca.pem",
"certificate": "/private/daedro/hub-cert.pem",
"key": "/private/daedro/hub-key.pem",
"mfa_method_id": "REPLACE_WITH_ENFORCED_TOTP_METHOD_ID",
"provisioner_token_file": "/private/daedro/issuer-token",
"entities": {"REPLACE_WITH_OPENBAO_ENTITY_ID": "demo"},
"deployment": "source-fixture",
"bindings": [{
"workspace": "demo",
"deployment": "source-fixture",
"ref": {"Workspace": "demo", "Name": "source-key", "Version": 1},
"role_id": "REPLACE_WITH_ROLE_ID",
"app_role": "demo-source-fixture"
}],
"mcp": {
"executable": "/private/daedro/approved-source-mcp",
"sha256": "REPLACE_WITH_VERIFIED_EXECUTABLE_SHA256",
"args": [],
"tool": "read_fixture",
"arguments": {"key": "public-demo"},
"secret_env": "SOURCE_API_KEY",
"environment": {}
}
}
These are placeholders, not a deployable preconfigured account. The configured MCP must implement stdio MCP, expose the selected read-only tool, and consume that environment variable. Only the operator can choose the executable, digest, arguments and environment. The executable must not be group/world writable or a symlink. Its digest is verified again on every execution.
./bin/daedro-mcp-hub secrets --config /private/daedro/secrets.json --port 8794
Open https://127.0.0.1:8794/. For a remote installation, use an authenticated
SSH tunnel preserving this origin. Do not publish this pilot with a public
reverse proxy. The catalog on port 8791 does not grant Secrets access.
OpenBao provisioning requirements
Enable KV v2 at daedro and AppRole at auth/approle. Store the value in the
value field at daedro/data/demo/source-key. Provision the workload role with
only read on that exact path, service tokens with TTL/max/explicit-max <=300s,
and single-use SecretIDs with TTL <=60s. No wildcard tenant paths.
The issuer token must have only update on
auth/approle/role/demo-source-fixture/secret-id; no root, secret-read or policy
administration privileges. Rotate its private file atomically. Bind the human
entity to enforced TOTP on the userpass authentication mount. The human token
requires no workload-secret read permission. Set human service-token TTL to at
most 300 seconds. A password-only login is rejected even if OpenBao accepts it.
Provisioning, TOTP enrollment and offline recovery handling require a separate operator ceremony. The hub does not persist root tokens or unseal shares.
Boundaries and unfinished deployment
- This runner is for operator-reviewed MCPs, not untrusted uploads. It is a subprocess, not a container sandbox. It does not enforce a network allowlist.
- Environment injection is a compatibility mode: host administrators and the MCP can access plaintext. Go cannot guarantee removal of every memory copy.
- Revoking an OpenBao token does not revoke a provider API key already read.
- Human-token revocation outside the hub is not rechecked on each request; the bounded local session can remain usable until expiry. Workload grants and AppRole access are checked afresh for every run.
- Requests are serialized in this private UI; this is not a scalable public multi-tenant control plane. There is no self-service enrollment, key upload, recovery UI, WebAuthn or off-host application audit pipeline yet.
- Automated checks use synthetic TLS fixtures and an actual fixture MCP process. They do not prove live cloud MFA or a real provider request.
- The existing Google VM is initialized and unsealed, and owner enrollment has completed. Browser MFA and a keyed MCP run still need live acceptance. No new cloud resources are required by this command and none are provisioned automatically.
See test handoff for acceptance gates and cloud rollout for the recorded infrastructure state.
Owner enrollment helper
python3 deploy/openbao/enroll-pilot.py operates through the existing verified
IAP tunnel at 127.0.0.1:8821. The owner enters the recovery passphrase locally,
creates the owner password, adds the displayed enrollment seed to an authenticator,
and supplies one TOTP code. Do not share the seed or terminal screenshot.
The helper refuses existing pilot mounts or an existing enrollment directory;
partial failures require operator inspection rather than overwriting credentials.
It provisions a synthetic keyed fixture, exact-path AppRole and 24-hour scoped
issuer, writes private configuration under ignored .local/secrets-owner, then
starts the local HTTPS UI on port 8794. The OpenBao server remains in Google Cloud;
the broker and approved fixture run locally for this acceptance step. Browser
certificate trust must be checked separately. Root recovery remains encrypted;
root-token revocation is still pending independent live acceptance.
Owner enrollment completed on November 4, 2026. The private TLS tunnel and unsealed status were rechecked during browser-login repair. This does not yet establish a successful browser MFA session or a live keyed MCP run.
Sign-in troubleshooting
Use owner and the hub password created during enrollment, then the current
six-digit authenticator code. The encrypted recovery-file passphrase belongs
only in the local recovery tools, never in the sign-in form.
The form response uses Referrer-Policy: same-origin. Using no-referrer makes
browser form POSTs carry Origin: null, which the strict origin check rejects.
Do not fix this by accepting null origins or removing CSRF checks. A Chrome
submission with deliberately invalid credentials now reaches the normal sign-in
error page instead of a bare Request denied response. Expired forms, including
forms opened before a server restart, return an explanation and a fresh sign-in
link; submissions are never replayed.
Chrome ignores macOS trust entries constrained by a policy hostname. A successful
security verify-cert alone therefore does not establish Chrome trust. Verify
the browser indicator separately. A dedicated non-CA leaf certificate containing
only the loopback IP can constrain trust without trusting a general-purpose CA.
Installing it requires macOS authorization; do not bypass browser warnings.
References: form Origin behavior, Chromium macOS trust constraints.
Local TLS verification — November 4, 2026
The owner approved macOS trust for a dedicated self-signed leaf certificate
with CA:FALSE, server-auth usage and the sole SAN 127.0.0.1. The private
pilot now uses this certificate; Chrome visibly reports a secure connection.
No general-purpose CA was trusted. The certificate expires December 4, 2026
at 06:55 UTC and must be renewed and trusted before then. Certificate and key
files remain in ignored local state. Owner browser MFA acceptance remains pending.