Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions .changeset/stash-env-mint-credentials.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
---
'stash': minor
---

`stash env` now works: it mints deployment credentials from your device-code
session and prints them as env vars — no dashboard copy-paste. The command
creates a fresh ZeroKMS client and a member-role CipherStash access key (named
via `--name`; the role is pinned in the request and verified on the response —
the CLI deliberately cannot mint admin keys), then emits `CS_WORKSPACE_CRN`,
`CS_CLIENT_ID`, `CS_CLIENT_KEY`, and `CS_CLIENT_ACCESS_KEY`.

Output goes to stdout by default — and stdout is pipe-clean (progress UI is on
stderr), so `stash env --name x > prod.env` and pipes into secret stores are
safe. `--write [path]` writes a file instead (default `.env.production.local`,
enforced mode 0600 even when overwriting), confirming before overwriting and
refusing non-interactively — always *before* anything is minted, so a refusal
never discards the shown-exactly-once access key. `--json` emits NDJSON; with
`--write` the confirmation event is deliberately secret-free. API responses
are schema-validated so a service change can never print `undefined` into a
credentials file. Creating access keys requires the admin role in the
workspace.

This is also the supported credential path for WASM/edge local development
(Supabase Edge Functions, Cloudflare Workers, Deno), where the runtime cannot
read the `~/.cipherstash` device profile: mint a key and feed it via
`supabase functions serve --env-file` or the platform's secret store.

The `STASH_EXPERIMENTAL_ENV_CMD` gate is removed.
16 changes: 14 additions & 2 deletions packages/cli/src/bin/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ Commands:
encrypt cutover Rename swap encrypted → primary column (EQL v2 only)
encrypt drop Generate a migration to drop the plaintext column

env (experimental) Print production env vars for deployment
env Mint deployment credentials and print them as env vars

Options:
--help, -h Show help
Expand Down Expand Up @@ -532,7 +532,19 @@ async function dispatch(
await runSchemaCommand(subcommand, flags, values)
break
case 'env':
await envCommand({ write: flags.write })
await envCommand({
// parseArgs puts `--write path/x` in values and bare `--write` in
// flags — accept both so a path after --write targets that file
// instead of silently printing secrets to stdout.
write: values.write ?? flags.write,
json: flags.json,
name: values.name,
// `--name` followed by another flag (or nothing) is booleanised by
// parseArgs; surface it as its own error instead of missing_name.
nameMissingValue: flags.name === true,
// `stash env my-app` would otherwise vanish into `subcommand`.
unexpectedArg: subcommand ?? commandArgs[0],
})
break
case 'manifest':
// Pure metadata (no native code) — safe to run anywhere, including when
Expand Down
40 changes: 37 additions & 3 deletions packages/cli/src/cli/registry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -530,15 +530,49 @@ export const registry: CommandGroup[] = [
],
},
{
title: 'Experimental',
title: 'Deployment',
commands: [
{
name: 'env',
summary: '(experimental) Print production env vars for deployment',
summary: 'Mint deployment credentials and print them as env vars',
long: [
'Mints a fresh ZeroKMS client and a CipherStash access key from your',
'device-code session (`stash auth login`), then prints the four env',
'vars a deployed app needs: CS_WORKSPACE_CRN, CS_CLIENT_ID,',
'CS_CLIENT_KEY, CS_CLIENT_ACCESS_KEY.',
'',
'The access key is created with the member role (the CLI never mints',
'admin keys) and is shown exactly once — pipe the output into your',
'deployment secret store. Creating access keys requires your user to',
'have the admin role in the workspace.',
'',
'Stdout carries only the dotenv block (or the --json events);',
'progress UI goes to stderr, so `stash env --name x > prod.env`',
'and pipes into secret stores are safe.',
].join('\n'),
examples: [
'env --name my-app-prod',
'env --name my-app-prod --write',
'env --name staging --write .env.staging.local',
'env --name edge-dev --json',
],
flags: [
{
name: '--name',
value: '<name>',
description:
'Name for the minted access key and ZeroKMS client. Prompted for interactively; required in non-interactive runs.',
},
{
name: '--write',
description: 'Write the vars to a file instead of printing them.',
value: '[path]',
description:
'Write the vars to a file (default .env.production.local, mode 0600) instead of printing them. An existing file prompts before overwriting — and is refused non-interactively — before anything is minted.',
},
{
name: '--json',
description:
'Emit machine-readable NDJSON (a { status: "minted" } object, or { status: "written" } with --write — deliberately secret-free since the secrets are in the file; failures are { status: "error" }). Implies no prompts.',
},
],
},
Expand Down
Loading
Loading