|
|
@@ -0,0 +1,212 @@ |
|
|
|
|
|
# CSP Report Review (PROD + UAT, 2026-08-14) |
|
|
|
|
|
|
|
|
|
|
|
Review of Content-Security-Policy-Report-Only violations from PNSPS PROD |
|
|
|
|
|
(`https://pnsps.gld.gov.hk`) and UAT (`https://pnspsuat.gld.gov.hk`), for |
|
|
|
|
|
deciding Apache CSP updates. |
|
|
|
|
|
|
|
|
|
|
|
Source logs (external): |
|
|
|
|
|
|
|
|
|
|
|
- `P1 CSP.txt` — PROD node, 168 reports, 2026-07-30 to 2026-08-14 |
|
|
|
|
|
- `P2 CSP.txt` — PROD node, 137 reports, 2026-07-30 to 2026-08-14 |
|
|
|
|
|
- `TT CSP.txt` — UAT, 133 reports, 2026-05-29 to 2026-08-03 |
|
|
|
|
|
|
|
|
|
|
|
Related app docs: |
|
|
|
|
|
|
|
|
|
|
|
- [csp-apache.conf.md](./csp-apache.conf.md) — Apache header snippets to deploy |
|
|
|
|
|
- [csp-report-review-2026-08-03.md](./csp-report-review-2026-08-03.md) — earlier PROD review |
|
|
|
|
|
|
|
|
|
|
|
--- |
|
|
|
|
|
|
|
|
|
|
|
## Verdict |
|
|
|
|
|
|
|
|
|
|
|
CSP is still **Report-Only** (`disposition: report`) — nothing is blocking users |
|
|
|
|
|
yet. There is **one real PNSPS SPA problem** (proof PDF preview `frame-src`). |
|
|
|
|
|
Everything else is browser-extension noise or a **different app** on the same |
|
|
|
|
|
UAT host. |
|
|
|
|
|
|
|
|
|
|
|
The 2026-08-03 recommendation to add `frame-src` is **still not on the live |
|
|
|
|
|
header**. |
|
|
|
|
|
|
|
|
|
|
|
--- |
|
|
|
|
|
|
|
|
|
|
|
## Add / remove summary |
|
|
|
|
|
|
|
|
|
|
|
**PROD — add one line, remove nothing:** |
|
|
|
|
|
|
|
|
|
|
|
```apache |
|
|
|
|
|
frame-src 'self' data: blob:; |
|
|
|
|
|
``` |
|
|
|
|
|
|
|
|
|
|
|
Keep `script-src 'self'`. Do not add `'unsafe-inline'` or `'unsafe-eval'`. |
|
|
|
|
|
|
|
|
|
|
|
**UAT — add the same line, remove nothing now:** |
|
|
|
|
|
|
|
|
|
|
|
```apache |
|
|
|
|
|
frame-src 'self' data: blob:; |
|
|
|
|
|
``` |
|
|
|
|
|
|
|
|
|
|
|
Keep `script-src 'self' 'unsafe-inline' 'unsafe-eval'` for now (JSF payment recon |
|
|
|
|
|
on the same host). Do not copy this `script-src` to PROD. |
|
|
|
|
|
|
|
|
|
|
|
**Do not add on either env:** `'wasm-unsafe-eval'`, Youdao, NetEase CDN, |
|
|
|
|
|
`todesktop-internal`, Google Fonts. |
|
|
|
|
|
|
|
|
|
|
|
**Optional later (not required for these reports):** UAT can drop |
|
|
|
|
|
`'unsafe-inline' 'unsafe-eval'` from the SPA header only after |
|
|
|
|
|
`/paymentrecon-dept_web/` has its own CSP. |
|
|
|
|
|
|
|
|
|
|
|
--- |
|
|
|
|
|
|
|
|
|
|
|
## Confirmed current PROD policy (Report-Only) |
|
|
|
|
|
|
|
|
|
|
|
This is the live header that generated the P1/P2 reports. It matches the |
|
|
|
|
|
`original-policy` in every PROD violation. |
|
|
|
|
|
|
|
|
|
|
|
```apache |
|
|
|
|
|
Header always set Content-Security-Policy-Report-Only "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" |
|
|
|
|
|
``` |
|
|
|
|
|
|
|
|
|
|
|
Gap vs the reports: **no `frame-src`**. Framing therefore falls back to |
|
|
|
|
|
`default-src 'self'`, which is why proof PDF preview reports `frame-src` with |
|
|
|
|
|
an empty `blocked-uri`. |
|
|
|
|
|
|
|
|
|
|
|
--- |
|
|
|
|
|
|
|
|
|
|
|
## Confirmed current UAT policy (Report-Only) |
|
|
|
|
|
|
|
|
|
|
|
```apache |
|
|
|
|
|
Header always set Content-Security-Policy-Report-Only "default-src 'self'; \ |
|
|
|
|
|
base-uri 'self'; \ |
|
|
|
|
|
object-src 'none'; \ |
|
|
|
|
|
frame-ancestors 'none'; \ |
|
|
|
|
|
form-action 'self'; \ |
|
|
|
|
|
script-src 'self' 'unsafe-inline' 'unsafe-eval'; \ |
|
|
|
|
|
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://pnspsuat.gld.gov.hk/api/csp-report" |
|
|
|
|
|
``` |
|
|
|
|
|
|
|
|
|
|
|
UAT vs PROD differences: |
|
|
|
|
|
|
|
|
|
|
|
- UAT `script-src` is `'self' 'unsafe-inline' 'unsafe-eval'` — PROD is `'self'` only. |
|
|
|
|
|
- Both still lack `frame-src`. |
|
|
|
|
|
- TT report `original-policy` was older: `script-src 'self' 'unsafe-inline'` |
|
|
|
|
|
**without** `'unsafe-eval'`. That is why TT logged 42 JSF `eval` hits. Those |
|
|
|
|
|
should stop under the current UAT header. |
|
|
|
|
|
|
|
|
|
|
|
Do **not** copy `'unsafe-inline'` or `'unsafe-eval'` onto PROD. The React SPA |
|
|
|
|
|
does not need them. `'unsafe-eval'` is only there for the shared-host JSF |
|
|
|
|
|
payment recon app. |
|
|
|
|
|
|
|
|
|
|
|
--- |
|
|
|
|
|
|
|
|
|
|
|
## Real problem (PROD P1 + P2) |
|
|
|
|
|
|
|
|
|
|
|
| Volume | Directive | Blocked | Pages | |
|
|
|
|
|
|---:|---|---|---| |
|
|
|
|
|
| 150 | `frame-src` | empty `""` | `/proof/create/{id}` | |
|
|
|
|
|
|
|
|
|
|
|
Source is always `static/js/4608.4af455e6.chunk.js`. Cause is proof file preview |
|
|
|
|
|
in `src/pages/Proof/Create_FromApp/UploadFileTable.js`: |
|
|
|
|
|
`FileReader.readAsDataURL` then `document.write` an `<iframe src="data:...">`. |
|
|
|
|
|
With no `frame-src`, CSP uses `default-src 'self'` and blocks `data:` frames. |
|
|
|
|
|
Browsers strip the `data:` URI from reports (empty `blocked-uri`). |
|
|
|
|
|
|
|
|
|
|
|
**If this policy is promoted to enforcing without `frame-src`, PDF preview on |
|
|
|
|
|
proof create will break.** |
|
|
|
|
|
|
|
|
|
|
|
Apache fix (already documented in [csp-apache.conf.md](./csp-apache.conf.md), |
|
|
|
|
|
not deployed): |
|
|
|
|
|
|
|
|
|
|
|
```apache |
|
|
|
|
|
frame-src 'self' data: blob:; |
|
|
|
|
|
``` |
|
|
|
|
|
|
|
|
|
|
|
Optional frontend follow-up: switch preview to `URL.createObjectURL` + `blob:` |
|
|
|
|
|
(still needs `frame-src ... blob:`). |
|
|
|
|
|
|
|
|
|
|
|
Optional (low priority): `media-src 'self' blob: data:;` — current `blob:` |
|
|
|
|
|
already covers captcha audio. |
|
|
|
|
|
|
|
|
|
|
|
--- |
|
|
|
|
|
|
|
|
|
|
|
## Not a PNSPS SPA problem — do not allowlist |
|
|
|
|
|
|
|
|
|
|
|
| Volume | Where | Directive | Blocked | Why ignore | |
|
|
|
|
|
|---:|---|---|---|---| |
|
|
|
|
|
| 109 | PROD | `script-src` | `wasm-eval` | `source-file: chrome-extension` | |
|
|
|
|
|
| 18 | PROD | `media-src` | `dict.youdao.com` | Youdao translation plugin | |
|
|
|
|
|
| 11 | PROD | `img-src` | `ydlunacommon-cdn.nosdn.127.net` | Youdao/NetEase CDN icons | |
|
|
|
|
|
| 3 | PROD P2 | `script-src-elem` | `todesktop-internal` | Desktop wrapper | |
|
|
|
|
|
| 105 | PROD 14 + UAT 91 | `img-src` | `www.w3.org/WAI/wcag2AA` | Already allowed in policy; mostly SPA `status-code: 404` noise. Badge is in `src/components/cards/AuthFooter.js`. | |
|
|
|
|
|
|
|
|
|
|
|
Do **not** add `'unsafe-eval'`, `'wasm-unsafe-eval'`, Youdao, NetEase, or |
|
|
|
|
|
`todesktop-internal` to the SPA policy. |
|
|
|
|
|
|
|
|
|
|
|
--- |
|
|
|
|
|
|
|
|
|
|
|
## UAT-only: payment recon JSF (not the React app) |
|
|
|
|
|
|
|
|
|
|
|
42 TT reports: `script-src` / `eval` on: |
|
|
|
|
|
|
|
|
|
|
|
- `/paymentrecon-dept_web/report/paymentReconRpt01Search.jsf` (38) |
|
|
|
|
|
- `/paymentrecon-dept_web/report/paymentReconRpt02Search.jsf` (3) |
|
|
|
|
|
- `/paymentrecon-dept_web/manualrecon/manualReconSearch.jsf` (1) |
|
|
|
|
|
|
|
|
|
|
|
This is a **separate JSF app** on the same Apache host. The TT logs were |
|
|
|
|
|
generated under the **old** UAT policy (`script-src 'self' 'unsafe-inline'`). |
|
|
|
|
|
Current UAT already adds `'unsafe-eval'`, so those `eval` reports should |
|
|
|
|
|
disappear. |
|
|
|
|
|
|
|
|
|
|
|
That UAT looseness is a workaround for JSF, not a requirement of PNSPS. Before |
|
|
|
|
|
enforcing CSP: |
|
|
|
|
|
|
|
|
|
|
|
- Keep PROD at `script-src 'self'` (do not add `'unsafe-eval'`). |
|
|
|
|
|
- Prefer a path-specific header for `/paymentrecon-dept_web/` on UAT so the SPA |
|
|
|
|
|
can later drop `'unsafe-inline'` / `'unsafe-eval'`. |
|
|
|
|
|
|
|
|
|
|
|
--- |
|
|
|
|
|
|
|
|
|
|
|
## Recommended actions (ops / Apache, not app code) |
|
|
|
|
|
|
|
|
|
|
|
1. Add `frame-src 'self' data: blob:;` to both PROD and UAT Report-Only headers. |
|
|
|
|
|
Do not change PROD `script-src 'self'`. |
|
|
|
|
|
2. Confirm proof-create preview reports drop after that deploy. |
|
|
|
|
|
3. Keep Report-Only until `frame-src` volume is gone; remaining extension noise |
|
|
|
|
|
is expected. |
|
|
|
|
|
4. Do not promote UAT's `'unsafe-inline' 'unsafe-eval'` to PROD. Those tokens |
|
|
|
|
|
exist for JSF payment recon on the shared UAT host. |
|
|
|
|
|
5. Optional: give `/paymentrecon-dept_web/` its own Apache CSP so UAT SPA can |
|
|
|
|
|
later match PROD (`script-src 'self'`). |
|
|
|
|
|
6. Optional later: host the WCAG badge locally to cut `img-src` noise; change |
|
|
|
|
|
preview to `blob:` URLs. |
|
|
|
|
|
|
|
|
|
|
|
No application code change is required for the reports to become clean after |
|
|
|
|
|
the Apache `frame-src` deploy. |
|
|
|
|
|
|
|
|
|
|
|
--- |
|
|
|
|
|
|
|
|
|
|
|
## Backend reference |
|
|
|
|
|
|
|
|
|
|
|
- Report endpoint: `POST /csp-report` (context path → `/api/csp-report`) |
|
|
|
|
|
- Spring Security also sets a short API CSP on API responses |
|
|
|
|
|
(`default-src 'self'; script-src 'self'; frame-ancestors 'self'`). |
|
|
|
|
|
Frontend/Apache CSP above is what browsers enforce for the SPA document. |