Tailscale Setec Provider
The Tailscale Setec provider stores versioned secrets in a lightweight service reached and authorized through your tailnet.
At a glance
Section titled “At a glance”| Provider | setec |
| URI | setec://HOST[:PORT][?prefix=PATH][&tls=false] |
| Access | Read, write, delete, and discovery |
| Best for | Self-hosted secrets shared with tailnet workloads |
| Authentication | Tailscale identity and Setec grants |
| Build feature | setec |
| Default storage | secretspec/{project}/{profile}/{key} |
Quick start
Section titled “Quick start”Complete the setup, then point SecretSpec at the Setec server:
$ secretspec set DATABASE_URL --provider setec://secrets.example.ts.netEnter value for DATABASE_URL: postgresql://localhost/mydb✓ Secret 'DATABASE_URL' saved to setec (profile: default)
$ secretspec get DATABASE_URL --provider setec://secrets.example.ts.net
$ secretspec run --provider setec://secrets.example.ts.net -- npm startPrerequisites
Section titled “Prerequisites”- A running Setec server reachable from the machine’s tailnet
- A Tailscale policy granting the caller the required Setec actions
- A SecretSpec build containing the
setecfeature (included by default in 0.21+)
Setec authenticates each request using its Tailscale connection identity. No API token, password, or SecretSpec provider credential is required.
Grant only the actions needed by the workflow:
| SecretSpec operation | Setec actions |
|---|---|
get, check, or run | get |
set or persistent generation | put, activate |
delete | info, delete |
init --from | info |
Setec’s put operation appends an inactive version when a secret already
exists. SecretSpec follows it with activate, making the written value the one
returned by subsequent unpinned reads.
See Setec’s API and permission documentation and server setup guide.
Configuration
Section titled “Configuration”URI format
Section titled “URI format”setec://HOST[:PORT][?prefix=PATH][&tls=false]HOSTis required and normally uses the Setec server’s tailnet DNS name.PORTselects a non-default server port.prefixprepends a slash-separated namespace to convention-created names.- HTTPS is the default. Set
tls=falseexplicitly for a local development server; do not use plaintext HTTP across an untrusted network.
URI paths, userinfo, fragments, and unknown query parameters are rejected.
URI examples
Section titled “URI examples”setec://secrets.example.ts.netsetec://secrets.example.ts.net:8443setec://secrets.example.ts.net?prefix=platformsetec://127.0.0.1:8080?prefix=local&tls=falseProject configuration
Section titled “Project configuration”Check a provider alias into secretspec.toml so the server and namespace are
shared by the team:
[providers]production_setec = "setec://secrets.example.ts.net?prefix=platform"
[profiles.production]DATABASE_URL = { description = "Production database", providers = ["production_setec"] }Storage model
Section titled “Storage model”Convention-managed secrets use this Setec name:
[prefix/]secretspec/{project}/{profile}/{key}For example, DATABASE_URL in project myapp, profile production, and a
platform prefix is stored as
platform/secretspec/myapp/production/DATABASE_URL.
Each set appends a Setec version and activates the returned version. If the
write succeeds but activation fails, SecretSpec reports the stored version so
an operator can inspect or activate it. SecretSpec does not cache Setec values;
each resolution reads the server’s current active version unless a reference
pins one.
Use existing secrets
Section titled “Use existing secrets”Set ref.item to the exact Setec secret name. Add a positive integer
ref.version to read a historical version instead of the active one:
[providers]setec_prod = "setec://secrets.example.ts.net"
[profiles.production]API_TOKEN = { description = "Current API token", ref = { item = "prod/api-token" }, providers = ["setec_prod"] }OLD_SIGNING_KEY = { description = "Signing key version 4", ref = { item = "prod/signing-key", version = "4" }, providers = ["setec_prod"] }Unversioned references can be written and deleted. Version-pinned references are read-only: writing creates a new Setec version, while SecretSpec deletion represents deleting the complete secret rather than one historical version.
Discover existing convention secrets
Section titled “Discover existing convention secrets”Setec discovery lists metadata only and never copies secret values into the manifest. It is bounded to the convention namespace for the requested project and profile:
$ secretspec init --from setec://secrets.example.ts.net?prefix=platform \ --project myapp --profile productionOnly direct children of
platform/secretspec/myapp/production/ become declarations. Other projects,
profiles, and nested names are ignored. The caller needs Setec info access
for names it should discover.
Join the runner or workload to the tailnet, grant its tagged Tailscale identity the minimum Setec actions, and use the same provider URI as local clients. No long-lived Setec credential needs to be injected into the job.
$ secretspec run --provider setec://secrets.example.ts.net -- deploySecurity considerations and limitations
Section titled “Security considerations and limitations”- Setec’s access decisions come from Tailscale identity and grants; access to the hostname alone does not grant a Setec action.
- SecretSpec disables redirects so a Setec request body cannot be replayed to an origin selected by a server response.
tls=falsesends secret values over plaintext HTTP and is intended only for isolated local development.- SecretSpec preserves arbitrary bytes in Setec values (0.21+). Text-only consumers still require valid UTF-8.
- A write can create a new inactive version before
activatefails. The error identifies this partial result; the previously active value remains active.