---
title: Dynamic Secrets
path: core-concepts/dynamic-secrets
status: published
---

# Dynamic Secrets

A dynamic secret is a credential that ScaiVault generates on demand, for a specific caller, with a short lifetime. When the lifetime expires, ScaiVault automatically revokes it at the source. Compared to static credentials stored in a secret, dynamic secrets drastically reduce blast radius: if one leaks, it's valid for minutes, not months.

## The mental model

A static secret:

```mermaid
sequenceDiagram
    participant App
    participant SV as ScaiVault

    App->>SV: read app/db/password
    SV-->>App: "xyz..."
    Note over SV: same password for everyone<br/>rotates quarterly
```

A dynamic secret:

```mermaid
sequenceDiagram
    participant App
    participant SV as ScaiVault
    participant DB as Postgres

    App->>SV: POST /dynamic/engines/.../creds/readonly
    SV->>DB: CREATE ROLE v_readonly_abc PASSWORD ...
    DB-->>SV: ok
    SV-->>App: lease (username, password, 1h TTL)
    Note over App,DB: app uses creds for 1h
    Note over SV: 1h later
    SV->>DB: DROP ROLE v_readonly_abc
    DB-->>SV: ok
```

Every caller gets their own short-lived credential. ScaiVault handles creation on the source system (CREATE USER, IAM policy, SSH cert) and revocation when the lease expires.

## Pieces

- **Engine.** A configuration pointing at a specific backend (a Postgres server, an AWS account, a GCP project). Has connection info and root credentials.
- **Role.** A recipe for what kind of credential to generate from an engine. Names the permissions (`SELECT ON public.*`), constraints (max TTL), and any templated statements.
- **Lease.** The result of generating credentials. Has an ID, a TTL, and the credential itself (username, password, token, etc.).

## Supported engines

| Engine | Generates | Typical TTL |
|--------|-----------|-------------|
| `database.postgresql` | DB user with GRANT/REVOKE | 1–8h |
| `database.mysql` | DB user | 1–8h |
| `database.mongodb` | DB user | 1–8h |
| `database.redis` | Redis ACL user | 1–8h |
| `aws` | IAM access key or assumed role | 15min–12h |
| `azure` | Service principal | 1–8h |
| `gcp` | Service account key or impersonated token | 1h–3h |
| `ssh` | Signed SSH cert or one-time password | 5min–1h |
| `custom` | Whatever you script | Your call |

## Creating an engine

An engine needs root credentials for the target system. Those credentials are stored in ScaiVault itself — typically at a path protected by a narrow policy so only the engine machinery can read them.

```bash
curl -X POST https://scaivault.scailabs.ai/v1/dynamic/engines \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "support-db",
    "type": "database",
    "config": {
      "plugin": "postgresql",
      "connection_url": "postgresql://{{username}}:{{password}}@db.internal:5432/support",
      "root_credentials_path": "infra/db/support/root"
    },
    "default_ttl": "1h",
    "max_ttl": "24h"
  }'
```

`root_credentials_path` points at another secret in ScaiVault. On startup, the engine reads that secret and substitutes it into `connection_url`. You can rotate the root creds the same way you rotate any secret — including on a rotation policy.

## Defining a role

A role says: "when someone asks for credentials from this engine with this role name, run these statements."

```bash
curl -X POST https://scaivault.scailabs.ai/v1/dynamic/engines/support-db/roles \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "readonly",
    "creation_statements": [
      "CREATE ROLE \"{{name}}\" WITH LOGIN PASSWORD '\''{{password}}'\'' VALID UNTIL '\''{{expiration}}'\''",
      "GRANT USAGE ON SCHEMA public TO \"{{name}}\"",
      "GRANT SELECT ON ALL TABLES IN SCHEMA public TO \"{{name}}\""
    ],
    "revocation_statements": [
      "REVOKE ALL PRIVILEGES ON ALL TABLES IN SCHEMA public FROM \"{{name}}\"",
      "DROP ROLE IF EXISTS \"{{name}}\""
    ],
    "default_ttl": "1h",
    "max_ttl": "8h"
  }'
```

Template variables:

- `{{name}}` — auto-generated username (e.g. `v_readonly_a1b2c3d4`).
- `{{password}}` — auto-generated password.
- `{{expiration}}` — the lease expiration in Postgres timestamp format.

## Generating credentials

An application with `dynamic:generate` on this engine asks for a credential:

```bash
curl -X POST https://scaivault.scailabs.ai/v1/dynamic/engines/support-db/creds/readonly \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"ttl": "2h"}'
```

Response:

```json
{
  "lease_id": "lease_abc123xyz",
  "data": {
    "username": "v_readonly_a1b2c3d4",
    "password": "kX9#mP2$vL5@nQ8&wR3!",
    "connection_url": "postgresql://v_readonly_a1b2c3d4:...@db.internal:5432/support"
  },
  "lease_duration": "2h",
  "renewable": true,
  "expires_at": "2026-04-23T22:00:00Z"
}
```

ScaiVault:

1. Picks a username (`v_<role>_<random>`).
2. Generates a random password.
3. Connects to Postgres as root, runs the creation statements.
4. Issues a lease with ID `lease_abc123xyz`.
5. Returns the credential to you.

The lease is yours for 2 hours. After that, ScaiVault connects back and runs the revocation statements — the user disappears from Postgres.

## Lease lifecycle

```mermaid
stateDiagram-v2
    [*] --> Active: POST /creds/{role}
    Active --> Active: renew (within max_ttl)
    Active --> Revoked: DELETE lease
    Active --> Expired: TTL elapsed
    Revoked --> [*]: revocation_statements run
    Expired --> [*]: revocation_statements run
```

**Renew.** Extend the lease without changing the credential:

```bash
curl -X POST https://scaivault.scailabs.ai/v1/dynamic/leases/lease_abc123/renew \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"increment": "1h"}'
```

Can't extend past `max_ttl`. After that, you must generate a new credential.

**Revoke.** Force immediate revocation (the credential stops working instantly):

```bash
curl -X DELETE https://scaivault.scailabs.ai/v1/dynamic/leases/lease_abc123 \
  -H "Authorization: Bearer $TOKEN"
```

**Revoke by prefix.** Bulk revoke during an incident — every lease for a compromised engine, or every lease issued by a specific role:

```bash
curl -X POST https://scaivault.scailabs.ai/v1/dynamic/leases/revoke-prefix \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"prefix": "lease_db_", "engine": "support-db"}'
```

## Using dynamic credentials in practice

The idiomatic pattern:

```python
import httpx, time

def with_db_credential(func):
    def wrapper(*args, **kwargs):
        # Ask for a fresh lease
        resp = httpx.post(
            "https://scaivault.scailabs.ai/v1/dynamic/engines/support-db/creds/readonly",
            headers={"Authorization": f"Bearer {TOKEN}"},
            json={"ttl": "2h"},
        )
        lease = resp.json()
        try:
            return func(lease["data"], *args, **kwargs)
        finally:
            httpx.delete(
                f"https://scaivault.scailabs.ai/v1/dynamic/leases/{lease['lease_id']}",
                headers={"Authorization": f"Bearer {TOKEN}"},
            )
    return wrapper

@with_db_credential
def run_report(creds):
    # creds["username"], creds["password"], creds["connection_url"]
    # use them to connect and run your query
    ...
```

For longer-lived workers, renew before expiration instead of regenerating.

## AWS, Azure, GCP engines

AWS: ScaiVault assumes a role or creates IAM users. Configure the engine with a role ARN or an IAM user with the `iam:CreateAccessKey` permission. Dynamic creds are short-lived IAM keys or STS tokens.

```json
{
  "name": "prod-aws",
  "type": "aws",
  "config": {
    "region": "us-east-1",
    "root_credentials_path": "infra/aws/prod/admin"
  }
}
```

Azure / GCP: analogous. Roles specify Azure RBAC roles or GCP IAM roles.

## SSH engine

Generates signed SSH certificates or one-time passwords for SSH logins. Pairs well with an SSH CA trust on your hosts.

```bash
curl -X POST https://scaivault.scailabs.ai/v1/dynamic/engines/ssh-prod/creds/operator \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "username": "alice",
    "public_key": "ssh-ed25519 AAAA...",
    "ttl": "30m"
  }'
```

Returns a signed OpenSSH certificate valid for 30 minutes.

## What's next

- [Dynamic Secrets Guide](../api-guides/dynamic-secrets) — end-to-end setup for Postgres.
- [Dynamic Reference](../reference/dynamic) — endpoints.
- [Rotation](./rotation) — rotating the engine's root credentials.
