Skip to content

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.

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.

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.

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:

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.

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.