Skip to main content
Version: 13 - TBD

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:

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.

Availability

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.

Cleartext never enters the flow

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 SECRET read/write on a project to let its members reference (or maintain) the secrets in that project.
  • An identity without a SECRET read 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 the value field 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:

OperationHow
Create / updatesystem.secret('<name>').save(value=..., project_name=...), the Console, MCP create_record / update_record (record_type='SECRET'), or a bundle
Reference in a flowsystem.secret('<name>')['value'] → masked handle, revealed at connector-exec time
Deletesystem.secret('<name>').delete() (add permanently=True to purge), the Console, or MCP delete_record
ListThe 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.

Escrow the secret-store passphrase — losing it destroys every secret

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.

Moving secrets between workspaces

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.