getreadystack commited on
Commit
6330ca6
·
verified ·
1 Parent(s): 2f3eb32

Upload index.html with huggingface_hub

Browse files
Files changed (1) hide show
  1. index.html +393 -17
index.html CHANGED
@@ -1,19 +1,395 @@
1
  <!doctype html>
2
- <html>
3
- <head>
4
- <meta charset="utf-8" />
5
- <meta name="viewport" content="width=device-width" />
6
- <title>My static Space</title>
7
- <link rel="stylesheet" href="style.css" />
8
- </head>
9
- <body>
10
- <div class="card">
11
- <h1>Welcome to your static Space!</h1>
12
- <p>You can modify this app directly by editing <i>index.html</i> in the Files and versions tab.</p>
13
- <p>
14
- Also don't forget to check the
15
- <a href="https://huggingface.co/docs/hub/spaces" target="_blank">Spaces documentation</a>.
16
- </p>
17
- </div>
18
- </body>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
19
  </html>
 
1
  <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8">
5
+ <meta name="viewport" content="width=device-width,initial-scale=1">
6
+ <title>SQL Card Data Lint (PCI DSS Req 3) — free online check</title>
7
+ <style>
8
+ :root{color-scheme:light}body{margin:0;font:15px/1.55 system-ui,-apple-system,"Segoe UI",sans-serif;background:#faf9f5;color:#1a1512}
9
+ .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}
10
+ 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}
11
+ .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}
12
+ input[type=date]{font:14px system-ui;padding:8px;border:1px solid #d9d0c4;border-radius:6px}
13
+ 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}
14
+ .sev.error{color:#a8551f;font-weight:700}.sev.warn,.sev.warning{color:#96691f}.ok{color:#2a6446;font-weight:600}.sum{font-weight:600}
15
+ .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}
16
+ </style>
17
+ <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>
18
+ <!--yj-canon-s101--><link rel="canonical" href="https://getreadystack.com/tools/sql-card-data-lint"></head>
19
+ <body>
20
+ <div class="wrap">
21
+ <h1>SQL Card Data Lint (PCI DSS Req 3)</h1>
22
+ <p class="lede">Finds cardholder and sensitive authentication data in your .sql migrations: CVV, track, PIN block, plaintext PAN columns and test card numbers, each mapped to a PCI DSS Requirement 3 rule. Runs entirely in your browser — nothing is uploaded.</p>
23
+ <textarea id="src" spellcheck="false">-- 0007_payments.sql
24
+ -- Generated from a prompt: "add card storage for saved payment methods"
25
+ BEGIN;
26
+
27
+ CREATE TABLE customer_cards (
28
+ id BIGSERIAL PRIMARY KEY,
29
+ customer_id BIGINT NOT NULL REFERENCES customers (id),
30
+ card_number VARCHAR(19) NOT NULL,
31
+ cvv CHAR(4),
32
+ pin_block CHAR(16),
33
+ track2_data VARCHAR(40),
34
+ exp_month SMALLINT,
35
+ exp_year SMALLINT,
36
+ created_at TIMESTAMPTZ DEFAULT now()
37
+ );
38
+
39
+ CREATE UNIQUE INDEX idx_customer_cards_pan ON customer_cards (card_number);
40
+
41
+ CREATE TABLE payment_audit_log (
42
+ id BIGSERIAL PRIMARY KEY,
43
+ customer_id BIGINT NOT NULL,
44
+ card_number VARCHAR(19),
45
+ gateway_response JSONB,
46
+ logged_at TIMESTAMPTZ DEFAULT now()
47
+ );
48
+
49
+ CREATE VIEW v_recent_charges AS
50
+ SELECT c.customer_id, c.card_number, ch.amount, ch.captured_at
51
+ FROM customer_cards c
52
+ JOIN charges ch ON ch.customer_id = c.customer_id;
53
+
54
+ INSERT INTO customer_cards (customer_id, card_number, cvv, exp_month, exp_year)
55
+ VALUES (1, '4111111111111111', '123', 12, 2029);
56
+
57
+ COMMIT;</textarea>
58
+ <div class="bar"><button id="go">Check</button><label>as of <input type="date" id="today"></label></div>
59
+ <div id="out" aria-live="polite"></div>
60
+ <div id="next" style="margin-top:14px;padding:14px 16px;border:1px solid #e2d8cb;border-radius:10px;background:#fffdfa"><a href="https://buy.polar.sh/polar_cl_HL23mfuzaP3RMTfsQGzbUYmVSiTyJBd8Czg9B1TIuJT" data-polar-checkout data-polar-checkout-theme="light" style="display:inline-block;padding:9px 14px;border-radius:8px;background:#1f5f4a;color:#fff;text-decoration:none;font-weight:600">Sweep every migration in the workspace and keep the report</a><div style="font-size:13px;color:#7a6e65;margin-top:8px">$29 once &middot; one licence key per person or team seat &middot; 7-day full refund. A QSA-led gap analysis of your data stores is commonly quoted as a five-figure engagement.</div></div>
61
+ <div id="paid"><strong>Free here:</strong> Check the .sql file open in your editor and name every column that stores card data, with its PCI DSS Requirement 3 rule id. <strong>In the editor:</strong> Sweep every migration in the workspace and write a dated, exportable findings report you keep — for team and commercial use — that part asks for a licence key.</div>
62
+ <p class="note">Same engine as the VS Code extension, byte for byte.</p>
63
+ </div>
64
+ <script>var SCD_RULES = [
65
+ {
66
+ "id": "sad-cvv-stored",
67
+ "sev": "error",
68
+ "req": "PCI DSS 3.3.1.2",
69
+ "kind": "line",
70
+ "all": [
71
+ "\\b(cvv2?|cvc2?|cav2?|csc|card_?verification(_?(code|value|data))?|security_?code)\\b"
72
+ ],
73
+ "none": [],
74
+ "msg": "Card verification code column. Sensitive authentication data must not be stored after authorisation, even encrypted.",
75
+ "fix": "Drop the column and read the code from the gateway request in memory only."
76
+ },
77
+ {
78
+ "id": "sad-track-stored",
79
+ "sev": "error",
80
+ "req": "PCI DSS 3.3.1.1",
81
+ "kind": "line",
82
+ "all": [
83
+ "\\b(track_?[12]\\w*|track_?data|magstripe|magnetic_?stripe|full_?track|discretionary_?data|equivalent_?data)\\b"
84
+ ],
85
+ "none": [],
86
+ "msg": "Full track / magnetic-stripe equivalent data. This is sensitive authentication data and may never be retained after authorisation.",
87
+ "fix": "Remove the column; keep only the token the processor returns."
88
+ },
89
+ {
90
+ "id": "sad-pin-stored",
91
+ "sev": "error",
92
+ "req": "PCI DSS 3.3.1.3",
93
+ "kind": "line",
94
+ "all": [
95
+ "\\b(pin|pin_?block|pinblock|pin_?verification|pvv|encrypted_?pin)\\b"
96
+ ],
97
+ "none": [
98
+ "\\bpin(g|ned|point)\\b"
99
+ ],
100
+ "msg": "PIN or PIN block column. Sensitive authentication data must not be stored after authorisation.",
101
+ "fix": "Remove the column. PIN translation belongs in the HSM, never in a table."
102
+ },
103
+ {
104
+ "id": "pan-plaintext-column",
105
+ "sev": "error",
106
+ "req": "PCI DSS 3.5.1",
107
+ "kind": "line",
108
+ "all": [
109
+ "\\b(pan|card_?number|cardnum|cc_?number|credit_?card(_?(no|number))?|primary_?account_?number|acct_?number)\\b",
110
+ "\\b(char|varchar|varchar2|nvarchar|nchar|text|string|clob|bigint|numeric|decimal|number)\\b"
111
+ ],
112
+ "none": [
113
+ "(hash|token|encrypt|_enc\\b|enc_|masked|mask\\b|last_?4|last4|first_?6|fingerprint|vault|sha\\d|hmac|surrogate)"
114
+ ],
115
+ "msg": "Primary account number stored in a readable column type. Stored PAN must be rendered unreadable wherever it is kept.",
116
+ "fix": "Store a network token or a keyed hash, plus first6/last4 in their own columns."
117
+ },
118
+ {
119
+ "id": "pan-searchable-key",
120
+ "sev": "error",
121
+ "req": "PCI DSS 3.5.1",
122
+ "kind": "line",
123
+ "all": [
124
+ "\\b(create\\s+(unique\\s+)?index|add\\s+(unique\\s+)?(index|key)|unique\\s*\\(|primary\\s+key\\s*\\()",
125
+ "\\b(pan|card_?number|cc_?number|credit_?card(_?(no|number))?)\\b"
126
+ ],
127
+ "none": [
128
+ "(hash|token|last_?4|last4|fingerprint|masked)"
129
+ ],
130
+ "msg": "Index or key built on the account number itself, which keeps the PAN readable and searchable in the index pages.",
131
+ "fix": "Index the token or the keyed hash column instead."
132
+ },
133
+ {
134
+ "id": "pan-in-log-table",
135
+ "sev": "error",
136
+ "req": "PCI DSS 3.3.1",
137
+ "kind": "table_col",
138
+ "all": [
139
+ "(log|logs|audit|history|events?|archive|trace|webhook|debug)",
140
+ "\\b(pan|card_?number|cc_?number|credit_?card(_?(no|number))?|cvv2?|cvc2?|track_?2|expiry|exp_?month|exp_?year)\\b"
141
+ ],
142
+ "none": [
143
+ "(hash|token|last_?4|last4|masked)"
144
+ ],
145
+ "msg": "Card data written into a log, audit or history table. Log stores are rarely in scope for key management and are copied everywhere.",
146
+ "fix": "Log the token and the last four digits only."
147
+ },
148
+ {
149
+ "id": "auth-response-blob",
150
+ "sev": "warn",
151
+ "req": "PCI DSS 3.3.2",
152
+ "kind": "line",
153
+ "all": [
154
+ "\\b(gateway_?response|auth(orisation|orization)?_?response|raw_?response|processor_?response|psp_?payload|payment_?payload|webhook_?body|request_?payload|card_?payload)\\b",
155
+ "\\b(json|jsonb|text|blob|clob|bytea|varbinary|xml|nvarchar\\s*\\(\\s*max)\\b"
156
+ ],
157
+ "none": [
158
+ "(redact|encrypt|scrub|filtered)"
159
+ ],
160
+ "msg": "Whole authorisation payload kept as a blob. These bodies routinely carry the PAN and the verification code, so the column inherits every rule above.",
161
+ "fix": "Persist named fields you have reviewed, or redact the payload before insert."
162
+ },
163
+ {
164
+ "id": "pan-view-unmasked",
165
+ "sev": "warn",
166
+ "req": "PCI DSS 3.4.1",
167
+ "kind": "view",
168
+ "all": [
169
+ "\\b(pan|card_?number|cc_?number|credit_?card(_?(no|number))?)\\b"
170
+ ],
171
+ "none": [
172
+ "(mask|substr|substring|right\\s*\\(|left\\s*\\(|last_?4|last4|token|hash|\\*{4})"
173
+ ],
174
+ "msg": "View exposes the full account number. When PAN is displayed it must be masked to at most the first six and last four digits.",
175
+ "fix": "Wrap the column: 'XXXXXX' || RIGHT(card_ref, 4) AS card_display."
176
+ },
177
+ {
178
+ "id": "live-pan-literal",
179
+ "sev": "error",
180
+ "req": "PCI DSS 6.5.5",
181
+ "kind": "luhn",
182
+ "all": [],
183
+ "none": [],
184
+ "msg": "Luhn-valid account number written straight into the migration. A card number in version control is card data in every clone, backup and CI log.",
185
+ "fix": "Seed with a processor test token, or a number that is not Luhn-valid."
186
+ },
187
+ {
188
+ "id": "no-retention-column",
189
+ "sev": "warn",
190
+ "req": "PCI DSS 3.2.1",
191
+ "kind": "table",
192
+ "all": [],
193
+ "none": [],
194
+ "msg": "Table holds card data but defines no retention or purge column, so nothing tells you when the row may be deleted.",
195
+ "fix": "Add purge_after DATE (or retain_until) and a scheduled delete that reads it."
196
+ }
197
+ ]
198
+ ;</script>
199
+ <script>// SQL Card Data Lint — the one brain. Runs in Node (the extension) and in the browser (the free web page).
200
+ 'use strict';
201
+ (function (root) {
202
+ var RULES = (typeof module !== 'undefined' && module.exports)
203
+ ? require('./rules.json')
204
+ : (root.SCD_RULES || []);
205
+
206
+ var CARD_TABLE_RE = /\b(pan|card_?number|cc_?number|credit_?card|cardholder|card_?token|card_?ref|card_?fingerprint|card_?data)\b/i;
207
+ var RETENTION_RE = /\b(purge_?after|purge_?at|retain_?until|retention|delete_?after|expires_?at|expire_?at|valid_?until|ttl)\b/i;
208
+ var CREATE_TABLE_RE = /\bcreate\s+(?:or\s+replace\s+)?(?:global\s+temporary\s+)?table\s+(?:if\s+not\s+exists\s+)?[`"\[]?([A-Za-z_][\w$.]*)[`"\]]?/i;
209
+ var ALTER_TABLE_RE = /\balter\s+table\s+[`"\[]?([A-Za-z_][\w$.]*)[`"\]]?/i;
210
+ var INSERT_RE = /\binsert\s+into\s+[`"\[]?([A-Za-z_][\w$.]*)[`"\]]?/i;
211
+ var CREATE_VIEW_RE = /\bcreate\s+(?:or\s+replace\s+)?(?:materialized\s+)?view\b/i;
212
+ var COL_RE = /^\s*[`"\[]?([A-Za-z_][\w$]*)[`"\]]?\s+/;
213
+ var DIGITS_RE = /\d[\d \-]{11,24}\d/g;
214
+
215
+ function rx(src, extra) { return new RegExp(src, 'i' + (extra || '')); }
216
+
217
+ function luhn(d) {
218
+ var sum = 0, alt = false;
219
+ for (var i = d.length - 1; i >= 0; i--) {
220
+ var n = d.charCodeAt(i) - 48;
221
+ if (alt) { n *= 2; if (n > 9) n -= 9; }
222
+ sum += n; alt = !alt;
223
+ }
224
+ return sum % 10 === 0;
225
+ }
226
+
227
+ function codeOf(line) {
228
+ var i = line.indexOf('--');
229
+ return i >= 0 ? line.slice(0, i) : line;
230
+ }
231
+
232
+ function isComment(line) {
233
+ var t = line.replace(/^\s+/, '');
234
+ return t.indexOf('--') === 0 || t.indexOf('#') === 0 || t.indexOf('/*') === 0 || t.indexOf('*') === 0;
235
+ }
236
+
237
+ function matchAll(rule, text) {
238
+ var i;
239
+ for (i = 0; i < (rule.all || []).length; i++) if (!rx(rule.all[i]).test(text)) return false;
240
+ for (i = 0; i < (rule.none || []).length; i++) if (rx(rule.none[i]).test(text)) return false;
241
+ return true;
242
+ }
243
+
244
+ function check(text, opts) {
245
+ opts = opts || {};
246
+ var lines = String(text == null ? '' : text).split(/\r?\n/);
247
+ var findings = [];
248
+ var byRule = {};
249
+ var table = null, inTable = false, inView = false;
250
+ var tableStart = 0, tableText = '', tables = 0, cardTables = 0, colCount = 0;
251
+
252
+ function push(rule, n, extra) {
253
+ byRule[rule.id] = (byRule[rule.id] || 0) + 1;
254
+ findings.push({
255
+ check: rule.id,
256
+ sev: rule.sev || 'error',
257
+ req: rule.req || '',
258
+ msg: rule.msg + (extra ? ' (' + extra + ')' : ''),
259
+ fix: rule.fix || '',
260
+ line: n
261
+ });
262
+ }
263
+
264
+ for (var n = 0; n < lines.length; n++) {
265
+ var raw = lines[n], code = codeOf(raw), lineNo = n + 1;
266
+ var mT = CREATE_TABLE_RE.exec(code);
267
+
268
+ if (mT) { table = mT[1]; inTable = true; tableStart = lineNo; tableText = ''; tables++; }
269
+ else if (ALTER_TABLE_RE.test(code)) { table = ALTER_TABLE_RE.exec(code)[1]; }
270
+ else if (INSERT_RE.test(code)) { table = INSERT_RE.exec(code)[1]; }
271
+ else if (/\bcreate\s+(?:unique\s+)?index\b/i.test(code)) {
272
+ var mi = /\bon\s+[`"\[]?([A-Za-z_][\w$.]*)/i.exec(code);
273
+ if (mi) table = mi[1];
274
+ }
275
+ if (CREATE_VIEW_RE.test(code)) { inView = true; table = null; }
276
+
277
+ if (inTable) {
278
+ tableText += ' ' + code;
279
+ if (COL_RE.test(code) && !mT) colCount++;
280
+ }
281
+
282
+ for (var r = 0; r < RULES.length; r++) {
283
+ var rule = RULES[r];
284
+ if (rule.kind === 'line') {
285
+ if (isComment(raw)) continue;
286
+ if (matchAll(rule, code)) push(rule, lineNo, table ? 'table ' + table : '');
287
+ } else if (rule.kind === 'table_col') {
288
+ if (isComment(raw) || !table) continue;
289
+ if (rx(rule.all[0]).test(table) && matchAll({ all: [rule.all[1]], none: rule.none }, code)) {
290
+ push(rule, lineNo, 'table ' + table);
291
+ }
292
+ } else if (rule.kind === 'view') {
293
+ if (isComment(raw) || !inView) continue;
294
+ if (matchAll(rule, code)) push(rule, lineNo, '');
295
+ } else if (rule.kind === 'luhn') {
296
+ var m, hits = [];
297
+ DIGITS_RE.lastIndex = 0;
298
+ while ((m = DIGITS_RE.exec(raw)) !== null) {
299
+ var d = m[0].replace(/[^0-9]/g, '');
300
+ if (d.length >= 13 && d.length <= 19 && /^[3-6]/.test(d) && luhn(d)) hits.push(d.slice(0, 6) + '…' + d.slice(-4));
301
+ }
302
+ if (hits.length) push(rule, lineNo, hits.join(', '));
303
+ }
304
+ }
305
+
306
+ if (inView && /;\s*$/.test(code)) inView = false;
307
+
308
+ if (inTable && /^\s*\)/.test(code)) {
309
+ inTable = false;
310
+ if (CARD_TABLE_RE.test(tableText)) {
311
+ cardTables++;
312
+ if (!RETENTION_RE.test(tableText)) {
313
+ for (var q = 0; q < RULES.length; q++) {
314
+ if (RULES[q].kind === 'table') push(RULES[q], tableStart, 'table ' + table);
315
+ }
316
+ }
317
+ }
318
+ tableText = '';
319
+ }
320
+ }
321
+
322
+ var errors = 0, warnings = 0;
323
+ for (var f = 0; f < findings.length; f++) {
324
+ if (findings[f].sev === 'error') errors++; else warnings++;
325
+ }
326
+
327
+ return {
328
+ findings: findings,
329
+ summary: {
330
+ today: opts.today || '',
331
+ lines: lines.length,
332
+ rules: RULES.length,
333
+ rules_hit: Object.keys(byRule).length,
334
+ tables: tables,
335
+ card_tables: cardTables,
336
+ columns: colCount,
337
+ errors: errors,
338
+ warnings: warnings
339
+ }
340
+ };
341
+ }
342
+
343
+ var API = { engine: { check: check }, RULES: RULES, RULE_COUNT: RULES.length };
344
+ root.SCDENGINE = API;
345
+ if (typeof module !== 'undefined' && module.exports) module.exports = API;
346
+ })(typeof window !== 'undefined' ? window : globalThis);
347
+ </script>
348
+ <script>// ⛔뼈대 — kit/scaffold.py 가 찍는다 (s143). findings [{check,sev,msg,line?}] → 글/HTML. 같은 파일이 Node 와 브라우저에서 산다.
349
+ 'use strict';
350
+ function escapeHtml(s) { return String(s == null ? '' : s).replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;'); }
351
+ function summaryLine(res) {
352
+ const f = (res && res.findings) || []; const e = f.filter(function (x) { return String(x.sev || 'error').toLowerCase().startsWith('err'); }).length;
353
+ return f.length ? (f.length + ' finding' + (f.length === 1 ? '' : 's') + ' (' + e + ' error' + (e === 1 ? '' : 's') + ')') : 'Clean — no findings.';
354
+ }
355
+ function toText(res, label) {
356
+ const f = (res && res.findings) || [];
357
+ const lines = ['== ' + (label || '') + ' — ' + summaryLine(res)];
358
+ 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 || '')); });
359
+ return lines.join('\n');
360
+ }
361
+ function toHtml(res) {
362
+ const f = (res && res.findings) || [];
363
+ if (!f.length) return '<p class="ok">' + escapeHtml(summaryLine(res)) + '</p>';
364
+ 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('');
365
+ 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>';
366
+ }
367
+ var SCDREPORT = { escapeHtml: escapeHtml, summaryLine: summaryLine, toText: toText, toHtml: toHtml };
368
+ if (typeof module !== 'undefined' && module.exports) module.exports = SCDREPORT;
369
+ else window.SCDREPORT = SCDREPORT;
370
+ </script>
371
+ <script>
372
+ (function(){
373
+ var src=document.getElementById('src'), out=document.getElementById('out'), go=document.getElementById('go'), today=document.getElementById('today');
374
+ today.value=new Date().toISOString().slice(0,10);
375
+ function run(){ try { var res=window.SCDENGINE.engine.check(src.value,{today:today.value}); out.innerHTML=window.SCDREPORT.toHtml(res); } catch(e){ out.innerHTML='<p class="sev error">Could not run: '+String(e.message||e)+'</p>'; } }
376
+ go.onclick=run; if (src.value.trim()) run();
377
+ })();
378
+ </script>
379
+ <script defer data-auto-init src="https://cdn.jsdelivr.net/npm/@polar-sh/checkout@latest/dist/embed.global.js"></script>
380
+
381
+ <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 SQL Card Data Lint (PCI DSS Req 3) 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:"sql-card-data-lint",lang:"en",source:"rule_alert"})}).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></section>
382
+
383
+
384
+ <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.sql-card-data-lint" rel="noopener" data-yj-install-door="mp" onclick="try{navigator.sendBeacon('/api/ev',new Blob([JSON.stringify({t:'install',slug:'sql-card-data-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><a href="https://open-vsx.org/extension/readystack/sql-card-data-lint" rel="noopener" data-yj-install-door="ovsx" onclick="try{navigator.sendBeacon('/api/ev',new Blob([JSON.stringify({t:'install',slug:'sql-card-data-lint',src:'ovsx',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">Open VSX (Cursor, VSCodium)</a><a href="https://www.npmjs.com/package/@readystack/sql-card-data-lint" rel="noopener" data-yj-install-door="npm" onclick="try{navigator.sendBeacon('/api/ev',new Blob([JSON.stringify({t:'install',slug:'sql-card-data-lint',src:'npm',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">npm</a><a href="https://www.npmjs.com/package/@readystack/sql-card-data-lint" rel="noopener" data-yj-install-door="mcp" onclick="try{navigator.sendBeacon('/api/ev',new Blob([JSON.stringify({t:'install',slug:'sql-card-data-lint',src:'mcp',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">MCP server (npx)</a><a href="https://hub.docker.com/r/getreadystack/sql-card-data-lint" rel="noopener" data-yj-install-door="docker" onclick="try{navigator.sendBeacon('/api/ev',new Blob([JSON.stringify({t:'install',slug:'sql-card-data-lint',src:'docker',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">Docker Hub</a><pre style="margin:.5rem 0 0;padding:.6rem .8rem;background:#f3f5f8;border-radius:8px;font-size:.9rem;overflow:auto">ext install ReadyStack.sql-card-data-lint
385
+ npx @readystack/sql-card-data-lint &lt;file&gt;
386
+ npx @readystack/sql-card-data-lint --mcp</pre><p style="margin:.6rem 0 0;font-size:.92rem;color:#41505f"><a href="https://buy.polar.sh/polar_cl_l6iN1uWt0FwWu7tBsczD0jWpP2vxFM54Wdwqb3KPi1G" rel="nofollow" style="color:#0f7b3f;text-decoration:underline">Team? One key for every ReadyStack linter, 5 seats, $149 once</a></p></section>
387
+ <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">Names every column, index, view and seed row in a .sql migration that stores card data, with its PCI DSS Requirement 3 rule id</p><p style="margin:0 0 1rem;color:#5b6b7c;font-size:.94rem">A QSA-led gap analysis of your data stores is commonly quoted as a five-figure engagement; the same migrations are read here in the editor.</p><a href="https://buy.polar.sh/polar_cl_HL23mfuzaP3RMTfsQGzbUYmVSiTyJBd8Czg9B1TIuJT" 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>
388
+ <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><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 SQL Card Data 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 .sql migration and reports every column, index, view and seed row that stores cardholder data or sensitive authentication data. Each of the 10 rules carries a PCI DSS Requirement 3 id and a replacement line. The 35-line sample migration returns 13 findings from 10 rules, with card numbers always masked.</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">Backend and platform engineers on a payments repository in Rails, Django, Laravel, .NET or Node, whose company sits inside a PCI DSS assessment scope, and the person who must answer where card data is stored on the SAQ. It is for teams merging migrations written with an assistant.</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 will a normal SQL linter not catch this?</summary><p style="margin:8px 0 2px;font:400 14.5px/1.6 system-ui,sans-serif;color:#33404b">sqlfluff, sqlint and database checks read syntax and style. None of them know that cvv CHAR(4) is sensitive authentication data that may never be stored after authorisation, or that a unique index on card_number keeps the account number readable inside the index pages. This tool checks meaning, not formatting.</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 key?</summary><p style="margin:8px 0 2px;font:400 14.5px/1.6 system-ui,sans-serif;color:#33404b">Free and keyless: the .sql file open in your editor, all 10 rules, rule ids and suggested replacements, forever. The full version sweeps every migration in the workspace at once and writes a dated findings report you keep for an assessor or a change ticket, and covers team and commercial use.</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 done by a person?</summary><p style="margin:8px 0 2px;font:400 14.5px/1.6 system-ui,sans-serif;color:#33404b">A QSA-led gap analysis of your data stores is commonly quoted as a five-figure engagement, and it happens once a year. The PCI DSS v4 requirements that were future-dated became mandatory on 31 March 2025, so Requirement 3 is assessed in full at your next annual assessment. This is $29 once.</p></details><script type="application/ld+json" data-yj="yj-faq-ld">{"@context": "https://schema.org", "@type": "FAQPage", "mainEntity": [{"@type": "Question", "name": "What does SQL Card Data Lint actually do?", "acceptedAnswer": {"@type": "Answer", "text": "It reads a .sql migration and reports every column, index, view and seed row that stores cardholder data or sensitive authentication data. Each of the 10 rules carries a PCI DSS Requirement 3 id and a replacement line. The 35-line sample migration returns 13 findings from 10 rules, with card numbers always masked."}}, {"@type": "Question", "name": "Who is it for?", "acceptedAnswer": {"@type": "Answer", "text": "Backend and platform engineers on a payments repository in Rails, Django, Laravel, .NET or Node, whose company sits inside a PCI DSS assessment scope, and the person who must answer where card data is stored on the SAQ. It is for teams merging migrations written with an assistant."}}, {"@type": "Question", "name": "Why will a normal SQL linter not catch this?", "acceptedAnswer": {"@type": "Answer", "text": "sqlfluff, sqlint and database checks read syntax and style. None of them know that cvv CHAR(4) is sensitive authentication data that may never be stored after authorisation, or that a unique index on card_number keeps the account number readable inside the index pages. This tool checks meaning, not formatting."}}, {"@type": "Question", "name": "What is free and what needs a licence key?", "acceptedAnswer": {"@type": "Answer", "text": "Free and keyless: the .sql file open in your editor, all 10 rules, rule ids and suggested replacements, forever. The full version sweeps every migration in the workspace at once and writes a dated findings report you keep for an assessor or a change ticket, and covers team and commercial use."}}, {"@type": "Question", "name": "What would this cost done by a person?", "acceptedAnswer": {"@type": "Answer", "text": "A QSA-led gap analysis of your data stores is commonly quoted as a five-figure engagement, and it happens once a year. The PCI DSS v4 requirements that were future-dated became mandatory on 31 March 2025, so Requirement 3 is assessed in full at your next annual assessment. This is $29 once."}}]}</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:0,slug:"sql-card-data-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, 7-day refund.</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/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/erb-rails-view-snippets-audit"><span class="yj-rel-t">ERB Snippets + View Audit for Rails</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/en301549-lint"><span class="yj-rel-t">EN 301 549 Lint — European Accessibility Act rules, with the clause number, in</span><span class="yj-rel-p">$29</span></a><a class="yj-rel-c" href="https://getreadystack.com/tools/eu-data-residency-lint"><span class="yj-rel-t">EU Data Residency Lint for Terraform</span><span class="yj-rel-p">$29</span></a><a class="yj-rel-c" href="https://getreadystack.com/tools/mcp-2026-migration-lint"><span class="yj-rel-t">MCP 2026 Migration Lint</span><span class="yj-rel-p">$29</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>
389
+
390
+
391
+ <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>
392
+
393
+ <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>
394
+ </body>
395
  </html>