Secret References
By default, SecretSpec owns the naming: it stores each secret under its own
{project}/{profile}/{key} convention. A secret reference overrides that for
one secret, naming a secret that already exists in the store and is managed
outside SecretSpec. SecretSpec then reads (and writes) that existing secret in
place, instead of a convention path it controls.
You declare a reference with the ref field, a table of provider-independent
coordinates:
[profiles.production]# The 1Password item "db", its "password" fieldDATABASE_URL = { description = "Postgres DSN", ref = { item = "db", field = "password" }, providers = ["prod_vault"] }
# An existing environment variableGITHUB_TOKEN = { description = "GitHub token", ref = { item = "GITHUB_PAT" }, providers = ["env"] }
# A root key in one SOPS file (SOPS provider available in 0.17+)SHARED_TOKEN = { description = "Shared token", ref = { item = "existing_token" }, providers = ["shared_sops"] }Coordinates address a secret from the outside in
Section titled “Coordinates address a secret from the outside in”A ref is not a store-specific address like op://vault/item/field. It is a set
of provider-independent coordinates, each naming a level of structure that some
stores have:
vault which container holds the item (1Password only)└── item the store's own name for the secret (always required) └── section a named group of fields (1Password only) └── field one component inside the item (structured stores) └── version which revision to read (GCSM only)Only item is universal, because every store names its secrets somehow. item
is the complete name, not a suffix: it replaces the entire convention path,
so nothing is prepended.
# Reads the .env key TOTALLY_DIFFERENT_NAME, not secretspec/myapp/default/DATABASE_URLDATABASE_URL = { description = "DB", ref = { item = "TOTALLY_DIFFERENT_NAME" }, providers = ["dotenv"] }The other coordinates exist because some stores give a secret internal structure
(field, section), nest it inside a container (vault), or keep revisions
(version). A store that has no equivalent for a coordinate rejects it with an
error naming the coordinate, rather than silently reading the wrong secret. The
configuration reference documents
exactly how each provider maps the coordinates.
For SOPS (0.17+), item selects a root key in a single-file provider. A
templated SOPS URI cannot be used with a ref because the ref deliberately
replaces the convention address that would otherwise supply {project} and
{profile} for the filename.
References name, providers route
Section titled “References name, providers route”A ref supplies naming only. It does not pin the secret to a particular store.
Which provider actually resolves the coordinates follows the ordinary
provider resolution order: a --provider override, then
the secret’s providers chain, then the profile and global defaults.
This is the difference from pasting a store URL into your config. Because the
store is not baked into the reference, the same ref works across providers.
Each provider in a fallback chain is asked for the same coordinates, and one that
cannot interpret them warns and the chain continues:
[profiles.production]DATABASE_URL = { description = "Postgres DSN", ref = { item = "db", field = "password" }, providers = ["onepassword://Production", "keyring"] }It also means --provider redirects reference secrets exactly like convention
secrets, which makes test fixtures trivial: point every reference at a .env
file without touching the manifest.
$ secretspec run --provider dotenv:.env.fixtures -- cargo testHow it works
Section titled “How it works”itemis required;field,vault,section, andversionare optional and only accepted by stores that have that structure.- Reads and writes are symmetric:
secretspec setand interactivecheckwrite through the coordinates in place wherever the store supports writes. Read-only stores fail with a clear error. refis always a table. String and URI forms (ref = "op://vault/item/field") are rejected, with an error that spells out the equivalent table.- Secrets sharing identical coordinates and store are fetched once, and audit log events carry the coordinates.
See the configuration reference for the full specification: the coordinate table, how every provider interprets each coordinate, and the exact rules.