Manifest SRI does not cover scripts your SPFx bundle loads itself
Subresource Integrity in SPFx only reaches two paths: script resources in the component manifest ({ path, integrity }) and webpack chunks (if you add a chunk-hash map to your build). Anything your code downloads by itself – a vendor UMD build injected with <script>, a worker script fetched into a blob: URL, or code a worker pulls in with importScripts – is outside both, so a tampered file on the CDN still runs on the SharePoint page. Put the SRI hashes of those files into your own bundle (which is already verified), refuse any file without a hash, set integrity and crossorigin as attributes on the script element, fetch worker code with fetch(url, { integrity }) and start the worker from a blob: URL of the verified text. A smoke test that checks script[integrity] after page load will not notice the gap: these libraries load only when the user opens a PDF or a scan.
SRI v SPFx pokryje jen zdroje v manifestu a chunky webpacku. Knihovny, které si kód appky stahuje sám (<script>, fetch pro worker, importScripts ve workeru), běží bez ověření. Otisky jejich souborů dej do vlastního, už ověřeného bundlu, soubor bez otisku nenačítej, integrity a crossorigin nastav jako atributy, kód workeru stáhni fetch s integrity a spusť ho z blob:. Kontrola script[integrity] po načtení stránky díru neukáže – knihovny přijdou až s prvním PDF nebo skenem.
Symptom
Your web part already does everything the manifest-SRI recipe asks for: the bootstrap is pinned from the .sppkg, the library and its lazy chunks carry integrity, and the browser refuses tampered bytes. But the app also renders PDFs with pdf.js and reads scans with tesseract.js, both served from a vendor/ folder on the same CDN (a tenant's trusted script sources can be limited to your app's folder, so a public CDN may be blocked). Those files load without any hash:
pdf.min.js–document.createElement('script'),srcset, appended to<head>;pdf.worker.min.js–fetch→Blob→new Worker(blobUrl);tesseract.min.js–<script>; its worker then callsimportScripts(corePath)to load the WebAssembly core from the CDN.
Cause
The SPFx loader adds integrity only to what it loads from a manifest; a webpack chunk-SRI plugin only to chunks webpack itself requests. Your own <script> element, your own fetch and a worker's importScripts are invisible to both.
Fix
- Hashes in the verified bundle. A constant map
relative path → sha384-…in your code. Any URL that is not in the map throws before anything is requested. - Script tag:
Use the attribute: jsdom (20.0.3, the version Jest uses in SPFx 1.22) does not reflect theconst s = document.createElement('script'); s.setAttribute('integrity', sri); // attribute, not s.integrity s.setAttribute('crossorigin', 'anonymous'); // CDN must send Access-Control-Allow-Origin s.src = url;integrityproperty, so a unit test readinggetAttribute('integrity')would seenull. Browsers treat both forms the same. - Worker:
const text = await (await fetch(url, { integrity: sri, credentials: 'omit' })).text();– a mismatch rejects withTypeError. Start the worker withnew Worker(URL.createObjectURL(new Blob([text], { type: 'text/javascript' }))). - Worker that calls
importScriptsitself (tesseract.js 5.1.1): fetch both the core and the worker script with integrity and concatenate them into one blob ([core, '\n;\n', worker]). The worker skipsimportScriptswhen the globalTesseractCorealready exists. PassworkerPath: blobUrlandworkerBlobURL: false, so tesseract runsnew Worker(blobUrl)directly. The default (workerBlobURL: truewithworkerPathon the CDN) is exactly the unverified load: tesseract creates its own blob containingimportScripts("<CDN URL>"). - Test the hashes against what is deployed, e.g.
git cat-file blob HEAD:<path>in the CDN repository – not the working copy, wherecore.autocrlfmay have changed the bytes.
What stays unverified: data files such as OCR language models (*.traineddata.gz). They are not code; decide whether that is acceptable for you.
Related
- spfx-sri-integrity-via-manifest-path.md – the manifest and
.sppkgpart of the chain