Skip to content

Commit e658a88

Browse files
committed
fix(cli): write the secrets file atomically
Regenerating any one key rewrites all of them, and a plain write truncates first — a crash mid-rewrite would strand a still-valid ENCRYPTION_KEY and orphan the data it protects. Write to a temp file and rename into place.
1 parent 610b9ea commit e658a88

1 file changed

Lines changed: 8 additions & 2 deletions

File tree

‎packages/cli/src/index.ts‎

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
import { execSync, spawn } from 'child_process'
44
import { randomBytes } from 'crypto'
5-
import { chmodSync, existsSync, mkdirSync, readFileSync, writeFileSync } from 'fs'
5+
import { chmodSync, existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from 'fs'
66
import { homedir } from 'os'
77
import { join } from 'path'
88
import { createInterface } from 'readline'
@@ -28,6 +28,10 @@ const AES_KEY_PATTERN = /^[0-9a-f]{64}$/i
2828
* leave that data permanently unreadable. Any value that is not a 32-byte hex key is
2929
* replaced.
3030
*
31+
* Regenerating one key rewrites the whole file, so the write goes to a temp file and is
32+
* renamed into place: a plain write truncates first, and a crash mid-write would strand a
33+
* still-valid `ENCRYPTION_KEY` and orphan the data it protects.
34+
*
3135
* Permissions are reasserted on every run: `writeFileSync`'s `mode` applies only when it
3236
* creates the file, so a file left by an earlier run — or one the user created — would
3337
* otherwise keep whatever mode it already had and stay readable by other local accounts.
@@ -50,7 +54,9 @@ function resolveSecrets(): Record<string, string> {
5054
if (missing.length > 0) {
5155
mkdirSync(configDir, { recursive: true })
5256
const contents = SECRET_KEYS.map((key) => `${key}=${secrets[key]}`).join('\n')
53-
writeFileSync(secretsPath, `${contents}\n`, { mode: 0o600 })
57+
const pending = `${secretsPath}.tmp`
58+
writeFileSync(pending, `${contents}\n`, { mode: 0o600 })
59+
renameSync(pending, secretsPath)
5460
console.log(chalk.gray(`🔑 Generated local secrets in ${secretsPath}`))
5561
}
5662

0 commit comments

Comments
 (0)