|
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197 |
- # CSP Report Review (PROD, 2026-09-22)
-
- Review of Content-Security-Policy-Report-Only violations from PNSPS PROD
- (`https://pnsps.gld.gov.hk`).
-
- Source logs (external):
-
- - `All_CSP.txt` — 196 reports, 2026-08-27 to 2026-09-17
-
- Related app docs:
-
- - [csp-apache.conf.md](./csp-apache.conf.md) — Apache header snippets to deploy
- - [csp-report-review-2026-08-14.md](./csp-report-review-2026-08-14.md) — earlier PROD + UAT review
- - [csp-report-review-2026-08-03.md](./csp-report-review-2026-08-03.md) — earlier PROD review
-
- ---
-
- ## Verdict
-
- **No app-side CSP concern in this dump.** Almost every line is a browser
- extension, not PNSPS code. `/proof/reply/{id}` is just a busy authenticated
- page, so the same extension noise shows up there a lot.
-
- CSP is still **Report-Only** (`disposition: report`) — the browser logged these
- hits and **did not block the page**.
-
- The 2026-08-14 app-owned issue (proof PDF preview `frame-src`) is **gone** from
- this dump. Live PROD now includes `frame-src 'self' data: blob:;`.
-
- ---
-
- ## Add / remove summary
-
- **PROD — add nothing required. Remove nothing.**
-
- Keep `script-src 'self'`. Do not add `'unsafe-inline'`, `'unsafe-eval'`, or
- `'wasm-unsafe-eval'`.
-
- **Optional (low priority, already in [csp-apache.conf.md](./csp-apache.conf.md)):**
-
- ```apache
- media-src 'self' blob: data:;
- ```
-
- Live PROD is still `media-src 'self' blob:` (no `data:`). This dump has only
- 2 matching hits in three weeks.
-
- **Do not add:** `'wasm-unsafe-eval'`, Perplexity CDN, Youdao, NetEase CDN,
- Google Fonts.
-
- ---
-
- ## Confirmed current PROD policy (Report-Only)
-
- This is the live header that generated every report in `All_CSP.txt`. It matches
- `original-policy` in all 196 violations.
-
- ```apache
- Header always set Content-Security-Policy-Report-Only "default-src 'self'; \
- base-uri 'self'; \
- object-src 'none'; \
- frame-ancestors 'none'; \
- frame-src 'self' data: blob:; \
- form-action 'self'; \
- script-src 'self'; \
- style-src 'self' 'unsafe-inline'; \
- style-src-elem 'self' 'unsafe-inline'; \
- img-src 'self' data:; \
- media-src 'self' blob:; \
- font-src 'self' data:; \
- connect-src 'self'; \
- upgrade-insecure-requests; \
- report-uri https://pnsps.gld.gov.hk/api/csp-report"
- ```
-
- Changes vs the 2026-08-14 live header:
-
- - `frame-src 'self' data: blob:;` is now present.
- - `img-src` no longer allowlists `https://www.w3.org` / `https://w3.org`
- (badge is hosted locally).
-
- `style-src 'unsafe-inline'` is expected for MUI and is the only real policy
- weakness. These reports do not show a new XSS issue.
-
- ---
-
- ## Summary of findings
-
- | Count | Directive | Blocked | Verdict |
- |---:|---|---|---|
- | **192** | `script-src` | `wasm-eval` | Noise — Chrome extension |
- | 2 | `media-src` | `data` | Optional leftover — `data:` not in live `media-src` |
- | 1 | `font-src` | `frontend-cdn.perplexity.ai` … `FKGroteskNeue.woff2` | Noise — Perplexity sidebar |
- | 1 | `img-src` | `ydlunacommon-cdn.nosdn.127.net` … `.svg` | Noise — Youdao / NetEase plugin |
-
- No `frame-src` reports. No app `eval`. No Google Fonts.
-
- ---
-
- ## Why `wasm-eval` volume is high
-
- 192 of 196 reports (98%) are `script-src` / `wasm-eval`.
-
- | Count | `source-file` | Notes |
- |---:|---|---|
- | 103 | `chrome-extension` | Every one with a location is **line 23, column 39316** — the same injected script |
- | 89 | (none) | Chrome often sends a second report for the same violation without `source-file` |
-
- About 46 timestamps have 2+ reports in the same second (paired duplicates).
- That is why the log looks twice as large as the number of real events.
-
- Password managers, translators, Grammarly-style tools, and similar extensions
- compile WebAssembly into the page. `script-src 'self'` (without
- `'wasm-unsafe-eval'`) reports that. Do **not** loosen `script-src` to silence
- it.
-
- If quieter logs are needed, filter `blocked-uri=wasm-eval` plus
- `source-file=chrome-extension` (and the paired empty-source twin).
-
- ---
-
- ## Why many `/proof/reply` reports
-
- | Count | Page group |
- |---:|---|
- | 90 | `/login` |
- | **38** | `/proof/reply/{id}` |
- | 23 | `/publicNotice/{id}` |
- | 15 | `/publicNotice/apply` |
- | 10 | `/publicNotice` |
- | 10 | `/proof/search` |
- | 4 | `/paymentPage/*` |
- | 3 | `/user/changePassword` |
- | 2 | `/registerFromOrganization` |
- | 1 | `/verify/*` |
-
- The 38 Reply Proof hits span **12 proof IDs** (`29518`, `29948`, `30112`,
- `30114`, `30134`, `30166`, `30190`, `30266`, `30306`, `30326`, `30484`,
- `30958`). They are the same `wasm-eval` + Chrome extension pattern as login,
- apply, and notice detail.
-
- Reply Proof is a long, authenticated screen. Anyone with a WASM extension
- triggers a report on every visit. **No Reply Proof code change is required.**
-
- ---
-
- ## Not a PNSPS SPA problem — do not allowlist
-
- | Count | Page | Directive | Blocked | Why ignore |
- |---:|---|---|---|---|
- | 192 | many (login 90, reply 38, …) | `script-src` | `wasm-eval` | `chrome-extension` (line 23, col 39316) or paired empty source |
- | 1 | `/login` | `font-src` | Perplexity `FKGroteskNeue.woff2` | Sidebar / extension |
- | 1 | `/login` | `img-src` | NetEase `ydlunacommon-cdn.nosdn.127.net` | Youdao-style translator |
- | 2 | `/publicNotice/apply`, `/proof/reply/30166` | `media-src` | `data` | Optional `data:` gap only; 2 hits in three weeks |
-
- `status-code: 404` on 166 reports is typical SPA client-route noise, not a
- broken document.
-
- ---
-
- ## Optional: `media-src` and `data:`
-
- Live policy: `media-src 'self' blob:;`
-
- Captcha audio already uses `blob:` (`CustomFormWizard.js` and similar). Adding
- `data:` is low risk and already documented:
-
- ```apache
- media-src 'self' blob: data:;
- ```
-
- Not required for this dump.
-
- ---
-
- ## Recommended actions
-
- 1. **No Apache change required** for these reports. `frame-src` deploy from
- 2026-08-14 looks effective (zero `frame-src` hits here).
- 2. Keep PROD at `script-src 'self'`. Do not add `'unsafe-eval'` or
- `'wasm-unsafe-eval'`.
- 3. Optional: add `data:` to `media-src` to match
- [csp-apache.conf.md](./csp-apache.conf.md).
- 4. Keep Report-Only until remaining volume is understood as extension noise;
- then promote the same policy to enforcing.
- 5. Optional log hygiene: drop or sample `wasm-eval` + `chrome-extension` so
- real app regressions stay visible.
-
- No application code change is required.
-
- ---
-
- ## Backend reference
-
- - Report endpoint: `POST /csp-report` (context path → `/api/csp-report`)
- - Controller: `CspReportController` in PNSPS-backend
- - Frontend/Apache CSP above is what browsers apply to the SPA document
|