diff --git a/docs/security-audit.md b/docs/security-audit.md index 9a78f91..e297e0d 100644 --- a/docs/security-audit.md +++ b/docs/security-audit.md @@ -447,3 +447,7 @@ An address-adjacent free-text field (such as a future memo field) currently bypa **Tests:** `src/lib/textSafety.test.ts` — real Unicode attack fixtures for every bidi control (U+202A–U+202E, U+2066–U+2069), every zero-width character (U+200B–U+200D, U+FEFF, U+00AD), combined payloads, safe ASCII/non-Latin strings asserted unchanged. + +## CSP `style-src 'unsafe-inline'` follow-up (2026-10-22) + +`buildCsp` keeps `style-src 'self' 'unsafe-inline'` even when `script-src` is nonce-based. A nonce-based `style-src` is **not currently feasible**: Next.js inline critical CSS and runtime style injection do not carry the request nonce, so enforcing it would break styling and hydration. Blocking condition: inline critical CSS must be disabled (or Next.js must nonce its inline styles), which is out of scope here. Trigger to revisit: `CSP_REPORT_ONLY` data from `/api/csp-report` showing no inline-style violations, or a Next.js upgrade that nonces inline styles. The accepted risk is CSS-injection data exfiltration, which requires a separate HTML-injection bug to exploit. diff --git a/docs/wallet-adapters.md b/docs/wallet-adapters.md index fa11e88..abf6793 100644 --- a/docs/wallet-adapters.md +++ b/docs/wallet-adapters.md @@ -3,3 +3,7 @@ Wallet integrations implement `WalletAdapter` in `src/lib/wallet/types.ts` and are registered in `src/lib/wallet/index.ts`. Adapters must expose connect, disconnect, address, network and transaction signing. Signing always receives the configured network; adapters should throw `WalletAdapterError` with `NETWORK_UNSUPPORTED` when they cannot sign that network. Never persist signed payloads or secret material. Add an install URL and capability flags to the registry, then exercise the adapter contract with a fake wallet in tests. + +## Message signing never uses a raw `Keypair` + +Production message signing always goes through `buildAuthTransaction` plus a wallet adapter's `signTransaction`; the app never constructs a `Keypair` from a secret. The former `signComment(keypair, payload)` helper in `signMessage.ts` was audited (2026-10-22): it had no call sites outside its own module (`ProposalDetailClient` only imports `signCommentMessage` and `verifySignedComment`), so it was dead code and has been removed. `Keypair.fromPublicKey` remains for signature verification only, which handles no secret material. diff --git a/src/lib/csp.ts b/src/lib/csp.ts index a30eb40..9b134a5 100644 --- a/src/lib/csp.ts +++ b/src/lib/csp.ts @@ -63,6 +63,12 @@ function safeOrigin(value: string | undefined, fallback: string): string { * feasible without a custom style loader, so `'unsafe-inline'` is kept * for styles only. This is a much smaller attack surface than inline * scripts because CSS cannot execute arbitrary JS. + * Reviewed 2026-10-22: still blocked. Next.js inline critical-CSS and + * runtime style injection do not carry the request nonce, so forwarding + * `options.nonce` to `style-src` would break styling. Dropping + * `'unsafe-inline'` requires disabling inline critical CSS (out of scope). + * Re-evaluate once `CSP_REPORT_ONLY` data from `/api/csp-report` shows no + * inline-style violations. See docs/security-audit.md. * * 3. `connect-src 'self' ` * SWR fetches go to API_ORIGIN (REST). The live intent WebSocket diff --git a/src/lib/wallet/signMessage.ts b/src/lib/wallet/signMessage.ts index d3b0891..8d7de37 100644 --- a/src/lib/wallet/signMessage.ts +++ b/src/lib/wallet/signMessage.ts @@ -69,25 +69,6 @@ export function buildCanonicalMessage( return `stellar-comment:${proposalId}:${textHash}:${timestamp}`; } -/** - * Sign a governance comment with the supplied keypair using SEP-53 style - * message signing. Returns the canonical payload plus signature metadata. - */ -export async function signComment( - keypair: Keypair, - payload: SignedCommentPayload, -): Promise { - const textHash = await hashCommentText(payload.text); - const message = buildCanonicalMessage(payload.proposalId, textHash, payload.timestamp); - const signature = keypair.sign(Buffer.from(message, 'utf8')).toString('base64'); - return { - ...payload, - author: keypair.publicKey(), - signature, - scheme: 'sep53', - }; -} - /** * Build a zero-fee auth transaction carrying the canonical message in a * `manageData` operation. Used as a documented shim for wallets that do not