|
|
|
@@ -0,0 +1,197 @@ |
|
|
|
# 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 |