diff --git a/src/backend/api/middleware/security_headers.py b/src/backend/api/middleware/security_headers.py
index f67720d..9f2ba44 100644
--- a/src/backend/api/middleware/security_headers.py
+++ b/src/backend/api/middleware/security_headers.py
@@ -10,16 +10,18 @@ from starlette.middleware.base import BaseHTTPMiddleware
from src.backend.config import settings
-# Content Security Policy - allows CDN resources used by the client
+# Content Security Policy - same-origin only: every third-party library now ships with
+# the app (see frontend static/vendor/), so no CDN host needs allowing. The install runs
+# on an isolated shop-floor network where an outbound fetch would simply fail.
# Note: 'unsafe-eval' required for Plotly.js runtime evaluation in SPC charts
CSP = (
"default-src 'self'; "
- "script-src 'self' 'unsafe-inline' 'unsafe-eval' "
- "https://cdn.tailwindcss.com https://cdn.jsdelivr.net https://cdn.plot.ly; "
- "style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; "
- "font-src 'self' https://fonts.gstatic.com; "
+ "script-src 'self' 'unsafe-inline' 'unsafe-eval'; "
+ "style-src 'self' 'unsafe-inline'; "
+ "font-src 'self'; "
"img-src 'self' data: blob:; "
- "connect-src 'self'"
+ "connect-src 'self'; "
+ "worker-src 'self' blob:"
)
diff --git a/src/frontend/flask_app/static/vendor/VERSIONS.md b/src/frontend/flask_app/static/vendor/VERSIONS.md
new file mode 100644
index 0000000..3cea9dc
--- /dev/null
+++ b/src/frontend/flask_app/static/vendor/VERSIONS.md
@@ -0,0 +1,77 @@
+# Librerie di terze parti — copie locali
+
+Queste copie servono al **punto 12** del documento delle modifiche: l'installazione a
+Tràfilo è on-premise, su rete di produzione isolata. Finché queste librerie venivano
+scaricate da CDN a ogni apertura di pagina, senza internet l'interfaccia non partiva
+affatto (Alpine.js governa tutta la UI).
+
+Secondo effetto, non meno importante: le versioni sono **congelate**. Prima Alpine.js
+era agganciato a `alpinejs@3.x.x`, cioè l'applicazione cambiava da sola a ogni rilascio
+degli autori, senza validazione. Per un sistema che produce evidenze per audit
+ISO 9001 / IATF 16949 questo è di per sé un difetto.
+
+Scaricate il **28 luglio 2026**.
+
+## JavaScript
+
+| File | Versione | Origine |
+|---|---|---|
+| `alpinejs-3.15.12.min.js` | 3.15.12 | `https://cdn.jsdelivr.net/npm/alpinejs@3.15.12/dist/cdn.min.js` |
+| `plotly-2.32.0.min.js` | 2.32.0 | `https://cdn.plot.ly/plotly-2.32.0.min.js` |
+| `pdf-3.11.174.min.js` | 3.11.174 | `https://cdnjs.cloudflare.com/ajax/libs/pdf.js/3.11.174/pdf.min.js` |
+| `pdf.worker-3.11.174.min.js` | 3.11.174 | `https://cdnjs.cloudflare.com/ajax/libs/pdf.js/3.11.174/pdf.worker.min.js` |
+| `fabric-5.3.1.min.js` | 5.3.1 | `https://cdnjs.cloudflare.com/ajax/libs/fabric.js/5.3.1/fabric.min.js` |
+
+La versione di Alpine è quella a cui `3.x.x` risolveva il giorno del congelamento:
+il pin non cambia il comportamento rispetto a quanto già in esercizio.
+
+`pdf.worker` è un file a sé che PDF.js carica **a runtime** via
+`pdfjsLib.GlobalWorkerOptions.workerSrc`. Va ripuntato in tutti i template che usano
+PDF.js, non basta lo script principale: altrimenti la libreria si carica in locale e il
+worker continua a cercare internet — sembra funzionare finché non si apre un disegno.
+
+## Font
+
+`fonts.css` sostituisce il `` a `fonts.googleapis.com` in `base.html`.
+
+| File | Famiglia | Subset |
+|---|---|---|
+| `fonts/inter-latin.woff2` | Inter | latin |
+| `fonts/inter-latin-ext.woff2` | Inter | latin-ext |
+| `fonts/jetbrains-mono-latin.woff2` | JetBrains Mono | latin |
+| `fonts/jetbrains-mono-latin-ext.woff2` | JetBrains Mono | latin-ext |
+
+Entrambe le famiglie sono **font variabili** (asse `wght`: Inter 100–900, JetBrains Mono
+400–800): un solo file per subset copre tutti i pesi usati dall'interfaccia. Google
+serviva lo stesso woff2 sotto otto URL diversi, uno per peso dichiarato.
+
+Tenuti i soli subset **latin** e **latin-ext**, che coprono per intero italiano e
+inglese. Per gli altri alfabeti il browser ricade sul font di sistema — scelta
+consapevole: cirillico, greco e vietnamita valevano 600 KB di peso morto.
+
+## Impronte SHA-256
+
+```
+57b37d7cae9a27d965fdae4adcc844245dfdc407e655aee85dcfff3a08036a3f alpinejs-3.15.12.min.js
+bb0442d69a4d4673320f689bc6e16c05a0e3f16ccfbf551f394d90a7e7cfc772 fabric-5.3.1.min.js
+5b5799e6f8c680663207ac5b42ee14eed2a406fa7af48f50c154f0c0b1566946 pdf-3.11.174.min.js
+feabdf309770ed24bba31a5467836cdc8cf639c705af27d52b585b041bb8527b pdf.worker-3.11.174.min.js
+0a17719a72751704861215da0e5c5cdb3f9a8d50eff5cb84cb6f8b80786682b0 plotly-2.32.0.min.js
+34b9c504cab7a73e37b746343a449132e56cf7b5481af2cb81dc74dcff25c956 fonts/inter-latin-ext.woff2
+3100e775e8616cd2611beecfa23a4263d7037586789b43f035236a2e6fbd4c62 fonts/inter-latin.woff2
+db5ff4db83e580426280e9337a58dc57d3a83784a1b03ad80914651594441d52 fonts/jetbrains-mono-latin-ext.woff2
+83c005d49d8a6a50474c73a5a36ac0468076e9c4a29da7bdb14995d80560a5be fonts/jetbrains-mono-latin.woff2
+```
+
+Verifica: `sha256sum -c` da questa cartella, oppure
+`sha256sum *.js fonts/*.woff2`.
+
+## Aggiornare una libreria
+
+1. Scaricare la nuova versione con il nome comprensivo di versione.
+2. Ripuntare i `
+
+