Skip to content

Commit ac9fd6a

Browse files
saint-Joyclaude
andcommitted
docs: add security audit to graph, fix blog link
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent da6cea8 commit ac9fd6a

2 files changed

Lines changed: 76 additions & 1 deletion

File tree

blog/2026_05_12.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ wallet: raw [[private key]] import.
44

55
raw [[secp256k1]] private keys (64 hex characters) can now be imported through the wallet modal. the same [[AES-256-GCM]] encryption with [[PBKDF2]] (1M iterations) protects both [[mnemonic]] and private key accounts.
66

7-
security audit passed. 0 critical, 0 high findings. audit and roadmap tracked in [[cyb]] repo.
7+
security audit passed. 0 critical, 0 high findings. see [[security audit private key import]].
88

99
## unlock bar
1010

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
---
2+
tags: cyber, cyb, article
3+
crystal-type: entity
4+
crystal-domain: cyber
5+
---
6+
# security audit: private key import
7+
8+
date: 2026-05-12
9+
status: passed — 0 critical, 0 high, 0 medium, 1 low (optional)
10+
scope: addition of raw [[secp256k1]] [[private key]] import to [[cyb]] wallet
11+
12+
## changes audited
13+
14+
| file | change |
15+
|---|---|
16+
| `defaultAccount.d.ts` | added `private-key` to keys union type |
17+
| `offlineSigner.ts` | `CybPrivateKeySigner` class, `getOfflineSignerFromPrivateKey()` |
18+
| `ConnectWalletModal.tsx` | UI tabs, private key input field |
19+
| `actionBarConnect.tsx` | import routing, encryption, account registration |
20+
| `signerClient.tsx` | unlock and auto-switch for private-key accounts |
21+
| `pocket.ts` | deletion cleanup for private-key accounts |
22+
23+
## threat model
24+
25+
| threat | mitigation | status |
26+
|---|---|---|
27+
| private key in React state | stored in `useRef` | fixed |
28+
| key leak on unmount | ref cleared in cleanup effect | fixed |
29+
| key leak on background | ref + display cleared on `visibilitychange` | fixed |
30+
| clipboard leak on paste | `navigator.clipboard.writeText('')` after paste | fixed |
31+
| pending ref retained after success | `clearState()` zeroes all refs | fixed |
32+
| invalid key accepted | 3-layer validation: regex, `fromHex()`, `fromKey()` | secure |
33+
| error messages expose key material | generic errors only | secure |
34+
| encryption at rest | [[AES-256-GCM]] + [[PBKDF2]] (1M iterations), same as [[mnemonic]] | secure |
35+
| password brute force | 8+ chars, 3/4 character classes if under 12 chars | adequate |
36+
| key type disclosure in Redux | `keys: 'private-key'` visible, contains no key material | accepted |
37+
| Tauri device key in localStorage | pre-existing trade-off | accepted |
38+
| auto-lock timer disabled | pre-existing design decision | accepted |
39+
40+
## encryption format
41+
42+
private key hex encrypted with identical format as mnemonic:
43+
44+
```
45+
version(1 byte) + salt(16 bytes) + iv(12 bytes) + AES-GCM-256(plaintext)
46+
→ base64 → localStorage['cyb:mnemonic:{address}']
47+
```
48+
49+
`decryptMnemonic()` returns any stored plaintext. the account type (`keys` field in Redux) determines whether to call `getOfflineSignerFromMnemonic()` or `getOfflineSignerFromPrivateKey()`.
50+
51+
## [[CosmJS]] validation chain
52+
53+
1. `fromHex(privkeyHex)` — validates hex format, throws on non-hex or odd-length
54+
2. `DirectSecp256k1Wallet.fromKey(privkey)` — validates 32-byte length, validates against [[secp256k1]] curve order
55+
3. `Secp256k1Wallet.fromKey(privkey)` — same validation for Amino signer
56+
57+
all three layers throw before any storage occurs.
58+
59+
## signArbitrary (ADR-036)
60+
61+
`CybPrivateKeySigner.signArbitrary()` composes `Secp256k1Wallet` (Amino) for ADR-036 signing — same MsgSignData format as `CybOfflineSigner`. `hasSignArbitrary()` type guard works via duck-typing.
62+
63+
## findings fixed before commit
64+
65+
1. moved `privateKeyHex` from `useState` to `useRef` — prevents React DevTools exposure
66+
2. added ref cleanup on unmount
67+
3. added ref + display cleanup on `visibilitychange` (background)
68+
4. added clipboard clearing on private key paste
69+
5. added `pendingImportModeRef` reset in `clearState()`
70+
71+
## accepted risks
72+
73+
- device key for Tauri auto-unlock stored in localStorage (pre-existing)
74+
- auto-lock timer disabled (pre-existing design decision)
75+
- account type visible in Redux state (information disclosure only)

0 commit comments

Comments
 (0)