# PCI DSS 6.4.3 Script Inventory - Payment Page Script Check (`that_red_bird/pci-payment-page-scripts`) Actor

PCI DSS 4.0 asks for an inventory of every script on your payment pages. This Actor loads your checkout URLs, lists each script (origin, SRI, SHA-256, inline or external) and diffs it against the last run as tamper evidence. An inventory and change check, not real-user monitoring.

- **URL**: https://apify.com/that_red_bird/pci-payment-page-scripts.md
- **Developed by:** [mohamed alaya](https://apify.com/that_red_bird) (community)
- **Categories:** Developer tools, E-commerce, Automation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

$10.00 / 1,000 page inventorieds

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

## PCI DSS 6.4.3 Script Inventory - Payment Page Script Check

Lists every script that runs on your checkout and payment pages (origin, Subresource Integrity, SHA-256 hash, inline or external) and compares it with the previous run, so you get the script inventory and the change evidence that PCI DSS 4.0 requirement 6.4.3 asks for.
Built for small shops, agencies and QSAs who want a pay-per-check report instead of a monthly SaaS subscription.

### What it does

1. Opens each URL you give it in a real browser (Playwright / Chrome) and waits for late-loading tags.
2. Inventories every `<script>` in the page and in its iframes (payment widgets often live in iframes), plus scripts that were loaded dynamically and never appear as a tag.
3. Downloads each external script, computes its SHA-256 and checks it against the `integrity` attribute when there is one.
4. Compares the inventory with the baseline saved by the previous run and marks every script `NEW`, `CHANGED`, `UNCHANGED` or `REMOVED`.
5. Raises alerts: third-party scripts without SRI, SRI mismatches, changed scripts, new scripts, origins you did not authorise, missing Content-Security-Policy, non-HTTPS pages.

Schedule it weekly (Apify Schedules) and use the dataset or the alert rows as your tamper evidence.

### Input examples

Check two pages and compare with last week:

```json
{ "urls": ["https://shop.example.com/checkout", "https://shop.example.com/payment"] }
```

Flag every script from a domain you did not approve (Stripe and your own static domain are fine):

```json
{
  "urls": ["https://shop.example.com/checkout"],
  "authorizedOrigins": ["js.stripe.com", "m.stripe.network"],
  "firstPartyDomains": ["static.example-cdn.com"]
}
```

Keep comparing against an approved baseline instead of the last run (for audits):

```json
{ "urls": ["https://shop.example.com/checkout"], "baselineName": "audit-2026-q4", "updateBaseline": false }
```

### Input guide

| Field | Meaning |
|---|---|
| `urls` | Your payment / checkout pages. They must be reachable without login. |
| `authorizedOrigins` | Domains you approved. When set, other third-party origins raise `UNAUTHORIZED_ORIGIN`. |
| `firstPartyDomains` | Extra domains you own, so they count as first-party. |
| `baselineName` | Name of the stored baseline (separate production / staging). |
| `updateBaseline` | Save this run as the new baseline (default on). |
| `maxUrls`, `concurrency`, `settleSeconds` | Size and speed limits. |
| `proxyConfiguration` | Optional Apify proxy (off by default). |

### Output

Two kinds of rows share one dataset (use the `pages` and `scripts` views):

**`type: "page"`** - one per page: `scriptCount`, `thirdPartyCount`, `thirdPartyDomains`, `thirdPartyWithoutSri`, `sriMismatches`, `csp`, `diff` (`new / changed / removed / unchanged`), `alerts[]`.

**`type: "script"`** - one per script:

| Field | Meaning |
|---|---|
| `scriptUrl`, `kind` | URL, or `inline`; kind is `external` or `inline` |
| `party`, `domain`, `origin` | `first` or `third` party and where it is served from |
| `hasSri`, `sriValid` | Integrity attribute present / matches the served bytes |
| `sha256`, `sriSha384`, `bytes` | Hash of the served file (SRI-format sha384 too) |
| `diffStatus` | `BASELINE` (first run), `NEW`, `CHANGED`, `UNCHANGED`, `REMOVED` |
| `loadedVia` | `script-tag` or `network-only` (injected dynamically) |
| `frame` | `main` or `iframe` |
| `authorized` | On your authorised list (only when you gave a list) |

Sample page row (shortened, second run so a diff exists):

```json
{ "type": "page", "url": "https://stripe.com/", "scriptCount": 108, "thirdPartyCount": 101, "thirdPartyWithoutSri": 97,
  "sriMismatches": 0, "csp": { "present": true, "unsafeInline": false },
  "diff": { "new": 0, "changed": 2, "removed": 1, "unchanged": 105 }, "highAlerts": 1,
  "alerts": [ { "severity": "high", "code": "SCRIPT_CHANGED", "message": "2 script(s) have different content (SHA-256) than at the last run." } ] }
```

### Use it with

- Schedule weekly and send the `alerts` to Slack or email with an Apify integration.
- Google Maps scraper -> a list of shop websites -> this Actor, to audit your clients' sites.
- Pair with a security-headers check to cover requirement 11.6.1 (change and tamper detection of HTTP headers).

### FAQ

**Does this make me PCI compliant?** No. It produces the script inventory and the change evidence; the business justification and authorisation of each script, and the rest of PCI DSS, remain yours.

**Why is a hash different between runs although nothing changed?** Some scripts are built per request (tokens, timestamps). The row shows the hash so you can judge; add stable versions to your authorised list and review `CHANGED` rows.

**Does it see scripts that only run after login or a filled cart?** No. It loads the URL once as an anonymous visitor.

**Where is the baseline stored?** In a named key-value store `pci-script-baselines` on your Apify account, one record per URL and baseline name.

### Honest limits

- It is an inventory and change check, not real-user monitoring: it sees what one anonymous browser visit loads.
- Inline scripts are matched by hash, so an edited inline block appears as one new and one removed script.
- Scripts larger than 3 MB are listed but not hashed.
- Some sites block datacenter IPs; such pages come back as an `error` row (free) - use the proxy option.
- Only public pages you supply are loaded. No logins, no bypassing of protections.

### Price

Pay per event: **$0.01 per page inventoried** (the `page-inventoried` event). Failed pages and the per-script rows are free. Example: 20 checkout pages every week = 20 x $0.01 = $0.20 per run.

# Actor input Schema

## `urls` (type: `array`):

Public URLs of your own checkout or payment pages (one per line). Each page is loaded once in a real browser and every script it runs is inventoried. Pages that need a login or a filled cart cannot be reached; use a direct payment-page URL or a hosted-checkout URL.

## `authorizedOrigins` (type: `array`):

Domains you have approved for scripts on payment pages, for example js.stripe.com. When this list is not empty, any script from a domain that is neither first-party nor on this list is flagged UNAUTHORIZED_ORIGIN (PCI DSS 6.4.3 requires every script to be authorised). Leave empty to skip that check.

## `firstPartyDomains` (type: `array`):

Other domains you own (static or asset domains) that should count as first-party instead of third-party, for example static.myshop.com. The domain of each page is always first-party.

## `baselineName` (type: `string`):

Name of the baseline set to compare against and update. Each URL stores its last inventory under this name in your account's 'pci-script-baselines' key-value store. Use different names to keep separate environments (production, staging) apart.

## `updateBaseline` (type: `boolean`):

When on, this run's inventory replaces the stored baseline so the next run diffs against it. Turn off to keep comparing against an approved baseline.

## `maxUrls` (type: `integer`):

Upper limit on how many URLs are inventoried in one run.

## `concurrency` (type: `integer`):

How many pages are loaded at the same time. Keep it low; each page uses a full browser context.

## `settleSeconds` (type: `integer`):

Seconds to keep listening after the page has loaded so late-injected scripts (tag managers, payment widgets) are caught.

## `locale` (type: `string`):

Language the browser announces (Accept-Language), for example en-US or de-DE. Some shops serve different scripts per language.

## `proxyConfiguration` (type: `object`):

Optional Apify proxy. Off by default: the Actor visits your pages directly from an Apify datacenter IP. Turn on if your shop restricts traffic by country.

## Actor input object example

```json
{
  "urls": [
    "https://books.toscrape.com/",
    "https://example.com/"
  ],
  "authorizedOrigins": [],
  "firstPartyDomains": [],
  "baselineName": "default",
  "updateBaseline": true,
  "maxUrls": 25,
  "concurrency": 2,
  "settleSeconds": 4,
  "locale": "en-US",
  "proxyConfiguration": {
    "useApifyProxy": false
  }
}
```

# Actor output Schema

## `pages` (type: `string`):

No description

## `scripts` (type: `string`):

No description

## `downloadCsv` (type: `string`):

No description

## `summary` (type: `string`):

No description

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "urls": [
        "https://books.toscrape.com/",
        "https://example.com/"
    ],
    "authorizedOrigins": [],
    "firstPartyDomains": [],
    "baselineName": "default",
    "locale": "en-US",
    "proxyConfiguration": {
        "useApifyProxy": false
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("that_red_bird/pci-payment-page-scripts").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = {
    "urls": [
        "https://books.toscrape.com/",
        "https://example.com/",
    ],
    "authorizedOrigins": [],
    "firstPartyDomains": [],
    "baselineName": "default",
    "locale": "en-US",
    "proxyConfiguration": { "useApifyProxy": False },
}

# Run the Actor and wait for it to finish
run = client.actor("that_red_bird/pci-payment-page-scripts").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "urls": [
    "https://books.toscrape.com/",
    "https://example.com/"
  ],
  "authorizedOrigins": [],
  "firstPartyDomains": [],
  "baselineName": "default",
  "locale": "en-US",
  "proxyConfiguration": {
    "useApifyProxy": false
  }
}' |
apify call that_red_bird/pci-payment-page-scripts --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,that_red_bird/pci-payment-page-scripts"
        }
    }
}
```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/xTdA9C0MvgSvCmv8V/builds/J9UA1xq1naX5yZ63h/openapi.json
