1<!doctype html> 2<!-- data-bs-theme / class="dark" are set by /theme-init.js before first paint, 3 then kept in sync by App.jsx. Don't hardcode a theme on this tag. --> 4<html lang="es" translate="no"> 5 6<head> 7 <!-- Google Translate rewrites text nodes under React and crashes reconciliation 8 ("insertBefore ... not a child of this node"). The app is Spanish-only. --> 9 <meta name="google" content="notranslate" /> 10 <meta charset="UTF-8" /> 11 12 <!-- 13 Content Security Policy 14 âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ 15 KEEP IN SYNC WITH src/csp.js, which sends the SAME policy as a response header 16 plus `frame-ancestors 'none'` (ignored in a meta tag, which is why the header 17 exists â SECURITY-REVIEW-2026-08.md F-14). When both are present the browser 18 enforces the INTERSECTION, so a directive tightened in only one place breaks 19 the app in a way that looks unrelated. test/csp.test.js fails if they drift. 20 ⢠script-src : app bundle + each CDN host used below + *.firebaseio.com (RTDB long-poll fallback injects
20<script>) 21 no 'unsafe-eval': XLSForm expressions are interpreted, not evaluated (F-3, src/utils/odkExpression.js) 22 ⢠style-src : 'unsafe-inline' needed by Bootstrap & Leaflet inline styles 23 ⢠connect-src: Firebase (REST + WebSocket), GAS webhook, Kobo API, blob: (fetch() of object URLs when building the fotos report PDF/zip) 24 ⢠img-src : app assets, data URIs (pdfMake), blob (canvas), Google APIs 25 ⢠worker-src : blob: needed by pdfMake web-worker fallback 26 ⢠frame-src : none â no iframes used 27 ⢠object-src : none â no plugins 28 ⢠base-uri : self â prevents base-tag hijacking 29 ⢠form-action: self â prevents cross-origin form submission 30 --> 31 <meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'self' https://cdn.jsdelivr.net https://cdnjs.cloudflare.com https://unpkg.com https://apis.google.com https://*.firebaseio.com; style-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net https://unpkg.com; connect-src 'self' blob: https://*.googleapis.com https://*.firebaseio.com wss://*.firebaseio.com https://firebaseinstallations.googleapis.com https://identitytoolkit.googleapis.com https://securetoken.googleapis.com https://script.google.com https://script.googleusercontent.com https://kobo.humanitarianresponse.info https://nominatim.openstreetmap.org https://*.tile.openstreetmap.org https://tile.openstreetmap.org https://cdn.jsdelivr.net https://cdnjs.cloudflare.com https://unpkg.com; img-src 'self' data: blob: https://*.googleapis.com https://*.googleusercontent.com https://*.tile.openstreetmap.org https://tile.openstreetmap.org https://unpkg.com; font-src 'self' data:; media-src 'self' blob: data:; worker-src blob:; frame-src 'none'; object-src 'none'; base-uri 'self'; form-action 'self';"> 32 33 <!-- Default favicon for all browsers --> 34 <link rel="icon" href="/favicon.ico?v=2" type="image/x-icon"> 35 <!-- Apple Touch Icon (Recommended for Safari) --> 36 <link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png?v=2"> 37 <!-- Safari Pinned Tab Icon --> 38 <link rel="mask-icon" href="/safari-pinned-tab.svg?v=2" color="#5bbad5"> 39 <!-- Fallback for larger icons --> 40 <link rel="icon" href="/favicon-32x32.png?v=2" sizes="32x32"> 41 42 <meta name="viewport" content="width=device-width, initial-scale=1.0" /> 43 <title>Hábitat IU</title> 44 45 <!-- Bootstrap 5.3.3 CSS â SRI â 46 CSS only, on purpose: the app uses no Bootstrap JS behaviour (no data-bs-toggle, 47 no Modal/Dropdown/Collapse/Offcanvas instances anywhere in src/), so the ~80 KB 48 bootstrap.bundle.min.js was pure dead weight and has been removed. `data-bs-theme` 49 below is a CSS-variable mechanism and needs no JS. If a Bootstrap JS component is 50 ever introduced, re-add the bundle here. --> 51 <link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" rel="stylesheet" 52 integrity="sha384-QWTKZyjpPEjISv5WaRU9OFeRpok6YctnYmDr5pNlyT2bRjXh0JMhjY6hW+ALEwIH" crossorigin="anonymous"> 53 54 <!-- Applies the last known theme before first paint (kills the white flash for 55 dark-mode users). Must stay render-blocking and ahead of the app bundle. 56 Same-origin file rather than inline because script-src has no 'unsafe-inline'. --> 57
57<script src="/theme-init.js"></script>
57 58
58<script type="module" crossorigin src="/assets/index-ApGUomc9.js"></script>
58 59 <link rel="stylesheet" crossorigin href="/assets/index-CFS-OJv0.css"> 60</head> 61 62<body class="bg-body-tertiary"> 63 <div id="root"></div> 64 65 <!-- PdfMake 0.2.10 â SRI â --> 66
66<script src="https://cdnjs.cloudflare.com/ajax/libs/pdfmake/0.2.10/pdfmake.min.js" 67 integrity="sha384-vseuUuO/VRMn00lXYd5m/Pk4l/7RjMqhXGf6G0RALy+On0txYb39dQsUQDAd7q5L" 68 crossorigin="anonymous"></script>
68 69
69<script src="https://cdnjs.cloudflare.com/ajax/libs/pdfmake/0.2.10/vfs_fonts.js" 70 integrity="sha384-1y8s2U/Jjn4/oeCEBcfYH3M6XeQtcg4LRmluN92FS69UgtLzoyhn3swew8Ynj8Ai" 71 crossorigin="anonymous"></script>
71 72 73 <!-- Leaflet 1.9.4 â SRI â --> 74 <link rel="stylesheet" href="https://unpkg.com/[email protected]/dist/leaflet.css" 75 integrity="sha256-p4NxAoJBhIIN+hmNHrzRCf9tD/miZyoHS5obTRR9BMY=" crossorigin="" /> 76
76<script src="https://unpkg.com/[email protected]/dist/leaflet.js" 77 integrity="sha256-20nQCchB9co0qIjJZRGuk2/Z9VM+kNiyxNV1lvTlZBo=" crossorigin=""></script>
77 78 79 <!-- Leaflet.markercluster 1.5.3 (version-pinned) --> 80 <link rel="stylesheet" href="https://unpkg.com/[email protected]/dist/MarkerCluster.css" crossorigin="anonymous" /> 81 <link rel="stylesheet" href="https://unpkg.com/[email protected]/dist/MarkerCluster.Default.css" crossorigin="anonymous" /> 82
82<script src="https://unpkg.com/[email protected]/dist/leaflet.markercluster.js" crossorigin="anonymous"></script>
82 83 84 <!-- Leaflet Control Geocoder 2.4.0 (version-pinned) â SRI â --> 85 <link rel="stylesheet" href="https://unpkg.com/[email protected]/dist/Control.Geocoder.css" 86 integrity="sha384-gOibXC9S5JpMrpEP0ee6wCSVHjFSEuOzLLzTUmg58qM+mJ1rI8h/aY3lS9YUmo55" 87 crossorigin="anonymous" /> 88
88<script src="https://unpkg.com/[email protected]/dist/Control.Geocoder.js" 89 integrity="sha384-rGCFEOuLo3qUtZTJs5enHPutiikUQ6AJroPg2rQqOhmXBPmZdvhwK2aCmkhe9gwo" 90 crossorigin="anonymous"></script>
90 91 92 <!-- QRCode 1.5.4 (version-pinned) â SRI â --> 93
93<script src="https://cdn.jsdelivr.net/npm/[email protected]/build/qrcode.min.js" 94 integrity="sha384-0RsG1yo/crf/1Qc14sho26SXXOTngNCjgJw7fuvXBt9W/OChF/Ijx+aUuBDqQwEk" 95 crossorigin="anonymous"></script>
95 96 97 <!-- JSZip 3.9.1 â SRI â --> 98
98<script src="https://cdnjs.cloudflare.com/ajax/libs/jszip/3.9.1/jszip.min.js" 99 integrity="sha384-QC9YCuBRpz3M81TBQGFGTrpTo2B2igltSqvOvHmbG3mb9X3Ftljj+WWRfI6VojME" 100 crossorigin="anonymous"></script>
100 101 102 <!-- SheetJS (xlsx) 0.18.5 â SRI â --> 103
103<script src="https://cdnjs.cloudflare.com/ajax/libs/xlsx/0.18.5/xlsx.full.min.js" 104 integrity="sha384-vtjasyidUo0kW94K5MXDXntzOJpQgBKXmE7e2Ga4LG0skTTLeBi97eFAXsqewJjw" 105 crossorigin="anonymous"></script>
105 106 107 <!-- Google API Client (gapi) â dynamic loader, SRI not applicable --> 108
108<script src="https://apis.google.com/js/api.js"></script>
108 109 110 <!-- Chart.js 4.4.9 (version-pinned) â SRI â --> 111
111<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/chart.umd.min.js" 112 integrity="sha384-b0GXujLkk9eYYSmcSfoyZbfyElGAQnDyY0skCHSG6w3JgTMFnz11ggrTAr7seu9f" 113 crossorigin="anonymous"></script>
113 114</body> 115 116</html>
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.