Secrets
API keys and tokens get their own service: the Secret Store (secret-store
for short), a platform-wide
encrypted catalog you manage in the dashboard, reached from app server code
as the secrets handle.
The idea
Section titled “The idea”Credentials are not data — they’re keys to other doors, and they earn their own storage class with stricter rules than anything else you store. Encrypted at rest, because a credential readable from disk is a credential leaked by any backup or sync. Never in code, chat, or logs, because those surfaces get copied, shared, and kept. And handed out on least privilege: each piece of code gets exactly the credentials it needs, nothing more.
The subtle fundamental is the difference between storing a secret and granting access to it. A good secret store keeps those as two separate acts: the value goes in once, and each consumer is then explicitly granted — or not. That separation is what lets one key serve several apps without being pasted into each, and what lets you see, and revoke, exactly who can use what.
When to use it
Section titled “When to use it”API keys, tokens, passwords — anything that unlocks something. Never put a credential in settings (it would sit readable in the dashboard), in the kv-store, or in the db-store (both store plaintext), and never let one be written into code or logs.
One refinement: when a credential’s only job is authorizing calls to a
provider, prefer a gateway — the key still lives in
this catalog, but you link it into the gateway’s slot and the app never holds
it at all. secrets.get remains for everything else: signing values, SDKs
that need the raw key, anything a gateway can’t carry for you.
How it works in Lodekit
Section titled “How it works in Lodekit”Secrets live in one platform-wide catalog, managed in the dashboard’s Secrets view. You create an entry — a label and a value — and the value is write-only from that moment: the dashboard never shows it back, you can only replace or delete it.
An app never asks for “the catalog”; it declares slots — named needs —
in src/secrets.ts:
import { defineSecrets, secret } from "@lodekit/sdk";
export default defineSecrets({ weather_api_key: secret({ label: "Weather API key", description: "Used to fetch the local forecast.", }),});The dashboard shows the slot on the app’s card (“1 secret needed”) and you link a catalog secret to it. The link is the grant: until you link, the app reads nothing; unlink, and access is revoked without touching the value. One catalog key can be linked into several apps’ slots.
Reading is server-only. A server function calls secrets.get with the slot
name:
const key = await secrets.get("weather_api_key");const res = await fetch(`https://api.example.com/forecast?key=${key}`);Browser code can’t read secrets at all — the value would be visible in
DevTools to any tab. And your agent is deliberately in the same position: it
can see that a slot exists and whether it’s linked, but never the value.
You can safely tell your agent “the app needs a weather key” — it declares
the slot, and you paste the actual key into the dashboard, never into chat.
If a slot isn’t linked yet, secrets.get throws a typed error your app can
catch to degrade gracefully — the SDK reference shows the
pattern.
At rest, values are encrypted with AES-256-GCM, and the master key lives in the macOS Keychain — outside the Lodekit root entirely. That split is the point: your backups contain only ciphertext, so a copied or synced backup never leaks a credential.
Limits
Section titled “Limits”| Limit | Value |
|---|---|
| Value size | ≤ 4 KB |
defineSecrets(), secret(), secrets.get, and the
SecretUnavailableError contract are specified in the
secrets SDK reference.
Your agent sees existence, never values: lodekit_secrets_list returns the
catalog’s names, or one app’s declared slots with their link status. No tool
reads a secret’s value — the agent stays in exactly the position described
above, able to wire slots without ever holding a key. Parameters and details
are in MCP tools.