Skip to content
Ironflow is pre-1.0: APIs can change between releases. Pin a version. What is ready →

Secrets Management

Ironflow provides environment-scoped secrets management with AES-256-GCM encryption backed by NATS KV. Secrets are write-only—values can be set but never retrieved through the API, CLI, or Dashboard.

In the dashboard, existing secret names are read-only because functions reference them by name. You can edit a secret’s description. Creating and deleting secrets remain available.

Every secret operation (create, update, delete) is a JetStream message. This ensures that secret state inherits the same durability and replication guarantees as your workflows.

  • Atomic: Secret updates use native JetStream revision tracking.
  • Observable: Create, update and delete emit the system events system.secret.{name}.created, .updated and .deleted (e.g., system.secret.STRIPE_KEY.updated).
  • Declarative: Functions declare exactly which secrets they need; the engine resolves and injects them only at runtime.

The architecture enforces a strict “Management vs. Runtime” separation:

  • Write-Only API: The CLI and Dashboard only expose metadata (revision, name). Plaintext values are never returned.
  • Runtime Injection: Values are only decrypted when a worker receives a job.
  • Master Key: Encryption is controlled by the IRONFLOW_MASTER_KEY environment variable, or by spec.auth.masterKey in the config file. The value must be a hex-encoded 32 bytes (generate with openssl rand -hex 32).

1. Set a secret via CLI:

Terminal window
ironflow secret set STRIPE_KEY sk_live_123 --env env_default --description "Stripe live key"

The --env / -e flag targets a specific environment; when omitted it falls back to IRONFLOW_ENV, then to current, the environment of your API key. The value is an environment ID, such as env_default. All ironflow secret subcommands (set, get, list, delete) accept --env. set also accepts --description / -d.

2. Declare and use in a function:

import { ironflow } from "@ironflow/node";
export const payout = ironflow.createFunction(
{
id: "payout",
triggers: [{ event: "payout.requested" }],
secrets: ["STRIPE_KEY"], // Declare dependency
},
async ({ event, secrets, step }) => {
// Access the decrypted value
const apiKey = secrets.get("STRIPE_KEY");
await step.run("charge", () => stripe(apiKey).charge(...));
}
);

In Python, ctx.secrets is a read-only mapping:

from ironflow.worker import function
@function(id="payout", triggers=[{"event": "payout.requested"}], secrets=["STRIPE_KEY"])
async def payout(ctx):
api_key = ctx.secrets["STRIPE_KEY"] # the decrypted value
await ctx.step.run("charge", lambda: stripe(api_key).charge(ctx.event.data))

FeatureUse For
SecretsAPI Keys, DB Passwords, Certificates.
ConfigRollout percentages, Theme settings, Timeouts.
KV StoreSessions, Caching, Counters.

You can subscribe to secret lifecycle events to audit rotations or trigger security workflows.

import { ironflow } from "@ironflow/browser";
await ironflow.subscribe<{ name: string }>("system.secret.*.updated", {
onEvent: (e) => console.log(`Secret ${e.data.name} was rotated!`),
});
from ironflow import IronflowRPC
from ironflow.rpc.v1 import SubscribeRequest
with IronflowRPC(server_url="http://localhost:9123") as rpc:
for e in rpc.pubsub.subscribe(SubscribeRequest(pattern="system.secret.*.updated")):
print(f"Secret {e.data.to_python()['name']} was rotated!")