Skip to content

Secrets

Plain credentials never appear in flow YAML, environment YAML, or connection YAML. They are referenced as ${secret:path} (whole-value) or ${secret:path:key} (sub-key form, useful when one secret entry has multiple fields like host/username/password).

# In a connection file
Host: "${secret:sftp/partner:host}"
UserName: "${secret:sftp/partner:username}"
Password: "${secret:sftp/partner:password}"

Secrets are resolved at flow start in a single async batch. A flow that fails secret resolution fails before any operator runs — there is no half-failed state where one step ran with a real credential and a later step crashed on a missing one. There is also no mid-flight credential swap: a running flow continues with the values it resolved at start.

Three surfaces, same primitives:

Terminal window
# CLI — one secret at a time
zen secrets set prod/db connection-string="Server=prod-db;Database=warehouse;…"
Terminal window
# REST
curl -X POST http://localhost:5000/api/v1/platform/entities/secret \
-H "Authorization: Bearer $TOKEN" \
-d '{"path":"prod/db","value":{"connection-string":"…"}}'
# MCP — Claude Code, VS Code, or any MCP client
Tool: set-secret args: { Path: "prod/db", Value: { ... } }

Rotation is a re-set: write the new value to the same path. The change takes effect for any invocation that starts after the write.

Two providers:

  • Encrypted file — AES-256-GCM with a 256-bit key, single-host. The encryption key comes from one of three sources: a direct base64 key, a file path, or a PBKDF2 password (100,000 SHA-256 iterations). The file lives at auth/secrets.enc and is never git-tracked (**/*.enc is a hardcoded exclude).
  • HashiCorp Vault — for multi-host and centralised secret management.

Secrets do not travel through git config sync — replicate them out-of-band (a shared Vault, or a copied secret blob + key).

The modern form ties a connection to a secret declaratively. A credentials: block names a multi-key serviceAccount: secret plus a fields: map of yamlField → blobKey:

type: zenvara/sftp
credentials:
serviceAccount: sftp/partner
fields:
Host: host
UserName: username
Password: password

Per-user credentials override the service-account default when personal/<owner>/<connection> is set — <owner> is the underlying user id. Global API keys, triggers, cron, and queue invocations silently use the service account by construction.

A policy says “flows tagged X may read paths matching Y” — this is what stops a careless author from referencing production credentials in a dev pipeline. The policy file (auth/secrets-policy.yaml) is configured by an administrator; flow authors interact with it through tags.

Tags live on a sibling annotation keyed by entity, not in the flow YAML — they are governance metadata, not pipeline logic. Add one with add-tag (MCP/REST): entityType: flow, entityId: my-flow, tag: production. The policy engine reads the annotation at flow-start. Path-based access rules are separate from user roles, so a dev flow cannot reach prod secrets even if the invoking user is an admin.