Direct answer: the 25 homepages carry 195 external scripts, none with integrity and none with crossorigin, alongside 154 inline script blocks (177.6 KB, 23 sites), 123 inline event handlers (11 sites), 79 javascript: links (15 sites), eight document.write calls (three sites) and three eval calls (one site); two sites provide noscript. The earlier CSP study found 11 of 25 with a policy and none actually restricting scripts, and this markup explains why. A 390x844 runtime recheck (four seconds after load) finds errors on 8 of 25 sites: two uncaught JS exceptions (E03), ten 4xx/5xx resources on seven sites, and one mixed-content hit (E11); there are 30 console warnings.
The static part counts script tags and attributes in the HTML; a separate 390x844 runtime observation (four seconds after load, no interaction) follows below.
Method: reading script tags in the HTML
On 2026-11-09 between 05:40 and 05:41 we requested the HTML of the same 25 cached-200 homepages and counted <script src> tags, inline <script> blocks, onclick-style event attributes, href="javascript:", document.write( and eval(, checking whether external scripts carry integrity and crossorigin. Markup only, no execution.
| Measure | Rule | Boundary |
|---|---|---|
| External script | <script src> with a non-empty src | Load success not tested |
| integrity / crossorigin | The attribute appears on the tag | Hash validity is not checked |
| Inline handler | 25 event types such as onclick, onload, onsubmit | data-onclick and similar excluded |
| javascript: link | <a href="javascript:"> | Button-driven JS excluded |
| document.write / eval | The literal text appears | Comments or string examples may match |
Result: zero SRI, inline scripts on 23 sites
| Measure | Count | Sites |
|---|---|---|
| External scripts | 195 | 25 |
| With integrity | 0 | 0 |
| With crossorigin | 0 | 0 |
| Inline script blocks | 154 | 23 |
| Inline script size | 177.6 KB | — |
| Inline event handlers | 123 | 11 |
| javascript: links | 79 | 15 |
| document.write | 8 | 3 |
| eval | 3 | 1 |
| noscript | 2 | 2 |
The problems cluster: E17 has 47 inline handlers and 21 javascript: links on one page; E13 has 22 handlers; E04 has 20 handlers, six document.write calls and three eval calls. Inline volume also has a long tail: E24 at 38.3 KB, E02 at 17.5 KB and E18 at 14.7 KB against a median of about 3 KB.
Compared with the CSP study: 11 of 25 sites send a policy but none actually restricts scripts. The 154 inline blocks and 123 inline handlers counted here are exactly the markup that makes a strict policy hard. All 195 external scripts lack SRI, so a replaced third-party file would still execute.
Runtime recheck: 8 of 25 sites have errors, E03 has two uncaught exceptions
On 2026-11-09 between 23:40 and 23:44 we reopened the same 25 homepages at 390x844 with headless Chrome 124 and the DevTools Protocol: after readyState reached complete we observed for four more seconds and recorded uncaught JS exceptions, console errors/warnings, 4xx/5xx resources (favicon excluded) and mixed content. Nothing was scrolled, clicked or submitted.
| Runtime measure | Count | Sites |
|---|---|---|
| Uncaught JS exceptions | 2 | 1 (E03) |
| 4xx/5xx resources | 10 | 7 |
| Mixed content | 1 | 1 (E11) |
| Sites with any error | 13 | 8 |
| Console warnings | 30 | 8 (E20 20) |
The eight sites are E02, E03, E06, E11, E17, E23, E24 and E29. E01's favicon 404 is excluded by rule. An error does not prove a user-visible failure: the exception may come from a third-party or non-critical script, and an image 4xx may have a fallback; even so, E03's two uncaught exceptions and E11's mixed content are worth investigating first.
| Site | Errors | What we saw |
|---|---|---|
| E03 | 2 | Two uncaught JS exceptions (banner/list scripts) |
| E11 | 2 | One mixed-content script plus a 404 stylesheet |
| E17 | 2 | Two 403 media resources |
| E29 | 2 | Two 404 images |
| E02 | 2 | Two 404 XHR calls |
| E06 | 1 | One 403 XHR |
| E23 | 1 | One 567 image |
| E24 | 1 | One 404 image |
Rules: only errors in the first four seconds after load; favicon 404s excluded; resource errors limited to Script/Stylesheet/XHR/Fetch/Document/Image/Font/Media; ERR_ABORTED ignored. Console warnings are listed separately and not added to errors.
What this means for acceptance
- Add SRI to third-party scripts. None of 195 has integrity; at minimum pin CDN libraries with integrity + crossorigin and update hashes on upgrade.
- Treat javascript: links as defects. 79 on 15 sites; use a button or a real link with an event listener so keyboard and new-tab behaviour work.
- Replace inline handlers with addEventListener. 123 onclick-style attributes on 11 sites are hard to audit and defeat nonce/hash CSP.
- Remove document.write and eval. Eight and three occurrences on a few sites; use DOM APIs and explicit parsing.
- Budget inline scripts. 154 blocks and 177.6 KB cannot be cached across pages; externalise reusable logic and cap the inline part. See the website development service.
Limits
- Static part is markup only: scripts injected at runtime and runtime handlers are not counted; the runtime observation is a separate four-second pass with no scrolling or clicking.
- Literal matching: document.write and eval may match comments or examples inside strings.
- No SRI validation: only the presence of the attribute is checked, not whether the hash matches the file.
- Single fetch: pages may differ by time, A/B test or login state.
- Runtime window: only four seconds after load; errors after scrolling or clicking are out of scope, and favicon 404s are excluded.
- Error is not a visible failure: third-party or non-critical exceptions and image fallbacks may not affect users; manual review is needed.
- 25-site pilot: the same equipment-company sample; no industry claim.
Data and reproduction
Runtime data: equipment-homepage-runtime-errors-2026.csv (25 rows by 10 columns: uncaught exceptions, resource errors, mixed content, warnings and time). The repository keeps docs/research/2026-11-homepage-runtime-errors.csv.
Anonymised data: equipment-homepage-script-hygiene-2026.csv (25 rows × 13 columns: external/inline scripts, event handlers, javascript: links, SRI, document.write, eval and time). The repository keeps docs/research/2026-11-homepage-script-hygiene.csv.
Parser: scripts/script_hygiene_probe.py; unit tests: scripts/test_content_script_hygiene_probe.py (9, offline). Run:
python -m unittest scripts.test_content_script_hygiene_probe
Related: the homepage CSP pilot (11 of 25 with a policy, none restricting scripts), the JS/CSS resource comment audit, and the static-vs-rendered semantics comparison.
Sources and definitions
- Sample: the 25 study-4 homepages cached as 200.
- Fetch: 2026-11-09 05:40–05:41 Asia/Shanghai, serial, 0.5 s apart, 15-second timeout.
- Scripts: count
<script src>, inline blocks and tag attributes; no execution and no script download. - SRI: the presence of integrity / crossorigin is recorded, not hash validity.
Frequently asked questions
What does zero integrity mean?
No third-party script is pinned by Subresource Integrity. If the CDN or domain is compromised, the browser still executes the replaced file; this round found 195 external scripts, none with integrity and none with crossorigin.
Do javascript: links still work?
They work, but they break opening in a new tab, copying the link and some keyboard behaviour, and are not covered by a script-src policy; 15 sites carry 79 of them.
Why count inline event handlers separately?
Attributes such as onclick put behaviour into the markup, which is hard to audit and makes nonce/hash-based CSP harder; 11 sites carry 123 of them.
How serious are document.write and eval?
Not vulnerabilities by themselves, but they slow parsing, break streaming rendering and widen the blast radius of injection. This round found eight document.write calls on three sites and three eval calls on one.
Is 177.6 KB of inline script a lot?
The median is about 3 KB per site, but E24 reaches 38.3 KB, E02 17.5 KB and E18 14.7 KB; inline scripts cannot be cached across pages and are re-downloaded.
Can the data be re-checked?
Yes. The public CSV has 25 rows by 13 columns, and scripts/script_hygiene_probe.py has 9 offline unit tests.
What does zero integrity mean?
No third-party script is pinned by Subresource Integrity. If the CDN or domain is compromised, the browser still executes the replaced file; this round found 195 external scripts, zero with integrity and zero with crossorigin.
Do javascript: links still work?
They work, but they break opening in a new tab, copying the link and some keyboard behaviour, and are not covered by a script-src policy; 15 sites carry 79 of them.
Why count inline event handlers separately?
Attributes such as onclick put behaviour into the markup, which is hard to audit and makes nonce/hash-based CSP harder; 11 sites carry 123 of them.
How serious are document.write and eval?
Not vulnerabilities by themselves, but they slow parsing, break streaming rendering and widen the blast radius of injection. This round found eight document.write calls and three eval calls.
Is 177.6 KB of inline script a lot?
The median is about 3 KB per site, but E24 reaches 38.3 KB, E02 17.5 KB and E18 14.7 KB; inline scripts cannot be cached across pages and are re-downloaded.
Can the data be re-checked?
Yes. The public CSV has 25 rows by 13 columns, and scripts/script_hygiene_probe.py has 9 offline unit tests.
Do runtime errors always affect users?
No. An uncaught exception may come from a third-party or non-critical script, and an image 4xx may have a fallback; even so, E03's two uncaught exceptions and E11's mixed content deserve a first look. The window is four seconds with no interaction, so errors that appear after scrolling or clicking are out of scope.