import { randomBytes } from 'node:crypto'; import { chmod, mkdir, open, readFile, rename, rm, stat, } from 'node:fs/promises'; import { dirname } from 'node:path'; export class PrivateFileTooPermissiveError extends Error { readonly code = 'EPRIVATE_FILE_TOO_PERMISSIVE'; constructor( readonly filePath: string, readonly mode: number, ) { super( `private file ${filePath} is too permissive (mode ${mode.toString(8).padStart(3, '0')}); expected 0600`, ); this.name = 'PrivateFileTooPermissiveError'; } } export async function writePrivateFile( filePath: string, data: string | Buffer, ): Promise { const dir = dirname(filePath); await mkdir(dir, { recursive: true, mode: 0o700 }); await chmod(dir, 0o700); const tmp = `${filePath}.tmp.${randomBytes(8).toString('hex')}`; let handle: Awaited> | undefined; try { handle = await open(tmp, 'w', 0o600); await handle.chmod(0o600); await handle.writeFile(data); await handle.sync(); await handle.close(); handle = undefined; await rename(tmp, filePath); } catch (err) { if (handle) { await handle.close().catch(() => {}); } await rm(tmp, { force: true }).catch(() => {}); throw err; } } export async function readPrivateFile(filePath: string): Promise { const info = await stat(filePath); if (process.platform !== 'win32' && (info.mode & 0o077) !== 0) { throw new PrivateFileTooPermissiveError(filePath, info.mode & 0o777); } return readFile(filePath); }