Project secrets

Encrypted per-workspace key/value store. Injected into agent runs and shell commands as env vars.

Why

Secrets you don't want in your code: API keys, DB passwords, signing keys, OAuth tokens. Set them once per workspace, KrowForge injects them at run time.

Set a secret

POST /api/secrets/<name> with the value in the body. Or in the UI: Workspace → Secrets → Add.

Once set:

  • The value is encrypted and written to .krowforge/secrets.enc.
  • The plaintext value never leaves the server.
  • The browser sees only the prefix (first 4 chars + ***).

List

GET /api/secrets returns names + previews + last-modified:

[
  { "name": "STRIPE_SECRET", "preview": "sk_l***", "updated_at": "…" }
]

Reveal

GET /api/secrets/<name> returns the plaintext once, with a confirmation modal. Logged in the audit log.

How it gets injected

When you run a shell command or an agent task, services/project_secrets.inject_env():

  1. Decrypts the secret store with the workspace's derived key.
  2. Adds each secret as an environment variable to the subprocess env.
  3. The subprocess (your Python, Node, agent, etc.) reads them via os.environ.

For agent tasks, the agent does not see the values — it sees the names. The values are only present in the subprocess env. This means an agent can call os.environ['STRIPE_SECRET'] in code it generates, but it can't print the value into chat.

Encryption

  • Per-workspace derived key (HKDF from server master key + workspace ID).
  • Fernet (AES-128-CBC + HMAC-SHA256).
  • Server master key from KROWFORGE_SECRET_KEY env var or /etc/krowforge/secret_key.
If you lose the master key, every workspace's secret store becomes unreadable. Back it up.

Rotation

POST /api/secrets/rotate-master re-encrypts all secret stores with a new master key. Old key must still be available during rotation.

Deletion

DELETE /api/secrets/<name> removes the secret from the store. Audit-logged.

Project secrets vs system env

Project secrets only inject into workspace subprocess. System env vars on the server are still visible to the agent's tool calls (via os.environ reads in code). To truly isolate, run the workspace inside a container (self-host).