Skip to main content

Native credential execution in Spaces

Status: implementation and rollout runbook, September 8, 2026. The roles, access, and activity improvements shipped in PR #367. The operator has approved one Spaces deployment with Infisical remaining the secret store. Production cutover is explicit; existing external deployments retain compatibility until CREDENTIAL_EXECUTION_MODE=native is enabled.

Target

The operator runs the existing Spaces service and database. Infisical stores and versions secrets. Agents continue using Commons APIs/MCP, receiving bounded provider responses rather than secret values. Credential pages and activity remain in Spaces. No standalone gateway, Infisical proxy container, or gateway audit database is required in the final deployment.

Reuse and changes

Implementation: packages/credential-gateway/src/core.ts is a transport-free library shared by both modes. packages/api/src/credential-execution.ts binds it to live Spaces authorization, exact database credential references, the Infisical reader, and the existing Spaces database. No gateway process is started in native mode. The writer remains in infisical-writer.ts. Infisical supports a machine identity exchanging its client credentials for a short-lived API token, then retrieving an individual secret by name. Reuse the deployment’s configured Infisical region/domain; don’t hardcode a new region. See Universal Auth and secret retrieval.

Security tradeoff

The legacy gateway uses a proxy identity without value-read permission; the separate Infisical proxy handles injection. Removing both processes means the Spaces executor will hold retrieved values transiently. Infisical still handles storage, but compromise of the Spaces process can then reach the configured read scope. An internal module is not a process isolation boundary. Use a separate runtime read identity limited to the required environment and paths; retain the write identity’s existing scope. Do not grant broad vault read/list/admin rights. The operator approved this tradeoff on September 8, 2026. Reuse the scoped proxy reader identity during cutover; keep reader and writer identities separate. Never return values through metadata/reveal APIs, place them in agent environments, persist them in Spaces, or log request headers/bodies. Drop secret references promptly; JavaScript cannot guarantee memory zeroization. Keep outbound requests bounded and redirects disabled. Authentication injection must happen only after server-derived destination and live access checks. Block request overrides of destination, secret reference, and protected headers. Guard provider responses against credential reflection and keep error messages allowlisted. Vault and authorization outages fail closed. Do not retry an ambiguous provider mutation automatically.

Configuration

Native execution is the default on a fresh host. A pre-existing CREDENTIAL_GATEWAY_URL preserves external mode until the operator explicitly sets CREDENTIAL_EXECUTION_MODE=native. Unknown modes fail closed. Configure the Spaces API environment:
  • CREDENTIAL_EXECUTION_SIGNING_KEY: a new random signing secret, at least 32 characters. Use a different key from the retired gateway so old tickets cannot cross into the new replay store.
  • COMMONS_CREDENTIAL_DESTINATIONS_JSON: the existing destination allowlist (HTTPS origins, methods, path prefixes, allowed caller/response headers, timeout, and response size). Provider authentication headers must not be allowed as caller headers.
  • COMMONS_CREDENTIAL_BINDINGS_JSON: exact Space/connection/destination bindings to credential IDs already in Spaces. Header names are lowercase; the prefix is literal, including any trailing space. For example:
  • INFISICAL_READER_CLIENT_ID / INFISICAL_READER_CLIENT_SECRET: the dedicated runtime reader identity, limited to the referenced environment and paths.
  • Existing INFISICAL_DOMAIN, INFISICAL_PROJECT_ID, INFISICAL_ENVIRONMENT, writer identity, connection policies, and credential definitions remain.
Bindings select database credential IDs. Callers cannot submit vault paths, secret names, injection headers, or a different destination. Every credential on a connection must be active and bound; rotation reads its current value for the next call. Secret imports and reference expansion are disabled. POST /v0/spaces/:space/credential-calls and MCP call_external_service retain their contracts. Low-level ticket clients use the returned gateway_url, which now points to POST /v0/credential-execution/call on the Spaces host. The legacy field name is retained for client compatibility. Tickets are bound to the complete request and consumed atomically before external execution. There is no automatic retry or fallback between execution modes. Readiness is included in /healthz as credentials: {mode, configured, ready}. A configured native executor with invalid configuration or unavailable persistence makes readiness fail. An unconfigured fresh host can still run Spaces. Readiness does not fetch vault values; each execution fails closed on vault or authorization failure. The credential page derives its Spaces service on Railway link from Railway’s own service environment metadata.

History and cutover

credential_execution_events and credential_execution_ticket_uses live in the existing Spaces database. Execution events omit request paths, headers, bodies, secret values, and provider-controlled request IDs. History imports retain the old sanitized records, timestamps, source sequence and digest. The authorized UI projection omits sensitive internals in either mode and sorts by occurrence time, so importing older records does not hide new calls.
  1. Deploy this code in existing external mode. Build the API, then run the operator-only migration command inside the Spaces deployment with its DATABASE_URL, old CREDENTIAL_GATEWAY_URL and read-only CREDENTIAL_GATEWAY_AUDIT_READER_KEY:
    The importer validates ordered pages, runs each page transactionally, and deduplicates by (source, source_sequence). A changed digest for an existing source sequence fails the import. It prints counts only and is safe to retry.
  2. Set the native variables, preserving all existing connection allowlists, credentials and grants, then redeploy Spaces. Verify native readiness, allowed read-only fixture calls, request substitution denial and replay rejection. Verify per-Space and per-agent history remains available.
  3. Drain legacy tickets for their configured maximum TTL plus request timeout. Run the history import again and verify a repeated import adds zero rows. Compare source counts and original timestamps before retiring the source.
  4. Remove the gateway and proxy deployments and stop the old audit database runtime. Preserve the old database volume for rollback/retention. Remove obsolete CREDENTIAL_GATEWAY_* variables from Spaces and redeploy with only the native configuration.
Rollback before retirement may explicitly restore external mode and its old configuration. Do not reuse signing keys across the two stores or reissue an ambiguous provider write. After retirement, recover the old database/runtime before selecting external mode. Infisical values and versions are unchanged.

Verification

Tests cover native API issuance/execution, concurrent replay, exact approvals, live grant/membership/task/credential checks, cross-Space isolation, exact Infisical path/name retrieval, vault failure, protected headers, DNS/SSRF, redirect refusal, compressed response limits, total timeout, secret reflection, durable activity and idempotent history import. The same core also runs the standalone gateway suite. Local-calendar timestamps cover Today, Yesterday, weekday/month/day, relative age, year boundaries and daylight-saving changes.