@detiq/crawler 1.1.9 → 1.1.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/accessibility-auditor.js +54 -74
- package/dist/ai-login.js +227 -249
- package/dist/auth-token-rotator.js +57 -67
- package/dist/auth.js +299 -336
- package/dist/browser-extension.js +104 -142
- package/dist/captcha-solver.js +98 -125
- package/dist/color-contrast.js +77 -89
- package/dist/content-chunker.js +126 -126
- package/dist/content-dedup.js +54 -71
- package/dist/cookie-consent.js +68 -83
- package/dist/cookie-jar.js +53 -60
- package/dist/crawler.js +4494 -5353
- package/dist/custom-js-injector.js +45 -61
- package/dist/dark-mode-capture.js +67 -81
- package/dist/dead-link-detector.js +47 -55
- package/dist/element-hash.js +8 -12
- package/dist/email-otp.js +154 -195
- package/dist/engine-router.js +87 -98
- package/dist/export/bulk-export.js +70 -55
- package/dist/export/vector-export.js +77 -105
- package/dist/extractors/advanced.js +249 -266
- package/dist/extractors/artifact-envelope.js +59 -58
- package/dist/failure-analysis-agent.js +37 -33
- package/dist/feature-flag-detector.js +51 -66
- package/dist/form-detector.js +55 -75
- package/dist/frontier.js +76 -101
- package/dist/gap-pass.js +68 -89
- package/dist/graphql-detector.js +62 -72
- package/dist/http-engine.js +282 -330
- package/dist/incremental-diff.js +54 -87
- package/dist/index.js +67 -28
- package/dist/input-fuzzer.js +93 -97
- package/dist/interactive-probe.js +715 -827
- package/dist/job-queue.js +2151 -2169
- package/dist/js-coverage.js +52 -56
- package/dist/js-router-detector.js +86 -117
- package/dist/language-detector.js +51 -63
- package/dist/link-classifier.js +32 -50
- package/dist/live-view.js +91 -108
- package/dist/locator-extractor.js +151 -187
- package/dist/logger.js +9 -7
- package/dist/markdown-extract.js +48 -61
- package/dist/native-crawler.js +780 -946
- package/dist/network-observer.js +135 -156
- package/dist/network-throttle.js +29 -40
- package/dist/openapi-generator.js +77 -92
- package/dist/pdf-extractor.js +72 -90
- package/dist/performance-metrics.js +80 -102
- package/dist/phash.js +125 -144
- package/dist/pii-scrubber.js +65 -68
- package/dist/playwright-recorder.js +70 -58
- package/dist/policies/crawl-safety.js +31 -34
- package/dist/profiles/domain-profile.js +130 -125
- package/dist/proxy.js +146 -172
- package/dist/rate-limiter.js +58 -65
- package/dist/redirect-tracker.js +60 -64
- package/dist/redis-frontier.js +116 -136
- package/dist/redis-queue.js +157 -144
- package/dist/response-cache.js +127 -140
- package/dist/responsive-detector.js +42 -46
- package/dist/retry-policy.js +46 -50
- package/dist/robots-txt.js +69 -83
- package/dist/screen-reader.js +45 -70
- package/dist/screenshot-masker.js +59 -77
- package/dist/seo-checker.js +88 -107
- package/dist/session-capture.js +192 -212
- package/dist/session-export.js +107 -133
- package/dist/sitemap-writer.js +38 -41
- package/dist/sitemap.js +141 -202
- package/dist/skyvern.js +123 -148
- package/dist/sqs-queue.js +42 -42
- package/dist/sso-token.js +74 -90
- package/dist/stealth/human-behavior.js +156 -173
- package/dist/telemetry.js +68 -82
- package/dist/test-executor.js +309 -335
- package/dist/tls-fingerprint.js +77 -88
- package/dist/url-pattern-completer.js +71 -90
- package/dist/visual-diff-verdict.js +23 -38
- package/dist/webhook-notifier.js +71 -69
- package/dist/websocket-detector.js +56 -68
- package/dist/worker.js +29 -38
- package/package.json +1 -1
package/dist/pii-scrubber.js
CHANGED
|
@@ -1,78 +1,75 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* PII scrubber — detects and redacts Personally Identifiable Information
|
|
3
|
-
* from crawled markdown, HTML, and extracted text before storage.
|
|
4
|
-
*
|
|
5
|
-
* GDPR Article 4(1): "any information relating to an identified or identifiable natural person"
|
|
6
|
-
*/
|
|
7
|
-
// Patterns — ordered from most-specific to least-specific
|
|
8
1
|
const PATTERNS = [
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
2
|
+
// Email — RFC 5321 simplified
|
|
3
|
+
{ type: "email", re: /\b[a-zA-Z0-9._%+\-]+@[a-zA-Z0-9.\-]+\.[a-zA-Z]{2,}\b/g },
|
|
4
|
+
// US SSN — 9 digits with dashes or spaces
|
|
5
|
+
{ type: "ssn", re: /\b(?!000|666|9\d\d)\d{3}[-\s](?!00)\d{2}[-\s](?!0000)\d{4}\b/g },
|
|
6
|
+
// Credit card — Visa/MC/Amex/Discover with optional spaces/dashes
|
|
7
|
+
{ type: "credit-card", re: /\b(?:4\d{3}|5[1-5]\d{2}|3[47]\d{2}|6(?:011|5\d{2}))[-\s]?\d{4}[-\s]?\d{4}[-\s]?\d{3,4}\b/g },
|
|
8
|
+
// IBAN — international bank account (15-34 chars)
|
|
9
|
+
{ type: "iban", re: /\b[A-Z]{2}\d{2}[A-Z0-9]{1,30}\b/g },
|
|
10
|
+
// Phone — international or US formats
|
|
11
|
+
{ type: "phone", re: /(?:\+?1[-.\s]?)?\(?\d{3}\)?[-.\s]\d{3}[-.\s]\d{4}\b|\+\d{1,3}[-.\s]\d{2,4}[-.\s]\d{3,4}[-.\s]\d{3,4}/g },
|
|
12
|
+
// Date of birth — common formats
|
|
13
|
+
{ type: "dob", re: /\b(?:0?[1-9]|1[0-2])\/(?:0?[1-9]|[12]\d|3[01])\/(?:19|20)\d{2}\b/g },
|
|
14
|
+
// IPv4 private/internal addresses — only flag private-looking ones in content
|
|
15
|
+
{ type: "ip-address", re: /\b(?:192\.168\.\d{1,3}\.\d{1,3}|10\.\d{1,3}\.\d{1,3}\.\d{1,3}|172\.(?:1[6-9]|2\d|3[01])\.\d{1,3}\.\d{1,3})\b/g }
|
|
23
16
|
];
|
|
24
17
|
const REDACT_MAP = {
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
18
|
+
email: "[EMAIL]",
|
|
19
|
+
phone: "[PHONE]",
|
|
20
|
+
ssn: "[SSN]",
|
|
21
|
+
"credit-card": "[CARD]",
|
|
22
|
+
"ip-address": "[IP]",
|
|
23
|
+
passport: "[PASSPORT]",
|
|
24
|
+
iban: "[IBAN]",
|
|
25
|
+
dob: "[DOB]"
|
|
33
26
|
};
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
}
|
|
27
|
+
function detectPii(text) {
|
|
28
|
+
const matches = [];
|
|
29
|
+
const seen = /* @__PURE__ */ new Set();
|
|
30
|
+
for (const { type, re } of PATTERNS) {
|
|
31
|
+
const r = new RegExp(re.source, re.flags.includes("g") ? re.flags : re.flags + "g");
|
|
32
|
+
let m;
|
|
33
|
+
while ((m = r.exec(text)) !== null) {
|
|
34
|
+
const key = `${type}:${m.index}`;
|
|
35
|
+
if (!seen.has(key)) {
|
|
36
|
+
seen.add(key);
|
|
37
|
+
matches.push({ type, value: m[0], start: m.index, end: m.index + m[0].length });
|
|
38
|
+
}
|
|
47
39
|
}
|
|
48
|
-
|
|
40
|
+
}
|
|
41
|
+
return matches.sort((a, b) => a.start - b.start);
|
|
49
42
|
}
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
}
|
|
60
|
-
return { text: scrubbed, matches, scrubbed: true };
|
|
43
|
+
function scrubPii(text) {
|
|
44
|
+
const matches = detectPii(text);
|
|
45
|
+
if (matches.length === 0) return { text, matches: [], scrubbed: false };
|
|
46
|
+
let scrubbed = text;
|
|
47
|
+
for (let i = matches.length - 1; i >= 0; i--) {
|
|
48
|
+
const { type, start, end } = matches[i];
|
|
49
|
+
scrubbed = scrubbed.slice(0, start) + REDACT_MAP[type] + scrubbed.slice(end);
|
|
50
|
+
}
|
|
51
|
+
return { text: scrubbed, matches, scrubbed: true };
|
|
61
52
|
}
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
return false;
|
|
53
|
+
function hasPii(text) {
|
|
54
|
+
for (const { re } of PATTERNS) {
|
|
55
|
+
const r = new RegExp(re.source, re.flags);
|
|
56
|
+
if (r.test(text)) return true;
|
|
57
|
+
}
|
|
58
|
+
return false;
|
|
69
59
|
}
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
}
|
|
60
|
+
function scrubObject(obj, fields) {
|
|
61
|
+
const result = { ...obj };
|
|
62
|
+
for (const field of fields) {
|
|
63
|
+
if (typeof result[field] === "string") {
|
|
64
|
+
result[field] = scrubPii(result[field]).text;
|
|
76
65
|
}
|
|
77
|
-
|
|
66
|
+
}
|
|
67
|
+
return result;
|
|
78
68
|
}
|
|
69
|
+
export {
|
|
70
|
+
detectPii,
|
|
71
|
+
hasPii,
|
|
72
|
+
scrubObject,
|
|
73
|
+
scrubPii
|
|
74
|
+
};
|
|
75
|
+
//# sourceMappingURL=pii-scrubber.js.map
|
|
@@ -1,18 +1,6 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Playwright test recorder — generate Playwright test code from crawl data.
|
|
3
|
-
*
|
|
4
|
-
* Given a list of crawled screens, generates a minimal Playwright test file that:
|
|
5
|
-
* 1. Navigates to each screen URL
|
|
6
|
-
* 2. Verifies the page title
|
|
7
|
-
* 3. Checks key elements visible on the page
|
|
8
|
-
* 4. Verifies no console errors (optional)
|
|
9
|
-
*
|
|
10
|
-
* This is a "smoke test" generator — not a full interaction recorder.
|
|
11
|
-
* Output: valid TypeScript Playwright test file ready to run with npx playwright test.
|
|
12
|
-
*/
|
|
13
1
|
function buildSmartLocatorHelper() {
|
|
14
|
-
|
|
15
|
-
// smartLocator
|
|
2
|
+
return `
|
|
3
|
+
// smartLocator \u2014 falls back to similarity search when primary selector fails.
|
|
16
4
|
// Generated by ZeTa Crawler. Do not edit manually.
|
|
17
5
|
async function smartLocator(page: any, primary: string, ref: { tag?: string; text?: string; role?: string; placeholder?: string }) {
|
|
18
6
|
try {
|
|
@@ -39,57 +27,76 @@ async function smartLocator(page: any, primary: string, ref: { tag?: string; tex
|
|
|
39
27
|
}`.trim();
|
|
40
28
|
}
|
|
41
29
|
function sanitizeTestName(name) {
|
|
42
|
-
|
|
30
|
+
return name.replace(/[^a-zA-Z0-9 _-]/g, "").trim().slice(0, 80) || "Screen";
|
|
43
31
|
}
|
|
44
32
|
function escapeString(s) {
|
|
45
|
-
|
|
33
|
+
return s.replace(/\\/g, "\\\\").replace(/'/g, "\\'").replace(/`/g, "\\`");
|
|
46
34
|
}
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
35
|
+
function generatePlaywrightTest(screens, opts = {}) {
|
|
36
|
+
const {
|
|
37
|
+
includeConsoleCheck = false,
|
|
38
|
+
testTimeout = 3e4,
|
|
39
|
+
projectName = "ZeTa Crawl",
|
|
40
|
+
useSmartLocators = true
|
|
41
|
+
} = opts;
|
|
42
|
+
const needsSmartLocator = useSmartLocators && screens.some((s) => s.smartLocator != null);
|
|
43
|
+
const lines = [
|
|
44
|
+
`// Generated by ZeTa Crawler \u2014 ${(/* @__PURE__ */ new Date()).toISOString()}`,
|
|
45
|
+
`// Project: ${escapeString(projectName)}`,
|
|
46
|
+
`// Screens: ${screens.length}`,
|
|
47
|
+
`// Run: npx playwright test`,
|
|
48
|
+
"",
|
|
49
|
+
"import { test, expect } from '@playwright/test';",
|
|
50
|
+
""
|
|
51
|
+
];
|
|
52
|
+
if (needsSmartLocator) {
|
|
53
|
+
lines.push(buildSmartLocatorHelper(), "");
|
|
54
|
+
}
|
|
55
|
+
lines.push(`test.setTimeout(${testTimeout});`, "");
|
|
56
|
+
if (includeConsoleCheck) {
|
|
57
|
+
lines.push(
|
|
58
|
+
`test.beforeEach(async ({ page }) => {`,
|
|
59
|
+
` const errors: string[] = [];`,
|
|
60
|
+
` page.on('console', msg => { if (msg.type() === 'error') errors.push(msg.text()); });`,
|
|
61
|
+
` page.on('pageerror', err => errors.push(err.message));`,
|
|
62
|
+
` (page as any).__consoleErrors = errors;`,
|
|
63
|
+
`});`,
|
|
64
|
+
""
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
for (const screen of screens) {
|
|
68
|
+
const testName = sanitizeTestName(screen.name);
|
|
69
|
+
lines.push(
|
|
70
|
+
`test('${escapeString(testName)}', async ({ page }) => {`,
|
|
71
|
+
` await page.goto('${escapeString(screen.url)}');`,
|
|
72
|
+
` await expect(page).toHaveURL(/${escapeString(screen.url.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"))}/);`
|
|
73
|
+
);
|
|
74
|
+
if (screen.title) {
|
|
75
|
+
lines.push(` await expect(page).toHaveTitle(/${escapeString(screen.title.slice(0, 40).replace(/[.*+?^${}()|[\]\\]/g, "\\$&"))}/i);`);
|
|
61
76
|
}
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
77
|
+
if (screen.heading) {
|
|
78
|
+
const h = escapeString(screen.heading.slice(0, 60));
|
|
79
|
+
if (needsSmartLocator && screen.smartLocator) {
|
|
80
|
+
const tag = escapeString(screen.smartLocator.tag);
|
|
81
|
+
const text = escapeString(screen.smartLocator.text ?? "");
|
|
82
|
+
lines.push(
|
|
83
|
+
` const headingEl = await smartLocator(page, 'h1, h2, h3', { tag: '${tag}', text: '${text}' });`,
|
|
84
|
+
` await expect(headingEl.first()).toContainText('${h}', { ignoreCase: true });`
|
|
85
|
+
);
|
|
86
|
+
} else {
|
|
87
|
+
lines.push(` await expect(page.locator('h1, h2, h3').first()).toContainText('${h}', { ignoreCase: true });`);
|
|
88
|
+
}
|
|
65
89
|
}
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
lines.push(`test('${escapeString(testName)}', async ({ page }) => {`, ` await page.goto('${escapeString(screen.url)}');`, ` await expect(page).toHaveURL(/${escapeString(screen.url.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'))}/);`);
|
|
69
|
-
if (screen.title) {
|
|
70
|
-
lines.push(` await expect(page).toHaveTitle(/${escapeString(screen.title.slice(0, 40).replace(/[.*+?^${}()|[\]\\]/g, '\\$&'))}/i);`);
|
|
71
|
-
}
|
|
72
|
-
if (screen.heading) {
|
|
73
|
-
const h = escapeString(screen.heading.slice(0, 60));
|
|
74
|
-
if (needsSmartLocator && screen.smartLocator) {
|
|
75
|
-
const tag = escapeString(screen.smartLocator.tag);
|
|
76
|
-
const text = escapeString(screen.smartLocator.text ?? '');
|
|
77
|
-
lines.push(` const headingEl = await smartLocator(page, 'h1, h2, h3', { tag: '${tag}', text: '${text}' });`, ` await expect(headingEl.first()).toContainText('${h}', { ignoreCase: true });`);
|
|
78
|
-
}
|
|
79
|
-
else {
|
|
80
|
-
lines.push(` await expect(page.locator('h1, h2, h3').first()).toContainText('${h}', { ignoreCase: true });`);
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
if (includeConsoleCheck) {
|
|
84
|
-
lines.push(` expect((page as any).__consoleErrors ?? []).toHaveLength(0);`);
|
|
85
|
-
}
|
|
86
|
-
lines.push(`});`, '');
|
|
90
|
+
if (includeConsoleCheck) {
|
|
91
|
+
lines.push(` expect((page as any).__consoleErrors ?? []).toHaveLength(0);`);
|
|
87
92
|
}
|
|
88
|
-
|
|
93
|
+
lines.push(`});`, "");
|
|
94
|
+
}
|
|
95
|
+
return lines.join("\n");
|
|
89
96
|
}
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
97
|
+
function generatePlaywrightConfig(opts = {}) {
|
|
98
|
+
const { baseUrl = "http://localhost:3000", reportDir = "playwright-report" } = opts;
|
|
99
|
+
return `// playwright.config.ts \u2014 generated by ZeTa Crawler
|
|
93
100
|
import { defineConfig } from '@playwright/test';
|
|
94
101
|
|
|
95
102
|
export default defineConfig({
|
|
@@ -109,3 +116,8 @@ export default defineConfig({
|
|
|
109
116
|
});
|
|
110
117
|
`;
|
|
111
118
|
}
|
|
119
|
+
export {
|
|
120
|
+
generatePlaywrightConfig,
|
|
121
|
+
generatePlaywrightTest
|
|
122
|
+
};
|
|
123
|
+
//# sourceMappingURL=playwright-recorder.js.map
|
|
@@ -1,40 +1,37 @@
|
|
|
1
1
|
const DESTRUCTIVE_ACTION = /\b(delete|remove|destroy|deactivate|disable|cancel|refund|void|transfer|withdraw|pay now|place order|submit order|confirm payment|send money|publish|approve|reject|bulk update|drop)\b/i;
|
|
2
2
|
const SECRET_LIKE = /\b(api[_ -]?key|secret|token|password|authorization|cookie|set-cookie)\b/i;
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
3
|
+
function assessActionSafety(label, profile) {
|
|
4
|
+
const riskyByProfile = profile.riskyActions.some((risk) => label.toLowerCase().includes(risk.toLowerCase()));
|
|
5
|
+
if (DESTRUCTIVE_ACTION.test(label) || riskyByProfile) {
|
|
6
|
+
return {
|
|
7
|
+
allowed: false,
|
|
8
|
+
severity: "block",
|
|
9
|
+
reason: `Blocked risky action "${label}" for ${profile.industry} profile.`
|
|
10
|
+
};
|
|
11
|
+
}
|
|
12
|
+
return { allowed: true, severity: "info" };
|
|
13
13
|
}
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
.replace(/\b(?:\d[ -]*?){13,19}\b/g, '[card-like-number]')
|
|
19
|
-
.replace(/\b[A-Za-z0-9_-]{24,}\b/g, '[token-like-value]');
|
|
20
|
-
if (SECRET_LIKE.test(redacted))
|
|
21
|
-
redacted = redacted.replace(/:\s*[^,\s}]+/g, ': [redacted]');
|
|
22
|
-
return redacted;
|
|
14
|
+
function redactSensitiveValue(value) {
|
|
15
|
+
let redacted = value.replace(/[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}/gi, "[email]").replace(/\b(?:\+?\d[\d -]{8,}\d)\b/g, "[phone-or-id]").replace(/\b(?:\d[ -]*?){13,19}\b/g, "[card-like-number]").replace(/\b[A-Za-z0-9_-]{24,}\b/g, "[token-like-value]");
|
|
16
|
+
if (SECRET_LIKE.test(redacted)) redacted = redacted.replace(/:\s*[^,\s}]+/g, ": [redacted]");
|
|
17
|
+
return redacted;
|
|
23
18
|
}
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
if (SECRET_LIKE.test(key))
|
|
33
|
-
out[key] = '[redacted]';
|
|
34
|
-
else
|
|
35
|
-
out[key] = redactJson(item);
|
|
36
|
-
}
|
|
37
|
-
return out;
|
|
19
|
+
function redactJson(value) {
|
|
20
|
+
if (typeof value === "string") return redactSensitiveValue(value);
|
|
21
|
+
if (Array.isArray(value)) return value.map(redactJson);
|
|
22
|
+
if (value && typeof value === "object") {
|
|
23
|
+
const out = {};
|
|
24
|
+
for (const [key, item] of Object.entries(value)) {
|
|
25
|
+
if (SECRET_LIKE.test(key)) out[key] = "[redacted]";
|
|
26
|
+
else out[key] = redactJson(item);
|
|
38
27
|
}
|
|
39
|
-
return
|
|
28
|
+
return out;
|
|
29
|
+
}
|
|
30
|
+
return value;
|
|
40
31
|
}
|
|
32
|
+
export {
|
|
33
|
+
assessActionSafety,
|
|
34
|
+
redactJson,
|
|
35
|
+
redactSensitiveValue
|
|
36
|
+
};
|
|
37
|
+
//# sourceMappingURL=crawl-safety.js.map
|