|
1 | 1 | #!/usr/bin/env node |
2 | 2 | // Ensure a single key is present in a Vercel-pulled dotenv file so the local |
3 | 3 | // prebuilt `vercel build` passes env.ts's build-time presence validation for a |
4 | | -// *sensitive* (write-only) Vercel env var — which `vercel pull` returns EMPTY |
5 | | -// (`KEY=""`). The value is consumed only at RUNTIME, where Vercel injects the |
6 | | -// real sensitive value and env.ts re-validates against it. The injected key is |
| 4 | +// *sensitive* (write-only) Vercel env var — which `vercel pull` redacts as |
| 5 | +// `KEY=""` or, since CLI 57, `KEY="[SENSITIVE]"`. The value is consumed only at |
| 6 | +// RUNTIME, where Vercel injects the real value and env.ts re-validates it. |
| 7 | +// The injected key is |
7 | 8 | // server-only (not `NEXT_PUBLIC_`), so it is never bundled client-side. |
8 | 9 | // |
9 | 10 | // Usage: node inject-prebuilt-env-secret.mjs <KEY> <ENV_FILE> [STANDIN] |
10 | 11 | // process.env[<KEY>] real value (for example, a GitHub Actions secret). If |
11 | 12 | // non-empty, it is injected unconditionally. |
12 | 13 | // [STANDIN] optional build-time stand-in. Used only when the real |
13 | 14 | // value is empty and Vercel pulled exactly one explicitly |
14 | | -// blank `<KEY>=`, `<KEY>=''`, or `<KEY>=""` entry. |
| 15 | +// blank `<KEY>=`, `<KEY>=''`, or `<KEY>=""` entry, or |
| 16 | +// the exact CLI marker `<KEY>="[SENSITIVE]"`. |
15 | 17 | // --generate-es256-jwk-standin |
16 | 18 | // generate an ephemeral ES256 private JWK only after the |
17 | | -// same explicit-blank check. When the optional key is |
| 19 | +// same redaction check. When the optional key is |
18 | 20 | // absent, leave the file unchanged so production can use |
19 | 21 | // the configured legacy signing-secret fallback. The JWK |
20 | 22 | // is written directly to the pulled file and is never |
@@ -50,8 +52,10 @@ function findDotenvAssignments(lines, targetKey) { |
50 | 52 | }); |
51 | 53 | } |
52 | 54 |
|
53 | | -function isExplicitlyBlankDotenvValue(value) { |
54 | | - return value === '' || value === "''" || value === '""'; |
| 55 | +function isRedactedDotenvValue(value) { |
| 56 | + return ( |
| 57 | + value === '' || value === "''" || value === '""' || value === '"[SENSITIVE]"' |
| 58 | + ); |
55 | 59 | } |
56 | 60 |
|
57 | 61 | function readExpandedDotenvValue(contents, file, targetKey) { |
@@ -143,19 +147,19 @@ if (usingGeneratedStandin && assignments.length === 0) { |
143 | 147 | process.exit(0); |
144 | 148 | } |
145 | 149 |
|
146 | | -// A stand-in may only substitute for Vercel's write-only blank placeholder. |
147 | | -// Refusing absent, duplicated, nonblank, or malformed-looking entries avoids |
| 150 | +// A stand-in may only substitute for Vercel's exact write-only placeholders. |
| 151 | +// Refusing absent, duplicated, non-redacted, or malformed-looking entries avoids |
148 | 152 | // replacing a value that Vercel pull did expose or an opaque dotenv construct. |
149 | 153 | if ( |
150 | 154 | usingStandin && |
151 | | - (assignments.length !== 1 || !isExplicitlyBlankDotenvValue(assignments[0].value)) |
| 155 | + (assignments.length !== 1 || !isRedactedDotenvValue(assignments[0].value)) |
152 | 156 | ) { |
153 | 157 | const state = |
154 | 158 | assignments.length === 0 |
155 | 159 | ? 'absent' |
156 | 160 | : assignments.length > 1 |
157 | 161 | ? 'ambiguous' |
158 | | - : 'not explicitly blank'; |
| 162 | + : 'not explicitly blank or a recognized sensitive marker'; |
159 | 163 | console.error( |
160 | 164 | `${key} is ${state} in ${file}. Refusing to replace it with a build-time ` + |
161 | 165 | 'stand-in; configure Vercel with a write-only sensitive value or provide a real value.', |
|
0 commit comments