diff --git a/docs/product/cli-style-guide.md b/docs/product/cli-style-guide.md index 51149b70..d747b20d 100644 --- a/docs/product/cli-style-guide.md +++ b/docs/product/cli-style-guide.md @@ -94,7 +94,7 @@ Rules: - align keys in a compact column - use accent color for keys and default text for values - prefer display labels in default human output and keep opaque ids in JSON unless a later verbose mode explicitly asks for them -- mask sensitive values rather than omitting their presence entirely when the value matters to the flow +- print values bare; a secret the command exists to hand over is never masked, because the human card is where its owner reads it (operator ruling, 2026-08-26) - include only rows that are actually known for the current command - use human labels such as `Not linked` instead of internal resolution terms such as `unbound` - hide internal resolution terms such as `local pin` from default human output when the visible binding is clearer @@ -212,8 +212,7 @@ Non-TTY behavior should be automation-friendly: - Do not rely on color alone. - Keep text compact and translatable. -- Never print secrets. -- Scrub sensitive values in logs, errors, and previews. +- Never leak secrets into logs, errors, telemetry, or previews. A secret the command exists to hand over prints bare, once. ## Design Rule diff --git a/docs/product/output-conventions.md b/docs/product/output-conventions.md index 8f4efd1e..a3614ea2 100644 --- a/docs/product/output-conventions.md +++ b/docs/product/output-conventions.md @@ -186,16 +186,13 @@ No current MVP command uses `verify` or `inspect`, but new commands must still c ### One-Time Secret Output -Commands that create one-time-view secrets may write the raw secret value to -stdout in human mode. This is still machine-readable output, not decorative -human output. +Commands that create one-time-view secrets print the secret bare in the human card and write the raw value to stdout. The card is the only place an interactive user ever sees the secret — when stdout and stderr render to one screen the stdout mirror is skipped, so masking the card would hide the secret from everyone including its owner (operator ruling, 2026-08-26). The stdout line is machine-readable output for pipes and redirection. Rules: -- write exactly one raw secret value per successful create command -- write human creation summaries to stderr before writing the raw secret to stdout -- do not repeat the secret on stderr -- do not wrap the secret in labels such as `DATABASE_URL=` +- show the bare secret in the human card on stderr +- write exactly one raw secret value per successful create command to stdout +- do not wrap the stdout secret in labels such as `DATABASE_URL=` - use `--verbose` for human metadata such as resource ids; keep generated names and opaque ids out of default human output unless they are the user-selected target - `--quiet` suppresses successful human stderr output and still writes the raw secret to stdout - list and show commands must never print or return secret values @@ -270,7 +267,7 @@ Rules: - use a flat aligned key-value card with no bullets - keys use the accent color and values use the default foreground unless status coloring applies -- sensitive values are masked rather than omitted +- values print bare; a secret the command exists to hand over is never masked (operator ruling, 2026-08-26) - human output prefers display labels, URLs, and statuses over opaque ids #### `mutate` diff --git a/packages/cli/src/commands/bucket/key-create.ts b/packages/cli/src/commands/bucket/key-create.ts index 5459f5cc..cac2795e 100644 --- a/packages/cli/src/commands/bucket/key-create.ts +++ b/packages/cli/src/commands/bucket/key-create.ts @@ -40,16 +40,8 @@ function createPresentations(result: BucketKeyCreateResult): Presentations { kind: "fields", rows: [ { label: "S3_ENDPOINT", value: result.endpoint }, - { - label: "S3_ACCESS_KEY_ID", - value: result.accessKeyId, - sensitive: true, - }, - { - label: "S3_SECRET_ACCESS_KEY", - value: result.secretAccessKey, - sensitive: true, - }, + { label: "S3_ACCESS_KEY_ID", value: result.accessKeyId }, + { label: "S3_SECRET_ACCESS_KEY", value: result.secretAccessKey }, { label: "S3_BUCKET", value: result.bucketName }, ], }, diff --git a/packages/cli/src/commands/postgres/presentation.ts b/packages/cli/src/commands/postgres/presentation.ts index 5aef8755..643b47f6 100644 --- a/packages/cli/src/commands/postgres/presentation.ts +++ b/packages/cli/src/commands/postgres/presentation.ts @@ -9,7 +9,6 @@ import type { export interface FieldRow { readonly label: string; readonly value: string; - readonly sensitive?: boolean; } /** Legacy `formatDatabaseTarget`. */ @@ -105,8 +104,8 @@ export function backupStdoutRows( ]); } -/** The one-time-secret card: the URL is masked in the human blocks - * and printed bare on stdout. */ +/** The one-time-secret card. The URL prints bare: this card is the only + * place an interactive user ever sees it. */ export function secretBlocks( headline: string, connectionString: string, @@ -119,9 +118,7 @@ export function secretBlocks( }, { kind: "fields", - rows: [ - { label: "connection URL", value: connectionString, sensitive: true }, - ], + rows: [{ label: "connection URL", value: connectionString }], }, ]; } diff --git a/packages/cli/tests/bucket.test.ts b/packages/cli/tests/bucket.test.ts index e75f4411..3d957550 100644 --- a/packages/cli/tests/bucket.test.ts +++ b/packages/cli/tests/bucket.test.ts @@ -741,12 +741,8 @@ describe("prisma bucket key create", () => { kind: "fields", rows: [ { label: "S3_ENDPOINT", value: "https://s3.prisma.io" }, - { - label: "S3_ACCESS_KEY_ID", - value: "AKIAEXAMPLE", - sensitive: true, - }, - { label: "S3_SECRET_ACCESS_KEY", value: "s3cr3t", sensitive: true }, + { label: "S3_ACCESS_KEY_ID", value: "AKIAEXAMPLE" }, + { label: "S3_SECRET_ACCESS_KEY", value: "s3cr3t" }, { label: "S3_BUCKET", value: "assets" }, ], }, diff --git a/packages/cli/tests/golden-rendering.test.ts b/packages/cli/tests/golden-rendering.test.ts index 201feb53..b31c0adf 100644 --- a/packages/cli/tests/golden-rendering.test.ts +++ b/packages/cli/tests/golden-rendering.test.ts @@ -1,7 +1,7 @@ /** * The sanctioned golden-rendering suite (S2 ruling: byte-exact pins * live here, one representative per rendering surface — card, table, - * error, masked secret). Every other test asserts semantically + * error, one-time secret). Every other test asserts semantically * (envelope / presented / events / exit code); when the engine's * rendering style changes deliberately, THIS file is the one place the * new bytes get re-pinned. The S1 whoami byte pins in @@ -120,12 +120,12 @@ describe("golden rendering", () => { }); /** - * What the mask is and is not: the card writes `********` to stderr - * while stdout prints the same secret in the clear a line later, - * because printing it is how the caller receives it. It is a - * scroll-back and screen-share courtesy, not containment. + * The card carries the bare secret: when stdout and stderr share a + * screen the stdout mirror is skipped, so the card is the only place + * an interactive user ever sees a one-time credential (operator + * ruling, 2026-08-26). */ - it("masked secret (representative: bucket key create)", async () => { + it("one-time secret card (representative: bucket key create)", async () => { const result = await makeCli( [record("ws_1", "Acme Inc")], "ws_1", @@ -140,8 +140,8 @@ describe("golden rendering", () => { "- Set these environment variables to use this bucket:\n" + "\n" + "S3_ENDPOINT: https://s3.prisma.io\n" + - "S3_ACCESS_KEY_ID: ********\n" + - "S3_SECRET_ACCESS_KEY: ********\n" + + "S3_ACCESS_KEY_ID: AKIAEXAMPLE\n" + + "S3_SECRET_ACCESS_KEY: s3cr3t\n" + "S3_BUCKET: assets\n", ); expect(result.stdout).toBe( @@ -174,7 +174,7 @@ describe("golden rendering", () => { * The cases above run with a non-terminal stderr and stay plain; these * two are the same surfaces with a terminal stderr. */ - it("coloured card and mask (representative: bucket key create)", async () => { + it("coloured card (representative: bucket key create)", async () => { const result = await makeCli( [record("ws_1", "Acme Inc")], "ws_1", @@ -190,8 +190,8 @@ describe("golden rendering", () => { "- Set these environment variables to use this bucket:\n" + "\n" + "\u001b[36mS3_ENDPOINT: \u001b[39m https://s3.prisma.io\n" + - "\u001b[36mS3_ACCESS_KEY_ID: \u001b[39m ********\n" + - "\u001b[36mS3_SECRET_ACCESS_KEY:\u001b[39m ********\n" + + "\u001b[36mS3_ACCESS_KEY_ID: \u001b[39m AKIAEXAMPLE\n" + + "\u001b[36mS3_SECRET_ACCESS_KEY:\u001b[39m s3cr3t\n" + "\u001b[36mS3_BUCKET: \u001b[39m assets\n", ); }); diff --git a/packages/cli/tests/postgres.test.ts b/packages/cli/tests/postgres.test.ts index d4aa9b98..11d0cee2 100644 --- a/packages/cli/tests/postgres.test.ts +++ b/packages/cli/tests/postgres.test.ts @@ -736,7 +736,6 @@ describe("prisma postgres create", () => { { label: "connection URL", value: "postgres://user:pass@host/db", - sensitive: true, }, ], }, @@ -2105,7 +2104,6 @@ describe("prisma postgres connection create", () => { { label: "connection URL", value: "postgres://pooled/db", - sensitive: true, }, ], });