Skip to content

    Secret references

    A plan carries a pointer to a secret, never the secret. That is what makes a plan safe to read, review, and keep in git.

    Opafra never stores a secret inside a plan. A plan carries a reference, resolved at the moment of use, and the value goes to the host without ever being written into the plan, the run log, or a preview.

    a reference, not a password
    - name: Flush the app database logs
      tool: ssh.cmd
      args:
        command: "mysql -u root -p'secret://opafra/mysql_root' -e 'FLUSH LOGS'"

    The shapes of a reference#

    A reference names where the secret lives, then which secret it is. There are three forms.

    secret://opafra/<key>
    secret://opafra/env/<environment id>/<KEY>
    secret://<connection name>/<path>/<key>
    • secret://opafra/<key> is a secret shared across the workspace, stored in Opafra. You create it under Inventory, Secrets, with the scope set to shared. Its key is saved in lowercase.
    • secret://opafra/env/<environment id>/<KEY> is an environment secret variable, stored in Opafra. You normally never write this one: a plan reaches it as {{ secrets.KEY }}, and the environment the run targets decides which value that is. Its key keeps its case.
    • secret://<connection name>/<path>/<key> is a secret in one of your workspace's own connections, for example secret://prod_vault/apps/db/password. It resolves only through the connection with that name in the workspace running the plan.

    When you store a secret through Opafra, the response carries its full reference, so you rarely assemble one by hand. The Secrets page shows it on each secret.

    A reference that is malformed, or that names neither opafra nor one of your connections, is refused before any store is asked.

    Where a secret created in Opafra lives#

    Secrets you create in Opafra (shared secrets, environment secret variables, and server credentials kept locally) are stored in Opafra's database, encrypted with a data key unique to your workspace. That data key is itself encrypted by a root key held outside the database, by whoever operates your Opafra deployment.

    So a copy of the database alone reveals no secret, and one workspace's key opens nothing belonging to another. Nothing to connect, nothing to configure: this is the default store.

    Where a reference resolves#

    A reference resolves only inside its own workspace (organization or personal workspace). The workspace comes from the plan being run, never from the reference.

    • A secret://opafra/... reference reads only the running workspace's own secrets, so it cannot be used to read, or even probe for, somebody else's.
    • A secret://<connection name>/... reference resolves through the connection of that name in the running workspace. Another workspace having a connection with the same name changes nothing. With no such connection, the step fails closed.

    An environment secret is narrower still. It resolves only for hosts in that environment: a step running on a staging host cannot read a production environment's secret, even in the same organization. A shared secret resolves for every host in the workspace.

    env: references#

    A second scheme reads a variable from the environment of the Opafra server itself:

    env:VAR_NAME

    It is accepted only as the credential of a discovery source (Inventory, Discovery), not in a plan, and it resolves only for variable names your Opafra operator has put on an allow-list (SECRET_ENV_REF_ALLOWLIST, a comma-separated list). The list is empty by default, so out of the box no env: reference resolves: the source's preview or sync stops with a message naming the variable and pointing at secret:// instead. The allow-list exists because the server's environment also holds its own keys and database address, and no tenant should be able to name those.

    Use secret:// for everything a team manages. Reach for env: only when your operator has deliberately provided a credential that way.

    Why a reference rather than encryption#

    Encrypting a secret in the plan still puts the secret in the plan. Every copy of that plan, every export, every backup, and every screen showing it then contains the material, protected by a key that also has to live somewhere.

    A reference is different in kind. It is not sensitive, so a plan holding one can be:

    • Read by anyone reviewing the automation, including people who should never see the credential itself
    • Kept in git, diffed in a pull request, and reviewed like code
    • Copied between environments, where a secret variable resolves to that environment's own value
    • Shown in a dry run to an approver, who sees exactly what would run without being handed a password

    That last point is the one that matters most in practice. Review and secrecy stop being in tension.

    Where a reference can be used#

    Anywhere a value is expected: a tool argument, a command, an environment variable, a server credential.

    An environment variable can be marked secret, in which case it is stored in Opafra, encrypted with the workspace's key. So {{ secrets.DB_PASSWORD }} in a plan resolves through the environment, and the plan names neither the value nor where it is kept. A secret variable whose value is itself a secret:// reference is stored as that reference, which is how an environment points at a secret in one of your connections.

    At run time#

    A reference in a command or a tool argument is resolved by Opafra when the step runs, just before the command is sent to the host. The value is substituted with quoting that matches where the reference sits, so a value containing a quote or $(...) cannot break out of the command around it.

    Before the step starts, every form the value can take is registered with the run's redaction, so wherever it would appear in the live console or the stored run log, it is shown as [REDACTED]. The command itself is displayed and stored with the reference, not the value.

    A reference that does not resolve fails the step before it runs: malformed, naming a connection this workspace does not have, belonging to another environment, or with no value in the store. The error names the reference and the reason. The literal reference is never sent to the host, because running a command with the wrong value is worse than not running it.

    Each resolution is written to the audit log as a secret read, with the reference and the step, never the value.

    Redaction#

    References are redacted everywhere a command or value is displayed: in a dry-run preview, in the live run console, in the stored run record, and in stderr captured from a failed connection.

    Redaction over-matches on purpose. Any argument whose name looks like a credential is masked as well, and the same masking runs over rendered config file contents in a diff. Hiding something that was not a secret is a smaller problem than printing one that was.

    What is stored#

    The reference. A value you give Opafra is stored encrypted with the workspace's key and comes back from the API only as ••••••••. A server credential can instead be kept in one of your connections, where Opafra writes it under opafra/servers/<server name>, a path it sets rather than asks for.

    A generated SSH private key is the strongest case: it is shown once, when created, and no endpoint returns it a second time.

    Next steps#