Named Secrets (system.secret)
A named secret is a first-class SECRET record: a stored, reusable secret
value that you address by name and protect with RBAC. Its value is held
encrypted at rest, is masked wherever it is read from outside a flow, and can be
handed to a connector without its cleartext ever entering your flow.
system.secret('<name>') is a record accessor — the same shape as
system.flow('<name>') or system.setting('<name>'). It resolves the SECRET
record with that name and gives you a handle to its value. Named secrets are
created and maintained with the ordinary record methods — the Console UI,
the MCP tools, the record API, git-sync, and bundles — so there is no bespoke
secret-management surface to learn.
This complements the two other secret features:
- Inline Secrets (
system.wrap_secret) protect a value your flow already holds so it does not leak when serialized. - External secret managers (HashiCorp Vault, Devolutions Server) retrieve a secret from an external store.
Reach for a named secret when a secret is a durable, shared piece of configuration — an API token, a service password, a signing key — that several flows reference by name and whose access you want to govern with roles.
Named secrets are available from Cloudomation Engine 13. The value of a SECRET
record is protected by the workspace secret store, which must be unsealed (a
master key configured — see Configuring the secret
store). On a standard install the
installer configures this for you.
Creating a named secret
A named secret is an ordinary record, so you create it the same way you create
any other resource. Its value is encrypted the moment it is stored, and every
read from outside a flow returns it masked.
From a flow, use the accessor's save() — it upserts the SECRET record by
name:
def handler(system, this, inputs):
system.secret('acme-api-token').save(
value='s3cr3t-token-value',
project_name='Integrations',
)
You can equally create the record through the Console, the MCP create_record
tool (record_type='SECRET'), or in a bundle. However it is created, the stored
value is an encrypted envelope: a display read, an API/MCP read, a log line, a
git-synced file, and a database dump all show *protected*, never the
cleartext.
Referencing a named secret in a flow
Fetch the secret by name and read its value. What you get back is a masked
handle, not the cleartext:
def handler(system, this, inputs):
token = system.secret('acme-api-token')['value']
this.log(f'token is {token}') # logs: token is *protected*
this.connect(
'rest',
name='call ACME API',
scheme='https',
host='api.acme.example',
path='/things',
headers=[{'name': 'Authorization', 'value': token}],
)
The connector receives the real value: Cloudomation reveals the handle to cleartext workspace-side, at the moment the connector runs, and the persisted connector input still carries only the encrypted envelope. Your flow passes the secret through without ever seeing it.
Unlike an inline secret you wrapped yourself, a named secret's cleartext is
never revealable inside a flow. The sandbox that runs flow scripts does not
hold the master key, so calling .reveal() on a named-secret handle raises
SecretStoreSealedError. The value is decrypted only workspace-side when it is
consumed by a connector. This is the intended security property: a flow can
use a named secret via a connector, but cannot read it.
Access control
Access to SECRET records is governed by RBAC, the same as any other
record type:
- Secrets are project-scoped. Grant a role
SECRETread/write on a project to let its members reference (or maintain) the secrets in that project. - An identity without a
SECRETread grant cannot read a secret — a direct record read is denied — even if it can see other resources in the project. - External reads (Console, API, MCP, generic
/record) never return thevaluefield in cleartext; they return it masked.
This is the core advantage of named secrets over keeping secret material inside
connector or configuration value blobs: access is fine-grained, role-based,
and auditable per secret.
Maintaining named secrets
Because a named secret is a normal record, the usual operations apply:
| Operation | How |
|---|---|
| Create / update | system.secret('<name>').save(value=..., project_name=...), the Console, MCP create_record / update_record (record_type='SECRET'), or a bundle |
| Reference in a flow | system.secret('<name>')['value'] → masked handle, revealed at connector-exec time |
| Delete | system.secret('<name>').delete() (add permanently=True to purge), the Console, or MCP delete_record |
| List | The Console resource list, MCP list_records (record_type='SECRET'), or the record API |
Recovering a deleted secret
A SECRET follows the ordinary soft-delete path: a plain delete moves the record
to the trash, where it stays for the workspace's retention
window and can be restored — value intact — from the Console like any other
record. The encrypted envelope travels with the record, so a restore within the
same installation brings the secret's value back unchanged.
Only a permanent delete (system.secret('<name>').delete(permanently=True),
or purging it from the trash) is irreversible. A permanently-purged secret is
recoverable only from a backup taken while it still existed.
A secret's value is encrypted under the workspace secret store's master
key, which is itself unwrapped by SECRET_STORE_PASSPHRASE (held in the
installation configuration; see Configuring the secret
store). This passphrase is the
linchpin for every stored secret: lose or change it and the wrapped master key
cannot be unwrapped, so every named and inline secret becomes unrecoverable
and reads fail closed with a SecretStoreSealedError.
Keep SECRET_STORE_PASSPHRASE stable across deploys, and keep a copy of it
escrowed off-host (a password manager or an operations-controlled vault). A
backup or database dump carries only ciphertext: restoring it into an
installation where the matching passphrase is unknown leaves those secrets
unrecoverable. The Engine Installer guide's backup section carries the
operations runbook for escrowing the passphrase off-host.
A bundle that carries a SECRET record does not transport a usable cleartext to
a workspace with a different secret store; recreate the secret's value in the
target workspace.
Related
- Inline Secrets (
system.wrap_secret) — protect a value your own flow holds. - Protecting Child Outputs (
secret_outputs) — mask sensitive leaves of a child execution's output. - Secret Handling with Vault and Devolutions — retrieve secrets from an external secret manager.
- Role-Based Access Control — grant per-project access to
SECRETrecords.