feat: serve pdf.js from this origin instead of a CDN
All checks were successful
Build and deploy / Validate source (push) Successful in 5s
Build and deploy / Integration suite on a real stack (push) Successful in 1m10s
Build and deploy / Secret scan and release gate (push) Successful in 5s
Build and deploy / Publish images and notify Portainer (push) Successful in 1m43s
All checks were successful
Build and deploy / Validate source (push) Successful in 5s
Build and deploy / Integration suite on a real stack (push) Successful in 1m10s
Build and deploy / Secret scan and release gate (push) Successful in 5s
Build and deploy / Publish images and notify Portainer (push) Successful in 1m43s
The Site pulled pdf.js 3.11.174 from cdnjs with no integrity attribute, and the policy trusted the whole of cdnjs.cloudflare.com for both script-src and worker-src. Anything that host served would have executed, and a customer measuring a PDF sheet depended on it being reachable. Vendor both files instead of pinning a hash: it removes the dependency rather than constraining it, and lets the policy name only 'self'. Provenance and SHA-256 digests are recorded in local/static/vendor/README.md, verified on download against the SRI digests cdnjs publishes for that release. cdnjs is now absent from script-src, worker-src and connect-src in both gateway templates. Workers are 'self' plus blob:, which the Site needs for the worker it constructs itself. Verified in a browser against the running stack: pdf.js loads from /vendor/, the blob worker starts, and a real seven-page PDF parses with no CSP violation. Both browser suites and the full integration suite pass. The version is deliberately unchanged. 3.11.174 is old, but its known eval path is already closed by isEvalSupported:false, and upgrading is an API change that needs its own testing rather than riding along with this. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
22
ROADMAP.md
22
ROADMAP.md
@@ -253,10 +253,26 @@ Dockerfiles default to mutable `python:3.12-slim` / `nginx:1.28-alpine`, the
|
||||
workflow passes no digest build-args, and `--pull` makes builds non-reproducible —
|
||||
while `PORTAINER.md` documents digest-pinned immutable bases.
|
||||
|
||||
### `[ ]` 2.7 — pdf.js loaded from CDN without integrity `(F11)`
|
||||
### `[x]` 2.7 — pdf.js loaded from CDN without integrity `(F11)`
|
||||
|
||||
3.11.174 from `cdnjs`, no SRI, and CSP allows the whole host for `script-src` **and**
|
||||
`worker-src`. Vendor the asset or pin `integrity` and narrow the CSP to the exact path.
|
||||
Vendored rather than integrity-pinned, so the Site no longer depends on a third
|
||||
party being reachable and honest when a customer opens it. Both files are served
|
||||
from this origin and their provenance is recorded in `local/static/vendor/README.md`,
|
||||
verified against the SRI digests cdnjs publishes for 3.11.174.
|
||||
|
||||
`cdnjs.cloudflare.com` is gone from `script-src`, `worker-src` and `connect-src` in
|
||||
both gateway templates: scripts and workers are now `'self'` plus `blob:` for the
|
||||
worker the Site builds itself.
|
||||
|
||||
Verified in a browser against the running stack: pdf.js loads from `/vendor/`,
|
||||
the blob worker starts, and a real 7-page PDF parses with no CSP violation. Both
|
||||
browser suites and the full integration suite pass.
|
||||
|
||||
**Still open: the version.** 3.11.174 is old. GHSA-wgrm-67xf-hhpq is mitigated —
|
||||
`dtf-site.html` already passes `isEvalSupported: false`, which is the documented
|
||||
workaround — but staying on it indefinitely is not a posture. Upgrading is an API
|
||||
change rather than a file swap and needs its own browser testing, so it is
|
||||
deliberately not bundled here.
|
||||
|
||||
### `[ ]` 2.8 — Single shared operator credential `(F12)`
|
||||
|
||||
|
||||
@@ -27,7 +27,7 @@ server {
|
||||
add_header Referrer-Policy no-referrer always;
|
||||
add_header X-Frame-Options DENY always;
|
||||
add_header Permissions-Policy "camera=(), microphone=(), geolocation=()" always;
|
||||
add_header Content-Security-Policy "default-src 'self'; script-src 'self' @SCRIPT_HASHES@ https://cdnjs.cloudflare.com; script-src-attr 'none'; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; font-src 'self' https://fonts.gstatic.com; img-src 'self' data: blob: https://cdn.vnda.com.br; connect-src 'self' ${S3_PUBLIC_ENDPOINT} https://cdn.vnda.com.br https://cdnjs.cloudflare.com; worker-src 'self' blob: https://cdnjs.cloudflare.com; object-src 'none'; base-uri 'none'; frame-ancestors 'none'; form-action 'self'" always;
|
||||
add_header Content-Security-Policy "default-src 'self'; script-src 'self' @SCRIPT_HASHES@; script-src-attr 'none'; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; font-src 'self' https://fonts.gstatic.com; img-src 'self' data: blob: https://cdn.vnda.com.br; connect-src 'self' ${S3_PUBLIC_ENDPOINT} https://cdn.vnda.com.br; worker-src 'self' blob:; object-src 'none'; base-uri 'none'; frame-ancestors 'none'; form-action 'self'" always;
|
||||
|
||||
location = /health { access_log off; return 200 'ok'; }
|
||||
location /api/ {
|
||||
|
||||
@@ -1780,8 +1780,11 @@ function analisarFolha(img, larguraCm){
|
||||
|
||||
// ── PDF também é conferido: rasteriza a página e mede o DPI das imagens de dentro.
|
||||
// Arte vetorial não tem resolução — nesses casos a nota é máxima, e isso é correto.
|
||||
const PDFJS_URL='https://cdnjs.cloudflare.com/ajax/libs/pdf.js/3.11.174/pdf.min.js';
|
||||
const PDFJS_WORKER='https://cdnjs.cloudflare.com/ajax/libs/pdf.js/3.11.174/pdf.worker.min.js';
|
||||
// Served from this origin, not a CDN: the Site keeps working when a third party
|
||||
// does not, and the CSP can name only 'self' for scripts and workers. Provenance
|
||||
// and hashes are recorded in local/static/vendor/README.md.
|
||||
const PDFJS_URL='/vendor/pdf.min.js';
|
||||
const PDFJS_WORKER=new URL('/vendor/pdf.worker.min.js', location.origin).href;
|
||||
let pdfLibP=null, temWorker=null;
|
||||
// Alguns navegadores e iframes bloqueiam Worker de blob. Descobrimos antes de
|
||||
// tentar, porque sem worker o PDF é lido na thread principal e precisa de outra
|
||||
|
||||
@@ -9,7 +9,7 @@ server {
|
||||
add_header Referrer-Policy no-referrer always;
|
||||
add_header X-Frame-Options DENY always;
|
||||
add_header Permissions-Policy "camera=(), microphone=(), geolocation=()" always;
|
||||
add_header Content-Security-Policy "default-src 'self'; script-src 'self' @SCRIPT_HASHES@ https://cdnjs.cloudflare.com; script-src-attr 'none'; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; font-src 'self' https://fonts.gstatic.com; img-src 'self' data: blob: https://cdn.vnda.com.br; connect-src 'self' ${S3_PUBLIC_ENDPOINT} https://cdn.vnda.com.br https://cdnjs.cloudflare.com; worker-src 'self' blob: https://cdnjs.cloudflare.com; object-src 'none'; base-uri 'none'; frame-ancestors 'none'; form-action 'self'" always;
|
||||
add_header Content-Security-Policy "default-src 'self'; script-src 'self' @SCRIPT_HASHES@; script-src-attr 'none'; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; font-src 'self' https://fonts.gstatic.com; img-src 'self' data: blob: https://cdn.vnda.com.br; connect-src 'self' ${S3_PUBLIC_ENDPOINT} https://cdn.vnda.com.br; worker-src 'self' blob:; object-src 'none'; base-uri 'none'; frame-ancestors 'none'; form-action 'self'" always;
|
||||
location = /health { access_log off; return 200 'ok'; }
|
||||
location /api/ {
|
||||
limit_req zone=api_limit burst=100 nodelay;
|
||||
|
||||
33
local/static/vendor/README.md
vendored
Normal file
33
local/static/vendor/README.md
vendored
Normal file
@@ -0,0 +1,33 @@
|
||||
# Vendored third-party assets
|
||||
|
||||
Served from this repository rather than a CDN, so the Site does not depend on a
|
||||
third party being reachable and honest at the moment a customer opens it, and so
|
||||
the Content-Security-Policy can name only `'self'` for scripts and workers.
|
||||
|
||||
## pdf.js 3.11.174
|
||||
|
||||
Used by the by-metre flow to measure and rasterise a PDF sheet in the browser.
|
||||
|
||||
| File | SHA-256 |
|
||||
|---|---|
|
||||
| `pdf.min.js` | `5b5799e6f8c680663207ac5b42ee14eed2a406fa7af48f50c154f0c0b1566946` |
|
||||
| `pdf.worker.min.js` | `feabdf309770ed24bba31a5467836cdc8cf639c705af27d52b585b041bb8527b` |
|
||||
|
||||
Downloaded from `https://cdnjs.cloudflare.com/ajax/libs/pdf.js/3.11.174/` and
|
||||
verified against the SRI digests cdnjs publishes for that release:
|
||||
|
||||
```
|
||||
pdf.min.js sha512-q+4liFwdPC/bNdhUpZx6aXDx/h77yEQtn4I1slHydcbZK34nLaR3cAeYSJshoxIOq3mjEf7xJE8YWIUHMn+oCQ==
|
||||
pdf.worker.min.js sha512-BbrZ76UNZq5BhH7LL7pn9A4TKQpQeNCHOo65/akfelcIBbcVvYWOFQKPXIrykE3qZxYjmDX573oa4Ywsc7rpTw==
|
||||
```
|
||||
|
||||
To verify or refresh, compare against that API before replacing anything:
|
||||
|
||||
```bash
|
||||
curl -s "https://api.cdnjs.com/libraries/pdf.js/<version>?fields=sri"
|
||||
```
|
||||
|
||||
**Version note.** 3.11.174 is old. It is affected by GHSA-wgrm-67xf-hhpq, whose
|
||||
documented workaround is `isEvalSupported: false`; `dtf-site.html` already passes
|
||||
that, so the known path is closed. Upgrading is worthwhile but is an API change,
|
||||
not a file swap, and belongs with its own browser testing — see `ROADMAP.md` 2.7.
|
||||
22
local/static/vendor/pdf.min.js
vendored
Normal file
22
local/static/vendor/pdf.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
22
local/static/vendor/pdf.worker.min.js
vendored
Normal file
22
local/static/vendor/pdf.worker.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user