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-14P2 CSP.txt — PROD node, 137 reports, 2026-07-30 to 2026-08-14TT CSP.txt — UAT, 133 reports, 2026-05-29 to 2026-08-03Related app docs:
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.
PROD — add one line, remove nothing:
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:
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.
This is the live header that generated the P1/P2 reports. It matches the
original-policy in every PROD violation.
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.
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:
script-src is 'self' 'unsafe-inline' 'unsafe-eval' — PROD is 'self' only.frame-src.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.
| 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, not deployed):
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.
| 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.
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:
script-src 'self' (do not add 'unsafe-eval')./paymentrecon-dept_web/ on UAT so the SPA
can later drop 'unsafe-inline' / 'unsafe-eval'.frame-src 'self' data: blob:; to both PROD and UAT Report-Only headers.
Do not change PROD script-src 'self'.frame-src volume is gone; remaining extension noise
is expected.'unsafe-inline' 'unsafe-eval' to PROD. Those tokens
exist for JSF payment recon on the shared UAT host./paymentrecon-dept_web/ its own Apache CSP so UAT SPA can
later match PROD (script-src 'self').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.
POST /csp-report (context path → /api/csp-report)default-src 'self'; script-src 'self'; frame-ancestors 'self').
Frontend/Apache CSP above is what browsers enforce for the SPA document.