From 4674303f3b54423f70ad6d00e6e096349a34ec3f Mon Sep 17 00:00:00 2001 From: SkateSpots Date: Mon, 12 Oct 2026 14:45:11 +0700 Subject: [PATCH] docs(security): add 2026-10-12 audit log; refresh NIP-05 and popup rendering notes Summarises the code review + dependency scan: no exploitable client/server issue found; documents why npm audit fix was reverted (broke the build) and which advisories are unreachable/accepted (xlsx export-only, HashRouter open-redirect, MCP client-side, build-tool deps). Fixes the outdated 'never innerHTML' claim (popups use innerHTML with escapeHtml) and updates the contact NIP-05 to the seeded skatespots@skate-spots.org. --- SECURITY.md | 66 +++++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 61 insertions(+), 5 deletions(-) diff --git a/SECURITY.md b/SECURITY.md index 90376fb..b13d1ec 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -18,7 +18,7 @@ Please report privately first — **do not** open a public issue or publish a Po before we've had a chance to respond. - **Nostr DM (preferred):** `npub1yrnuj56rnen08zp2h9h7p74ghgjx6ma39spmpj6w9hzxywutevsst7k5cx` -- **NIP-05:** `skatespots@goskatespots.nostr1.com` +- **NIP-05:** `skatespots@skate-spots.org` (seeded platform handle) Please include: @@ -178,10 +178,10 @@ Additional headers: `X-Content-Type-Options: nosniff`, - **React/JSX** auto-escapes all interpolated strings — the default rendering path. Do not bypass it. - **Leaflet map popups & marker icons** (`src/components/SpotMap.tsx`) are the - one place we build DOM by hand. Every untrusted field (spot/tour/crew/event - titles & locations, meeting points, BTC Map name/address/hours/phone/ - description) is written via `textContent`, never `innerHTML`. A `popupRow()` - helper enforces this. Image/website fields are validated before use. + one place we build DOM by hand. Popups set `innerHTML` from a template in which + **every dynamic value is passed through `escapeHtml`** (re-verified 2026-10-12); + marker icons and the weather card use `textContent`. Image/website fields are + validated before use. - Rich Nostr note content is rendered through the shared `NoteContent` component/tokenizer rather than raw HTML. @@ -294,6 +294,62 @@ stronger handling than other secrets: --- +--- + +## Audit log — 2026-10-12 (code review + dependency scan) + +A targeted review of the client (`src/`), the standalone servers (`cvm-server`, +`push-service`, `ai-proxy`, `netlify/functions`) and the dependency tree. + +**No exploitable issue was found in app code.** + +- **Injection / XSS:** no `eval` / `new Function`; `dangerouslySetInnerHTML` + appears only in shadcn's `chart.tsx` (static, developer-defined chart CSS). The + hand-built Leaflet popup HTML interpolates values but every dynamic value is + `escapeHtml`'d (verified). `document.write` (recovery-phrase QR and rider-card + print windows) interpolates only canvas data-URLs and i18n strings. No + `__proto__` / prototype pollution. No secrets in web storage beyond the + documented `nsec`/NWC/Cashu. +- **Reverse tabnabbing:** every `target="_blank"` also carries + `rel="noopener noreferrer"`. +- **SSRF:** the only `fetch` to a derived host is + `https:///.well-known/lnurlp/…` where the domain comes from a + **hardcoded platform Lightning address** (not user input). Servers do not fetch + user-supplied URLs. The push service additionally validates subscription + endpoints (`isSafePushEndpoint`: https-only; blocks localhost + private / + link-local / CGNAT ranges). +- **Auth:** NIP-98 (kind 27235) signature + timestamp checks gate push + subscribe/unregister and the AI proxy; zap receipts and owner-grant labels are + signature-verified (`verifyEvent`) on both client and servers; the CVM server + signature-verifies event content before serving it to agents. +- **CORS:** the AI proxy and push service enforce origin allowlists; the NIP-05 + well-known function is intentionally `Access-Control-Allow-Origin: *` (public). +- **Randomness:** `Math.random` is used only for UI ids, probe/subscription ids, + PoW nonces and privacy timestamp jitter — never for secrets or tokens. + +**Dependencies (`npm audit`).** `npm audit fix` updated `@remix-run/router` (open +redirect), `@modelcontextprotocol/sdk`, `@humanfs/node`, `valibot` and +`brace-expansion`, but was **reverted** because it re-resolved the lockfile and +broke the build (a nested `@nostrify/nostrify` duplicate introduced a type +conflict). Re-assessment: none of the remaining advisories is reachable from the +shipped app — + +- `xlsx` (high, prototype pollution + ReDoS, no fix): used only to **generate** the + deck-cost workbook (`XLSX.writeFile`); imported files are never **parsed**, so + the parsing flaws are unreachable. Accepted. +- `@remix-run/router` open redirect: unreachable — the app uses **HashRouter**, so + navigation stays inside the `#` fragment and never crosses to an external URL. +- `@modelcontextprotocol/sdk` OAuth flaw: affects MCP **clients** connecting to + untrusted servers; `cvm-server` is an MCP **server**, not a client. Not + applicable. +- `braces`, `@vitest/mocker`, `@capacitor/cli`'s `uuid`, `tailwindcss`: build/test + tooling only — no runtime reach into the served app. Accepted. + +A broken build is a larger risk than an unreachable CVE, so the broken fix was +reverted rather than forced through. + +--- + ## For auditors: where to look first | Area | Files |