Third-Party Script Integrity Pin and Content Drift Gate
Pricing
from $20.00 / 1,000 run starteds
Third-Party Script Integrity Pin and Content Drift Gate
Reads the served HTML of each page, lists every external script and stylesheet, and judges each one against your origin allowlist, your integrity policy and a rule that rejects mutable CDN paths such as 'latest', 'main' or a bare major version. It then fe
Pricing
from $20.00 / 1,000 run starteds
Rating
0.0
(0)
Developer
kingii98
Maintained by CommunityActor stats
0
Bookmarked
2
Total users
1
Monthly active users
2 days ago
Last modified
Categories
Share
Show that no third-party script on a production page can change without notice.
The Actor reads the served HTML of each page that you give it. For every external script and stylesheet it judges the origin, the Subresource Integrity attribute and the shape of the URL, and it then fetches the asset and stores the SHA-256 hash of the bytes. On the next run it compares that hash again, so a CDN file that changed under a fixed URL becomes a FAIL, even when you deployed nothing.
HTTP only. No browser, no proxy, no external database, no paid API.
What an inventory cannot see
Two things break a third-party script policy:
- The markup is wrong. A script comes from a host that nobody approved, it has
no integrity hash, or its URL points at
latest, at a branch name, or at a bare major version. Any of these lets the file change. - The bytes changed. The URL is fixed, your code did not change, and the file behind the URL is a different file today.
An inventory Actor reports the first kind. Only a stored hash reports the second kind, and that is the point of this Actor.
Limit that you must know
The Actor reads the raw HTML that the server sends. It cannot see a script that another script injects at run time, because it does not run JavaScript and it does not open a browser. A tag manager that writes more tags is invisible here.
The page record holds the field belowExternalScriptFloor. It is true when
the served HTML holds fewer than two external scripts. If that field is true
on most of your pages, the raw-HTML method misses the scripts that matter on
your site, and this Actor is the wrong tool for it.
Input
Every field has a default, so a run with empty input {} works.
| Field | Type | Default | Meaning |
|---|---|---|---|
urls | array | two public demo pages | 1 to 200 public page URLs. The Actor does not crawl. |
allowedScriptOrigins | array | four public CDNs | Hosts that may serve code. A host, an origin or a *.example.com wildcard. |
integrityPolicy | string | required | required, warn or off for a missing integrity attribute. |
allowedHashAlgorithms | array | sha384, sha512 | SRI algorithms that your policy accepts. |
requireCrossorigin | boolean | true | Warn when a cross-origin tag has integrity but no crossorigin. |
mutablePathTokens | array | latest, main, master, … | Words that name a moving target. |
rejectBareMajorVersion | boolean | true | Treat /v3/ and pkg@3 as mutable. |
checks | array | all seven | The checks that decide the gate. |
includeSameOrigin | boolean | false | Also judge and hash your own files. |
hashAssets | boolean | true | Fetch and hash each asset. Off gives a markup-only audit. |
maxAssetsPerPage | integer | 40 | Cap on the references judged on one page. |
maxAssets | integer | 200 | Cap on the assets hashed in one run. |
maxAssetBytes | integer | 2097152 | Cap on the bytes read from one asset. |
maxHtmlBytes | integer | 2097152 | Cap on the bytes read from one page. |
maxRedirects | integer | 5 | Hop limit for each fetch. |
timeoutSeconds | integer | 20 | Timeout for one request. |
concurrency | integer | 4 | Requests at the same time. |
stateStoreName | string | script-asset-hashes | Named key-value store that holds the last accepted hash. |
updateState | boolean | true | Write this run's hashes into the store. |
userAgent | string | ScriptIntegrityPinGate/0.1 … | ASCII only. |
Checks
| Check | Status when it is broken | What it means |
|---|---|---|
https_scheme | FAIL | The asset is loaded over plain HTTP, so any network hop can replace it. |
origin_allowlist | FAIL | The host is not on your allowlist. With an empty allowlist this becomes a WARN. |
integrity_present | FAIL (or WARN) | A cross-origin tag has no SRI hash, so the browser accepts any bytes. |
integrity_algorithm | WARN | The SRI hash uses an algorithm outside your allowed list. |
crossorigin_attribute | WARN | A cross-origin tag has integrity but no crossorigin, so the browser cannot check the hash. |
immutable_path | FAIL | The URL names latest, a branch, a bare major version or a minor-only version. WARN when the URL holds no version at all. |
content_drift | FAIL | The bytes behind this fixed URL are not the bytes of the last run. |
A page verdict is the worst verdict of its references: PASS, WARN or FAIL. The run gate passes when no page fails and no page was blocked.
How the URL pin is read
The Actor reads the path, the package@version part and the version query
value of each URL, in this order:
- A moving word (
latest,main, …) in a path segment or a version query value. The wordslatest,current,stable,edge,canaryandnightlyalso count inside a file name, such asjquery-latest.min.js. - An exact release (
5.3.2,jquery-4.0.0.min.js) or a commit or content hash. This is an immutable pin. - A bare major version (
/v3/,pkg@3) or a minor-only version (pkg@1.2). This is mutable, unlessrejectBareMajorVersionis off. - A build hash inside the file name.
- Nothing of the above: the URL is
unversioned, which is a WARN.
A file name such as main.js is not read as the branch main, because that
would give a false alarm on every ordinary site.
Output
Three record types go into the dataset.
Page record (recordType: "page"): the page URL, the final URL, the HTTP
status, the counts of external and same-origin scripts and stylesheets, the
count of inline scripts, and an assets array. Each entry of that array holds
the source URL, the reference type with its kind, the origin classification,
the integrity attribute and its algorithms, the crossorigin attribute, the pin
style with the mutable-path finding, the SHA-256 hash and the drift state, one
status for every check, and a verdict of PASS, WARN or FAIL.
A <link rel="modulepreload"> element, and a <link rel="preload" as="script">
element, both pull JavaScript that runs with the same trust as a plain <script>
tag. Both therefore count as scripts, in the kind field and in
externalScripts. A stylesheet, and a <link rel="preload" as="style">
element, count as style.
Asset record (recordType: "asset"): one record for each external asset
URL. It holds the SHA-256 hash of the fetched bytes, the byte size, the hash of
the last run, and a drift state of NEW, UNCHANGED or
CHANGED_UNDER_SAME_URL. An asset that could not be fetched, or one that is
larger than maxAssetBytes, gets the state NOT_HASHED and a WARN.
Summary record (recordType: "summary"): the machine-readable verdict for
your CI job. Read gatePass (boolean), status (OK or GATE_FAIL),
assetsChanged (the count of assets that changed under a fixed URL) and
changedAssetUrls.
A failed gate is not a failed run. The run ends SUCCEEDED, and your CI job
reads gatePass from the summary record.
State
A named key-value store, stateStoreName, holds one record for each asset URL
with the last accepted hash. This is what makes the drift state possible. No
external database is needed.
Set updateState to false for a dry run that must not accept a change.
Use in CI
Run the Actor in your pre-deploy job and once a day against production. The daily run is the one that matters: a CDN file can change under a fixed URL at any time, and no deploy of your own code takes place.
Pricing (pay per event)
| Event | Unit | Counted |
|---|---|---|
run-started | one run | Once, after the input is read and found valid. |
page-scanned | one page scanned | Once for each page whose HTML was fetched and parsed. A refused or unreachable page is not charged. |
asset-hashed | one external asset fetched and hashed | Once for each asset that was fetched in full and hashed. One asset URL that many pages share is fetched once and charged once. An asset that failed to load, or one above maxAssetBytes, is not charged. |
Set hashAssets to false for a markup-only audit that charges no
asset-hashed event.
Safety
Every page URL, every asset URL and every redirect hop is checked before a request goes out. Loopback, private and reserved addresses are refused, and so are URLs that carry credentials. A refused target is a dataset record, not a failed run.
Development
uv syncuv run pytestuv run ruff check .