Skip to content

Commit 0b0224d

Browse files
committed
fix(config): use atomic write (tmp+rename) for credentials and config files
Write to a .tmp file first, then atomically rename to the target path. This prevents file corruption if the process is killed mid-write.
1 parent 5ab315c commit 0b0224d

2 files changed

Lines changed: 9 additions & 4 deletions

File tree

src/auth/credentials.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { readFileSync, writeFileSync, unlinkSync, existsSync, statSync } from 'fs';
1+
import { readFileSync, writeFileSync, renameSync, unlinkSync, existsSync, statSync } from 'fs';
22
import { getCredentialsPath, ensureConfigDir } from '../config/paths';
33
import type { CredentialFile } from './types';
44

@@ -20,7 +20,9 @@ export async function loadCredentials(): Promise<CredentialFile | null> {
2020
export async function saveCredentials(creds: CredentialFile): Promise<void> {
2121
await ensureConfigDir();
2222
const path = getCredentialsPath();
23-
writeFileSync(path, JSON.stringify(creds, null, 2) + '\n', { mode: 0o600 });
23+
const tmp = path + '.tmp';
24+
writeFileSync(tmp, JSON.stringify(creds, null, 2) + '\n', { mode: 0o600 });
25+
renameSync(tmp, path);
2426
}
2527

2628
export async function clearCredentials(): Promise<void> {

src/config/loader.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { readFileSync, writeFileSync, existsSync } from 'fs';
1+
import { readFileSync, writeFileSync, renameSync, existsSync } from 'fs';
22
import { parseConfigFile, REGIONS, type Config, type ConfigFile, type Region } from './schema';
33
import { ensureConfigDir, getConfigPath } from './paths';
44
import { detectOutputFormat, type OutputFormat } from '../output/formatter';
@@ -16,7 +16,10 @@ export function readConfigFile(): ConfigFile {
1616

1717
export async function writeConfigFile(data: Record<string, unknown>): Promise<void> {
1818
await ensureConfigDir();
19-
writeFileSync(getConfigPath(), JSON.stringify(data, null, 2) + '\n', { mode: 0o600 });
19+
const path = getConfigPath();
20+
const tmp = path + '.tmp';
21+
writeFileSync(tmp, JSON.stringify(data, null, 2) + '\n', { mode: 0o600 });
22+
renameSync(tmp, path);
2023
}
2124

2225
export function loadConfig(flags: GlobalFlags): Config {

0 commit comments

Comments
 (0)