# Secrets

How Kortix stores, injects, and rotates encrypted project credentials.

Canonical page: https://kortix.com/docs/project/secrets

A secret is a per-project credential — an API key, token, or connection
string — that a session needs but that must not live in the repository.
Kortix stores secrets on the project, never on the account, and encrypts
every value with AES-256-GCM using a key derived per project.

## Identifier and name

Each secret has two names. The `identifier` is the handle you use in the
CLI and in an agent's `secrets` grant. The `name` is the uppercase
environment-variable key injected into the sandbox, for example
`STRIPE_API_KEY`. In most projects the identifier and the name match. They
differ only when a project holds several candidate values for one name —
for example, a primary and a backup Google Maps key can both resolve to
`GOOGLE_MAPS_API_KEY`.

## Shared and personal scope

A secret is either shared or a personal override:

- **Shared** — the project-wide value. Every project member with read
  access sees every shared secret. Kortix has no per-member sharing control
  for a single secret.
- **Personal override** — your own value for one name, used instead of the
  shared row for sessions you start. Today Kortix uses this only for one
  OAuth login credential.

Kortix never lets an LLM provider key, for example `ANTHROPIC_API_KEY`,
become a personal override. The model gateway always reads the shared row.

An agent only receives the secrets its manifest grants. In `kortix.yaml`,
the agent's `secrets` field lists identifiers, or is empty to deny all —
the default when you omit it. See [Agents](/docs/project/agents).

A secret with scope `connector` is different: it authenticates a
[connector](/docs/connect/connectors), not a session. Kortix resolves it
server-side and never injects it into a sandbox.

## Add a secret

### Set the value from the CLI

```bash
kortix secrets set STRIPE_API_KEY=sk_live_...
```

Kortix saves it as a shared secret for the project. To store more than one
value under the same name, add `--identifier <id>`. Names can't start with
`KORTIX_` — Kortix reserves that prefix for platform values.

### Or use the dashboard

Open the project's Environment variables page, enter the key and value,
and save. Kortix encrypts the value immediately.

### Grant it to an agent

Add the identifier to the agent's `secrets` list in `kortix.yaml`. A
session only receives the secrets its agent is granted.

## List your secrets

Run `kortix secrets ls` to see which secrets a project declares and which
ones have a value set.

## Rotate a secret

### Set a new value

Run the same command with the new value, or set it again on the project's
Environment variables page:

```bash
kortix secrets set STRIPE_API_KEY=sk_live_new...
```

### Kortix pushes it to running sessions

Kortix pushes the new value to every sandbox with an active session for the
project, on a best-effort basis. If the key is a model or gateway
credential, Kortix also restarts the agent process so it picks up the new
value.

## Remove a secret

Run `kortix secrets unset STRIPE_API_KEY` (or `unset <identifier>`), or
delete it from the project's Environment variables page.

> **Removal is immediate, propagation is not**
> Kortix deletes a shared secret right away. Push to already-running
> sandboxes is best-effort, the same as rotation.

## Share a value without seeing it

Run `kortix secrets request STRIPE_API_KEY --scope runtime --expires 60` to
create a link. Anyone with the link can enter the value. You never see it.

## CLI commands

| Command | What it does |
|---|---|
| `kortix secrets ls` | List secrets declared and set for the project |
| `kortix secrets set KEY=VALUE [--identifier <id>]` | Create or update a secret. `KEY=-` reads the value from stdin |
| `kortix secrets unset IDENTIFIER` | Remove a secret |
| `kortix secrets request NAME [--scope runtime\|connector] [--expires <min>]` | Create a link so someone else can enter a value |
| `kortix env push --from <path>` | Upload a `.env` file as secrets |
| `kortix env pull [--out <path>] [--force]` | Export secret names, not values, to a `.env` file |

## Scopes reference

Every secret has a `scope`. Default: `runtime`.

| Scope | Injected into the sandbox? | Notes |
|---|---|---|
| `runtime` | Yes, as an environment variable | Supports both shared and personal-override values. |
| `connector` | No | Resolved server-side, inside the connector executor. `GET /secrets` does not list connector-scope secrets. |

Format rules for the two names:

| Name | Format |
|---|---|
| `identifier` | `^[A-Za-z0-9][A-Za-z0-9_.-]{0,127}$` |
| `name` | `^[A-Z_][A-Z0-9_]{0,63}$` |

Reading or writing a secret needs the `project.secret.read` or
`project.secret.write` permission. A custom project role can withhold
either permission, even from a manager.

## REST routes

All routes sit under `/v1/projects/{projectId}`.

| Method | Path | Description |
|---|---|---|
| GET | `/secrets` | List secrets. Scoped to the caller's grant if the caller is a scoped agent token. |
| POST | `/secrets` | Create or update the shared value. Body: `{name, identifier?, value}`. |
| DELETE | `/secrets/{name}` | Delete the shared value. Personal overrides stay in place. |
| PUT | `/secrets/{name}/personal` | Set or turn on the caller's personal override. |
| DELETE | `/secrets/{name}/personal` | Remove the caller's personal override. |

`POST /secrets` rejects names that start with `KORTIX_`. It returns `409`
if the `identifier` already exists with a different `name`. It rejects the
exact name `CODEX_AUTH_JSON` with `400` — Kortix manages that secret
through ChatGPT subscription onboarding.

## Rotation and propagation

A secret write does not wait for a session restart. Kortix pushes the
change to every active sandbox in the project:

1. Kortix builds a new environment snapshot, using the running agent's
   `secrets` grant.
2. The sandbox writes the snapshot to the live agent environment. New tool
   calls pick up the change right away.
3. If the changed secret is an LLM provider credential, Kortix also
   restarts the sandbox's `opencode` process. The next prompt in the
   session uses the new credential.

This push is best-effort. The API call that changes the secret returns
before the push finishes. A failed push is only logged, not retried. A
sandbox with a failed push keeps the old value until the next successful
push, or until the session restarts.
