T09 · Insecure Skill Coding Practices
Warning
- Location
- scripts/setup.js:54
- Finding
- Plaintext Private Key Is Created Before Restrictive Permissions Are Applied<![CDATA[ ## Vulnerability Details **File Location**: `scripts/setup.js`, lines 54-61 **Vulnerability Type**: Insecure creation of a plaintext credential file **Risk Level**: Medium ### Vulnerable Code ```javascript const walletData = { address: account.address, privateKey: privateKey, chain: CHAIN.name, chainId: CHAIN.id, createdAt: new Date().toISOString() }; writeFileSync(WALLET_PATH, JSON.stringify(walletData, null, 2)); chmodSync(WALLET_PATH, 0o600); ``` ### Technical Analysis The wallet's raw private key is stored unencrypted. More importantly, the file is first created using `writeFileSync` and is only subsequently restricted to mode `0600` with a separate `chmodSync` operation. The initial file mode is determined by Node.js's default creation mode and the process umask. Under a permissive or commonly used umask, the file may temporarily be readable by group members or other local users. The separate write and permission-change operations create a race window during which another local process can observe and copy the key. The check for an existing file and its later creation are also separate operations elsewhere in the same function. Atomic file creation is therefore not enforced. The implementation does not use an exclusive creation flag, a restrictive mode at open time, encryption, or an operating-system credential store. The same insecure pattern is duplicated in `lib/wallet.js` at lines 85-92. ### Attack Path 1. The victim runs `node scripts/setup.js` on a multi-user system or in an environment with a permissive umask. 2. A local attacker monitors `~/.hyperliquid-wallet.json` or its containing directory for file creation. 3. `writeFileSync` creates and populates the file before `chmodSync` restricts its permissions. 4. During that interval, the attacker opens and copies the JSON file. 5. The attacker extracts the `privateKey` field and imports it into another wallet client. 6. The attacker can sign arbitrary transactions as the v ...[truncated 761 chars]
- Remediation
- <![CDATA[ ## Remediation Suggestions Create the file atomically with restrictive permissions from the outset: ```javascript writeFileSync( WALLET_PATH, JSON.stringify(walletData, null, 2), { mode: 0o600, flag: 'wx' } ); ``` The `wx` flag prevents silently following or overwriting an existing path, while `mode: 0o600` applies restrictive permissions when the file is opened rather than afterward. Additional hardening should include: 1. Replace plaintext key storage with an operating-system keychain, hardware wallet, external signer, or encrypted keystore. 2. If an encrypted keystore is used, obtain its passphrase through a protected interactive input mechanism rather than command-line arguments. 3. Validate that the wallet path is a regular file and not a symbolic link. 4. Ensure the parent directory is owned by the current user and has restrictive permissions such as `0700`. 5. Apply the same correction to `createWallet()` in `lib/wallet.js`. 6. Document backup and key-rotation procedures for users whose plaintext wallet files may already have been exposed. ]]>
