diff --git a/src/commands/account/export.ts b/src/commands/account/export.ts index bed07a48..7cff66f1 100644 --- a/src/commands/account/export.ts +++ b/src/commands/account/export.ts @@ -1,6 +1,6 @@ import {BaseAction} from "../../lib/actions/BaseAction"; import {ethers} from "ethers"; -import {writeFileSync, existsSync, readFileSync} from "fs"; +import {writeFileSync, existsSync, readFileSync, chmodSync} from "fs"; import path from "path"; export interface ExportAccountOptions { @@ -60,7 +60,12 @@ export class ExportAccountAction extends BaseAction { const encryptedJson = await wallet.encrypt(password); // Write standard web3 keystore format (compatible with geth, foundry, etc.) - writeFileSync(outputPath, encryptedJson); + writeFileSync(outputPath, encryptedJson, { mode: 0o600 }); + try { + chmodSync(outputPath, 0o600); + } catch { + // chmod can fail on Windows (no POSIX permissions) + } this.succeedSpinner(`Account '${accountName}' exported to: ${outputPath}`); this.logInfo(`Address: ${wallet.address}`); diff --git a/src/commands/account/import.ts b/src/commands/account/import.ts index a7ec0518..cbdd38c3 100644 --- a/src/commands/account/import.ts +++ b/src/commands/account/import.ts @@ -1,6 +1,6 @@ import {BaseAction} from "../../lib/actions/BaseAction"; import {ethers} from "ethers"; -import {writeFileSync, existsSync, readFileSync} from "fs"; +import {writeFileSync, existsSync, readFileSync, chmodSync} from "fs"; export interface ImportAccountOptions { privateKey?: string; @@ -62,7 +62,12 @@ export class ImportAccountAction extends BaseAction { const encryptedJson = await wallet.encrypt(password); // Write standard web3 keystore format directly - writeFileSync(keystorePath, encryptedJson); + writeFileSync(keystorePath, encryptedJson, { mode: 0o600 }); + try { + chmodSync(keystorePath, 0o600); + } catch { + // chmod can fail on Windows (no POSIX permissions) + } if (options.setActive !== false) { this.setActiveAccount(options.name); diff --git a/src/lib/actions/BaseAction.ts b/src/lib/actions/BaseAction.ts index 92a0d665..6a891dcc 100644 --- a/src/lib/actions/BaseAction.ts +++ b/src/lib/actions/BaseAction.ts @@ -59,7 +59,7 @@ export function resolveNetwork(stored: string | undefined, customNetworks?: Cust } } import { ethers } from "ethers"; -import { writeFileSync, existsSync, readFileSync } from "fs"; +import { writeFileSync, existsSync, readFileSync, chmodSync } from "fs"; export class BaseAction extends ConfigFileManager { private static readonly DEFAULT_ACCOUNT_NAME = "default"; @@ -219,7 +219,16 @@ export class BaseAction extends ConfigFileManager { // Write standard web3 keystore format directly const encryptedJson = await wallet.encrypt(password); - writeFileSync(keystorePath, encryptedJson); + writeFileSync(keystorePath, encryptedJson, { mode: 0o600 }); + // Enforce restrictive permissions even if the file already existed (writeFileSync + // does not change the mode of an existing file, so an attacker who pre-created it + // with 0644 would keep world-read access to the encrypted private key). + try { + chmodSync(keystorePath, 0o600); + } catch { + // chmod can fail on Windows (no POSIX permissions) — the encrypted keystore + // still protects the key, and Windows ACLs govern access there anyway. + } // Set as active account if no active account exists if (!this.getActiveAccount()) {