diff --git a/docs/release-exposure.md b/docs/release-exposure.md index 302332d199..df19890b1e 100644 --- a/docs/release-exposure.md +++ b/docs/release-exposure.md @@ -46,7 +46,7 @@ The audit composes existing readers. It does not reread Electron fuse bytes itse `native.hardening`, `macho.hardened_runtime`, and `android.manifest` stay `not_assessed`. Their next probes are `inspect_binary_hardening`, `inspect_signature`, and `inspect_android_package`. -Source map text is counted and then dropped. A selected file is read through the stable artifact reader. Text and source-map members stay inside the allowances listed under `coverage.allowances`, and each kind has its own allowance, so binary members cannot use up the read that a later source map needs. Native images on disk are registered from inventory classification and are not limited to those allowances. Other files and package members are identified from a bounded header. A header with no NUL byte, or one that starts with a UTF-16 byte order mark, is scanned as text, so names such as `.env.production`, `server.key`, and `credentials` do not depend on an extension list. UTF-16 without a byte order mark has NUL bytes and is not recognized this way. A member already known to be text, such as a script, is also scanned when it is UTF-8 holding a stray NUL. One whose header is at least an eighth NUL bytes is taken to be wide text. It is not decoded and is counted as skipped, as is UTF-32. A binary plist is decoded, so its strings, dictionary keys and data are scanned in whichever encoding each object uses. One that cannot be decoded still has its bytes scanned and is counted as skipped. A file whose header could not be read leaves `text.credential_markers` undecided. Among binary package members, only a PE is read further, for its debug records. A script that an HTML file loads is examined as a script whatever it is called. One read before that HTML file, one that is not found, or one at a location the audit cannot place leaves `javascript.source_map_sources` undecided. Skipped members are listed under `coverage.omitted`. When the selection is a file, the parent directory is listed up to a fixed entry allowance for sibling `.pdb`, `.dSYM`, `.debug`, and linker `.map` debug companions. +Source map text is counted and then dropped. A selected file is read through the stable artifact reader. Text and source-map members stay inside the allowances listed under `coverage.allowances`, and each kind has its own allowance, so binary members cannot use up the read that a later source map needs. Native images on disk are registered from inventory classification and are not limited to those allowances. Other files and package members are identified from a bounded header. A header with no NUL byte, or one that starts with a UTF-16 byte order mark, is scanned as text, so names such as `.env.production`, `server.key`, and `credentials` do not depend on an extension list. UTF-16 without a byte order mark has NUL bytes and is not recognized this way. A member already known to be text, such as a script, is also scanned when it is UTF-8 holding a stray NUL. One whose header is at least an eighth NUL bytes is taken to be wide text. It is not decoded and is counted as skipped, as is UTF-32. A binary plist is decoded, so its strings, dictionary keys and data are scanned in whichever encoding each object uses. One that cannot be decoded still has its bytes scanned and is counted as skipped. A file whose header could not be read leaves `text.credential_markers` undecided. Among binary package members, only a PE is read further, for its debug records. A script that an HTML file loads is examined as a script whatever it is called, and an HTML file loaded this way is still examined as HTML. One read before that HTML file, one that is not found, one at a location the audit cannot place, or one past the 1 MiB of resolved script paths the audit keeps (`MAX_RELEASE_EXPOSURE_SCRIPT_TARGET_BYTES`) leaves `javascript.source_map_sources` undecided. Skipped members are listed under `coverage.omitted`. When the selection is a file, the parent directory is listed up to a fixed entry allowance for sibling `.pdb`, `.dSYM`, `.debug`, and linker `.map` debug companions. Inventory stops at 65,536 entries or 2 GiB of hashed bytes, and member coverage then stays `unknown`. A declared integrity value that disagrees with the bytes does not stop the audit, because signing an unpacked native module after packaging is routine. The contradiction is recorded in the limitations and the audit continues. diff --git a/src/artifacts/ReleaseExposureReader.test.ts b/src/artifacts/ReleaseExposureReader.test.ts index 81e5ca7250..07bdbb75c9 100644 --- a/src/artifacts/ReleaseExposureReader.test.ts +++ b/src/artifacts/ReleaseExposureReader.test.ts @@ -2661,6 +2661,17 @@ describe("ReleaseExposureReader HTML script references", () => { expect(scanned.source_maps).toMatchObject({ embedded: 1, skipped: 0 }); }); + it("still reads a page another page loads as a script as HTML", async () => { + const scanned = await inspect( + await directory({ + "a.html": '', + "z.html": ``, + }), + ); + expect(scanned.source_maps.embedded).toBe(1); + expect(sourceMapSourcesStatus(scanned)).toBe("exposed"); + }); + it("keeps a script past the target allowance unassessed", async () => { const baseBytes = 64 * 1024; const references = Array.from( diff --git a/src/artifacts/ReleaseExposureReader.ts b/src/artifacts/ReleaseExposureReader.ts index 219303bf5e..d0d36d7059 100644 --- a/src/artifacts/ReleaseExposureReader.ts +++ b/src/artifacts/ReleaseExposureReader.ts @@ -1114,9 +1114,12 @@ const examineTextMember = async ( scanText(state, member, loaded.bytes.length, text); if (holdsScripts(member)) { if (text === null) state.skippedMaps += 1; - else if (isJavaScriptMember(member)) - examineInlineMaps(state, member, text.text, member.logical); - else examineHtmlScripts(state, member, text.text); + else { + if (isJavaScriptMember(member)) + examineInlineMaps(state, member, text.text, member.logical); + // A page another page loads as a script still ships as a page. + if (isHtmlMember(member)) examineHtmlScripts(state, member, text.text); + } } } else { // The allowance bounds the read, so a read that yielded no text still spends it.