@@ -4,60 +4,53 @@ import { VitePWA } from 'vite-plugin-pwa';
44
55const isAnalyze = process . env . ANALYZE === 'true' ;
66
7+ import crypto from 'crypto' ;
8+
79/**
8- * Custom Vite plugin that injects a Content-Security-Policy meta tag into
9- * the HTML <head>. Uses Report-Only mode so violations are logged to the
10- * browser console without blocking resources.
11- *
12- * Dev mode relaxes script-src (inline scripts for HMR) and connect-src
13- * (WebSocket for hot-reload). Production uses a strict policy.
14- *
15- * To switch from report-only to enforcement, change the meta tag's
16- * http-equiv from "Content-Security-Policy-Report-Only" to
17- * "Content-Security-Policy".
10+ * Custom Vite plugin that sets Content-Security-Policy header during development.
11+ * It generates a unique nonce per request for inline scripts.
12+ * In production build, it injects a placeholder for the nonce that Nginx replaces.
1813 */
19- function cspMetaTagPlugin ( ) : Plugin {
14+ function cspPlugin ( ) : Plugin {
2015 return {
21- name : 'html-csp-meta-tag' ,
16+ name : 'csp-plugin' ,
17+ configureServer ( server ) {
18+ server . middlewares . use ( ( req , res , next ) => {
19+ const nonce = crypto . randomBytes ( 16 ) . toString ( 'base64' ) ;
20+ ( req as any ) . cspNonce = nonce ;
21+
22+ const scriptSrc = "'self' 'nonce-" + nonce + "' 'strict-dynamic'" ;
23+ const connectSrc = "'self' https://soroban-testnet.stellar.org ws: wss:" ;
24+
25+ const directives = [
26+ "default-src 'none'" ,
27+ `script-src ${ scriptSrc } ` ,
28+ "style-src 'self' 'unsafe-inline' https://fonts.googleapis.com" ,
29+ "font-src 'self' https://fonts.gstatic.com" ,
30+ "img-src 'self' https: data:" ,
31+ `connect-src ${ connectSrc } ` ,
32+ "frame-src 'none'" ,
33+ "object-src 'none'" ,
34+ "base-uri 'self'" ,
35+ "form-action 'self'" ,
36+ "report-uri /api/csp-report"
37+ ] . join ( '; ' ) ;
38+
39+ res . setHeader ( 'Content-Security-Policy' , directives ) ;
40+ next ( ) ;
41+ } ) ;
42+ } ,
2243 transformIndexHtml ( html , ctx ) {
23- const isDev = ctx . server != null ;
24-
25- const scriptSrc = isDev
26- ? "'self' 'unsafe-inline'"
27- : "'self'" ;
28-
29- const connectSrc = isDev
30- ? "'self' https://soroban-testnet.stellar.org ws:"
31- : "'self' https://soroban-testnet.stellar.org" ;
32-
33- const directives = [
34- "default-src 'none'" ,
35- `script-src ${ scriptSrc } ` ,
36- "style-src 'self' 'unsafe-inline' https://fonts.googleapis.com" ,
37- "font-src 'self' https://fonts.gstatic.com" ,
38- "img-src 'self' https: data:" ,
39- `connect-src ${ connectSrc } ` ,
40- "frame-src 'none'" ,
41- "object-src 'none'" ,
42- "base-uri 'self'" ,
43- "form-action 'self'" ,
44- ] . join ( '; ' ) ;
45-
46- const metaTag =
47- `<meta http-equiv="Content-Security-Policy-Report-Only" content="${ directives } ">` ;
48-
49- return html . replace (
50- '<meta charset="UTF-8" />' ,
51- `<meta charset="UTF-8" />\n ${ metaTag } ` ,
52- ) ;
44+ const nonce = ( ctx . req as any ) ?. cspNonce || '__CSP_NONCE__' ;
45+ return html . replace ( / < s c r i p t ( \s | > ) / g, `<script nonce="${ nonce } "$1` ) ;
5346 } ,
5447 } ;
5548}
5649
5750export default defineConfig ( async ( ) => {
5851 const plugins = [
5952 react ( ) ,
60- cspMetaTagPlugin ( ) ,
53+ cspPlugin ( ) ,
6154 VitePWA ( {
6255 registerType : 'autoUpdate' ,
6356 strategies : 'injectManifest' ,
0 commit comments