#10296: fix(ui): store Ed25519 private key as non-extractable CryptoKey in IndexedDB
app: web-ui
stale
Cluster:
Android Ed25519 Enhancements
## Fix Summary
- Replace `@noble/ed25519` with Web Crypto API Ed25519 for key generation and signing
- Store the private key as a **non-extractable `CryptoKey`** in IndexedDB instead of plaintext in localStorage
- Non-extractable keys cannot have their raw bytes read by JavaScript, preventing exfiltration via XSS or malicious extensions
- Migrate legacy plaintext keys from localStorage on first load via JWK import, then delete the plaintext
- Graceful fallback to ephemeral (non-persisted) identity when IndexedDB is unavailable
## Issue Linkage
Fixes #10294
## Security Snapshot
| Metric | Value |
|--------|-------|
| **Score** | 8.1 / 10.0 |
| **Severity** | High |
| **Vector** | CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:N |
## Implementation Details
### Files Changed
- `ui/src/ui/device-identity.ts` (+143/-66)
### Technical Analysis
- Replace `@noble/ed25519` with Web Crypto API Ed25519 for key generation and signing
## Validation Evidence
- Command: `pnpm tsgo`
- Status: passed
## Risk and Compatibility
non-breaking; compatibility impact was not explicitly documented in the original PR body.
## AI-Assisted Disclosure
- AI-assisted: yes
- Model: Claude Code
<!-- greptile_comment -->
<h2>Greptile Overview</h2>
<h3>Greptile Summary</h3>
- Switches UI device identity signing from `@noble/ed25519` to Web Crypto Ed25519.
- Persists the private key as a non-extractable `CryptoKey` in IndexedDB (instead of plaintext in localStorage) and adjusts signing API accordingly.
- Adds a one-time migration path for legacy `openclaw-device-identity-v1` localStorage entries to IndexedDB.
- Falls back to an ephemeral (non-persisted) identity if IndexedDB is unavailable.
<h3>Confidence Score: 4/5</h3>
- Mostly safe to merge, but legacy migration has a decoding bug that can break existing device identities.
- Core WebCrypto + IndexedDB approach is straightforward and call sites were updated, but the legacy migration decodes base64url without padding (can throw and force identity reset) and malformed legacy storage can cause repeated migration exceptions without cleanup.
- ui/src/ui/device-identity.ts (legacy migration path)
<!-- greptile_other_comments_section -->
<!-- /greptile_comment -->
Most Similar PRs
#21053: security(infra): OS keychain storage for device private keys
by richvincent · 2026-02-19
76.9%
#19543: security: add encrypted localStorage wrapper using Web Crypto API
by Mozzzaic · 2026-02-17
73.0%
#5867: Android: Add BouncyCastle Ed25519 fallback and gateway token UI
by brandonpollack23 · 2026-02-01
69.9%
#10508: fix(android): fix identity signature failure on Android devices
by kilbertert · 2026-02-06
69.2%
#14197: fix(security): harden browser API auth, token comparisons, and hook...
by leecarollyn-gif · 2026-02-11
69.1%
#10597: fix(android): add BouncyCastle fallback for Ed25519 on Samsung devices
by bingluo-coder · 2026-02-06
69.1%
#17605: fix: preserve scopes when disableControlUiDeviceAuth is enabled
by MisterGuy420 · 2026-02-16
69.1%
#20772: fix(security): OC-15 encrypt Nostr private keys to prevent plaintex...
by aether-ai-agent · 2026-02-19
68.7%
#23060: Security/Test: isolate node.invoke approval e2e identity
by bmendonca3 · 2026-02-21
68.5%
#5649: fix(security): harden file permissions and header merging
by sfo2001 · 2026-01-31
68.4%