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.
This commit is contained in:
SkateSpots 2026-10-12 14:45:11 +07:00
parent ca810326f3
commit 4674303f3b

View file

@ -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://<domain>/.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 |