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
5 changes: 2 additions & 3 deletions docs/product/cli-style-guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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

Expand Down
13 changes: 5 additions & 8 deletions docs/product/output-conventions.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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`
Expand Down
12 changes: 2 additions & 10 deletions packages/cli/src/commands/bucket/key-create.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 },
],
},
Expand Down
9 changes: 3 additions & 6 deletions packages/cli/src/commands/postgres/presentation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import type {
export interface FieldRow {
readonly label: string;
readonly value: string;
readonly sensitive?: boolean;
}

/** Legacy `formatDatabaseTarget`. */
Expand Down Expand Up @@ -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,
Expand All @@ -119,9 +118,7 @@ export function secretBlocks(
},
{
kind: "fields",
rows: [
{ label: "connection URL", value: connectionString, sensitive: true },
],
rows: [{ label: "connection URL", value: connectionString }],
},
];
}
8 changes: 2 additions & 6 deletions packages/cli/tests/bucket.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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" },
],
},
Expand Down
22 changes: 11 additions & 11 deletions packages/cli/tests/golden-rendering.test.ts
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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",
Expand All @@ -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(
Expand Down Expand Up @@ -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",
Expand All @@ -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",
);
});
Expand Down
2 changes: 0 additions & 2 deletions packages/cli/tests/postgres.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -736,7 +736,6 @@ describe("prisma postgres create", () => {
{
label: "connection URL",
value: "postgres://user:pass@host/db",
sensitive: true,
},
],
},
Expand Down Expand Up @@ -2105,7 +2104,6 @@ describe("prisma postgres connection create", () => {
{
label: "connection URL",
value: "postgres://pooled/db",
sensitive: true,
},
],
});
Expand Down
Loading