imprint-mcp 0.2.0
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/CHANGELOG.md +168 -0
- package/LICENSE +21 -0
- package/README.md +322 -0
- package/examples/discoverandgo/README.md +57 -0
- package/examples/discoverandgo/book_discoverandgo_museum_pass/cron.json +8 -0
- package/examples/discoverandgo/book_discoverandgo_museum_pass/index.ts +89 -0
- package/examples/discoverandgo/book_discoverandgo_museum_pass/workflow.json +39 -0
- package/examples/echo/README.md +37 -0
- package/examples/echo/echo_test/index.ts +31 -0
- package/examples/google-flights/search_google_flights/index.ts +101 -0
- package/examples/google-flights/search_google_flights/parser.test.ts +140 -0
- package/examples/google-flights/search_google_flights/parser.ts +189 -0
- package/examples/google-flights/search_google_flights/playbook.yaml +130 -0
- package/examples/google-flights/search_google_flights/workflow.json +48 -0
- package/examples/google-hotels/search_google_hotels/index.ts +194 -0
- package/examples/google-hotels/search_google_hotels/parser.test.ts +168 -0
- package/examples/google-hotels/search_google_hotels/parser.ts +330 -0
- package/examples/google-hotels/search_google_hotels/playbook.yaml +125 -0
- package/examples/google-hotels/search_google_hotels/workflow.json +111 -0
- package/examples/namecheap-domains/search_namecheap_domains/index.ts +144 -0
- package/examples/namecheap-domains/search_namecheap_domains/parser.ts +380 -0
- package/examples/namecheap-domains/search_namecheap_domains/playbook.yaml +50 -0
- package/examples/namecheap-domains/search_namecheap_domains/request-transform.ts +136 -0
- package/examples/namecheap-domains/search_namecheap_domains/workflow.json +97 -0
- package/examples/southwest/README.md +81 -0
- package/examples/southwest/search_southwest_flights/backends.json +23 -0
- package/examples/southwest/search_southwest_flights/cron.json +19 -0
- package/examples/southwest/search_southwest_flights/index.ts +110 -0
- package/examples/southwest/search_southwest_flights/playbook.yaml +46 -0
- package/examples/southwest/search_southwest_flights/workflow.json +54 -0
- package/package.json +78 -0
- package/prompts/compile-agent.md +580 -0
- package/prompts/intent-detection.md +198 -0
- package/prompts/playbook-compilation.md +279 -0
- package/prompts/request-triage.md +74 -0
- package/prompts/tool-candidate-detection.md +104 -0
- package/src/cli.ts +1287 -0
- package/src/imprint/agent.ts +468 -0
- package/src/imprint/app-api-hosts.ts +53 -0
- package/src/imprint/backend-ladder.ts +568 -0
- package/src/imprint/check.ts +136 -0
- package/src/imprint/chromium.ts +211 -0
- package/src/imprint/claude-cli-compile.ts +640 -0
- package/src/imprint/cli-credential.ts +394 -0
- package/src/imprint/codex-cli-compile.ts +712 -0
- package/src/imprint/compile-agent-types.ts +40 -0
- package/src/imprint/compile-agent.ts +404 -0
- package/src/imprint/compile-tools.ts +1389 -0
- package/src/imprint/compile.ts +720 -0
- package/src/imprint/cookie-jar.ts +246 -0
- package/src/imprint/credential-bundle.ts +195 -0
- package/src/imprint/credential-extract.ts +290 -0
- package/src/imprint/credential-store.ts +707 -0
- package/src/imprint/cron.ts +312 -0
- package/src/imprint/doctor.ts +223 -0
- package/src/imprint/emit.ts +154 -0
- package/src/imprint/etld.ts +134 -0
- package/src/imprint/freeform-redact.ts +216 -0
- package/src/imprint/inject-listener.ts +137 -0
- package/src/imprint/install.ts +795 -0
- package/src/imprint/integrations.ts +385 -0
- package/src/imprint/is-compiled.ts +2 -0
- package/src/imprint/json-path.ts +100 -0
- package/src/imprint/llm.ts +998 -0
- package/src/imprint/load-json.ts +54 -0
- package/src/imprint/log.ts +33 -0
- package/src/imprint/login.ts +166 -0
- package/src/imprint/mcp-compile-server.ts +282 -0
- package/src/imprint/mcp-maintenance.ts +1790 -0
- package/src/imprint/mcp-server.ts +350 -0
- package/src/imprint/multi-progress.ts +69 -0
- package/src/imprint/notify.ts +155 -0
- package/src/imprint/paths.ts +64 -0
- package/src/imprint/playbook-parser.ts +21 -0
- package/src/imprint/playbook-runner.ts +465 -0
- package/src/imprint/probe-backends.ts +251 -0
- package/src/imprint/progress.ts +28 -0
- package/src/imprint/record.ts +470 -0
- package/src/imprint/redact.ts +550 -0
- package/src/imprint/replay-capture.ts +387 -0
- package/src/imprint/request-context.ts +66 -0
- package/src/imprint/runtime-link.ts +73 -0
- package/src/imprint/runtime.ts +942 -0
- package/src/imprint/sensitive-keys.ts +156 -0
- package/src/imprint/session-diff.ts +409 -0
- package/src/imprint/session-merge.ts +198 -0
- package/src/imprint/session-writer.ts +149 -0
- package/src/imprint/sites.ts +27 -0
- package/src/imprint/stealth-fetch.ts +434 -0
- package/src/imprint/teach-state.ts +235 -0
- package/src/imprint/teach.ts +2120 -0
- package/src/imprint/tool-candidates.ts +423 -0
- package/src/imprint/tool-loader.ts +186 -0
- package/src/imprint/tool-selection.ts +70 -0
- package/src/imprint/tracing.ts +508 -0
- package/src/imprint/types.ts +472 -0
- package/src/imprint/version.ts +21 -0
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
/** Registrable-domain extraction (eTLD+1) for hostname filtering.
|
|
2
|
+
*
|
|
3
|
+
* Naive `split('.').slice(-2)` is wrong for multi-part public suffixes:
|
|
4
|
+
* api.example.co.uk → "co.uk" (wrong; should be "example.co.uk")
|
|
5
|
+
* which then over-matches every other .co.uk hostname.
|
|
6
|
+
*
|
|
7
|
+
* We're not pulling in the full Mozilla Public Suffix List — too much
|
|
8
|
+
* weight for a CLI tool. Instead, a small allow-list of the common
|
|
9
|
+
* multi-part suffixes covers ~all real-world cases. If we ever record
|
|
10
|
+
* against an exotic ccTLD we'll add it here. */
|
|
11
|
+
const MULTI_PART_SUFFIXES = new Set([
|
|
12
|
+
// United Kingdom
|
|
13
|
+
'co.uk',
|
|
14
|
+
'org.uk',
|
|
15
|
+
'me.uk',
|
|
16
|
+
'ltd.uk',
|
|
17
|
+
'plc.uk',
|
|
18
|
+
'net.uk',
|
|
19
|
+
'sch.uk',
|
|
20
|
+
'ac.uk',
|
|
21
|
+
'gov.uk',
|
|
22
|
+
'nhs.uk',
|
|
23
|
+
// Australia
|
|
24
|
+
'com.au',
|
|
25
|
+
'net.au',
|
|
26
|
+
'org.au',
|
|
27
|
+
'edu.au',
|
|
28
|
+
'gov.au',
|
|
29
|
+
'asn.au',
|
|
30
|
+
'id.au',
|
|
31
|
+
// Japan
|
|
32
|
+
'co.jp',
|
|
33
|
+
'ne.jp',
|
|
34
|
+
'or.jp',
|
|
35
|
+
'ac.jp',
|
|
36
|
+
'ad.jp',
|
|
37
|
+
'go.jp',
|
|
38
|
+
'gr.jp',
|
|
39
|
+
// Brazil
|
|
40
|
+
'com.br',
|
|
41
|
+
'net.br',
|
|
42
|
+
'org.br',
|
|
43
|
+
'gov.br',
|
|
44
|
+
'edu.br',
|
|
45
|
+
'mil.br',
|
|
46
|
+
// South Africa
|
|
47
|
+
'co.za',
|
|
48
|
+
'ac.za',
|
|
49
|
+
'gov.za',
|
|
50
|
+
'org.za',
|
|
51
|
+
'net.za',
|
|
52
|
+
// Mexico
|
|
53
|
+
'com.mx',
|
|
54
|
+
'gob.mx',
|
|
55
|
+
'org.mx',
|
|
56
|
+
'edu.mx',
|
|
57
|
+
// India
|
|
58
|
+
'co.in',
|
|
59
|
+
'gov.in',
|
|
60
|
+
'ac.in',
|
|
61
|
+
'org.in',
|
|
62
|
+
'net.in',
|
|
63
|
+
'edu.in',
|
|
64
|
+
// South Korea
|
|
65
|
+
'co.kr',
|
|
66
|
+
'ne.kr',
|
|
67
|
+
'or.kr',
|
|
68
|
+
'go.kr',
|
|
69
|
+
'ac.kr',
|
|
70
|
+
// China
|
|
71
|
+
'com.cn',
|
|
72
|
+
'net.cn',
|
|
73
|
+
'org.cn',
|
|
74
|
+
'gov.cn',
|
|
75
|
+
'edu.cn',
|
|
76
|
+
'ac.cn',
|
|
77
|
+
// Hong Kong
|
|
78
|
+
'com.hk',
|
|
79
|
+
'org.hk',
|
|
80
|
+
'gov.hk',
|
|
81
|
+
'edu.hk',
|
|
82
|
+
'net.hk',
|
|
83
|
+
// New Zealand
|
|
84
|
+
'co.nz',
|
|
85
|
+
'org.nz',
|
|
86
|
+
'net.nz',
|
|
87
|
+
'govt.nz',
|
|
88
|
+
'ac.nz',
|
|
89
|
+
// Singapore
|
|
90
|
+
'com.sg',
|
|
91
|
+
'org.sg',
|
|
92
|
+
'gov.sg',
|
|
93
|
+
'edu.sg',
|
|
94
|
+
'net.sg',
|
|
95
|
+
// Israel
|
|
96
|
+
'co.il',
|
|
97
|
+
'org.il',
|
|
98
|
+
'gov.il',
|
|
99
|
+
'ac.il',
|
|
100
|
+
'net.il',
|
|
101
|
+
// Argentina
|
|
102
|
+
'com.ar',
|
|
103
|
+
'gov.ar',
|
|
104
|
+
'edu.ar',
|
|
105
|
+
'org.ar',
|
|
106
|
+
]);
|
|
107
|
+
|
|
108
|
+
/** Return the registrable domain (eTLD+1) of a hostname.
|
|
109
|
+
* Examples:
|
|
110
|
+
* api.example.com → example.com
|
|
111
|
+
* api.example.co.uk → example.co.uk
|
|
112
|
+
* example.com → example.com
|
|
113
|
+
* localhost → localhost
|
|
114
|
+
* 192.168.1.1 → 192.168.1.1 (no further reduction) */
|
|
115
|
+
export function registrableDomain(hostname: string): string {
|
|
116
|
+
// IPs and bare hosts pass through unchanged.
|
|
117
|
+
if (hostname.length === 0) return hostname;
|
|
118
|
+
if (/^\d+\.\d+\.\d+\.\d+$/.test(hostname)) return hostname;
|
|
119
|
+
|
|
120
|
+
const parts = hostname.split('.');
|
|
121
|
+
if (parts.length <= 2) return hostname;
|
|
122
|
+
|
|
123
|
+
const lastTwo = parts.slice(-2).join('.');
|
|
124
|
+
if (MULTI_PART_SUFFIXES.has(lastTwo) && parts.length >= 3) {
|
|
125
|
+
return parts.slice(-3).join('.');
|
|
126
|
+
}
|
|
127
|
+
return parts.slice(-2).join('.');
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/** True when `hostname` is the registrable domain `root` or a subdomain
|
|
131
|
+
* of it. Used by request-filtering and cookie-scoping. */
|
|
132
|
+
export function isSameRegistrableDomain(hostname: string, root: string): boolean {
|
|
133
|
+
return hostname === root || hostname.endsWith(`.${root}`);
|
|
134
|
+
}
|
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
import { Policies, type PolicyName, createRedactum } from 'redactum';
|
|
2
|
+
|
|
3
|
+
const FREEFORM_POLICIES: PolicyName[] = [
|
|
4
|
+
Policies.EMAIL_ADDRESS,
|
|
5
|
+
Policies.PHONE_NUMBER_US,
|
|
6
|
+
Policies.PHONE_NUMBER_INTERNATIONAL,
|
|
7
|
+
Policies.PHONE_NUMBER_UK,
|
|
8
|
+
Policies.PHONE_NUMBER_CANADIAN,
|
|
9
|
+
Policies.SSN,
|
|
10
|
+
Policies.CREDIT_CARD,
|
|
11
|
+
Policies.CREDIT_CARD_WITH_SEPARATORS,
|
|
12
|
+
Policies.CREDIT_CARD_CVV,
|
|
13
|
+
Policies.URL_WITH_CREDENTIALS,
|
|
14
|
+
Policies.JWT_TOKEN,
|
|
15
|
+
Policies.BASIC_AUTH_HEADER,
|
|
16
|
+
Policies.BEARER_TOKEN_HEADER,
|
|
17
|
+
Policies.API_KEY_HEADER,
|
|
18
|
+
Policies.SESSION_ID_COOKIE,
|
|
19
|
+
Policies.API_KEY_GENERIC,
|
|
20
|
+
Policies.OPENAI_API_KEY,
|
|
21
|
+
Policies.ANTHROPIC_API_KEY,
|
|
22
|
+
Policies.GOOGLE_API_KEY,
|
|
23
|
+
Policies.GCP_API_KEY,
|
|
24
|
+
Policies.GITHUB_TOKEN,
|
|
25
|
+
Policies.GITHUB_FINE_GRAINED_TOKEN,
|
|
26
|
+
Policies.GITLAB_TOKEN,
|
|
27
|
+
Policies.BITBUCKET_TOKEN,
|
|
28
|
+
Policies.AWS_ACCESS_KEY,
|
|
29
|
+
Policies.AWS_SECRET_KEY,
|
|
30
|
+
Policies.AWS_SESSION_TOKEN,
|
|
31
|
+
Policies.AZURE_STORAGE_CONNECTION_STRING,
|
|
32
|
+
Policies.DIGITALOCEAN_TOKEN,
|
|
33
|
+
Policies.HEROKU_API_KEY,
|
|
34
|
+
Policies.RAILWAY_TOKEN,
|
|
35
|
+
Policies.CLOUDFLARE_API_TOKEN,
|
|
36
|
+
Policies.DOCKER_HUB_TOKEN,
|
|
37
|
+
Policies.DOCKER_REGISTRY_TOKEN,
|
|
38
|
+
Policies.NPM_TOKEN,
|
|
39
|
+
Policies.PYPI_TOKEN,
|
|
40
|
+
Policies.RUBYGEMS_API_KEY,
|
|
41
|
+
Policies.QUAY_IO_TOKEN,
|
|
42
|
+
Policies.JFROG_ARTIFACTORY_TOKEN,
|
|
43
|
+
Policies.NEXUS_REPOSITORY_TOKEN,
|
|
44
|
+
Policies.SLACK_WEBHOOK,
|
|
45
|
+
Policies.DISCORD_WEBHOOK,
|
|
46
|
+
Policies.WEBHOOK_URL,
|
|
47
|
+
Policies.SLACK_TOKEN,
|
|
48
|
+
Policies.DISCORD_TOKEN,
|
|
49
|
+
Policies.TWILIO_AUTH_TOKEN,
|
|
50
|
+
Policies.TWILIO_API_KEY,
|
|
51
|
+
Policies.SENDGRID_API_KEY,
|
|
52
|
+
Policies.STRIPE_KEY,
|
|
53
|
+
Policies.MAILGUN_API_KEY,
|
|
54
|
+
Policies.MAILCHIMP_API_KEY,
|
|
55
|
+
Policies.MONGODB_CONNECTION_STRING,
|
|
56
|
+
Policies.POSTGRESQL_CONNECTION_STRING,
|
|
57
|
+
Policies.MYSQL_CONNECTION_STRING,
|
|
58
|
+
Policies.REDIS_CONNECTION_STRING,
|
|
59
|
+
Policies.ELASTICSEARCH_URL,
|
|
60
|
+
Policies.RABBITMQ_CONNECTION_STRING,
|
|
61
|
+
Policies.KAFKA_CONNECTION_STRING,
|
|
62
|
+
Policies.CASSANDRA_CONNECTION_STRING,
|
|
63
|
+
Policies.DATABASE_CONNECTION_STRING,
|
|
64
|
+
Policies.DATABASE_URL,
|
|
65
|
+
Policies.LDAP_CONNECTION_STRING,
|
|
66
|
+
Policies.JDBC_CONNECTION_STRING,
|
|
67
|
+
Policies.SMTP_CONNECTION_STRING,
|
|
68
|
+
Policies.SSH_PRIVATE_KEY,
|
|
69
|
+
Policies.RSA_PRIVATE_KEY,
|
|
70
|
+
Policies.EC_PRIVATE_KEY,
|
|
71
|
+
Policies.OPENSSH_PRIVATE_KEY,
|
|
72
|
+
Policies.GENERIC_PRIVATE_KEY,
|
|
73
|
+
Policies.PGP_PRIVATE_KEY,
|
|
74
|
+
Policies.PASSWORD_ASSIGNMENT,
|
|
75
|
+
Policies.ENVIRONMENT_VARIABLE_SECRET,
|
|
76
|
+
Policies.GENERIC_PASSWORD,
|
|
77
|
+
Policies.GENERIC_TOKEN,
|
|
78
|
+
Policies.GENERIC_CREDENTIAL,
|
|
79
|
+
Policies.GENERIC_SECRET,
|
|
80
|
+
Policies.OAUTH_CLIENT_SECRET,
|
|
81
|
+
Policies.OAUTH_REFRESH_TOKEN,
|
|
82
|
+
Policies.OAUTH_ACCESS_TOKEN,
|
|
83
|
+
Policies.OKTA_API_TOKEN,
|
|
84
|
+
Policies.AUTH0_API_TOKEN,
|
|
85
|
+
Policies.KEYCLOAK_CLIENT_SECRET,
|
|
86
|
+
Policies.JENKINS_TOKEN,
|
|
87
|
+
Policies.CIRCLECI_TOKEN,
|
|
88
|
+
Policies.TRAVIS_CI_TOKEN,
|
|
89
|
+
Policies.GITLAB_CI_TOKEN,
|
|
90
|
+
Policies.AZURE_DEVOPS_TOKEN,
|
|
91
|
+
Policies.BITBUCKET_TOKEN_ALT,
|
|
92
|
+
Policies.SENTRY_DSN,
|
|
93
|
+
Policies.NEW_RELIC_LICENSE_KEY,
|
|
94
|
+
Policies.DATADOG_API_KEY,
|
|
95
|
+
Policies.PAGERDUTY_INTEGRATION_KEY,
|
|
96
|
+
Policies.GRAFANA_API_KEY,
|
|
97
|
+
Policies.SPLUNK_HEC_TOKEN,
|
|
98
|
+
Policies.BUGSNAG_API_KEY,
|
|
99
|
+
Policies.ROLLBAR_ACCESS_TOKEN,
|
|
100
|
+
Policies.AIRBRAKE_API_KEY,
|
|
101
|
+
Policies.LOGDNA_INGESTION_KEY,
|
|
102
|
+
Policies.LOGGLY_TOKEN,
|
|
103
|
+
Policies.PAPERTRAIL_TOKEN,
|
|
104
|
+
Policies.TERRAFORM_CLOUD_TOKEN,
|
|
105
|
+
Policies.HASHICORP_VAULT_TOKEN,
|
|
106
|
+
Policies.AWS_SECRETS_MANAGER_ARN,
|
|
107
|
+
Policies.AZURE_KEY_VAULT_SECRET,
|
|
108
|
+
Policies.GCP_SECRET_MANAGER,
|
|
109
|
+
Policies.CONSUL_TOKEN,
|
|
110
|
+
Policies.RANCHER_TOKEN,
|
|
111
|
+
Policies.AGE_SECRET_KEY,
|
|
112
|
+
Policies.MASTER_KEY,
|
|
113
|
+
];
|
|
114
|
+
|
|
115
|
+
const REDACTION_HINT_RE =
|
|
116
|
+
/@|\beyJ[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.|\b\d{3}[-.\s]\d{2}[-.\s]\d{4}\b|\b\d{3}[-.\s]\d{3}[-.\s]\d{4}\b|\b\d{4}[-\s]\d{4}[-\s]\d{4}[-\s]\d{4}\b|api[_-]?key|auth|bearer|card|cookie|credential|key|password|postgres|mysql|mongodb|redis|secret|session|sk-|token|-----BEGIN/i;
|
|
117
|
+
|
|
118
|
+
const PROTECTED_PATTERNS = [
|
|
119
|
+
/\[REDACTED:\d+\]/g,
|
|
120
|
+
/\$\{credential\.[A-Za-z0-9_.-]+\}/g,
|
|
121
|
+
/\b[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}\b/gi,
|
|
122
|
+
/\b[0-9a-f]{40}\b/gi,
|
|
123
|
+
];
|
|
124
|
+
|
|
125
|
+
const REDACTOR = createRedactum({
|
|
126
|
+
policies: FREEFORM_POLICIES,
|
|
127
|
+
replacement: () => '[REDACTED]',
|
|
128
|
+
});
|
|
129
|
+
const CACHE_MAX = 512;
|
|
130
|
+
const cache = new Map<string, FreeformRedaction>();
|
|
131
|
+
|
|
132
|
+
interface FreeformRedaction {
|
|
133
|
+
redacted: string;
|
|
134
|
+
redactionsCount: number;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
interface Range {
|
|
138
|
+
start: number;
|
|
139
|
+
end: number;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
export function redactFreeformText(text: string): FreeformRedaction {
|
|
143
|
+
if (!hasFreeformRedactionHint(text)) {
|
|
144
|
+
return { redacted: text, redactionsCount: 0 };
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
const protectedRanges = collectProtectedRanges(text);
|
|
148
|
+
if (protectedRanges.length === 0) {
|
|
149
|
+
return redactUnprotectedText(text);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
let redacted = '';
|
|
153
|
+
let cursor = 0;
|
|
154
|
+
let redactionsCount = 0;
|
|
155
|
+
for (const range of protectedRanges) {
|
|
156
|
+
const segment = text.slice(cursor, range.start);
|
|
157
|
+
const segmentResult = redactUnprotectedText(segment);
|
|
158
|
+
redacted += segmentResult.redacted;
|
|
159
|
+
redactionsCount += segmentResult.redactionsCount;
|
|
160
|
+
redacted += text.slice(range.start, range.end);
|
|
161
|
+
cursor = range.end;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
const tail = redactUnprotectedText(text.slice(cursor));
|
|
165
|
+
redacted += tail.redacted;
|
|
166
|
+
redactionsCount += tail.redactionsCount;
|
|
167
|
+
|
|
168
|
+
return { redacted, redactionsCount };
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
function redactUnprotectedText(text: string): FreeformRedaction {
|
|
172
|
+
if (text.length === 0 || !hasFreeformRedactionHint(text)) {
|
|
173
|
+
return { redacted: text, redactionsCount: 0 };
|
|
174
|
+
}
|
|
175
|
+
const cached = cache.get(text);
|
|
176
|
+
if (cached) return cached;
|
|
177
|
+
|
|
178
|
+
const result = REDACTOR.redactum(text);
|
|
179
|
+
const redaction = {
|
|
180
|
+
redacted: result.redactedText,
|
|
181
|
+
redactionsCount: result.stats.totalFindings,
|
|
182
|
+
};
|
|
183
|
+
cache.set(text, redaction);
|
|
184
|
+
if (cache.size > CACHE_MAX) {
|
|
185
|
+
const oldest = cache.keys().next().value;
|
|
186
|
+
if (oldest !== undefined) cache.delete(oldest);
|
|
187
|
+
}
|
|
188
|
+
return redaction;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
export function hasFreeformRedactionHint(text: string): boolean {
|
|
192
|
+
return REDACTION_HINT_RE.test(text);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
function collectProtectedRanges(text: string): Range[] {
|
|
196
|
+
const ranges: Range[] = [];
|
|
197
|
+
for (const pattern of PROTECTED_PATTERNS) {
|
|
198
|
+
pattern.lastIndex = 0;
|
|
199
|
+
for (const match of text.matchAll(pattern)) {
|
|
200
|
+
if (match.index === undefined) continue;
|
|
201
|
+
ranges.push({ start: match.index, end: match.index + match[0].length });
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
ranges.sort((a, b) => a.start - b.start || b.end - a.end);
|
|
205
|
+
|
|
206
|
+
const merged: Range[] = [];
|
|
207
|
+
for (const range of ranges) {
|
|
208
|
+
const previous = merged.at(-1);
|
|
209
|
+
if (!previous || range.start > previous.end) {
|
|
210
|
+
merged.push({ ...range });
|
|
211
|
+
} else if (range.end > previous.end) {
|
|
212
|
+
previous.end = range.end;
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
return merged;
|
|
216
|
+
}
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Injected per-page (Page.addScriptToEvaluateOnNewDocument). Passive
|
|
3
|
+
* capture-phase listeners emit sentinel-prefixed console.log lines that
|
|
4
|
+
* the recorder picks up via Runtime.consoleAPICalled. Sentinel
|
|
5
|
+
* `[IMPRINT]` is exact-match — keep stable.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
export const INJECTED_LISTENER_SOURCE = `
|
|
9
|
+
(function imprintInjector() {
|
|
10
|
+
if (window.__imprint_injected__) return;
|
|
11
|
+
window.__imprint_injected__ = true;
|
|
12
|
+
|
|
13
|
+
const SENTINEL = '[IMPRINT]';
|
|
14
|
+
const MAX_VAL = 200;
|
|
15
|
+
|
|
16
|
+
function safeStr(v) {
|
|
17
|
+
try {
|
|
18
|
+
if (v == null) return null;
|
|
19
|
+
const s = String(v);
|
|
20
|
+
return s.length > MAX_VAL ? s.slice(0, MAX_VAL) + '…' : s;
|
|
21
|
+
} catch (e) { return null; }
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function selectorFor(el) {
|
|
25
|
+
try {
|
|
26
|
+
if (!el || !el.tagName) return null;
|
|
27
|
+
if (el.id) return '#' + el.id;
|
|
28
|
+
const parts = [];
|
|
29
|
+
let node = el;
|
|
30
|
+
let depth = 0;
|
|
31
|
+
while (node && node.nodeType === 1 && depth < 5) {
|
|
32
|
+
let part = node.tagName.toLowerCase();
|
|
33
|
+
if (node.className && typeof node.className === 'string') {
|
|
34
|
+
const cls = node.className.trim().split(/\\s+/).slice(0, 2).join('.');
|
|
35
|
+
if (cls) part += '.' + cls;
|
|
36
|
+
}
|
|
37
|
+
parts.unshift(part);
|
|
38
|
+
node = node.parentElement;
|
|
39
|
+
depth++;
|
|
40
|
+
}
|
|
41
|
+
return parts.join(' > ');
|
|
42
|
+
} catch (e) { return null; }
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function describe(el) {
|
|
46
|
+
try {
|
|
47
|
+
if (!el || !el.tagName) return {};
|
|
48
|
+
return {
|
|
49
|
+
tag: el.tagName.toLowerCase(),
|
|
50
|
+
id: el.id || null,
|
|
51
|
+
name: el.getAttribute && el.getAttribute('name') || null,
|
|
52
|
+
type: el.getAttribute && el.getAttribute('type') || null,
|
|
53
|
+
text: safeStr((el.textContent || '').trim()),
|
|
54
|
+
ariaLabel: el.getAttribute && el.getAttribute('aria-label') || null,
|
|
55
|
+
href: el.tagName === 'A' ? el.getAttribute('href') : null,
|
|
56
|
+
selector: selectorFor(el),
|
|
57
|
+
};
|
|
58
|
+
} catch (e) { return {}; }
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function emit(type, payload) {
|
|
62
|
+
try {
|
|
63
|
+
console.log(SENTINEL, type, JSON.stringify(payload));
|
|
64
|
+
} catch (e) { /* ignore */ }
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function onClick(ev) {
|
|
68
|
+
try {
|
|
69
|
+
const tgt = ev.target;
|
|
70
|
+
emit('click', describe(tgt));
|
|
71
|
+
} catch (e) { /* ignore */ }
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function onInput(ev) {
|
|
75
|
+
try {
|
|
76
|
+
const tgt = ev.target;
|
|
77
|
+
// For inputs we capture the field name + a TRUNCATED value preview.
|
|
78
|
+
// Sensitive fields (type=password) get value redacted.
|
|
79
|
+
const desc = describe(tgt);
|
|
80
|
+
if (desc.type === 'password') {
|
|
81
|
+
desc.value = '[redacted password]';
|
|
82
|
+
} else if (tgt && 'value' in tgt) {
|
|
83
|
+
desc.value = safeStr(tgt.value);
|
|
84
|
+
}
|
|
85
|
+
emit('input', desc);
|
|
86
|
+
} catch (e) { /* ignore */ }
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function onChange(ev) {
|
|
90
|
+
try {
|
|
91
|
+
const tgt = ev.target;
|
|
92
|
+
const desc = describe(tgt);
|
|
93
|
+
if (desc.type === 'password') {
|
|
94
|
+
desc.value = '[redacted password]';
|
|
95
|
+
} else if (tgt && 'value' in tgt) {
|
|
96
|
+
desc.value = safeStr(tgt.value);
|
|
97
|
+
}
|
|
98
|
+
emit('change', desc);
|
|
99
|
+
} catch (e) { /* ignore */ }
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function onSubmit(ev) {
|
|
103
|
+
try {
|
|
104
|
+
const form = ev.target;
|
|
105
|
+
const fields = [];
|
|
106
|
+
if (form && form.elements) {
|
|
107
|
+
for (let i = 0; i < form.elements.length; i++) {
|
|
108
|
+
const el = form.elements[i];
|
|
109
|
+
if (!el || !el.name) continue;
|
|
110
|
+
let value = null;
|
|
111
|
+
if (el.type === 'password') {
|
|
112
|
+
value = '[redacted]';
|
|
113
|
+
} else if ('value' in el) {
|
|
114
|
+
value = safeStr(el.value);
|
|
115
|
+
}
|
|
116
|
+
fields.push({ name: el.name, type: el.type || null, value: value });
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
emit('submit', {
|
|
120
|
+
selector: selectorFor(form),
|
|
121
|
+
action: form && form.getAttribute && form.getAttribute('action') || null,
|
|
122
|
+
method: form && form.getAttribute && form.getAttribute('method') || 'get',
|
|
123
|
+
fields: fields,
|
|
124
|
+
});
|
|
125
|
+
} catch (e) { /* ignore */ }
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// Capture phase = true so we see the event before the site has a chance to
|
|
129
|
+
// stopPropagation it.
|
|
130
|
+
document.addEventListener('click', onClick, true);
|
|
131
|
+
document.addEventListener('input', onInput, true);
|
|
132
|
+
document.addEventListener('change', onChange, true);
|
|
133
|
+
document.addEventListener('submit', onSubmit, true);
|
|
134
|
+
})();
|
|
135
|
+
`;
|
|
136
|
+
|
|
137
|
+
export const IMPRINT_SENTINEL = '[IMPRINT]';
|