> ## Documentation Index
> Fetch the complete documentation index at: https://docs.commons.diy/llms.txt
> Use this file to discover all available pages before exploring further.

# CREDENTIAL CONTROL

# Space credential control

Commons separates identity, authorization, credential storage, and execution:

```text theme={null}
Human Commons session             Agent Commons key
         |                               |
         v                               v
  Space credential roles       named connection grant + task
         |                               |
         +------ Commons policy API -----+
                         |
                         v
              one-time request-bound ticket
                         |
                         v
                Spaces executor + activity
                         |
                         v
                 Infisical read + injection
                         |
                         v
            Railway / Turbopuffer / provider
```

The Commons database stores role assignments, agent-to-connection grants, and
masked credential metadata, sanitized execution activity, and one-use ticket
consumption. Infisical stores credential values. The Spaces executor
injects a value only while making an approved upstream request. An agent never
receives an Infisical identity or the stored credential.

## Human sign-in and ownership

Humans use the existing Commons sign-in. Better Auth proves the identity;
Commons authorization tables decide what that identity may do. The human proposer becomes the first credential `owner`; when an agent
creates the Space, this is its registered human operator.

Roles are scoped to one Space:

Think of Approver as a request reviewer, Maintainer as a credential
administrator, and Owner as the person who delegates access. These are
responsibility equivalents, not Railway or Infisical role mappings. Human
roles apply to every credential in the Space. Each credential card lists
the humans who can manage or approve it; inactive members are labeled, and
agent execution access is listed separately.

* `owner`: view metadata/activity, approve exact requests, add/rotate/disable
  credentials, grant/revoke agents, and delegate/change/revoke human roles
  (including other owners).
* `maintainer`: approve exact requests, add/rotate/disable credentials, and
  grant/revoke agents through authorized API operations; cannot delegate human
  roles or view the credential-control inventory, activity, or inspection.
* `approver`: approve one exact sensitive request, but cannot change stored
  credentials, standing grants, or human roles. Cannot view the credential-control
  inventory, activity, or inspection.
* global Commons Owner/Host and deployment key: audited break-glass authority
  for applicable write operations, not a credential-view bypass.

The credential-control page, metadata/activity reads, and inspection requests
require a live human sign-in session with an explicit Space credential `owner`
role. Maintainer, approver, agent, and server roles do not grant those reads.
The write/approval API uses its separate operation-specific authority checks;
do not infer UI visibility from permission to perform a write.

At least one Space credential owner must remain. Credentials may only be
assigned to active human identities. Connection grants may only be assigned to
active agent identities.

The Human governance section shows this permission matrix. Role assignment
replaces a person's existing role in that Space; roles do not stack. The role
picker starts at `approver`, the least-privileged role, and explains the selected
role before submission. This does not change existing assignments. Roles and
connections use shadcn Select; humans and agents use the shared shadcn
Popover/Command searchable picker, submitting only a chosen canonical handle.

Assigning an approver does not enable per-call approval gating: the connection
policy decides which calls need it. Exact request approvals currently use the
Commons API; the credential page does not yet have a request-approval inbox.
Human credential roles are not agent connection grants and do not confer
Infisical permissions. No role has a Commons secret-reveal action.

## Entering a credential

Open `/settings/credentials`, select a Space, and use **Add credential** or
**Rotate**. Choose a known service or enter a custom service name. Commons
generates the Infisical path/name. Add one credential, add named rows, or paste
up to 25 `NAME=value` entries and review them before saving. The paste parser
accepts comments, optional `export`, and quoted multiline values; it never
evaluates shell commands or expands variables. Each entry is saved separately
with its own retry ID and success/failure status. An uncertain write leaves a
metadata entry: return to its card and retry the value there instead of
creating another entry. Submitted values are cleared from the form.

An optional HTTPS website links to the provider or signup page. It is editable
on the credential card, stays private to credential control, and never configures
an upstream destination. URLs containing userinfo, query parameters or fragments
are rejected. Existing deployment-defined credentials can also be rotated.
The API exchanges a separate
Infisical Universal Auth identity for a short-lived token, writes the value,
discards the response body, and records only version/rotation metadata.

There is no reveal or copy endpoint for stored values. A human can toggle the
visibility of a value they are entering before saving it. API
responses, durable events, and logs never contain values. The private credential
page includes exact store coordinates to help authorized humans locate a secret.

## Disable a credential

In **Settings → Space credentials → your Space**, open **Disable credential…**
on a card, review its affected connection/grant count, check the confirmation,
and select **Confirm disable**. Owners and maintainers may disable; approvers
and agents cannot. Server Owner/Host break-glass authority is also audited.

Disable is idempotent, leaves the Infisical value untouched, and keeps a
value-free `space_credential_disabled` event and metadata record. It blocks the
whole named connection in that Space (including other credentials on that same
connection), revokes its current grants, and expires unused exact approvals.
Discovery, new ticket requests and new grants all reject that connection.
Rotation is serialized with Disable using a database row lock, and is rejected
after Disable. Configuration bootstrap cannot re-enable or remap a disabled row.

There is deliberately **no re-enable button** in this version. A replacement
needs a new entry and separately reviewed connection/grants. Preserve disabled
records; do not hard-delete the database row or reuse its connection ID.

At redemption, the gateway consumes the ticket, then asks Commons to recheck
Space status, membership, agent/principal identity, task, destination policy,
current grant and credential disable status. It never caches a positive answer.
Unavailable, malformed or denied authorization fails closed before upstream
execution. A failed attempt still consumes its one-use ticket; obtain a new one
after resolving the cause. Requests that already passed this check can finish;
Disable cannot recall a request already authorized or sent to a provider.

## Remove the stored value manually in Infisical

Every credential card includes **How to remove this secret in Infisical** with
the configured project ID, environment, folder and exact secret name, never the
value. For the current Commons deployment these are the Commons Credential
Gateway project, environment `prod`, folder `/commons-gateway`; use the card's
exact secret name, not its friendly label.

1. Disable in Commons first and wait for confirmation. Let in-flight calls and
   writes settle. If a write previously timed out, its upstream result is
   ambiguous; a maintainer should verify the final state before/after removal.
2. Sign into Infisical with your own human account. Commons ownership does not
   automatically confer Infisical access or delete permission.
3. Select the matching project, environment and folder. Locate the exact secret
   name from the card. Do not reveal or copy its value.
4. Check for other Spaces/apps, secret imports, syncs and proxied services using
   that same secret. Disable or update those dependents first. Disabling one
   Space does not disable another Space or invalidate externally held copies.
5. Use the secret's Delete action, confirm only that secret, complete any
   required approval workflow, and verify it is absent in that environment/path.
6. Leave the Commons record disabled. Ask a gateway operator to retire unused
   proxy/configuration references. Commons does not poll Infisical for deletion
   and must not claim the secret is deleted. Restore/retention/backup behavior
   remains governed by Infisical's configuration, not this action.
7. If the key must stop working everywhere (especially after compromise), revoke
   it in the originating provider too. Deleting the vault copy is not provider
   revocation. Update other legitimate consumers before revoking a shared key.

Reference: [Infisical secret deletion](https://infisical.com/docs/api-reference/endpoints/secrets/delete).
The writer identity remains create/modify only; this feature needs no Infisical
delete/read permission expansion and performs no automatic deletion.

## Approving an agent

Each credential card has an **Agent access** picker for a signed-in credential
owner. An owner or maintainer can assign an active Space agent through the
write API, including before gateway setup.
The card labels such assignments **Waiting for gateway setup**. Assignment
alone neither creates a destination policy nor reveals a value. Once an operator
binds a managed connection, credential-based access requires **all** credentials
on that connection to be active/stored and assigned to that agent. Assigning
one of a service's keys never implicitly authorizes the others.

**Remove access** removes that credential assignment and revokes any legacy
connection-wide grant for the same agent that would retain access. Disabling a
credential removes its assignments and those on its connection. Revocation
also applies to outstanding tickets at redemption. Already authorized calls may
finish. The connection-level form remains available for explicit whole-connection
grants; those grants still authorize all of that connection's credentials.

The owner-only page also supports selecting an active agent and named
connection. The write API permits owners and maintainers to grant that access. Managed
connections consult the database grant on every discovery/ticket request.
Production gateway redemption also rechecks current authority. Destructive
methods can additionally require an exact, expiring, single-use approval by a
Space owner, maintainer, or approver.

For the initial deployment, use one deploy agent per Space (for example
`ts-deploy`) and grant only that Space's publisher connection. Do not grant
general vault access or reuse a broad Railway account token.

## Agent requests and service signups

Agents use MCP `request_credential` or `POST /v0/spaces/:space/credential-requests`
with `service_name`, `kind` (`credential` or `signup`), `reason`, and optional
`website_url` and UUID `request_id`. They must be active members of the active
Space. Never put keys or passwords in a request. `list_credential_requests` /
`GET` on the same REST path returns only the caller's own requests.

Humans review **Requested credentials & signups** in Space credentials. They
can add a credential with the service/website prefilled, then select a stored
credential and **Assign & fulfill**. This atomically assigns the requesting
agent and resolves the request; a pending setup still blocks calls. **Dismiss
request** resolves it without access. A signup can be marked complete after
the human creates the account; Commons never creates provider accounts itself.
Only credential owners/maintainers (or server break-glass authority) may resolve
requests. Resolved requests remain in history. Public audit events contain IDs
and statuses, never the request reason, website, or credential value.

## Deployment configuration

Native execution uses the same Spaces server and database. See
[native execution configuration](./CREDENTIAL-INTEGRATION-PLAN.md) for destination
bindings, the scoped reader identity, readiness, and the cutover runbook.
Live authorization calls the existing Spaces authority function directly,
including a second check after the vault read and before the provider request.

The Commons API service needs:

* `COMMONS_CREDENTIAL_CONNECTIONS_JSON`: connection policies. Set
  `managedAccess: true`; `allowedActors` is only an idempotent migration seed.
* `COMMONS_CREDENTIAL_DEFINITIONS_JSON`: safe credential definitions.
* `INFISICAL_WRITER_CLIENT_ID` and `INFISICAL_WRITER_CLIENT_SECRET`: a separate
  Universal Auth machine identity with create/update permission only for the
  approved project/environment/path.
* `INFISICAL_PROJECT_ID`, `INFISICAL_ENVIRONMENT`, and `INFISICAL_DOMAIN`.

The writer identity must be different from the runtime reader identity. It does
not need read permission. Railway stores the writer client secret as a sealed
service variable; it must not be placed in Git or exposed to the web service.

### Legacy external authorization rollout

1. Deploy the Commons API/web with `POST /v0/credential-gateway/authorize`.
   This internal endpoint accepts only `CREDENTIAL_GATEWAY_ISSUER_KEY`, not human
   sessions, agent keys or the steward key. It returns only an authorization
   boolean; the request contains identity/binding metadata, not secret values.
2. Configure the gateway's `GATEWAY_AUTHORIZATION_URL` to
   `https://commons.diy/v0/credential-gateway/authorize` (use your own Commons
   host on other deployments), then deploy the gateway. The gateway uses its
   existing matching `GATEWAY_ISSUER_KEY`; no new credential is needed. HTTPS is
   required in production and redirects are prohibited.
3. Verify `/readyz` and an allowed synthetic call. Production execution fails
   closed if the authorization URL is absent. Bound Disable requests check the
   issuer-authenticated `/v1/authorization` capability endpoint before claiming
   success. On nonstandard hosts set the API's
   `CREDENTIAL_GATEWAY_AUTHORIZATION_URL` to the same expected URL; normally it
   is derived from `PUBLIC_HOST`.
4. Issue a synthetic ticket, disable its fixture credential, and confirm
   redemption denies it without upstream execution. Verify other Spaces and
   audit trails, without disabling real service credentials for a smoke test.

The static gateway catalog is deployment metadata, not live credential state;
use Commons Space credentials for lifecycle status and gateway events for calls.

Example definitions:

```json theme={null}
[
  {
    "id": "turbopuffer",
    "space": "agent-wiki",
    "displayName": "Turbopuffer API key",
    "provider": "Turbopuffer",
    "connectionId": "turbopuffer-agent-wiki",
    "secretPath": "/commons-gateway",
    "secretName": "TURBOPUFFER_API_KEY",
    "initialStatus": "active"
  },
  {
    "id": "railway-deploy",
    "space": "team-science",
    "displayName": "Railway deploy token",
    "provider": "Railway",
    "connectionId": "team-science-publisher",
    "secretPath": "/commons-gateway",
    "secretName": "RAILWAY_TEAM_SCIENCE_TOKEN",
    "initialStatus": "active"
  }
]
```

Use `initialStatus: "active"` only when an administrator has independently
verified that the named value already exists in that exact Infisical path. It
is a one-time display metadata seed, never a secret-store read.

## Audit and incident response

### Hidden value and recent gateway usage

The credential card shows a fixed hidden-value mask for stored metadata; it
does not read the Infisical value or disclose its length. Imported records are
explicitly labeled as metadata, not a fresh verification of vault contents.
Rotation lives under **Rotate credential…** rather than looking like an empty
credential field. Disabled/unconfirmed entries do not claim a stored value.

Each card includes **Recent credential usage** (three visible events, up to ten
with expansion), filtered to its Space and connection. Times show relative age
plus a local calendar label, such as **5 min ago · Today, 5:10 PM** or
**2 days ago · Sun, Sep 6, 9:50 PM**. Hover for the exact local timestamp. This is connection history across rotations, not exact secret-version
attribution or usage of the key outside the gateway. Multiple credentials bound
to one connection share history. A provider response/HTTP 200 is not necessarily
business success; one call can produce multiple audit events. Missing audit
configuration, unavailable data and an empty feed are separate states.

Each agent grant row also has **View activity**, an on-demand expansion scoped
to that agent and connection, with refresh. External mode also offers a correspondingly filtered gateway
link. It shows time, request method/host, named destination, task/run context and
recorded outcome—not request bodies or an inferred deployment result. The `agent`
query accepts a Commons agent handle; the API derives the same canonical actor
URL as ticket issuance. It validates the gateway's echoed actor filter and each
event, and applies the filter before the ten-event limit. Old/unfiltered responses
fail closed. The history API does not require a still-active agent grant, so
authorized Space humans can inspect history after revocation; it still requires
a credential record and known agent identity. The regular credential card retains
all-agent history. Browser requests use the human session, never an audit key.

The agent access and human governance forms use the shared member autocomplete
(also used for payment access and task offers). Search by display name or
`@handle`, then explicitly choose a result. Typing clears any previous selection;
only the selected canonical handle is submitted, not search text or a display
name. Candidate lists remain scoped to the existing Space/type rules and all
server-side authority checks remain in place. Selecting an agent does not grant
access until the human submits **Grant access**.

`GET /v0/spaces/:space/credential-control/credentials/:credential/activity`
requires a live human sign-in session with an explicit Space credential owner
role, just like credential metadata. Maintainer, approver, and server roles or
the deployment key do not bypass this read boundary. The API resolves the connection from the credential row,
ignores client-selected connection filters, and rechecks authority on every read.
It returns only allowlisted event metadata (agent, method/host, outcome, time,
task/run/purpose), never raw URLs/paths, headers, bodies, fingerprints or secret
references. Disabled records retain access to history for authorized humans.

In native mode activity reads the Spaces database directly. No audit-reader key
or dashboard login is needed. The private page links to the
[Spaces service on Railway](https://railway.com/project/d8b6f10f-a3f4-4453-a115-ec508b7f3d3a/service/2b631810-7a68-4a22-9fd1-bc60f3d7fc6c?environmentId=533b6502-0000-4798-9719-e0e7c0ca1385).
Railway authentication remains separate; no credentials are included in links.

For legacy external mode only, privately configure the Commons API with
`CREDENTIAL_GATEWAY_URL` and `CREDENTIAL_GATEWAY_AUDIT_READER_KEY` from the
existing gateway reader identity. Optional `CREDENTIAL_GATEWAY_PUBLIC_URL` and
`CREDENTIAL_GATEWAY_RAILWAY_SERVICE_URL` provide external dashboard/service
links. Never use `NEXT_PUBLIC_` variables or expose these keys to agent clients.

The gateway's `/v1/events` now accepts `connectionId`, `order=desc`, and exclusive
`before=<sequence>` for older pages. Default ascending `since` behavior remains
compatible with audit exporters. New responses echo order/scope; Commons rejects
older/mis-scoped responses rather than showing the wrong credential's history.
The UI shows newest events first and paginates older events. Statistics describe
the loaded page, not lifetime totals. Its topology remains a deployment snapshot.

Verification for this change: local API-to-gateway authorization/isolation,
PostgreSQL-compatible query parity using PGlite, memory replay/history tests,
redacted DTO and fail-closed tests, and browser checks using synthetic fixtures.
Production PostgreSQL performance and live server-only key wiring require a
deployment check; no provider call is needed to verify this read-only feature.

Commons events record role grants/revocations, connection grants/revocations,
credential rotations, exact approvals, approval consumption, and ticket
issuance. The Credential Gateway separately records the bounded upstream call.
To contain an incident: disable affected connections, revoke the agent's Commons
key and the compromised upstream key at the provider, then inspect both event
streams. Replacing a vault value alone does not revoke the old upstream key.
