← Back to PRs

#10296: fix(ui): store Ed25519 private key as non-extractable CryptoKey in IndexedDB

by coygeek open 2026-02-06 09:30 View on GitHub →
app: web-ui stale
## 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