|
|
|
@@ -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 `<iframe src="data:...">` |
|
|
|
|
|
|
|
With no `frame-src`, CSP uses `default-src 'self'`, which blocks `data:` frames. |
|
|
|
Browsers often report `blocked-uri` as empty (`""`) for privacy. |
|
|
|
|
|
|
|
Almost all of these hits are on `/proof/create/...`, source |
|
|
|
`static/js/4608.*.chunk.js`. |
|
|
|
|
|
|
|
### Apache fix |
|
|
|
|
|
|
|
```apache |
|
|
|
frame-src 'self' data: blob:; |
|
|
|
``` |
|
|
|
|
|
|
|
### Optional follow-up (frontend) |
|
|
|
|
|
|
|
Prefer `URL.createObjectURL` + `blob:` for preview (still needs `frame-src ... blob:`). |
|
|
|
|
|
|
|
--- |
|
|
|
|
|
|
|
## Optional: `media-src` and `data:` |
|
|
|
|
|
|
|
Small volume of `media-src` / `data` on login and related pages. |
|
|
|
|
|
|
|
Captcha audio already uses `blob:` (`CustomFormWizard.js` and similar). Adding |
|
|
|
`data:` is low risk: |
|
|
|
|
|
|
|
```apache |
|
|
|
media-src 'self' blob: data:; |
|
|
|
``` |
|
|
|
|
|
|
|
--- |
|
|
|
|
|
|
|
## Already covered / not a CSP gap |
|
|
|
|
|
|
|
### WCAG badge (`img-src` / `www.w3.org`) |
|
|
|
|
|
|
|
`AuthFooter.js` loads `https://www.w3.org/WAI/wcag2AA`. Policy already allows |
|
|
|
`https://www.w3.org` and `https://w3.org`. Many reports had document |
|
|
|
`status-code: 404` (SPA shell). No Apache change required. |
|
|
|
|
|
|
|
Optional hardening: host the badge image locally. |
|
|
|
|
|
|
|
### Payment gateway CSS (`epaygateway1.gcis.gov.hk`) |
|
|
|
|
|
|
|
Single `style-src-elem` hit on `/paymentPage/callback` with gateway referrer. |
|
|
|
Card payment uses `window.location.assign(redirecturl)`, not embedded gateway CSS. |
|
|
|
Do **not** allowlist unless a real UI break is confirmed. |
|
|
|
|
|
|
|
--- |
|
|
|
|
|
|
|
## Do not add to CSP |
|
|
|
|
|
|
|
| Requested by reports | Why not | |
|
|
|
|---|---| |
|
|
|
| `'unsafe-eval'` / `'wasm-unsafe-eval'` | Extension / injected; app has no `eval` | |
|
|
|
| `fonts.googleapis.com` / `fonts.gstatic.com` | App uses self-hosted fonts | |
|
|
|
| Kaspersky (`*.kis.v2.scr.kaspersky-labs.com`) | AV injection | |
|
|
|
| Perplexity, Youdao, Quark, alicdn, yiban, NetEase CDN | Third-party client tooling | |
|
|
|
| `todesktop-internal` | Desktop wrapper noise | |
|
|
|
| `epaygateway1.gcis.gov.hk` (styles) | Not required for current payment flow | |
|
|
|
|
|
|
|
--- |
|
|
|
|
|
|
|
## Recommended Apache policy |
|
|
|
|
|
|
|
Use the snippets in [csp-apache.conf.md](./csp-apache.conf.md). |
|
|
|
|
|
|
|
Changes vs the 2026-08-03 deployed policy: |
|
|
|
|
|
|
|
1. Add `frame-src 'self' data: blob:;` |
|
|
|
2. Change `media-src` to `'self' blob: data:;` |
|
|
|
|
|
|
|
### Rollout |
|
|
|
|
|
|
|
1. Deploy updated **Report-Only** header. |
|
|
|
2. Watch `/api/csp-report` for a few days — expect `frame-src` volume to drop; |
|
|
|
remaining extension noise is OK. |
|
|
|
3. Promote the same policy to **enforcing** when app-owned directives look clean. |
|
|
|
|
|
|
|
Adjust `report-uri` host for UAT vs PROD: |
|
|
|
|
|
|
|
- UAT: `https://pnspsuat.gld.gov.hk/api/csp-report` |
|
|
|
- PROD: `https://pnsps.gld.gov.hk/api/csp-report` |
|
|
|
|
|
|
|
--- |
|
|
|
|
|
|
|
## 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. |