Secrets
Blocks often need credentials — a database connection string, an AI API key — that must not appear in the pipeline file, logs, or on disk. Spade delivers these through secrets: named values a block reads at run time with get_secret, resolved from a secure store and injected into the sandbox by the runtime.
How it works🔗
The block author reads a secret by a logical name:
from spade import run, get_secret def handler(): dsn = get_secret("db") # logical name — the credential the block needs # ... connect and query ...The pipeline binds that logical name to one of your stored secrets. Only the secret's name appears in the pipeline — never its value:
- id: 019cf4bc-2222-7000-0000-000000000000 name: db.query args: query: "SELECT * FROM parcels" secrets: db: prod-postgres-dsnAt run time the runtime resolves the value — from the OS keychain for a local
spade run, or from the cloud KMS for a cloud run — and injects it into the block.get_secret("db")returns the value.
The value only ever exists in the block process's memory; it is never written to disk.
get_secret in each language🔗
The function is the same across every runtime library — it takes the logical name and returns the value, and raises/returns an error if the secret was not provided.
| Language | Call | Reference |
|---|---|---|
| Python | get_secret("db") | Python handlers |
| R | get_secret("db") | R handlers |
| TypeScript | getSecret("db") | TypeScript handlers |
| Go | GetSecret("db") | Go handlers |
| Rust | get_secret("db")? | Rust handlers |
Setting secrets🔗
For local runs, store secrets in the OS keychain with the CLI:
spade secret set prod-postgres-dsnSee spade secret for the full command reference.
Local vs. cloud🔗
Local secrets (keychain) and cloud secrets (KMS) are independent namespaces, so the same pipeline can resolve a name to a development credential locally and a production credential in the cloud. A block that references a secret must find it in the store for wherever it runs.
How the cloud KMS protects secret values🔗
The cloud store is more than a database column: it's a small, dedicated key-management service (KMS), isolated from the rest of the platform so that a compromise elsewhere doesn't expose secret plaintext.
- Encrypted at rest. Each secret is encrypted with its own per-secret key, which is itself wrapped by a master key held only inside the KMS process. A database breach alone yields ciphertext, not usable values.
- No standing worker credential. A cloud worker never holds a blanket credential to your whole secret store. For each invocation, the scheduler mints a short-lived, invocation-scoped token authorizing only the specific secret names that invocation declared. A compromised worker can read only the secrets of jobs currently flowing through it — not your entire store.
- Audited. Every write, delete, and resolve is logged (who, which secret name, when) for incident response.
None of this is visible from block code — get_secret behaves identically whether it's backed by this cloud machinery or the local OS keychain.
Network access is separate🔗
Reading a secret does not require network access. If a block also needs the network — for example to reach the database whose connection string it just read — its block.yaml must additionally declare network: true. The two are independent: get_secret works without network, and network access is surfaced separately so users can see which blocks reach out.