Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions docs/security-audit.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
4 changes: 4 additions & 0 deletions docs/wallet-adapters.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
6 changes: 6 additions & 0 deletions src/lib/csp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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' <API_ORIGIN> <WS_ORIGIN>`
* SWR fetches go to API_ORIGIN (REST). The live intent WebSocket
Expand Down
19 changes: 0 additions & 19 deletions src/lib/wallet/signMessage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<SignedComment> {
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
Expand Down
Loading