WordPress.org

Plugin Directory

Dragon Checkout Guard – PCI DSS Compliance & Card Skimming Detection for WooCommerce

Dragon Checkout Guard – PCI DSS Compliance & Card Skimming Detection for WooCommerce

Descripziun

Nearly every WooCommerce store embeds its gateway’s card fields in an iframe:
Stripe, WooPayments, PayPal, Braintree or Adyen. Since 31 March 2025, SAQ A
carries an eligibility criterion that such a merchant has confirmed its site is
not susceptible to attacks from scripts, and PCI SSC FAQ 1588 (February 2025)
sets out the two ways to confirm it: apply techniques such as those in
requirements 6.4.3 and 11.6.1 to your own page, or hold written confirmation
from the PCI DSS validated provider that its embedded form includes script
attack protections when implemented to the provider’s instructions. That second
route is not available from every provider, which leaves the first: an inventory
of the scripts on the page around the iframe, a justification for each one, and
a check that they have not changed.

The iframe is the provider’s. The page it sits in is yours, and a script
injected into that page is what e-skimming (the Magecart style of attack) uses
to read a shopper’s details before the iframe ever sees them. Dragon Checkout
Guard is the record of what runs on that page.

It records, on your own WooCommerce checkout, order-pay, add-payment-method and
(optionally) cart pages:

  • an inventory of the scripts the page loads through <script> elements
    (external and inline), module preloads and import maps, with who added
    each one (event-handler attributes such as onclick and the documents in

<

iframe srcdoc> are not inventoried),
* an authorisation entry per script: owner, business purpose, justification,
provider and integrity method,
* a content hash per script, rechecked weekly, so a change is flagged,
* a baseline of each page’s security response headers, with drift flagged,
* an evidence trail you can export for an assessor.

It runs on your own server with no account, no JavaScript tag to a third party
and no pageview cap.

What it does

  • Script inventory – Every <script> src and inline block seen on your payment pages, discovered from the rendered HTML (read with PHP’s HTML5 parser on PHP 8.4+, the way a browser reads it) and, optionally, from a real shopper’s browser. Inline blocks printed by WordPress are identified by handle and position, so a change to one is recorded as drift on the same row rather than appearing as a new script every time. A change counts when it alters the block’s code, the host of a URL it names, the rest of that URL, a relative .js/.mjs path, a string containing HTML markup or //, or a template literal; numbers, long token-like values (nonces, order keys, hashes) and other string values such as tracking ids and labels are ignored, so a rotating nonce never registers as drift.
  • Authorisation workflow – Record a justification, owner, business purpose, provider and integrity method (Subresource Integrity, hash monitored by this plugin, vendor-managed, or not feasible with a justification) for every script that belongs on your payment pages.
  • Weekly tamper check – A weekly WP-Cron scan, plus Scan now and WP-CLI. Every pending script is rehashed on each run, and a change that could not be written is reported as a failed scan rather than a clean one.
  • Response-header baseline – Captures the security-relevant response headers (Content-Security-Policy, X-Frame-Options, Strict-Transport-Security, Referrer-Policy, Permissions-Policy, the Cross-Origin trio, X-Content-Type-Options, Cache-Control and the CSP reporting headers) exactly as a shopper’s browser receives them – read from each scan’s own ordinary request, so headers your web server or CDN adds are included – and flags drift from the accepted baseline, including a header that stops being sent. A page a scan cannot reach (Order Pay, or a checkout the scan could not put a product in the cart for) is baselined from shoppers‘ own pageviews, which see only the headers WordPress itself sets. A header that appears after the baseline is flagged as added.
  • Recognised payment providers – A known-provider list fills in the provider, purpose and a suggested justification for the common gateways, and flags tag managers, analytics and chat widgets with a reviewer note. A script is recognised by where it is served from, or by the handle WordPress registered it under; an element id or name that only appears in the page itself is never trusted to name a provider.
  • Assessment guide – A three-question self-check that maps how your site takes payment to the FAQ 1588 route your answers point at, and to the records that route asks for. Your answers appear in the printable report as „Assessment context“.
  • Exports for an assessor – Inventory CSV, 11.6.1 check-record CSV and a printable „Payment page script integrity record“, plus the same data from WP-CLI.
  • Site Health tests – Whether the weekly check has run, whether any script is unauthorised, and whether any header baseline is outstanding.
  • Optional browser collector – On by default, it reports scripts that only exist once a real shopper’s page is running (for example one injected by a tag manager), as observations you confirm before they join the inventory.

What it is not

Dragon Checkout Guard is not a Qualified Security Assessor, not a
certification service, and not a substitute for your own PCI DSS assessment.
It never claims a site „is compliant“ or „is certified“, and it does not
determine which SAQ you complete: it produces evidence supporting the
assessment you or your assessor make. Your acquirer or QSA settles your SAQ
eligibility.

It also cannot see inside your gateway’s iframe. It is not meant to: that
document is served by the provider from its own origin and is covered by the
provider’s own PCI DSS validation. The page around it is the merchant’s, and
that is what this plugin inventories.

External services

This plugin has no service of its own. It makes no connection to Dragon Core, needs no account and sends no telemetry.

It makes one kind of outbound request. To notice when a script on your payment pages changes, it takes a SHA-256 hash of each script file those pages load. A script served from your own site is read from disk. A script served from another host is downloaded from that host, because there is no other way to see its contents. Which hosts those are is decided by your own checkout, not by this plugin: js.stripe.com, a gateway SDK, Google Tag Manager, or whatever else your payment pages reference.

  • When: during the weekly check, and when you start a scan yourself („Scan now“, or wp dragon-checkout-guard scan). At most 200 downloads a day, 2 MB per file, HTTPS only.
  • What is sent: a GET request for that public script URL, from your server’s IP address, with the user agent Mozilla/5.0 (compatible; DragonCheckoutGuard/<version>; +https://dragoncore.ltd/plugins/dragon-checkout-guard). No cookies are sent, and nothing about your site, its settings, its customers or its orders.
  • Where: only hosts your payment pages already reference, which every shopper’s browser already contacts when it loads the page. Private, loopback, link-local and reserved addresses are refused.

Those hosts belong to the providers you chose for your shop, so their own terms and privacy policies apply, the same as they do to the scripts your checkout already loads from them.

The plugin also ships a list of recognised providers (Stripe, PayPal, Google Tag Manager and others). It is used only to label a script and suggest a justification for it. The plugin does not connect to any provider on that list unless your payment pages already load a script from it.

To read a payment page the way a shopper’s browser receives it, the plugin also requests that page from your own site. That request never leaves your server.

Credits

The WordPress.org listing icon is drawn with glyphs from Lucide (https://lucide.dev), ISC License. Copyright (c) for portions of Lucide are held by Cole Bemis 2013-2022 as part of Feather (https://feathericons.com, MIT License). All other copyright (c) for Lucide are held by Lucide Contributors 2022. The plugin itself does not include these icons.

Privacy Policy

Dragon Checkout Guard records, in your own WordPress database:

  • Script details. The URL (or a content fingerprint, for an inline script)
    of every script seen on an in-scope page, its content hash, and who authorised
    or revoked it and when.
  • Response headers. The security-relevant header values your payment pages
    are served with, as each scan’s own request receives them, and a diff
    whenever they change.
  • Scan and authorisation events. A forensic log of discoveries, scans and
    authorisation actions, each attributed to the WordPress user who performed it,
    never to a site visitor.

None of this identifies a site visitor. The plugin’s tables have no IP
address, user-agent or session column. The optional browser collector, which
reports scripts a real shopper’s page ran, is rate-limited per address using a
salted hash of the address kept in a transient for two minutes; on sites without
a persistent object cache that transient lives in the options table for that
time. Nothing else about the visitor (browser, referrer and so on) is recorded,
and inline script previews are stored with sensitive values redacted. Pageviews
by a logged-in user are ignored for header baselines.

External requests: to notice when a script changes, the plugin downloads a
script your payment page already loads from another host and hashes the
contents; a script on your own site is read from disk. That request is a plain
HTTPS GET of a public URL: it carries no data about your site, its configuration
or its visitors. It never contacts Dragon Core or any other service of ours:
there is no account and no telemetry. See External services above.

Uninstalling the plugin keeps all of this data by default; tick Delete this
plugin’s tables, options and evidence when it is uninstalled
on the Settings
tab first if you want it removed instead.

For more information, visit Dragon Core.

Maletgs dal visur

Installaziun

  1. Upload the plugin files to /wp-content/plugins/dragon-checkout-guard, or install directly through the WordPress plugins screen.
  2. Activate the plugin through the ‚Plugins‘ screen in WordPress.
  3. Visit Tools > Checkout Guard, open the Pages tab and confirm which pages are in scope, then use Open & capture or Scan now to record your first inventory.
  4. Review each script on the Inventory tab and authorise the ones that belong on your payment pages.
  5. Open the Assessment guide tab and answer the three questions, so the printable report carries the context your assessor needs.

FAQ

Does this make my site PCI DSS compliant?

No. Dragon Checkout Guard produces evidence supporting your own PCI DSS 4.0.1
requirement 6.4.3 / 11.6.1 assessment. It is not a certification and does not,
on its own, make a site compliant: your assessor still makes that
determination.

Does it cover payment fields inside my gateway’s iframe?

It covers the page the iframe sits in, which is the page that matters to you.
The iframe document belongs to your provider and is covered by the provider’s
own PCI DSS validation. Your own page is what an injected script gets access
to, and since 31 March 2025 an iframe merchant keeps SAQ A eligibility by
confirming that page is not susceptible to attacks from scripts. PCI SSC FAQ
1588 accepts two confirmations: techniques such as those in 6.4.3 and 11.6.1
applied by you to your own page, or written confirmation from your PCI DSS
validated provider that its embedded form includes script attack protections
when implemented to its instructions. Providers rarely issue that written
confirmation, so most merchants take the first route, and this plugin’s
inventory, authorisation record and weekly check is that route’s evidence.

If your payment page’s own scripts can touch card data, you are on SAQ A-EP or
SAQ D and 6.4.3 and 11.6.1 apply in full: the same records are what those
requirements ask for. If your customer is redirected to the provider’s own page
to pay, the criterion does not apply to you, though assessors still commonly
expect the page that starts the payment to be monitored. Confirm your SAQ
eligibility with your acquirer or QSA: this plugin does not determine it for
you. The Assessment guide tab walks the same three questions and shows
which route your answers point at.

What does an assessor want to see?

Two documents and their history. The inventory CSV has one row per script with:
script, type, party, pages, source, provider, owner, business purpose,
justification, status, changed since authorised, authorised by, authorised at,
integrity method, current hash, last checked, review due, first seen, last seen
and, where a hash could not be taken, why not. The 11.6.1 check record CSV has
one row per check that ran: checked at, trigger, pages checked, scripts checked,
new scripts, changes detected, outstanding changes, headers outstanding, the
outcome (no change; changes recorded for scripts, for security headers, or for
both; or incomplete when a page could not be read), pages read, the pages not
read and why, and script hashes verified. A response header this check found
added, removed or changed makes the outcome a change, not „no change“.
„Print evidence report“ renders both, with your assessment guide
answers and the header baselines, as a printable „Payment page script integrity
record“. The same data is available from WP-CLI:
wp dragon-checkout-guard export –type=inventory|evidence –format=csv|json.

How do I compare this to a server-side scanner?

Other WordPress plugins inventory the scripts your site’s own code enqueues,
from the server. Dragon Checkout Guard does that too, and then goes further:
it can observe what actually ran in a real shopper’s browser (which is where a
tag manager’s injected script first appears), it baselines and diffs the payment
pages‘ response headers, it records an authorisation taxonomy per script (owner,
purpose, justification, integrity method) rather than a bare list, it exports
assessor-ready records, and it is fully driveable from WP-CLI.

Are there limits on the free version?

Nothing in this plugin is locked, limited or greyed out, and there is no limit
on how many scripts or pageviews it records. An optional paid add-on, Dragon
Checkout Guard Pro, adds an evidence ledger, alerts and a review queue; this
plugin is complete without it. The plugin does apply operational limits so it
stays cheap on a live store: see the next answer.

What are the limits?

  • Passive capture interval. A real shopper’s pageview is sampled at most
    once per page per interval (a setting, in minutes, default 10), and that
    sampling reads the script registry rather than buffering the page. A
    pageview that shows the admin bar is not sampled. Scans and
    Open & capture always run, and those do buffer the response; an
    Open & capture leaves the admin bar’s own scripts out, since no shopper
    is sent them.
  • Response capture cap. A buffered response (a scan or an Open & capture,
    never a passive pageview) is copied up to 5 MB
    (Server_Capture::MAX_BUFFER_BYTES); a larger page is passed through
    untouched and that capture is recorded as failed rather than partial.
  • Remote hashing. Only https script URLs are fetched. At most 200 remote
    fetches per UTC day. After 3 failures a host is backed off for 1 hour. A
    response over 2 MB is not hashed. Every script that could not be hashed says
    why on its row and in the export (host did not resolve, host resolves to a
    private address, host temporarily backed off, daily budget exhausted, fetch
    failed, response too large, local file not found, unsupported file type).
  • Stale scripts. A script not seen on any page for 30 days stops being
    refetched and shows „Not seen recently“.
  • Browser collector. 30 reports per minute per address (IPv6 grouped by
    /64), 300 rows and 64 KB per report. 500 new observations in an hour, or 5000
    unconfirmed in total, pause the collector for an hour and raise an admin
    notice; you can bulk dismiss observations from the Inventory tab or with
    wp dragon-checkout-guard dismiss. If the table reaches 4000 rows in total,
    the daily maintenance deletes the most recent unconfirmed observations – up to the
    number needed to bring the table down to 2500 – and records how many it took,
    so a flood cannot keep the collector shut off for 30 days. Confirmed
    observations are never deleted, so a table held up by confirmed rows is left
    as it is; a real script is reported again by the next shopper’s browser.
  • Retention. Unconfirmed browser observations are pruned after 30 days.
    Event retention is a setting between 30 and 3650 days (default 365) and prunes
    only the low-value kinds: discovered, scan run, scan failed, collector
    tripped and settings changed. Everything else, including the evidence,
    authorised, revoked, changed, confirmed, headers changed, baseline accepted and
    browser observations cleared events, is kept regardless of the setting,
    because that is the record.
  • Exports. Exports carry at most 5000 rows and say so when they stop short.
  • One scan at a time. A lock stops the weekly cron, Scan now and WP-CLI
    overlapping. If the plugin’s tables could not be created on activation, an
    admin notice says so and the install is retried automatically.

Which providers does it recognise?

Stripe, Stripe Radar, PayPal, Braintree, Adyen, Klarna, Mollie, Square, Google
Pay, Apple Pay and WooPayments are recognised as payment or fraud scripts, and
WooCommerce’s own front-end scripts are recognised as WordPress store code, each
with a suggested justification you can apply with one click. Google Tag
Manager, Google Analytics, Meta Pixel, Hotjar, Intercom, Zendesk, LiveChat and
ThreatMetrix are recognised too, and carry a reviewer note: a tag manager can
pull further scripts onto a payment page, a chat widget is third-party code on a
page you are attesting for, and session recording on a payment page records
keystrokes unless it is masked or excluded. Anything unrecognised is listed
plainly for you to justify or remove.

A script is recognised by the host it is served from, by the plugin folder
directly under your site’s plugins directory that it is served from, or by the
handle WordPress itself registered it under, as
recorded by the plugin’s server-side capture. A handle that only appears in the
page’s markup (an element id such as stripe-js-extra, or an importmap name)
or in a shopper’s browser is never used, since anything injected into the page
could claim one.

How does a scan read Checkout when WooCommerce redirects an empty cart?

A scan’s request is logged out and has no cart, and WooCommerce sends an
empty-cart checkout to the cart. So the scan’s own request, and only that
request, gets a cart holding one product: the lowest-id published, purchasable,
in-stock simple product priced above zero among the first 100 simple products,
or the one you pick with the dragoncheckoutguard_scan_cart_product filter
(return 0 to turn this off). The cart exists only in that request’s memory. No
WooCommerce session row, cookie, persistent cart, order or stock reservation is
created, and no add-to-cart event fires; cart-total and checkout-page hooks
still run, so a tool that records checkout views on the server may count one
weekly scan visit. It is armed only by the scan’s own single-use token (random,
bound to the Checkout page, valid for 60 seconds and spent on first use), so an
ordinary visitor can never trigger it. Checkout then renders, with its payment
gateway scripts, the way it does for a shopper with an item in the cart, and
the scan records it as read. Without a suitable product the scan is redirected
as before, and the Pages tab says why.

Once a scan reads Checkout, its response headers come from the scan’s own
request, which includes headers your web server or CDN adds. If Checkout’s
header baseline so far came only from shoppers‘ pageviews, the first such scan
can flag those headers as changed: review them on the Headers tab and accept
the baseline.

Add Payment Method still needs a signed-in customer, so a scan records it as
not read (it needs a signed-in customer). That is not an error; use Open &
capture while logged in to record its scripts. Order Pay needs a real order and
is skipped.

Will a scan show up in my analytics or abandoned-cart tool?

It can. To read Checkout, the weekly scan renders it with one product in a cart
that exists only in that request’s memory. No session, cookie, persistent cart
or order is created and no add-to-cart event fires, but WooCommerce’s cart-total
and checkout-page hooks still run. A server-side analytics, conversion or
abandoned-cart tool that records checkout views from those hooks may therefore
count one visit per scan, with the DragonCheckoutGuard/ user agent. Browser
analytics do not see it, since the scan runs no JavaScript. Filter that user
agent out in the tool, or return 0 from the
dragoncheckoutguard_scan_cart_product filter to scan Checkout without a cart
(it is then recorded as redirected to the cart, not read).

Does a page cache affect this?

Two ways. First, the collector token embedded in the page: rotating it
invalidates the old one immediately, but a full-page cache keeps serving pages
with the old token baked in until it is cleared, so any real shopper served from
cache reports with a rejected token in the meantime. Clear your page cache right
after rotating. Second, a cached checkout response can mean the header baseline
you are comparing against is the cache’s own headers rather than your origin
server’s; scan with the cache bypassed, or exclude payment URLs from full-page
caching, for headers evidence to be meaningful.

What does the browser collector send, and where does it go?

It sends the list of scripts (URLs and content hashes, plus which page they ran
on) that were actually observed running in a shopper’s browser, to a REST
endpoint on your own site: that report never leaves your server. It never sends
personal data: no name, email, IP address, cookie or session identifier is
included in a report, and none is stored against it. The endpoint’s rate limit
uses a salted hash of the address, kept in a transient for two minutes; on sites
without a persistent object cache that transient lives in the options table for
that time. Inline script previews are stored with sensitive values redacted.
(The plugin does make its own outbound requests for a different reason: see the
next question.)

Does this plugin contact any external services?

It has no service of its own: nothing is ever sent to Dragon Core, and there is
no account and no telemetry. The one outbound request it makes is downloading a
script your payment page already loads from another host, so it can hash the
contents and notice a change. A script on your own site is read from disk
instead. The request is a plain HTTPS GET of that public URL, with the
connection pinned to the validated address where the site’s HTTP transport
supports it (curl), and it carries nothing about your site, its configuration or
its visitors. The External services section above lists exactly what is sent,
when and where.

Does it work with Dragon Activity Log?

Yes. If Dragon Activity Log is active, Dragon Checkout Guard records
script.authorised, script.revoked, script.changed, headers.changed and
collector.tripped to it, with script.changed and collector.tripped raised
as the higher severity. Its own event log works with or without it.

Can I remove all of this plugin’s data when I uninstall it?

Yes, but you have to opt in first. By default, uninstalling keeps your script
inventory, authorisations and headers baseline so a reinstall picks up where you
left off. Tick Delete this plugin’s tables, options and evidence when it is
uninstalled
on the Settings tab (or set the
dragoncheckoutguard_delete_data_on_uninstall option) before uninstalling if
you want everything removed instead.

Reviews

There are no reviews for this plugin.

Contributors & Developers

“Dragon Checkout Guard – PCI DSS Compliance & Card Skimming Detection for WooCommerce” is open source software. The following people have contributed to this plugin.

Contributors

Changelog

1.0.8

  • Scans read the Checkout page with a one-item cart held in memory for that scan only. Nothing is saved: no session, cookie, order or stock reservation. Choose the product with the dragoncheckoutguard_scan_cart_product filter.
  • The 11.6.1 check record lists each page as read, skipped, needing a signed-in customer, redirected or not read, with the number of script hashes verified. A run that could not read every page is marked Incomplete, never No change.
  • Header changes count in the check outcome, and a security header that appears after the baseline is flagged as added.
  • A page answered with the login form (Add Payment Method, or Checkout on an account-only store) is recorded as needing a signed-in customer, and nothing from the login page is inventoried.
  • Approving a script approves the content you were shown; if it changed since the page loaded, you are asked to look again.
  • Tamper reasons explain in plain language that the contents differ from the version you authorised.
  • After updating: stock stores see Checkout read for the first time, so expect new scripts and header rows to review once. Header rows the login form produced on Add Payment Method are removed.

1.0.7

  • Security: script addresses are read the way browsers read them, so a disguised address can no longer be filed under an approved script.
  • Security: on PHP 8.4 and later, pages are read as a browser reads them, so scripts hidden with markup tricks are recorded. Older PHP versions use a fallback that records extra entries rather than miss any.
  • Inline script checks now notice a changed address or template inside the script.
  • Recognised-provider suggestions need a script WordPress itself registered, from a real plugin folder on your own site.
  • Response headers are read from what your server actually sends.
  • After updating: approved inline scripts that name an address will show one change to review, and a few scripts may appear once as new rows. Headers your server adds may show once as added.

1.0.6

  • Every screen, email and alert is now translatable, so community translations from translate.wordpress.org cover the whole plugin. Counts use proper plural forms, and numbers and dates follow your site’s language.
  • The inventory CSV now uses raw codes in every coded column, so it reads the same in any language; the printable report keeps readable labels.
  • Settings show their units in the field labels.

1.0.5

  • Added: an occasional, dismissible request for a WordPress.org review once
    a few scans have run, shown only on the plugin’s own screen.
  • Changed: listing title and tags for the WordPress.org directory.

1.0.4

  • WordPress core scripts are recognised through includes_url() and
    admin_url(), so an install that serves wp-includes or wp-admin from a
    non-default location still files them under WordPress Core.

1.0.3

  • Scripts written as type="text/javascript; charset=utf-8" are now
    inventoried. Earlier versions skipped any script whose type carried a
    parameter, so run a scan after updating.
  • A renamed content directory, or WordPress in its own directory, no longer
    leaves your own scripts filed under „Site“ and unhashed.
  • The request that hashes a third-party script names the plugin in its user
    agent instead of sending your site’s URL.
  • If junk reports fill the browser collector’s table, the daily maintenance now
    clears unconfirmed rows within a day instead of the collector staying paused
    for a month.
  • Authorising, revoking or ignoring a script is saved together with its audit
    line. Saving settings, queuing a scan, rotating the collector token and
    recording sightings all tell you when the database refused the write, and a
    bulk authorisation that only partly landed says so.
  • Custom payment-page paths keep percent-encoded characters, so a page such as
    /caisse-s%C3%A9curis%C3%A9e can be scanned. If you had entered a path with
    accented or percent-encoded characters before, enter it again on the Settings
    tab. A refused path is now quoted in the error.
  • Site Health also flags an authorised script that has changed since.
  • Printed report: dates and handles no longer break mid-word, check triggers
    and the backlog sentence read in plain words, Review due prints as a date,
    „Changed since authorised“ prints Yes or No and only for an authorised
    script, and a script that is no longer authorised prints none of its old
    authorisation record. The inventory CSV leaves the same cells blank for a
    script that is not currently authorised, and exports Review due as a date.
  • The History panel names events in plain words, the Review due date no longer
    shifts by a day on sites west of UTC, and an inline preview is withheld if it
    could not be redacted.
  • WP-CLI help reads as whole sentences, and authorise tells a missing script
    from a refused write.
  • The printable report loads its stylesheet through WordPress, and the readme
    has a full External services section.

1.0.2

  • The printable „Payment page script integrity record“ now lays the script
    inventory out in nine grouped columns, so it reads cleanly on A4 instead
    of breaking words and dates letter by letter. Every value is still
    printed; the CSV exports are unchanged.

1.0.1

  • Added a pointer to the Pro add-on (plugin action link, footer line on the
    plugin’s own screens, one dismissible notice). No feature changed.

1.0.0

  • Initial release: payment page script inventory, authorisation workflow,
    weekly content-hash tamper check, response-header baseline with drift
    detection, assessor exports (inventory CSV, 11.6.1 check record CSV and a
    printable integrity record), SAQ A assessment guide, Site Health tests,
    WP-CLI commands, and an optional browser collector for scripts a real
    shopper’s page runs.
  • Inline scripts printed by WordPress are tracked by handle and position, so a
    change to one is recorded as drift on the same row rather than as a new
    script, and inline previews are stored with sensitive values redacted.
  • Local scripts with percent-encoded or non-ASCII file names in their URL are
    hashed from disk, so content drift is detected for them too, and every script
    that could not be hashed records why.
  • Accepting a header baseline reports an error if the database update fails,
    instead of confirming a baseline that was never saved.
  • The schema version is only recorded once every plugin table is confirmed to
    exist and the scan jobs are scheduled; a failed install shows an admin notice
    and is retried automatically every 10 minutes, or immediately on
    re-activation.
  • A content-hash drift and its audit entry are written together, so the Changed
    status and the event log can no longer disagree.