From 06acc53c992eaee13563774dda085ad786f6904b Mon Sep 17 00:00:00 2001 From: Jason Chuang Date: Fri, 7 Aug 2026 13:58:50 +0800 Subject: [PATCH] update CSP --- docs/csp-apache.conf.md | 17 ++- docs/csp-report-review-2026-08-03.md | 166 +++++++++++++++++++++++++++ 2 files changed, 178 insertions(+), 5 deletions(-) create mode 100644 docs/csp-report-review-2026-08-03.md diff --git a/docs/csp-apache.conf.md b/docs/csp-apache.conf.md index 1e252f6..b712a4b 100644 --- a/docs/csp-apache.conf.md +++ b/docs/csp-apache.conf.md @@ -2,7 +2,9 @@ Self-hosted fonts: no `fonts.googleapis.com` or `fonts.gstatic.com`. Static assets (including `woff2`) are served from `'self'`. -Adjust `report-uri` if your API base path or host differs (example below targets UAT). +Adjust `report-uri` if your API base path or host differs (UAT example below; PROD uses `https://pnsps.gld.gov.hk/api/csp-report`). + +See also: [csp-report-review-2026-08-03.md](./csp-report-review-2026-08-03.md) for the PROD report analysis that drove `frame-src` / `media-src` updates. ## Enforcing @@ -12,11 +14,12 @@ Header always set Content-Security-Policy "default-src 'self'; \ object-src 'none'; \ frame-ancestors 'none'; \ form-action 'self'; \ + frame-src 'self' data: blob:; \ script-src 'self'; \ style-src 'self' 'unsafe-inline'; \ style-src-elem 'self' 'unsafe-inline'; \ img-src 'self' data: https://www.w3.org https://w3.org; \ - media-src 'self' blob:; \ + media-src 'self' blob: data:; \ font-src 'self' data:; \ connect-src 'self'; \ upgrade-insecure-requests" @@ -32,11 +35,12 @@ Header always set Content-Security-Policy-Report-Only "default-src 'self'; \ object-src 'none'; \ frame-ancestors 'none'; \ form-action 'self'; \ + frame-src 'self' data: blob:; \ script-src 'self'; \ style-src 'self' 'unsafe-inline'; \ style-src-elem 'self' 'unsafe-inline'; \ img-src 'self' data: https://www.w3.org https://w3.org; \ - media-src 'self' blob:; \ + media-src 'self' blob: data:; \ font-src 'self' data:; \ connect-src 'self'; \ upgrade-insecure-requests; \ @@ -45,7 +49,10 @@ Header always set Content-Security-Policy-Report-Only "default-src 'self'; \ ## Notes +- **`frame-src`**: Required for proof file preview iframes that use `data:` / `blob:` URLs (`UploadFileTable.js`). Without this, framing falls back to `default-src 'self'` and browsers report `frame-src` violations (often with empty `blocked-uri`). - **`style-src-elem`**: Explicit, alongside `style-src`, for `` behaviour in modern browsers. -- **`img-src`**: Includes `https://www.w3.org` and `https://w3.org` so W3C URLs that redirect between hosts are allowed. +- **`img-src`**: Includes `https://www.w3.org` and `https://w3.org` so W3C WCAG badge URLs are allowed. +- **`media-src`**: `blob:` for captcha audio object URLs; `data:` for data-URI media if used. - **`font-src`**: `'self' data:` covers bundled fonts and `data:` URLs if used. -- Add origins to the relevant directive only if you introduce third-party scripts, styles, fonts, or APIs. +- **Report noise**: Browser extensions (Kaspersky, Perplexity, Youdao, Quark, Google Fonts injected by tooling, `wasm-eval` from chrome-extension) will still appear under Report-Only. Do **not** allowlist those origins. +- Add origins to the relevant directive only if you introduce third-party scripts, styles, fonts, or APIs that the **application** itself loads. diff --git a/docs/csp-report-review-2026-08-03.md b/docs/csp-report-review-2026-08-03.md new file mode 100644 index 0000000..1e0aaea --- /dev/null +++ b/docs/csp-report-review-2026-08-03.md @@ -0,0 +1,166 @@ +# CSP Report Review (PROD, 2026-08-03) + +Review of Content-Security-Policy-Report-Only violations from PNSPS PROD +(`https://pnsps.gld.gov.hk`), for deciding Apache CSP updates. + +Source logs (external): + +- `PNSPS PROD CSP Report 2026-08-03.txt` +- `summary.txt` +- `CSP Issue 1.txt` (`script-src` / `eval` samples) + +Related app docs: + +- [csp-apache.conf.md](./csp-apache.conf.md) — Apache header snippets to deploy + +--- + +## Policy in effect at report time + +``` +default-src 'self'; +base-uri 'self'; +object-src 'none'; +frame-ancestors 'none'; +form-action 'self'; +script-src 'self'; +style-src 'self' 'unsafe-inline'; +style-src-elem 'self' 'unsafe-inline'; +img-src 'self' data: https://www.w3.org https://w3.org; +media-src 'self' blob:; +font-src 'self' data:; +connect-src 'self'; +upgrade-insecure-requests; +report-uri https://pnsps.gld.gov.hk/api/csp-report +``` + +Notes: + +- Disposition was `report` (Report-Only); nothing was enforced. +- No `frame-src` → framing falls back to `default-src 'self'`. + +--- + +## Summary of findings + +Most reports are **browser / extension noise**. Only one directive change is +required for **app functionality**: add `frame-src`. + +| Approx. volume | Directive | Blocked | Verdict | +|---:|---|---|---| +| ~3000 | `frame-src` | empty / `data:` (URI often stripped) | **App** — PDF preview iframes | +| ~1500 | `script-src` | `wasm-eval` | Noise — `chrome-extension` | +| ~1400 | `font-src` | `fonts.gstatic.com` (Inter / Nunito) | Noise — not app fonts | +| ~500 | `img-src` | `https://www.w3.org/WAI/wcag2AA` | Already allowed; mostly doc `404` noise | +| ~240 | `font-src` | `at.alicdn.com`, `cdn.yiban.io`, Perplexity CDN | Noise — toolbars / extensions | +| ~180 | `style-src-elem` | `fonts.googleapis.com` | Noise — not loaded by app | +| ~70 | `script-src` | `eval` (line ~67) | Noise — no app `eval` | +| rest | various | Kaspersky, Youdao, Quark, `todesktop-internal`, etc. | Noise | + +App fonts are self-hosted (`@fontsource` Public Sans / Noto; see `public/index.html` +and `src/assets/fonts.css`). Google Fonts (Roboto / Inter / Nunito) are **not** +part of the app and must not be allowlisted. + +--- + +## App-owned issue: `frame-src` + +### Cause + +Proof upload preview in +`src/pages/Proof/Create_FromApp/UploadFileTable.js`: + +1. `FileReader.readAsDataURL(...)` builds a `data:` URL +2. `window.open("")` then `document.write` an `