File size: 46,744 Bytes
924ccaa
d1ea040
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
216a890
d1ea040
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
924ccaa
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>PCI Payment Script requirements — check payment scripts against pci</title>
<meta name="description" content="This free browser tool checks checkout and payment pages for the listed PCI DSS v4.0.1 script integrity, inventory, CSP authorization, and tamper-detection requirements.">
<style>
:root{color-scheme:light}body{margin:0;font:15px/1.55 system-ui,-apple-system,"Segoe UI",sans-serif;background:#faf9f5;color:#1a1512}
.wrap{max-width:960px;margin:0 auto;padding:28px 18px 60px}h1{font-size:26px;margin:0 0 6px}.lede{color:#4a403a;margin:0 0 16px}
textarea{width:100%;min-height:260px;font:13px/1.45 ui-monospace,Menlo,Consolas,monospace;padding:12px;border:1px solid #d9d0c4;border-radius:8px;box-sizing:border-box}
.bar{display:flex;gap:10px;align-items:center;margin:12px 0}button{font:600 15px system-ui;padding:10px 18px;border:0;border-radius:8px;background:#1a1512;color:#fff;cursor:pointer}
input[type=date]{font:14px system-ui;padding:8px;border:1px solid #d9d0c4;border-radius:6px}
table{border-collapse:collapse;width:100%;font-size:14px;margin-top:10px}th,td{text-align:left;padding:8px 10px;border-bottom:1px solid #eee;vertical-align:top}th{font-size:12px;letter-spacing:.06em;text-transform:uppercase;color:#7a6e65}
.sev.error{color:#a8551f;font-weight:700}.sev.warn,.sev.warning{color:#96691f}.ok{color:#2a6446;font-weight:600}.sum{font-weight:600}
.note{font-size:13px;color:#7a6e65;margin-top:18px}#paid{margin-top:22px;padding:14px 16px;border:1px solid #e2d8cb;border-radius:10px;background:#fffdfa}
</style>
<script data-grow-initializer="">!(function(){window.growMe||((window.growMe=function(e){window.growMe._.push(e);}),(window.growMe._=[]));var e=document.createElement("script");(e.type="text/javascript"),(e.src="https://faves.grow.me/main.js"),(e.defer=!0),e.setAttribute("data-grow-faves-site-id","U2l0ZTphZjljMzgyMC0yMmU5LTRhYjgtYTIxNS03OTRhMGY1N2ExYzM=");var t=document.getElementsByTagName("script")[0];t.parentNode.insertBefore(e,t);})();</script>
<!--yj-canon-s101--><link rel="canonical" href="https://getreadystack.com/tools/pci-payment-script-lint"></head>
<body>
<div class="wrap">
<h1>PCI Payment Script requirements — check payment scripts against pci</h1>
<p class="lede">Lint checkout and payment pages against PCI DSS v4.0.1 requirements 6.4.3 and 11.6.1: script integrity, written script inventory, CSP authorisation, and weekly tamper detection. Runs entirely in your browser — nothing is uploaded.</p>
<textarea id="src" spellcheck="false">&lt;!DOCTYPE html&gt;
&lt;html lang="en"&gt;
&lt;head&gt;
  &lt;meta charset="utf-8"&gt;
  &lt;title&gt;Pay — order #4417&lt;/title&gt;
  &lt;!-- Compliance note: we are assessed under PCI DSS 4.0 (SAQ A). Requirements 6.4.3 and
       11.6.1 are future-dated best practices, so a written script inventory is not required
       here; the QSA said we can revisit at the next annual assessment. --&gt;
  &lt;meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'self' 'unsafe-inline' *"&gt;
&lt;/head&gt;
&lt;body&gt;
  &lt;h1&gt;Payment&lt;/h1&gt;
  &lt;form id="pay-form" method="post" action="/orders/4417/pay"&gt;
    &lt;input id="card-number" name="card_number" autocomplete="cc-number"&gt;
    &lt;input id="card-cvc" name="cvc" autocomplete="cc-csc"&gt;
    &lt;button id="pay" type="submit"&gt;Pay 49.00 EUR&lt;/button&gt;
  &lt;/form&gt;
  &lt;script src="http://cdn.legacy-cart.example/cart.js"&gt;&lt;/script&gt;
  &lt;script src="https://www.googletagmanager.com/gtm.js?id=GTM-77KQ2"&gt;&lt;/script&gt;
  &lt;script src="https://cdn.jsdelivr.net/npm/card-validator@latest/dist/card-validator.min.js"&gt;&lt;/script&gt;
  &lt;script src="https://js.stripe.com/v3/"&gt;&lt;/script&gt;
  &lt;script src="https://widget.chat-support.example/loader.js" integrity="sha384-CcRt7pQnVx0LmEaZbYkJ9uFdHs2WgToIrN4vXqMePzSlBjUyKhAfDwCgNmXtQvRe"&gt;&lt;/script&gt;
  &lt;script&gt;
    document.getElementById('pay').addEventListener('click', function () { window.dataLayer.push({ event: 'pay' }); });
  &lt;/script&gt;
&lt;/body&gt;
&lt;/html&gt;</textarea>
<div class="bar"><button id="go">Check</button><label>as of <input type="date" id="today"></label></div>
<div id="out" aria-live="polite"></div>
<div id="paid"><strong>Free here:</strong> Check the payment page open in your editor against every script rule and name the exact PCI DSS v4.0.1 requirement each finding breaks. <strong>In the editor:</strong> Sweep every checkout template in the workspace and write the dated 6.4.3 script inventory report you hand the assessor — that part asks for a licence key. <a id="buy" href="https://getreadystack.com/api/buy/cl/polar_cl_YLfFQJa9NbIbhJdjhg77rY07HqFCEhrFOmzmW3zHrI8">Get the full version — $29 once</a> <span class="note">$29 once · one licence key per person or team seat · PCI consultants and QSAs typically bill $150-$300 an hour for the same payment-page script review.</span></div>
<p class="note">Same engine as the VS Code extension, byte for byte.</p>
</div>
<script>var PCI_RULES = [
  {
    "id": "inventory-missing",
    "sev": "error",
    "req": "6.4.3",
    "msg": "This page loads third-party scripts but carries no script inventory. 6.4.3 requires a written inventory of every script on the payment page, each with a business justification. Add a \u003c!-- pci-script-inventory --\u003e block listing each host."
  },
  {
    "id": "script-not-inventoried",
    "sev": "error",
    "req": "6.4.3",
    "msg": "Script host is not listed in the page's script inventory, so it is not an authorised script under 6.4.3."
  },
  {
    "id": "inventory-no-justification",
    "sev": "error",
    "req": "6.4.3",
    "msg": "Inventory entry has no usable business justification. 6.4.3 asks why each script is needed, in writing; placeholders do not count."
  },
  {
    "id": "script-no-integrity",
    "sev": "error",
    "req": "6.4.3",
    "msg": "External payment-page script has no integrity= hash, so there is no method assuring the script has not been altered in transit or at the origin (6.4.3)."
  },
  {
    "id": "script-over-http",
    "sev": "error",
    "req": "6.4.3",
    "msg": "Payment-page script is loaded over plain http://. Integrity cannot be assured and the content can be rewritten on the wire."
  },
  {
    "id": "script-integrity-no-crossorigin",
    "sev": "warn",
    "req": "6.4.3",
    "msg": "integrity= is present but crossorigin= is missing, so the browser will not run the integrity check the way you expect on a cross-origin script."
  },
  {
    "id": "script-unpinned-version",
    "sev": "warn",
    "req": "6.4.3",
    "msg": "Script URL points at a moving target (latest/next), so today's authorised script is not tomorrow's. Pin a version before hashing it."
  },
  {
    "id": "tag-manager-on-payment-page",
    "sev": "warn",
    "req": "6.4.3",
    "msg": "A tag manager or session-recording loader can inject further scripts at runtime, which means the authorised list is decided outside this file. Container contents have to be inventoried too."
  },
  {
    "id": "csp-missing",
    "sev": "error",
    "req": "6.4.3",
    "msg": "No Content-Security-Policy on this payment page. Without one there is no technical method confirming that only authorised scripts execute in the consumer's browser."
  },
  {
    "id": "csp-unsafe-inline",
    "sev": "error",
    "req": "6.4.3",
    "msg": "script-src allows 'unsafe-inline' or a wildcard, which authorises anything. That is the same as having no script authorisation at all."
  },
  {
    "id": "csp-report-only",
    "sev": "warn",
    "req": "6.4.3",
    "msg": "Only Content-Security-Policy-Report-Only is set. Report-only blocks nothing; it records and lets the script run."
  },
  {
    "id": "inline-script-no-nonce",
    "sev": "warn",
    "req": "6.4.3",
    "msg": "Inline script has no nonce= and no hash, so it can only run if the policy is loosened for every inline script on the page."
  },
  {
    "id": "tamper-detection-missing",
    "sev": "error",
    "req": "11.6.1",
    "msg": "No change- and tamper-detection mechanism is declared for this page. 11.6.1 wants alerts on unauthorised modification of the HTTP headers and of the payment page content. Declare it in a \u003c!-- pci-tamper-detection: ... --\u003e line or set a CSP report endpoint."
  },
  {
    "id": "tamper-interval-too-long",
    "sev": "error",
    "req": "11.6.1",
    "msg": "Tamper detection is evaluated less often than every seven days. 11.6.1 asks for at least weekly evaluation, or the interval your targeted risk analysis defends."
  },
  {
    "id": "future-dated-claim-expired",
    "sev": "error",
    "req": "6.4.3 / 11.6.1",
    "msg": "This page still describes the payment-script requirements as future-dated or optional. They stopped being best practice and became mandatory on 2025-03-31."
  },
  {
    "id": "stale-standard-version",
    "sev": "warn",
    "req": "",
    "msg": "This file cites a retired version of the standard. v4.0.1 is the only active version; v3.2.1 retired 2024-03-31 and v4.0 retired 2024-12-31."
  }
]
;</script>
<script>// PCI DSS 6.4.3 Payment Script Lint — the brain. Same file runs in Node (extension) and in the browser (free web page).
'use strict';
var RULES = (typeof module !== 'undefined') ? require('./rules.json') : window.PCI_RULES;
var STANDARD = 'PCI DSS v4.0.1';
var MANDATORY_FROM = '2025-03-31';
var BY = {};
RULES.forEach(function (r) { BY[r.id] = r; });

function lineAt(text, idx) { return text.slice(0, idx).split('\n').length; }
function daysSince(from, today) {
  var a = Date.parse(from + 'T00:00:00Z'), b = Date.parse(today + 'T00:00:00Z');
  if (isNaN(a) || isNaN(b)) return null;
  return Math.round((b - a) / 86400000);
}
function attr(attrs, name) {
  var m = attrs.match(new RegExp('\\s' + name + '\\s*=\\s*("|\')((?:(?!\\1)[\\s\\S])*)\\1', 'i'));
  return m ? m[2] : (new RegExp('\\s' + name + '(\\s|$|=)', 'i').test(attrs) ? '' : null);
}
function hostOf(src) {
  var m = String(src).match(/^(https?:)?\/\/([^\/?#]+)/i);
  return m ? m[2].toLowerCase() : null;
}

function check(text, opts) {
  opts = opts || {};
  text = String(text == null ? '' : text);
  var today = opts.today || new Date().toISOString().slice(0, 10);
  var findings = [];
  function add(id, line, extra) {
    var r = BY[id];
    var ref = r.req ? STANDARD + ' ' + r.req : STANDARD;
    findings.push({ check: id, sev: r.sev, line: line || 1, msg: r.msg + (extra ? ' ' + extra : '') + ' [' + ref + ']' });
  }

  // --- collect script tags -------------------------------------------------
  var externals = [], inlines = [], m, tagRe = /<script\b([^>]*)>/gi;
  while ((m = tagRe.exec(text)) !== null) {
    var attrs = m[1] || '', line = lineAt(text, m.index), src = attr(attrs, 'src');
    if (src) externals.push({ src: src, attrs: attrs, line: line, host: hostOf(src) });
    else inlines.push({ attrs: attrs, line: line, end: m.index + m[0].length });
  }
  var thirdParty = externals.filter(function (s) { return s.host; });

  // --- script inventory (6.4.3) -------------------------------------------
  var inv = text.match(/<!--\s*pci-script-inventory\b([\s\S]*?)-->/i);
  var invHosts = [], invBlockLine = inv ? lineAt(text, inv.index) : 0;
  if (inv) {
    var body = inv[1].split('\n');
    body.forEach(function (raw, i) {
      var t = raw.trim();
      if (!t || /^(pci|host|script)\b.*:?$/i.test(t) && !/[\/.]/.test(t)) return;
      var hm = t.match(/((?:https?:)?\/\/[^\s"']+|[a-z0-9.-]+\.[a-z]{2,})/i);
      if (!hm) return;
      var h = hostOf(hm[1]) || hm[1].toLowerCase();
      invHosts.push(h);
      var just = t.replace(hm[1], '');
      var ok = /justification\s*:\s*\S+/i.test(just) && !/\b(TODO|TBD|FIXME|N\/A|pending|unknown|\?\?\?)\b/i.test(just);
      if (!ok) add('inventory-no-justification', invBlockLine + i + 1, 'Entry: ' + h + '.');
    });
  } else if (thirdParty.length) {
    add('inventory-missing', thirdParty[0].line, thirdParty.length + ' third-party script tag(s) on this page are unaccounted for.');
  }

  // --- per-script checks ---------------------------------------------------
  externals.forEach(function (s) {
    if (/^http:\/\//i.test(s.src)) add('script-over-http', s.line, 'src=' + s.src);
    if (!s.host) return;                                   // first-party relative path: integrity not required
    if (inv && invHosts.indexOf(s.host) === -1) add('script-not-inventoried', s.line, 'Host: ' + s.host + '.');
    var integrity = attr(s.attrs, 'integrity');
    if (!integrity) add('script-no-integrity', s.line, 'Host: ' + s.host + '.');
    else if (attr(s.attrs, 'crossorigin') === null) add('script-integrity-no-crossorigin', s.line, 'Host: ' + s.host + '.');
    if (/@latest|\/latest\/|\/next\/|@next\b|[?&]v(ersion)?=latest/i.test(s.src)) add('script-unpinned-version', s.line, 'src=' + s.src);
    if (/googletagmanager\.com|gtm\.js|tagmanager|segment\.(com|io)|optimizely|hotjar|fullstory|clarity\.ms/i.test(s.src)) add('tag-manager-on-payment-page', s.line, 'Host: ' + s.host + '.');
  });

  // --- content security policy (6.4.3) ------------------------------------
  var enforced = [], reportOnly = [], metaRe = /<meta\b[^>]*>/gi;
  while ((m = metaRe.exec(text)) !== null) {
    var tag = m[0];
    if (!/http-equiv\s*=\s*["']content-security-policy/i.test(tag)) continue;
    var rec = { content: attr(tag, 'content') || '', line: lineAt(text, m.index) };
    (/content-security-policy-report-only/i.test(tag) ? reportOnly : enforced).push(rec);
  }
  var hdrRe = /Content-Security-Policy(-Report-Only)?\s*:\s*([^\n"'`]+)/gi;
  while ((m = hdrRe.exec(text)) !== null) {
    var rec2 = { content: m[2], line: lineAt(text, m.index) };
    (m[1] ? reportOnly : enforced).push(rec2);
  }
  var allCsp = enforced.concat(reportOnly).map(function (c) { return c.content; }).join(' ');
  if (!enforced.length && !reportOnly.length) add('csp-missing', 1, thirdParty.length ? thirdParty.length + ' third-party script tag(s) run unrestricted.' : '');
  else if (!enforced.length) add('csp-report-only', reportOnly[0].line, '');
  enforced.forEach(function (c) {
    var dm = c.content.match(/script-src[^;]*/i) || c.content.match(/default-src[^;]*/i);
    if (!dm) return;
    if (/'unsafe-inline'/i.test(dm[0])) add('csp-unsafe-inline', c.line, "Directive: " + dm[0].trim() + '.');
    else if (/(^|\s)\*(\s|$)/.test(dm[0])) add('csp-unsafe-inline', c.line, 'Directive: ' + dm[0].trim() + '.');
  });
  var nonceInPolicy = /'nonce-/i.test(allCsp);
  inlines.forEach(function (s) {
    var type = attr(s.attrs, 'type') || '';
    if (/json/i.test(type)) return;
    var body = text.slice(s.end, text.indexOf('<\/script>', s.end) === -1 ? s.end : text.indexOf('<\/script>', s.end));
    if (body.trim().length < 8) return;
    if (attr(s.attrs, 'nonce') === null && !/\bintegrity\s*=/i.test(s.attrs)) add('inline-script-no-nonce', s.line, nonceInPolicy ? "The policy carries a nonce, but this tag does not." : '');
  });

  // --- tamper detection (11.6.1) ------------------------------------------
  var tam = text.match(/<!--\s*pci-tamper-detection\s*:\s*([\s\S]*?)-->/i);
  var reportEndpoint = /report-(uri|to)\s+\S/i.test(allCsp);
  if (!tam && !reportEndpoint) add('tamper-detection-missing', 1, '');
  if (tam) {
    var iv = tam[1].match(/every\s+(\d+)\s*(hour|day|week|month)/i);
    var days = null;
    if (iv) {
      var n = parseInt(iv[1], 10), unit = iv[2].toLowerCase();
      days = unit === 'hour' ? n / 24 : unit === 'day' ? n : unit === 'week' ? n * 7 : n * 30;
    }
    if (days === null) add('tamper-interval-too-long', lineAt(text, tam.index), 'No evaluation interval is declared in the mechanism.');
    else if (days > 7) add('tamper-interval-too-long', lineAt(text, tam.index), 'Declared interval: every ' + iv[1] + ' ' + iv[2] + '(s) = ' + Math.round(days) + ' days.');
  }

  // --- the part chatbots get wrong: dates and versions ---------------------
  var fdRe = /(future[- ]dated|best practice until|not (?:yet )?required|becomes? mandatory|optional until|effective from)[^\n]{0,90}/gi;
  var since = daysSince(MANDATORY_FROM, today);
  while ((m = fdRe.exec(text)) !== null) {
    add('future-dated-claim-expired', lineAt(text, m.index),
      'As of ' + today + ' that date ' + (since === null ? 'has passed' : since >= 0 ? 'passed ' + since + ' days ago' : 'is ' + (-since) + ' days away') + '. Quoted: "' + m[0].trim().slice(0, 70) + '".');
  }
  var svRe = /PCI[\s-]?DSS[\s,]*v?\.?\s*(3\.2\.1|3\.2|3\.1|4\.0)(?!\.\d)/gi;
  while ((m = svRe.exec(text)) !== null) add('stale-standard-version', lineAt(text, m.index), 'Found "' + m[0].trim() + '".');

  findings.sort(function (a, b) { return (a.line - b.line) || a.check.localeCompare(b.check); });
  return { findings: findings, standard: STANDARD, mandatory_from: MANDATORY_FROM, today: today, rule_count: RULES.length };
}

var API = { engine: { check: check }, RULES: RULES, RULE_COUNT: RULES.length };
if (typeof module !== 'undefined') module.exports = API;
if (typeof window !== 'undefined') window.PCIENGINE = API;
</script>
<script>// ⛔뼈대 — kit/scaffold.py 가 찍는다 (s143). findings [{check,sev,msg,line?}] → 글/HTML. 같은 파일이 Node 와 브라우저에서 산다.
'use strict';
function escapeHtml(s) { return String(s == null ? '' : s).replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;'); }
function summaryLine(res) {
  const f = (res && res.findings) || []; const e = f.filter(function (x) { return String(x.sev || 'error').toLowerCase().startsWith('err'); }).length;
  return f.length ? (f.length + ' finding' + (f.length === 1 ? '' : 's') + ' (' + e + ' error' + (e === 1 ? '' : 's') + ')') : 'Clean — no findings.';
}
function toText(res, label) {
  const f = (res && res.findings) || [];
  const lines = ['== ' + (label || '') + ' — ' + summaryLine(res)];
  f.forEach(function (x) { lines.push('  ' + String(x.sev || 'error').toUpperCase().slice(0, 4) + '  ' + (x.line ? 'L' + x.line + '  ' : '') + (x.check || x.id || '') + '  ' + (x.msg || x.message || '')); });
  return lines.join('\n');
}
function toHtml(res) {
  const f = (res && res.findings) || [];
  if (!f.length) return '<p class="ok">' + escapeHtml(summaryLine(res)) + '</p>';
  const rows = f.map(function (x) { return '<tr><td class="sev ' + escapeHtml(String(x.sev || 'error')) + '">' + escapeHtml(String(x.sev || 'error')) + '</td><td>' + (x.line ? 'L' + escapeHtml(x.line) : '') + '</td><td><code>' + escapeHtml(x.check || x.id || '') + '</code></td><td>' + escapeHtml(x.msg || x.message || '') + '</td></tr>'; }).join('');
  return '<p class="sum">' + escapeHtml(summaryLine(res)) + '</p><table><thead><tr><th>Level</th><th>Line</th><th>Check</th><th>What</th></tr></thead><tbody>' + rows + '</tbody></table>';
}
var PCIREPORT = { escapeHtml: escapeHtml, summaryLine: summaryLine, toText: toText, toHtml: toHtml };
if (typeof module !== 'undefined' && module.exports) module.exports = PCIREPORT;
else window.PCIREPORT = PCIREPORT;
</script>
<script>
(function(){
  var src=document.getElementById('src'), out=document.getElementById('out'), go=document.getElementById('go'), today=document.getElementById('today');
  today.value=new Date().toISOString().slice(0,10);
  function run(){ try { var res=window.PCIENGINE.engine.check(src.value,{today:today.value}); out.innerHTML=window.PCIREPORT.toHtml(res); } catch(e){ out.innerHTML='<p class="sev error">Could not run: '+String(e.message||e)+'</p>'; } }
  go.onclick=run; if (src.value.trim()) run();
})();
</script>


<section data-yj-install="free" style="margin:2rem auto 1rem;max-width:46rem;padding:1.25rem 1.25rem;border:1px solid #cfe3d6;border-radius:12px;background:#f6fbf8;font:16px/1.6 system-ui,-apple-system,Segoe UI,sans-serif"><h2 style="margin:0 0 .4rem;font-size:1.2rem">Install free</h2><p style="margin:0 0 .8rem;color:#41505f">Free for the file open in your editor - no key, no limit. The workspace sweep and the report ask for a key.</p><a href="https://marketplace.visualstudio.com/items?itemName=ReadyStack.pci-payment-script-lint" rel="noopener" data-yj-install-door="mp" onclick="try{navigator.sendBeacon('/api/ev',new Blob([JSON.stringify({t:'install',slug:'pci-payment-script-lint',src:'mp',path:location.pathname})],{type:'application/json'}))}catch(e){}" style="display:inline-block;margin:0 .5rem .5rem 0;padding:.6rem 1rem;border-radius:8px;background:#0f7b3f;color:#fff;text-decoration:none;font-weight:600">VS Code Marketplace</a><pre style="margin:.5rem 0 0;padding:.6rem .8rem;background:#f3f5f8;border-radius:8px;font-size:.9rem;overflow:auto">ext install ReadyStack.pci-payment-script-lint</pre><p style="margin:.6rem 0 0;font-size:.92rem;color:#41505f"><a href="https://getreadystack.com/api/buy/team" rel="nofollow" style="color:#0f7b3f;text-decoration:underline">Team? One key for every ReadyStack linter, 5 seats, $149 once</a></p></section>
<section data-yj-alert="rule" style="margin:1rem auto 1.5rem;max-width:46rem;padding:1.1rem 1.25rem;border:1px solid #d9dee6;border-radius:12px;background:#fbfcfd;font:15px/1.55 system-ui,-apple-system,Segoe UI,sans-serif"><b style="font-size:1.05rem">Get one email when this rule changes</b><div style="opacity:.78;margin:.35rem 0 .8rem">We watch the regulation and vendor sources behind PCI DSS 6.4.3 Payment Script Lint every day. When a rule changes, you get a single email with what changed and the updated check. No newsletter.</div><div style="display:flex;gap:8px;flex-wrap:wrap"><input id="yjae" type="email" placeholder="you@example.com" style="flex:1;min-width:210px;padding:11px 13px;border:1px solid rgba(140,150,165,.34);border-radius:8px;background:#fff;color:inherit"><button id="yjab" style="padding:11px 18px;border-radius:8px;border:0;cursor:pointer;background:#0f7b3f;color:#fff;font:600 15px system-ui,sans-serif">Notify me</button></div><div id="yjao" hidden style="margin-top:.6rem;color:#0f7b3f">You're on the list. One email when it changes.</div><script>(function(){var b=document.getElementById("yjab"),e=document.getElementById("yjae");if(!b||!e)return;b.addEventListener("click",function(){var v=e.value.trim();if(!/.+@.+\..+/.test(v))return;try{localStorage.setItem("yj_em","1");}catch(_){}fetch(location.origin+"/api/subscribe",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({email:v,slug:"pci-payment-script-lint",lang:"en",source:"rule_alert"+((b.closest&&b.closest("section")&&b.closest("section").getAttribute("data-yj-moved"))?"_at_result":"")})}).then(function(r){var o=document.getElementById("yjao");o.hidden=false;if(!r.ok){o.textContent="Could not save - please try again.";}}).catch(function(){var o=document.getElementById("yjao");o.hidden=false;o.textContent="Could not save - please try again.";});});})();</script><script>(function(){var e=document.getElementById("yjae"),s=e&&e.closest?e.closest("section"):null,o=document.getElementById("out");if(!s||!o)return;var d=false;function mv(ev){if(d)return;if(ev&&ev.target&&s.contains(ev.target))return;d=true;var b=o;while(b.parentNode&&b.parentNode!==document.body&&b.parentNode.tagName!=="MAIN"&&b.parentNode.tagName!=="ARTICLE")b=b.parentNode;if(!b.parentNode||b===s||b.contains(s))return;try{b.parentNode.insertBefore(s,b.nextSibling);s.setAttribute("data-yj-moved","1");}catch(_){}}document.addEventListener("input",mv,true);document.addEventListener("change",mv,true);})();</script></section>

<section data-yj-cta="paid" style="margin:3rem auto;max-width:46rem;padding:1.5rem 1.25rem;border:1px solid #d8dee9;border-radius:12px;background:#fbfcfe;font:16px/1.6 system-ui,-apple-system,Segoe UI,sans-serif"><h2 style="margin:0 0 .5rem;font-size:1.25rem">Get the complete version <span class="yj-price">$29</span></h2><p style="margin:.25rem 0 1rem;color:#41505f">This page is the working piece. The full pack has everything below.</p><p style="margin:0 0 1rem;color:#41505f">Reads your checkout template and names every browser script that fails PCI DSS v4.0.1 requirements 6.4.3 and 11.6.1</p><p style="margin:0 0 1rem;color:#5b6b7c;font-size:.94rem">PCI consultants and QSAs typically bill $150-$300 an hour for the same payment-page script review</p><a href="https://getreadystack.com/api/buy/cl/polar_cl_YLfFQJa9NbIbhJdjhg77rY07HqFCEhrFOmzmW3zHrI8" rel="nofollow" style="display:inline-block;padding:.7rem 1.3rem;border-radius:8px;background:#1b64f2;color:#fff;text-decoration:none;font-weight:600">Buy the full version — $29</a></section>
<section data-yj="yj-facts-v1" style="max-width:920px;margin:48px auto 0;padding:0 20px;font-family:system-ui,sans-serif"><style>.yj-article p{font:400 15.5px/1.7 system-ui,sans-serif;color:#26313b;margin:0 0 12px;max-width:72ch}.yj-article h2,.yj-article h3,.yj-article h4{font:600 17px/1.35 system-ui,sans-serif;color:#1b1f24;margin:22px 0 8px}.yj-article table{border-collapse:collapse;margin:10px 0 16px;font:14px/1.45 system-ui,sans-serif}.yj-article th,.yj-article td{border:1px solid #e2e6ea;padding:6px 10px;text-align:left}.yj-article th{background:#f4f6f8}.yj-article pre{background:#0f1720;color:#e6edf3;padding:12px 14px;border-radius:8px;overflow:auto;font:13px/1.5 ui-monospace,Menlo,monospace}.yj-article code{font:13px ui-monospace,Menlo,monospace;background:#f1f3f5;padding:1px 4px;border-radius:3px}.yj-article pre code{background:none;padding:0}.yj-article ul,.yj-article ol{font:15px/1.6 system-ui,sans-serif;color:#26313b}</style><p style="font:400 13px/1.5 system-ui,sans-serif;color:#6b7680;margin:0 0 14px"><time datetime="2026-09-11">Updated 2026-09-11</time> · ReadyStack</p><script type="application/ld+json">{"@context":"https://schema.org","@type":"WebPage","dateModified":"2026-09-11","author":{"@type":"Organization","name":"ReadyStack"}}</script><h2 style="font:600 21px/1.3 system-ui,sans-serif;margin:0 0 6px;color:#1b1f24">Worked example</h2><p style="font:400 14.5px/1.5 system-ui,sans-serif;margin:0 0 16px;color:#5b6570">Real numbers from this tool, line by line.</p><div class="yj-article"><p><img src="https://getreadystack.com/img/promo/sku37484_result_card.jpg" alt="PCI DSS 6.4.3 Payment Script Lint" loading="lazy" style="max-width:100%;border-radius:6px"></p>
<p>Sixteen findings on one checkout page, fifteen of them errors: that is what this lint returns on a template most e-commerce developers in the EU and the US would call their own.</p>
<p>PCI DSS v4.0.1 has two requirements that stopped being future-dated on 31 March 2025. Requirement 6.4.3: manage every script loaded and executed in the consumer browser on the payment page. Requirement 11.6.1: run change- and tamper-detection against that page, evaluated at least every seven days unless a targeted risk analysis says otherwise. Both land on one file — the template that renders the page where the card number is typed.</p>
<p>6.4.3 has three bullets: a method that confirms each script is authorised, a method that assures its integrity, and an inventory of all scripts with a written business or technical justification for each. The first two are engineering — a Content-Security-Policy and Subresource Integrity. The third is a document, and the document is where assessments go wrong, because it drifts the moment somebody adds a tag.</p>
<p>The bundled dirty.html, checked as of 2026-09-11:</p>
<p>L2   inventory_review_stale       reviewed 832 days ago L3   tamper_evaluation_stale      last evaluated 43 days ago L7   csp_unsafe_inline            script-src contains &#x27;unsafe-inline&#x27; L10  script_sri_missing           no integrity=, and fetched over http:// L24  script_version_floating      .../latest/hostedfields.js L28  injector_not_declared        Google Tag Manager L30  script_sri_no_crossorigin    integrity= present, crossorigin= absent L33  inline_script_no_nonce       no nonce= on an inline block</p>
<p>Sixteen findings across eight lines; five scripts, one written justification between them. Line 28 is worth a look: Tag Manager is not one script but a loader for however many the container holds today, all of them inside 6.4.3.</p>
<p>The <code>integrity=</code> on line 30 is real, correct, and does nothing. Subresource Integrity is enforced cross-origin only when the request is made in CORS mode, so <code>crossorigin</code> has to be on the tag. Without it the browser never compares the hash. It reads like evidence and it is not.</p>
<p>The <code>/latest/</code> on line 24 is the same failure in another costume: if the bytes behind an authorised URL can change upstream, the authorisation expired without telling anyone.</p>
<p>The two date-driven checks are what make this a lint rather than a checklist. The tamper note on line 3 is not missing; it is 43 days old against a 7-day cadence. The inventory note on line 2 was reviewed 832 days ago, by someone who has left. A page that passes on a Monday fails the following Tuesday with no code change — which is what 11.6.1 is describing.</p>
<p>So the inventory lives in the page: a <code>&lt;!-- pci:script owner=&quot;...&quot; reason=&quot;...&quot; --&gt;</code> comment above the tag it justifies, a <code>&lt;!-- pci:tamper ... evaluated=YYYY-MM-DD --&gt;</code> note recording the mechanism and its last evaluation, and <code>&lt;!-- pci:inventory reviewed=YYYY-MM-DD --&gt;</code> dating the review. The lint fails when a tag arrives without a justification, so the register is maintained by the review that merges it. Fourteen checks; the clean fixture returns zero.</p>
<p>Checking one page is free and finishes the job: all fourteen checks, every finding on its line with the requirement reference, in VS Code or in the browser, nothing uploaded. The licence buys a different job — a sweep of every payment template in the repository, written out as a dated register for the assessor. $29 once. A QSA hour compiling the same register bills $150-$300.</p>
<p>Not legal advice: your QSA, acquirer and SAQ or ROC scope decide what applies.</p></div><h2 style="font:600 21px/1.3 system-ui,sans-serif;margin:34px 0 6px;color:#1b1f24">Questions people ask</h2><details style="border-bottom:1px solid #e6e9ed;padding:10px 0"><summary style="cursor:pointer;font:600 15px/1.4 system-ui,sans-serif;color:#1b1f24">What does PCI DSS 6.4.3 Payment Script Lint actually do?</summary><p style="margin:8px 0 2px;font:400 14.5px/1.6 system-ui,sans-serif;color:#33404b">It reads a checkout or payment page and reports every browser script that fails PCI DSS v4.0.1 requirements 6.4.3 and 11.6.1: scripts with no integrity hash, scripts missing from the written inventory, entries with no business justification, a policy that allows unsafe-inline, and missing or slower-than-weekly tamper detection. Sixteen checks, each tagged with its requirement number.</p></details><details style="border-bottom:1px solid #e6e9ed;padding:10px 0"><summary style="cursor:pointer;font:600 15px/1.4 system-ui,sans-serif;color:#1b1f24">Who is it for?</summary><p style="margin:8px 0 2px;font:400 14.5px/1.6 system-ui,sans-serif;color:#33404b">E-commerce developers who own the checkout template at a merchant that takes card details on its own pages: Shopify Plus and WooCommerce theme developers, Laravel and Rails shop teams, and anyone whose acquirer has just asked for a payment-page script inventory. It is a code linter, so it belongs to whoever edits the HTML, not to the compliance inbox.</p></details><details style="border-bottom:1px solid #e6e9ed;padding:10px 0"><summary style="cursor:pointer;font:600 15px/1.4 system-ui,sans-serif;color:#1b1f24">Why can&#x27;t I just ask a chatbot or use a generic HTML linter?</summary><p style="margin:8px 0 2px;font:400 14.5px/1.6 system-ui,sans-serif;color:#33404b">Generic linters do not know what a payment page is; they flag unused tags, not unauthorised scripts. Chatbots still answer from PCI DSS v3.2.1 or describe 6.4.3 and 11.6.1 as future-dated best practices, which stopped being true on 2025-03-31. Neither one reads your actual checkout file line by line.</p></details><details style="border-bottom:1px solid #e6e9ed;padding:10px 0"><summary style="cursor:pointer;font:600 15px/1.4 system-ui,sans-serif;color:#1b1f24">What is free and what needs a licence?</summary><p style="margin:8px 0 2px;font:400 14.5px/1.6 system-ui,sans-serif;color:#33404b">Free, with no key and no limit: check the file open in your editor against all sixteen rules, with each finding on its line and its requirement number. The licence adds a different job, ownership of the evidence: sweeping every checkout template in the workspace and writing the dated script inventory report you hand to an assessor.</p></details><details style="border-bottom:1px solid #e6e9ed;padding:10px 0"><summary style="cursor:pointer;font:600 15px/1.4 system-ui,sans-serif;color:#1b1f24">What would this cost me otherwise?</summary><p style="margin:8px 0 2px;font:400 14.5px/1.6 system-ui,sans-serif;color:#33404b">PCI consultants and QSAs typically bill $150 to $300 an hour for the same payment-page script review, and they bill again after each template change. The licence is $29 once. The other cost is the assessment itself: arriving without a script inventory means a finding you close under deadline pressure.</p></details><details style="border-bottom:1px solid #e6e9ed;padding:10px 0"><summary style="cursor:pointer;font:600 15px/1.4 system-ui,sans-serif;color:#1b1f24">Does it change my files or send my code anywhere?</summary><p style="margin:8px 0 2px;font:400 14.5px/1.6 system-ui,sans-serif;color:#33404b">No. The extension reads the file, reports findings in the editor, and writes nothing except the report you ask for. There is no network call in the checker itself; the only request the extension ever makes is validating a licence key you typed.</p></details><script type="application/ld+json" data-yj="yj-faq-ld">{"@context": "https://schema.org", "@type": "FAQPage", "mainEntity": [{"@type": "Question", "name": "What does PCI DSS 6.4.3 Payment Script Lint actually do?", "acceptedAnswer": {"@type": "Answer", "text": "It reads a checkout or payment page and reports every browser script that fails PCI DSS v4.0.1 requirements 6.4.3 and 11.6.1: scripts with no integrity hash, scripts missing from the written inventory, entries with no business justification, a policy that allows unsafe-inline, and missing or slower-than-weekly tamper detection. Sixteen checks, each tagged with its requirement number."}}, {"@type": "Question", "name": "Who is it for?", "acceptedAnswer": {"@type": "Answer", "text": "E-commerce developers who own the checkout template at a merchant that takes card details on its own pages: Shopify Plus and WooCommerce theme developers, Laravel and Rails shop teams, and anyone whose acquirer has just asked for a payment-page script inventory. It is a code linter, so it belongs to whoever edits the HTML, not to the compliance inbox."}}, {"@type": "Question", "name": "Why can't I just ask a chatbot or use a generic HTML linter?", "acceptedAnswer": {"@type": "Answer", "text": "Generic linters do not know what a payment page is; they flag unused tags, not unauthorised scripts. Chatbots still answer from PCI DSS v3.2.1 or describe 6.4.3 and 11.6.1 as future-dated best practices, which stopped being true on 2025-03-31. Neither one reads your actual checkout file line by line."}}, {"@type": "Question", "name": "What is free and what needs a licence?", "acceptedAnswer": {"@type": "Answer", "text": "Free, with no key and no limit: check the file open in your editor against all sixteen rules, with each finding on its line and its requirement number. The licence adds a different job, ownership of the evidence: sweeping every checkout template in the workspace and writing the dated script inventory report you hand to an assessor."}}, {"@type": "Question", "name": "What would this cost me otherwise?", "acceptedAnswer": {"@type": "Answer", "text": "PCI consultants and QSAs typically bill $150 to $300 an hour for the same payment-page script review, and they bill again after each template change. The licence is $29 once. The other cost is the assessment itself: arriving without a script inventory means a finding you close under deadline pressure."}}, {"@type": "Question", "name": "Does it change my files or send my code anywhere?", "acceptedAnswer": {"@type": "Answer", "text": "No. The extension reads the file, reports findings in the editor, and writes nothing except the report you ask for. There is no network call in the checker itself; the only request the extension ever makes is validating a licence key you typed."}}]}</script><div id="yj-ask" style="margin:36px 0 0;padding:18px 18px 16px;border:1px solid #e2e6ea;border-radius:10px;background:#fbfbfc"><h2 style="font:600 19px/1.3 system-ui,sans-serif;margin:0 0 4px;color:#1b1f24">Ask about this tool</h2><p style="font:400 13.5px/1.5 system-ui,sans-serif;margin:0 0 12px;color:#5b6570">One question, answered by the person who built it. Your email only if you want the answer sent.</p><form onsubmit="return yjAsk(event)" style="display:grid;gap:8px"><textarea name="question" required minlength="10" maxlength="2000" rows="3" placeholder="Your question" style="font:14px/1.5 system-ui,sans-serif;padding:10px;border:1px solid #cfd6dd;border-radius:6px"></textarea><div style="display:flex;gap:8px;flex-wrap:wrap"><input type="email" name="email" placeholder="Email (optional)" style="flex:1;min-width:200px;font:14px system-ui,sans-serif;padding:9px 10px;border:1px solid #cfd6dd;border-radius:6px"><button type="submit" style="font:600 14px system-ui,sans-serif;padding:9px 18px;border:0;border-radius:6px;background:#1b1f24;color:#fff;cursor:pointer">Send</button></div><p id="yj-ask-msg" style="font:13px system-ui,sans-serif;margin:2px 0 0;color:#0a6238;min-height:1em"></p></form><script>function yjAsk(e){e.preventDefault();var f=e.target,m=document.getElementById("yj-ask-msg");m.style.color="#0a6238";fetch(location.origin+"/api/ask",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({sku:37484,slug:"pci-payment-script-lint",lang:"en",question:f.question.value,email:f.email.value,page:location.pathname})}).then(function(r){if(!r.ok)throw 0;m.textContent="Thanks \u2014 noted. Answers land on this page and, if you left an email, in your inbox.";f.question.value="";}).catch(function(){m.style.color="#a8551f";m.textContent="Could not send. Please try again in a minute.";});return false;}</script></div></section><section class="yj-related" style="max-width:920px;margin:56px auto 40px;padding:0 20px"><h2 style="font:600 21px/1.3 system-ui,sans-serif;margin:0 0 6px;color:#1b1f24">Same job, next tool</h2><p style="font:400 14.5px/1.5 system-ui,sans-serif;margin:0 0 20px;color:#5b6570">Free in the browser. The full version is a one-time licence.</p><div style="display:grid;grid-template-columns:repeat(auto-fill,minmax(250px,1fr));gap:14px"><a class="yj-rel-c" href="https://getreadystack.com/tools/payment-page-script-audit"><span class="yj-rel-t">PCI DSS 6.4.3 Payment Page Script Inventory Auditor</span><span class="yj-rel-p">$29</span></a><a class="yj-rel-c" href="https://getreadystack.com/tools/pci-payment-page-script-lint"><span class="yj-rel-t">PCI DSS 6.4.3 Payment Page Script Lint</span><span class="yj-rel-p">$29</span></a><a class="yj-rel-c" href="https://getreadystack.com/tools/pci-payment-page-script-audit"><span class="yj-rel-t">PCI Payment Page Script Audit: requirement 6.4.3 and 11.6.1 on your checkout m</span><span class="yj-rel-p">$29</span></a><a class="yj-rel-c" href="https://getreadystack.com/tools/webflow-restaurant-template"><span class="yj-rel-t">Webflow Restaurant Template — delivered as self-contained HTML you own: 26 pag</span><span class="yj-rel-p">$49</span></a><a class="yj-rel-c" href="https://getreadystack.com/tools/personal-data-map-dsar-audit"><span class="yj-rel-t">Personal Data Map: find every column a GDPR subject access request has to reac</span><span class="yj-rel-p">$29</span></a><a class="yj-rel-c" href="https://getreadystack.com/tools/about-page-template"><span class="yj-rel-t">About Page Template — 6 Finished About Pages for Small Service Businesses (Sel</span><span class="yj-rel-p">$49</span></a></div><style>.yj-rel-c{display:flex;flex-direction:column;gap:8px;padding:15px 16px;border:1px solid #e2e6ea;border-radius:6px;text-decoration:none;background:#fff}.yj-rel-c:hover{border-color:#b9c2cb;background:#fafbfc}.yj-rel-t{font:500 14.5px/1.42 system-ui,sans-serif;color:#1b1f24}.yj-rel-p{font:600 13px system-ui,sans-serif;color:#0a6238}</style></section><!--yj-share-s101--><div style="margin:28px 0 8px;text-align:center"><button id="yjcp" type="button" style="font:inherit;padding:10px 18px;border:1px solid #8884;border-radius:10px;background:transparent;cursor:pointer">Copy link</button><button id="yjimg" type="button" style="font:inherit;padding:10px 18px;margin-left:8px;border:1px solid #8884;border-radius:10px;background:transparent;cursor:pointer">Save result as image</button></div><script>(function(){var b=document.getElementById("yjimg");if(!b)return;b.addEventListener("click",function(){var o=document.getElementById("out");var txt=(o&&(o.innerText||o.textContent)||"").trim();if(!txt){b.textContent="(no result yet)";setTimeout(function(){b.textContent="Save result as image";},1500);return;}var lines=txt.split(String.fromCharCode(10)).filter(function(x){return x.trim();}).slice(0,10);var W=1200,P=56,c=document.createElement("canvas");c.width=W;var lh=38,H=P*2+70+lines.length*lh+60;c.height=H;var x=c.getContext("2d");x.fillStyle="#1a1512";x.fillRect(0,0,W,H);x.fillStyle="#f3e9d2";x.font="600 34px system-ui,Segoe UI,Arial";var t=(document.title||"").split("|")[0].trim();x.fillText(t.slice(0,54),P,P+34);x.font="22px ui-monospace,Menlo,Consolas,monospace";x.fillStyle="#e8dcc4";for(var i=0;i<lines.length;i++){x.fillText(lines[i].slice(0,88),P,P+70+i*lh+22);}x.font="600 22px system-ui,Segoe UI,Arial";x.fillStyle="#c9a227";var d=location.hostname.replace("www.","");x.fillText(d,W-P-x.measureText(d).width,H-P+6);c.toBlob(function(bl){var u=URL.createObjectURL(bl),a=document.createElement("a");a.href=u;a.download=(t||"result").replace(/[^a-z0-9]+/gi,"-").toLowerCase()+"-"+d+".png";document.body.appendChild(a);a.click();setTimeout(function(){URL.revokeObjectURL(u);a.remove();},2000);},"image/png");});})();</script><script>(function(){var b=document.getElementById("yjcp");if(!b)return;var t=b.textContent;b.addEventListener("click",function(){var u=location.href.split("#")[0];function d(){b.textContent="Link copied";setTimeout(function(){b.textContent=t;},1800);}if(navigator.clipboard&&navigator.clipboard.writeText){navigator.clipboard.writeText(u).then(d,function(){prompt("",u);});}else{prompt("",u);}});})();</script><div class="yj-home-find"><div class="yj-lgbar"><a href="/" class="yj-lg on">EN</a><a href="/de/" class="yj-lg">DE</a><a href="/ja/" class="yj-lg">JA</a><a href="/es/" class="yj-lg">ES</a><a href="/pt/" class="yj-lg">PT</a></div><section class="yj-find"><h2>Find a tool</h2><input id="yjq" type="search" autocomplete="off" placeholder="Type a product name, format or task"><div id="yjr"></div></section><style>.yj-lgbar{position:absolute;top:14px;right:18px;display:flex;gap:6px;z-index:60}.yj-lg{font:600 12px/1 system-ui,sans-serif;padding:6px 9px;border-radius:4px;text-decoration:none;color:#8b96a3;border:1px solid rgba(140,150,165,.28)}.yj-lg:hover{color:#e8eaed;border-color:rgba(160,175,195,.6)}.yj-lg.on{color:#0e1116;background:#cfd6de;border-color:#cfd6de}.yj-find{max-width:760px;margin:34px auto 8px;padding:0 20px}.yj-find h2{font:600 20px/1.3 system-ui,sans-serif;margin:0 0 10px}#yjq{width:100%;box-sizing:border-box;padding:13px 15px;font:400 15.5px system-ui,sans-serif;border:1px solid rgba(140,150,165,.34);border-radius:8px;background:rgba(255,255,255,.04);color:inherit}#yjq:focus{outline:none;border-color:#7FD1C4}#yjr{margin-top:10px;display:flex;flex-direction:column;gap:6px}#yjr a{display:flex;justify-content:space-between;gap:12px;padding:11px 14px;border:1px solid rgba(140,150,165,.22);border-radius:6px;text-decoration:none;font:400 14.5px/1.4 system-ui,sans-serif;color:inherit}#yjr a:hover{border-color:#7FD1C4}#yjr .pr{font:600 13px system-ui,sans-serif;color:#5fbf9a;white-space:nowrap}</style><script>(function(){var q=document.getElementById("yjq"),r=document.getElementById("yjr"),D=null;function load(){if(D)return Promise.resolve(D);return fetch("/search-index.json").then(function(x){return x.json();}).then(function(j){D=j;return j;}).catch(function(){D=[];return D;});}function esc(t){return String(t).replace(/[&<>"]/g,function(c){return {"&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;"}[c];});}function run(){var v=q.value.trim().toLowerCase();if(v.length<2){r.innerHTML="";return;}load().then(function(j){var w=v.split(/\s+/),o=[];for(var i=0;i<j.length&&o.length<12;i++){var t=j[i].t.toLowerCase();var ok=true;for(var k=0;k<w.length;k++){if(t.indexOf(w[k])<0){ok=false;break;}}if(ok)o.push(j[i]);}r.innerHTML=o.length?o.map(function(x){return "<a href=\""+esc(x.u)+"\">"+"<span>"+esc(x.t)+"</span>"+(x.p?"<span class=\"pr\">$"+x.p+"</span>":"")+"</a>";}).join(""):"";});}q.addEventListener("input",run);q.addEventListener("focus",load);})();</script></div>


<script data-yj="yj-view-beacon">(function(){try{var p=location.pathname;var k='yjv:'+p;if(sessionStorage.getItem(k))return;sessionStorage.setItem(k,'1');fetch('/api/ev',{method:'POST',keepalive:true,headers:{'Content-Type':'application/json'},body:JSON.stringify({path:p,lang:"en",ref:(document.referrer||''),src:(function(){try{return (new URLSearchParams(location.search).get('ref')||'').replace(/[^a-z_]/g,'').slice(0,12)}catch(e){return ''}})()})}).catch(function(){});}catch(e){}})();</script>

<script data-yj="yj-use-beacon">(function(){try{var o=document.getElementById('out');if(!o)return;var p=location.pathname,k='yju:'+p,sent=false;function fire(){if(sent||sessionStorage.getItem(k))return;if(!(o.textContent||'').replace(/\s+/g,'').length)return;sent=true;sessionStorage.setItem(k,'1');fetch('/api/ev',{method:'POST',keepalive:true,headers:{'Content-Type':'application/json'},body:JSON.stringify({path:p,lang:"en",t:'use',src:(function(){try{return (new URLSearchParams(location.search).get('ref')||'').replace(/[^a-z_]/g,'').slice(0,12)}catch(e){return ''}})()})}).catch(function(){});}var mo=new MutationObserver(function(){fire();if(sent)mo.disconnect();});mo.observe(o,{childList:true,characterData:true,subtree:true});}catch(e){}})();</script>
</body>
</html>