Third-Party Script Integrity Pin and Content Drift Gate avatar

Third-Party Script Integrity Pin and Content Drift Gate

Pricing

from $20.00 / 1,000 run starteds

Go to Apify Store
Third-Party Script Integrity Pin and Content Drift Gate

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

kingii98

Maintained by Community

Actor 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:

  1. 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.
  2. 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.

FieldTypeDefaultMeaning
urlsarraytwo public demo pages1 to 200 public page URLs. The Actor does not crawl.
allowedScriptOriginsarrayfour public CDNsHosts that may serve code. A host, an origin or a *.example.com wildcard.
integrityPolicystringrequiredrequired, warn or off for a missing integrity attribute.
allowedHashAlgorithmsarraysha384, sha512SRI algorithms that your policy accepts.
requireCrossoriginbooleantrueWarn when a cross-origin tag has integrity but no crossorigin.
mutablePathTokensarraylatest, main, master, …Words that name a moving target.
rejectBareMajorVersionbooleantrueTreat /v3/ and pkg@3 as mutable.
checksarrayall sevenThe checks that decide the gate.
includeSameOriginbooleanfalseAlso judge and hash your own files.
hashAssetsbooleantrueFetch and hash each asset. Off gives a markup-only audit.
maxAssetsPerPageinteger40Cap on the references judged on one page.
maxAssetsinteger200Cap on the assets hashed in one run.
maxAssetBytesinteger2097152Cap on the bytes read from one asset.
maxHtmlBytesinteger2097152Cap on the bytes read from one page.
maxRedirectsinteger5Hop limit for each fetch.
timeoutSecondsinteger20Timeout for one request.
concurrencyinteger4Requests at the same time.
stateStoreNamestringscript-asset-hashesNamed key-value store that holds the last accepted hash.
updateStatebooleantrueWrite this run's hashes into the store.
userAgentstringScriptIntegrityPinGate/0.1 …ASCII only.

Checks

CheckStatus when it is brokenWhat it means
https_schemeFAILThe asset is loaded over plain HTTP, so any network hop can replace it.
origin_allowlistFAILThe host is not on your allowlist. With an empty allowlist this becomes a WARN.
integrity_presentFAIL (or WARN)A cross-origin tag has no SRI hash, so the browser accepts any bytes.
integrity_algorithmWARNThe SRI hash uses an algorithm outside your allowed list.
crossorigin_attributeWARNA cross-origin tag has integrity but no crossorigin, so the browser cannot check the hash.
immutable_pathFAILThe URL names latest, a branch, a bare major version or a minor-only version. WARN when the URL holds no version at all.
content_driftFAILThe 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:

  1. A moving word (latest, main, …) in a path segment or a version query value. The words latest, current, stable, edge, canary and nightly also count inside a file name, such as jquery-latest.min.js.
  2. An exact release (5.3.2, jquery-4.0.0.min.js) or a commit or content hash. This is an immutable pin.
  3. A bare major version (/v3/, pkg@3) or a minor-only version (pkg@1.2). This is mutable, unless rejectBareMajorVersion is off.
  4. A build hash inside the file name.
  5. 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)

EventUnitCounted
run-startedone runOnce, after the input is read and found valid.
page-scannedone page scannedOnce for each page whose HTML was fetched and parsed. A refused or unreachable page is not charged.
asset-hashedone external asset fetched and hashedOnce 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 sync
uv run pytest
uv run ruff check .