managing-secrets · git:20260929.a1388c6 · 2026-09-29 · sha256 96f00225c692b483
managing-secrets git:20260929.a1388c6A
Immutable. This exact content never changes and is served at /api/v1/blob/96f00225c692b483.
--- name: managing-secrets description: Import, export, rotate, and inject Turnkey Secrets with tk, including secret env as a process's startup environment and finishing an approval-gated export. Use to store a value an agent will read, give a process its environment, complete a pending export, or replace a value; not for wallet or private-key export. --- # Managing secrets Result: named, property-tagged secrets that the right agent reads at startup with one command, with approval-gated values handled by rerunning that same command after a human approves. ## Reference - [secrets](../../docs/secrets.md): `secret` command records, `secret env` quoting, and pending-export state. ## Rules - Values never enter the transcript. Import from `--from-file` or a pipe; export to `--out` or into the consuming process. Verify by exit code and metadata. - Every secret carries exactly one `consensus` property, `unilateral` or `approval`, and is named `<scope>/<VAR>` so `secret env` can build an environment. Properties are immutable and policy-visible; never put a secret value in one. - Export and `env` are rerun, not resumed: the same credential from the same home directory picks up its own pending export. - Access is granted by policy on the property, not by name prefix. The prefix selects; the policy authorizes. ## Instructions Inputs: the root profile (`admin`), the agent's profile (`agent`), the scope prefix (`service/`), and for each value its file and approval level. The export policies from [policy-patterns.md](../references/policy-patterns.md) must already exist. 1. **Import a value.** Root, from a file the agent must not read: <!-- example: secrets.import --> ```sh tk --profile admin --message-format json secret import --name service/API_TOKEN --property consensus=unilateral --from-file ./api-token.txt tk --profile admin --message-format json secret import --name service/DEPLOY_KEY --property consensus=approval --from-file ./deploy-key.txt ``` Each record is `command: "secret.import"` with `data.secretId`, `data.name`, and `data.activity`. Import is not idempotent; a `pending` import is waited on, never repeated. Add further `--property` flags for isolation scopes the policies select. 2. **List metadata.** Any identity. <!-- example: secrets.list --> ```sh tk --profile agent --message-format json secret list --limit 100 ``` `data.secrets[]` carries `secretId`, `name`, `staticProperties[]` (`key`, `value`), and `createdAtUnixMs`; `data.nextCursor` pages. Values are never listed. 3. **Build the agent's environment.** The agent's process, at startup: <!-- shared: secret-env-startup --> <!-- example: secrets.env --> ```sh tk --profile agent --message-format json secret env --name-prefix service/ --property consensus=unilateral ``` Exit `0`: `data.env` maps each `VAR` to its value and `data.exported[]` names the secrets; in human mode the same command prints dotenv lines for the process to source. The property filter excludes approval-gated secrets silently. Without the filter, any approval-gated secret in the selection makes the command exit `1` with `code: "approval_required"` and `details.pending[]` (`name`, `secretId`, `activityId`), and prints no values at all. 4. **Finish an approval-gated read.** Submitter runs the export; a human approves; the submitter reruns the identical command: <!-- example: secrets.export-approved --> ```sh tk --profile agent --message-format json secret export --name service/DEPLOY_KEY --out ./deploy-key tk --profile approver --message-format json activity approve --id ACTIVITY_ID tk --profile agent --message-format json secret export --name service/DEPLOY_KEY --out ./deploy-key ``` The first run returns `status: "pending"`, `activity.id`, and `data.nextStep`; nothing is written. The rerun returns `completed` with `data.out`. `--out` refuses an existing path. Rerunning while the approval is outstanding returns the same `pending` activity, not a second one. 5. **Rotate a value.** Root. Secrets are immutable and names are unique, so a rotation is a delete followed by an import under the same name with the same properties. The name is unavailable between the two. <!-- example: secrets.rotate --> ```sh tk --profile admin --message-format json secret delete --name service/API_TOKEN tk --profile admin --message-format json secret import --name service/API_TOKEN --property consensus=unilateral --from-file ./api-token-new.txt ``` Wait for a `pending` deletion before importing. The new secret has a new id. Deleting does not revoke a value already exported; rotate the credential at its provider first when access is being reduced. 6. **Hand off.** Report each secret's id, name, and properties, the pending activity ids still awaiting approval, and the exact `secret env` command the process runs. Stop. ## Runtime examples The process, not the agent in conversation, runs step 3 and sources its output. Hermes Agent, for example, takes the command as `secrets.command`; a systemd unit, for example, runs it in `ExecStartPre` and writes to an `EnvironmentFile`. Approval-gated secrets stay out of startup paths that cannot wait. ## Verified by | Examples | Test | |---|---| | secrets.import, secrets.list, secrets.env, secrets.rotate | secrets::managing_secrets_env_and_rotation | | secrets.export-approved | secrets::secret_export_with_consensus_finishes_by_rerunning_the_command | | secrets.env | secrets::secret_env_exports_matching_secrets_as_dotenv | ## Troubleshooting - `secret env` exits `1` with `approval_required`: an `approval` secret is in the selection. Either add `--property consensus=unilateral` for the startup path, or approve each `details.pending[].activityId` and rerun. - `secret export` fails with `unauthorized` 403: no export policy selects this user for this secret's properties. Inspect `tk --profile admin --message-format json policy evaluations --activity-id ACTIVITY_ID` when the error carries an activity; otherwise compare the user's tags with the policy consensus. Do not switch to root. - `secret export` fails with `api_error` and `details.activity`: the export was rejected or failed; local state is cleared and the next run starts a new export. - `secret import` fails with `api_error` 400: the name is taken. A pending deletion holds the name until it completes. - `secret env` fails with `invalid_input` "no secrets match": the prefix or property selects nothing in this organization. Check `secret list`. - `secret export` from a different machine or profile returns a new `pending`: pending state belongs to the credential and home that started it. Finish from the original. ## Related Skills - [managing-policies](../managing-policies/SKILL.md): the export policies that make step 3 succeed. - [monitoring-activities](../monitoring-activities/SKILL.md): approve a pending export or deletion. - [managing-identities](../managing-identities/SKILL.md): the agent user and profile that runs `secret env`.