autokap 1.0.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.
Files changed (93) hide show
  1. package/assets/chrome/ios-statusbar-comparison-reference.jpg +0 -0
  2. package/assets/chrome/ios-statusbar-dark-reference.jpg +0 -0
  3. package/assets/chrome/ios-statusbar-light-reference.jpg +0 -0
  4. package/assets/devices/ipad-pro-11-m4.json +52 -0
  5. package/assets/devices/iphone-16-pro.json +53 -0
  6. package/assets/devices/macbook-air-13.json +45 -0
  7. package/assets/frames/MacBook Air 13.svg +242 -0
  8. package/assets/frames/Status bar - iPhone.png +0 -0
  9. Menu bar- iPad.png +0 -0
  10. package/assets/frames/iPad Pro M4 11_.png +0 -0
  11. package/assets/frames/iPhone 16 Pro.png +0 -0
  12. package/assets/icons/Cellular Connection.svg +3 -0
  13. package/assets/icons/Union.svg +6 -0
  14. package/assets/icons/Wifi.svg +3 -0
  15. package/assets/icons/battery.svg +5 -0
  16. package/assets/icons/battery_charging.svg +8 -0
  17. package/assets/skill/SKILL.md +575 -0
  18. package/dist/abort.d.ts +5 -0
  19. package/dist/abort.js +44 -0
  20. package/dist/agent.d.ts +142 -0
  21. package/dist/agent.js +4504 -0
  22. package/dist/browser-bar.d.ts +40 -0
  23. package/dist/browser-bar.js +147 -0
  24. package/dist/browser-pool.d.ts +34 -0
  25. package/dist/browser-pool.js +122 -0
  26. package/dist/browser.d.ts +279 -0
  27. package/dist/browser.js +2902 -0
  28. package/dist/cli-utils.d.ts +25 -0
  29. package/dist/cli-utils.js +80 -0
  30. package/dist/cli.d.ts +4 -0
  31. package/dist/cli.js +365 -0
  32. package/dist/clip-orchestrator.d.ts +148 -0
  33. package/dist/clip-orchestrator.js +950 -0
  34. package/dist/clip-postprocess.d.ts +42 -0
  35. package/dist/clip-postprocess.js +192 -0
  36. package/dist/cookie-dismiss.d.ts +5 -0
  37. package/dist/cookie-dismiss.js +172 -0
  38. package/dist/credential-templates.d.ts +5 -0
  39. package/dist/credential-templates.js +60 -0
  40. package/dist/element-capture.d.ts +53 -0
  41. package/dist/element-capture.js +766 -0
  42. package/dist/hybrid-navigator.d.ts +138 -0
  43. package/dist/hybrid-navigator.js +468 -0
  44. package/dist/index.d.ts +15 -0
  45. package/dist/index.js +11 -0
  46. package/dist/llm-usage.d.ts +17 -0
  47. package/dist/llm-usage.js +45 -0
  48. package/dist/logger.d.ts +46 -0
  49. package/dist/logger.js +79 -0
  50. package/dist/mockup-html.d.ts +119 -0
  51. package/dist/mockup-html.js +253 -0
  52. package/dist/mockup.d.ts +94 -0
  53. package/dist/mockup.js +604 -0
  54. package/dist/mouse-animation.d.ts +46 -0
  55. package/dist/mouse-animation.js +100 -0
  56. package/dist/overlay-utils.d.ts +14 -0
  57. package/dist/overlay-utils.js +13 -0
  58. package/dist/posthog.d.ts +4 -0
  59. package/dist/posthog.js +26 -0
  60. package/dist/prompt-cache.d.ts +10 -0
  61. package/dist/prompt-cache.js +24 -0
  62. package/dist/prompts.d.ts +167 -0
  63. package/dist/prompts.js +1165 -0
  64. package/dist/security.d.ts +20 -0
  65. package/dist/security.js +569 -0
  66. package/dist/session-profile.d.ts +86 -0
  67. package/dist/session-profile.js +1471 -0
  68. package/dist/sf-pro-fonts.d.ts +4 -0
  69. package/dist/sf-pro-fonts.js +7 -0
  70. package/dist/status-bar-l10n.d.ts +14 -0
  71. package/dist/status-bar-l10n.js +177 -0
  72. package/dist/status-bar.d.ts +44 -0
  73. package/dist/status-bar.js +336 -0
  74. package/dist/tools.d.ts +4 -0
  75. package/dist/tools.js +578 -0
  76. package/dist/types.d.ts +796 -0
  77. package/dist/types.js +2 -0
  78. package/dist/video-agent.d.ts +143 -0
  79. package/dist/video-agent.js +4783 -0
  80. package/dist/video-observation.d.ts +36 -0
  81. package/dist/video-observation.js +192 -0
  82. package/dist/video-planner.d.ts +12 -0
  83. package/dist/video-planner.js +500 -0
  84. package/dist/video-prompts.d.ts +37 -0
  85. package/dist/video-prompts.js +554 -0
  86. package/dist/video-tools.d.ts +3 -0
  87. package/dist/video-tools.js +59 -0
  88. package/dist/video-variant-state.d.ts +29 -0
  89. package/dist/video-variant-state.js +80 -0
  90. package/dist/vision-model.d.ts +17 -0
  91. package/dist/vision-model.js +74 -0
  92. package/package.json +165 -0
  93. package/readme.md +61 -0
@@ -0,0 +1,20 @@
1
+ import type { ActionType, AgentConfig, CaptureObjective, CaptureRepairCause, InteractiveElement } from './types.js';
2
+ export interface SecurityContext {
3
+ rootUrl?: string;
4
+ currentUrl?: string;
5
+ credentials?: AgentConfig['credentials'];
6
+ interactiveElements: InteractiveElement[];
7
+ currentLang?: string;
8
+ currentTheme?: 'light' | 'dark';
9
+ runMode?: AgentConfig['runMode'];
10
+ currentObjective?: CaptureObjective;
11
+ activeRepairCause?: CaptureRepairCause | null;
12
+ }
13
+ export interface SecurityDecision {
14
+ allowed: boolean;
15
+ reason?: string;
16
+ target?: InteractiveElement | null;
17
+ }
18
+ export declare function evaluateResolvedActionSecurity(action: ActionType, args: Record<string, unknown>, context: SecurityContext, target: InteractiveElement | null): SecurityDecision;
19
+ export declare function evaluateActionSecurity(action: ActionType, args: Record<string, unknown>, context: SecurityContext): SecurityDecision;
20
+ export declare function describeSecurityTarget(target: InteractiveElement | null | undefined): string;
@@ -0,0 +1,569 @@
1
+ const AUTH_URL_RE = /\b(login|log-in|signin|sign-in|auth|session|password|otp|verify|2fa|connexion|connect|identif|acceder|anmeldung|beta|invite|access|early-access|waitlist)\b/i;
2
+ const AUTH_ACTION_RE = /\b(log ?in|sign ?in|se connecter|connexion|continue|continuer|next|suivant|verify|verifier|submit|soumettre|use email|email|e-mail|password|mot de passe|otp|code|enter|entrer|unlock|access|acceder)\b/i;
3
+ const AUTH_ENTRY_RE = /\b(log ?in|sign ?in|se connecter|connexion|continue with email|continuer avec l[‘’]?e-?mail|sign ?in with email|log ?in with email|connexion par email|use email|use password)\b/i;
4
+ const AUTH_SUBMIT_RE = /\b(continue|continuer|next|suivant|verify|verifier|submit|soumettre|otp|code|unlock|access|acceder|enter|entrer)\b/i;
5
+ const AUTH_FIELD_RE = /\b(email|e-mail|username|user name|identifiant|login|mot de passe|password|passcode|otp|verification code|code|beta|invite|invitation|access.?code|access.?key|cl[eé] d’acc[eè]s|code d’acc[eè]s|code d’invitation)\b/i;
6
+ const SEARCH_FIELD_RE = /\b(search|recherche|filter|filtre|query|keyword|find|lookup)\b/i;
7
+ const TYPE_BLOCK_RE = /\b(comment|message|reply|chat|prompt|post|tweet|caption|description|bio|status|content|body)\b/i;
8
+ const DANGEROUS_RE = /\b(delete|remove|destroy|deactivate|disable|disconnect|unlink|revoke|regenerate|archive|trash|wipe|terminate|logout|log out|sign out|buy|purchase|checkout|pay|order|book|subscribe|upgrade|acheter|payer|checkout|commander|supprimer|effacer|desactiver|deconnecter|cancel subscription|close account|delete account)\b/i;
9
+ const MUTATING_RE = /\b(save|submit|publish|post|send|reply|comment|share|invite|upload|import|apply|approve|merge|sync|commit|confirm|validate|soumettre|valider|envoyer|publier|partager)\b/i;
10
+ const CREATIVE_RE = /\b(generate|continue with ai|ai generate|auto.?generate|compose|draft|write with ai|produce|fabricate|synthesize|g[eé]n[eé]rer avec|cr[eé]er avec l['']?ia|r[eé]diger|continuer avec l['']?ia|continuer avec ai)\b/i;
11
+ const INPUT_FOCUS_RE = /\b(email|e-mail|username|user name|identifiant|login|mot de passe|password|passcode|otp|verification code|code|beta|invite|invitation|access.?code|access.?key|search|recherche|filter|filtre|query|keyword|find|lookup)\b/i;
12
+ const TOGGLE_ROLE_RE = /\b(switch|checkbox|radio|menuitemcheckbox|menuitemradio)\b/i;
13
+ const BUTTON_ROLE_RE = /\b(button)\b/i;
14
+ const EXPAND_TRIGGER_RE = /\b(menu|navigation|nav|more|show|open|expand|details|accordion|filter|filters|sort|language|locale|theme|account|profile|settings|categories|sections|faq|plans|pricing)\b/i;
15
+ const LANGUAGE_CONTROL_RE = /\b(lang|locale|language|langue|idioma|sprache|lingua|hreflang)\b/i;
16
+ const THEME_CONTROL_RE = /\b(theme|appearance|mode|dark|light|clair|sombre|oscuro|claro|dunkel|hell|color.?scheme)\b/i;
17
+ const SHARED_HOSTING_SUFFIXES = [
18
+ 'github.io',
19
+ 'vercel.app',
20
+ 'netlify.app',
21
+ 'pages.dev',
22
+ 'workers.dev',
23
+ 'surge.sh',
24
+ 'web.app',
25
+ 'firebaseapp.com',
26
+ 'herokuapp.com',
27
+ 'onrender.com',
28
+ 'azurewebsites.net',
29
+ 'cloudfront.net',
30
+ 'appspot.com',
31
+ ];
32
+ const ALLOWED_KEYS = new Set([
33
+ 'Escape',
34
+ 'Tab',
35
+ 'ArrowUp',
36
+ 'ArrowDown',
37
+ 'ArrowLeft',
38
+ 'ArrowRight',
39
+ 'PageUp',
40
+ 'PageDown',
41
+ 'Home',
42
+ 'End',
43
+ ]);
44
+ const MAX_TYPED_TEXT_LENGTH = 256;
45
+ const LANGUAGE_ALIASES = [
46
+ 'ar', 'arabic', 'arab', 'العربية',
47
+ 'cs', 'czech', 'cesky', 'čeština',
48
+ 'da', 'danish', 'dansk',
49
+ 'de', 'german', 'deutsch',
50
+ 'en', 'english',
51
+ 'es', 'spanish', 'espanol', 'español',
52
+ 'fi', 'finnish', 'suomi',
53
+ 'fr', 'french', 'francais', 'français',
54
+ 'hu', 'hungarian', 'magyar',
55
+ 'it', 'italian', 'italiano',
56
+ 'ja', 'japanese', 'japonais', '日本語',
57
+ 'ko', 'korean', '한국어',
58
+ 'nl', 'dutch', 'nederlands',
59
+ 'norwegian', 'norsk',
60
+ 'pl', 'polish', 'polski',
61
+ 'pt', 'portuguese', 'portugues', 'português',
62
+ 'ru', 'russian', 'русский',
63
+ 'sv', 'swedish', 'svenska',
64
+ 'tr', 'turkish', 'turkce', 'türkçe',
65
+ 'zh', 'chinese', '中文',
66
+ ];
67
+ const THEME_ALIASES = ['light', 'dark', 'clair', 'sombre', 'claro', 'oscuro', 'hell', 'dunkel'];
68
+ function parseHttpUrl(value) {
69
+ if (!value)
70
+ return null;
71
+ try {
72
+ const parsed = new URL(value);
73
+ if (!['http:', 'https:'].includes(parsed.protocol))
74
+ return null;
75
+ return parsed;
76
+ }
77
+ catch {
78
+ return null;
79
+ }
80
+ }
81
+ function effectivePort(url) {
82
+ if (url.port)
83
+ return url.port;
84
+ return url.protocol === 'https:' ? '443' : '80';
85
+ }
86
+ function isIpv4Hostname(hostname) {
87
+ return /^\d{1,3}(?:\.\d{1,3}){3}$/.test(hostname);
88
+ }
89
+ function isIpv6Hostname(hostname) {
90
+ return hostname.includes(':');
91
+ }
92
+ function isLocalHostname(hostname) {
93
+ if (hostname === 'localhost' || hostname === '::1')
94
+ return true;
95
+ if (hostname.endsWith('.localhost'))
96
+ return true;
97
+ if (!isIpv4Hostname(hostname))
98
+ return false;
99
+ const [a, b] = hostname.split('.').map(Number);
100
+ return a === 127
101
+ || a === 10
102
+ || (a === 192 && b === 168)
103
+ || (a === 172 && b >= 16 && b <= 31);
104
+ }
105
+ function isSharedHostingHostname(hostname) {
106
+ return SHARED_HOSTING_SUFFIXES.some(suffix => hostname === suffix || hostname.endsWith(`.${suffix}`));
107
+ }
108
+ function normalizeScopeHostname(hostname) {
109
+ return hostname.startsWith('www.') ? hostname.slice(4) : hostname;
110
+ }
111
+ function buildNavigationScopes(context) {
112
+ const sources = [
113
+ context.rootUrl,
114
+ context.credentials?.loginUrl,
115
+ ];
116
+ const dedup = new Map();
117
+ for (const value of sources) {
118
+ const parsed = parseHttpUrl(value);
119
+ if (!parsed)
120
+ continue;
121
+ const hostname = parsed.hostname.toLowerCase();
122
+ const exactHost = isLocalHostname(hostname) || isIpv4Hostname(hostname) || isIpv6Hostname(hostname) || isSharedHostingHostname(hostname);
123
+ const scopeHostname = exactHost ? hostname : normalizeScopeHostname(hostname);
124
+ const scope = {
125
+ hostname: scopeHostname,
126
+ port: effectivePort(parsed),
127
+ exactHost,
128
+ };
129
+ dedup.set(`${scope.hostname}:${scope.port}:${scope.exactHost ? 'exact' : 'family'}`, scope);
130
+ }
131
+ return Array.from(dedup.values());
132
+ }
133
+ function isWithinProjectScope(candidate, scopes) {
134
+ const hostname = candidate.hostname.toLowerCase();
135
+ const port = effectivePort(candidate);
136
+ return scopes.some(scope => {
137
+ if (scope.exactHost) {
138
+ return hostname === scope.hostname && port === scope.port;
139
+ }
140
+ return (hostname === scope.hostname || hostname.endsWith(`.${scope.hostname}`));
141
+ });
142
+ }
143
+ function combineElementText(el) {
144
+ return [el.text, el.ariaLabel, el.href, el.inputType, el.role]
145
+ .filter(Boolean)
146
+ .join(' ')
147
+ .toLowerCase();
148
+ }
149
+ function normalizeVariantText(value) {
150
+ return (value ?? '')
151
+ .normalize('NFD')
152
+ .replace(/[\u0300-\u036f]/g, '')
153
+ .toLowerCase();
154
+ }
155
+ function escapeRegExp(value) {
156
+ return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
157
+ }
158
+ function textContainsAlias(haystack, alias) {
159
+ const normalizedAlias = normalizeVariantText(alias);
160
+ if (!normalizedAlias)
161
+ return false;
162
+ if (/^[a-z]{2,3}$/.test(normalizedAlias)) {
163
+ return new RegExp(`(^|[^a-z])${escapeRegExp(normalizedAlias)}([^a-z]|$)`, 'i').test(haystack);
164
+ }
165
+ return haystack.includes(normalizedAlias);
166
+ }
167
+ function collectVariantTargetText(target) {
168
+ return normalizeVariantText([
169
+ target.text,
170
+ target.ariaLabel,
171
+ target.href,
172
+ target.selector,
173
+ target.role,
174
+ target.inputType,
175
+ ]
176
+ .filter(Boolean)
177
+ .join(' '));
178
+ }
179
+ function hasVariantAdjustmentContext(context) {
180
+ return context.runMode === 'language_preflight'
181
+ || context.currentObjective === 'repair'
182
+ || context.activeRepairCause === 'lang'
183
+ || context.activeRepairCause === 'theme'
184
+ || !!context.currentLang
185
+ || !!context.currentTheme;
186
+ }
187
+ function looksLikeLanguageControlTarget(target, context) {
188
+ const text = collectVariantTargetText(target);
189
+ if (!text)
190
+ return false;
191
+ if (LANGUAGE_CONTROL_RE.test(text))
192
+ return true;
193
+ if (/\b(data-lang|data-locale|lang=|locale=|hreflang)\b/i.test(text))
194
+ return true;
195
+ if (context.currentLang && textContainsAlias(text, context.currentLang))
196
+ return true;
197
+ return LANGUAGE_ALIASES.some(alias => textContainsAlias(text, alias));
198
+ }
199
+ function looksLikeThemeControlTarget(target, context) {
200
+ const text = collectVariantTargetText(target);
201
+ if (!text)
202
+ return false;
203
+ if (THEME_CONTROL_RE.test(text))
204
+ return true;
205
+ if (/\b(data-theme|theme=|color-scheme)\b/i.test(text))
206
+ return true;
207
+ if (context.currentTheme && textContainsAlias(text, context.currentTheme))
208
+ return true;
209
+ return THEME_ALIASES.some(alias => textContainsAlias(text, alias));
210
+ }
211
+ function isSafeVariantToggleTarget(target, context) {
212
+ if (!hasVariantAdjustmentContext(context))
213
+ return false;
214
+ return looksLikeLanguageControlTarget(target, context)
215
+ || looksLikeThemeControlTarget(target, context);
216
+ }
217
+ function inferAuthContext(context) {
218
+ // Objective check 1: URL explicitly indicates an auth flow
219
+ const currentUrl = (context.currentUrl || '').toLowerCase();
220
+ if (AUTH_URL_RE.test(currentUrl))
221
+ return true;
222
+ // Objective check 2: page has a password input field — strong auth signal
223
+ const hasPasswordField = context.interactiveElements.some(el => el.inputType === 'password');
224
+ if (hasPasswordField)
225
+ return true;
226
+ // Auth field/action text matching is only trusted when combined with
227
+ // a password field or auth URL. A standalone email field could be
228
+ // a newsletter signup, not an auth form.
229
+ // The gate pattern (few text inputs, no textarea) is removed — it was
230
+ // too broad and could match contact forms, search pages, or settings.
231
+ return false;
232
+ }
233
+ function isAllowedNavigation(candidateUrl, context) {
234
+ try {
235
+ const base = context.currentUrl || context.rootUrl || 'https://example.com';
236
+ const parsed = new URL(candidateUrl, base);
237
+ if (!['http:', 'https:'].includes(parsed.protocol))
238
+ return false;
239
+ const scopes = buildNavigationScopes(context);
240
+ if (scopes.length === 0)
241
+ return false;
242
+ return isWithinProjectScope(parsed, scopes);
243
+ }
244
+ catch {
245
+ return false;
246
+ }
247
+ }
248
+ function resolveTargetFromAction(args, interactiveElements) {
249
+ if (args.index !== undefined) {
250
+ return interactiveElements.find(el => el.index === args.index) || null;
251
+ }
252
+ if (args.selector) {
253
+ return interactiveElements.find(el => el.selector === args.selector) || null;
254
+ }
255
+ if (args.x !== undefined && args.y !== undefined) {
256
+ const x = args.x;
257
+ const y = args.y;
258
+ return interactiveElements.find(el => {
259
+ const bb = el.boundingBox;
260
+ return !!bb && x >= bb.x && x <= bb.x + bb.width && y >= bb.y && y <= bb.y + bb.height;
261
+ }) || null;
262
+ }
263
+ return null;
264
+ }
265
+ function isButtonLike(target) {
266
+ return target.tag === 'button'
267
+ || BUTTON_ROLE_RE.test(target.role)
268
+ || ['button', 'submit', 'reset', 'image'].includes(target.inputType || '');
269
+ }
270
+ function isToggleLike(target) {
271
+ return TOGGLE_ROLE_RE.test(target.role)
272
+ || ['checkbox', 'radio'].includes(target.inputType || '');
273
+ }
274
+ function isTextEntryTarget(target) {
275
+ if (target.tag === 'textarea')
276
+ return true;
277
+ if (target.tag === 'input') {
278
+ const type = (target.inputType || 'text').toLowerCase();
279
+ return !['submit', 'button', 'checkbox', 'radio', 'file', 'hidden', 'image', 'reset'].includes(type);
280
+ }
281
+ return INPUT_FOCUS_RE.test(combineElementText(target));
282
+ }
283
+ function isSelectLikeTarget(target) {
284
+ return target.tag === 'select'
285
+ || target.inputType === 'select'
286
+ || /\b(combobox|listbox)\b/i.test(target.role);
287
+ }
288
+ function isDisclosureLikeTarget(target) {
289
+ return target.tag === 'summary'
290
+ || (target.ariaExpanded !== undefined && target.ariaExpanded !== null)
291
+ || !!target.ariaControls
292
+ || !!target.ariaHasPopup;
293
+ }
294
+ function evaluateClickTarget(target, context) {
295
+ const text = combineElementText(target);
296
+ const authContext = inferAuthContext(context);
297
+ const hasLabel = !!(target.text || target.ariaLabel || target.href);
298
+ if (target.inputType === 'submit' && !authContext) {
299
+ return { allowed: false, reason: 'Blocked by security policy: submit buttons are only allowed during authentication.', target };
300
+ }
301
+ if (target.inputType === 'file') {
302
+ return { allowed: false, reason: 'Blocked by security policy: file upload controls are not allowed.', target };
303
+ }
304
+ if (target.href && !isAllowedNavigation(target.href, context)) {
305
+ return { allowed: false, reason: 'Blocked by security policy: external navigation is not allowed.', target };
306
+ }
307
+ if (DANGEROUS_RE.test(text)) {
308
+ return { allowed: false, reason: 'Blocked by security policy: destructive, financial, logout, or account-changing action.', target };
309
+ }
310
+ if (CREATIVE_RE.test(text)) {
311
+ return { allowed: false, reason: 'Blocked by security policy: creative/generative action that would create or modify content via AI.', target };
312
+ }
313
+ if (MUTATING_RE.test(text)) {
314
+ if (authContext && AUTH_ACTION_RE.test(text)) {
315
+ return { allowed: true, target };
316
+ }
317
+ return { allowed: false, reason: 'Blocked by security policy: content creation/update or form submission action.', target };
318
+ }
319
+ if (isToggleLike(target)) {
320
+ if (isSafeVariantToggleTarget(target, context)) {
321
+ return { allowed: true, target };
322
+ }
323
+ return { allowed: false, reason: 'Blocked by security policy: toggles, checkboxes, and radio controls are not allowed.', target };
324
+ }
325
+ const isSubmitLike = target.inputType === 'submit'
326
+ || target.tag === 'button'
327
+ || target.role.includes('button');
328
+ if (isSubmitLike && AUTH_ENTRY_RE.test(text)) {
329
+ return { allowed: true, target };
330
+ }
331
+ if (isSubmitLike && AUTH_SUBMIT_RE.test(text) && !authContext) {
332
+ return { allowed: false, reason: 'Blocked by security policy: submit-like action outside authentication flow.', target };
333
+ }
334
+ if (isTextEntryTarget(target)) {
335
+ return { allowed: true, target };
336
+ }
337
+ if (target.href) {
338
+ return { allowed: true, target };
339
+ }
340
+ // Unlabeled buttons are allowed — blocking them prevents the agent from
341
+ // clicking close/dismiss/icon buttons (×) which are safe UI operations.
342
+ // Dangerous actions are already caught by DANGEROUS_RE, MUTATING_RE, and CREATIVE_RE
343
+ // checks above, which match on text content. An unlabeled button has no text to match,
344
+ // so it cannot be a known dangerous action.
345
+ return { allowed: true, target };
346
+ }
347
+ function evaluateTypingTarget(target, typedText, context) {
348
+ const text = combineElementText(target);
349
+ const authContext = inferAuthContext(context);
350
+ if (typedText.length > MAX_TYPED_TEXT_LENGTH) {
351
+ return { allowed: false, reason: `Blocked by security policy: typed text exceeds ${MAX_TYPED_TEXT_LENGTH} characters.`, target };
352
+ }
353
+ if (/[\r\n]/.test(typedText)) {
354
+ return { allowed: false, reason: 'Blocked by security policy: multi-line text entry is not allowed.', target };
355
+ }
356
+ if (target.tag === 'textarea') {
357
+ return { allowed: false, reason: 'Blocked by security policy: typing into textareas is not allowed.', target };
358
+ }
359
+ if (['submit', 'button', 'checkbox', 'radio', 'file', 'hidden'].includes(target.inputType || '')) {
360
+ return { allowed: false, reason: 'Blocked by security policy: target field is not a safe text input.', target };
361
+ }
362
+ if (TYPE_BLOCK_RE.test(text)) {
363
+ return { allowed: false, reason: 'Blocked by security policy: content-composition field.', target };
364
+ }
365
+ if (authContext) {
366
+ return { allowed: true, target };
367
+ }
368
+ if (target.inputType === 'search' || SEARCH_FIELD_RE.test(text)) {
369
+ return { allowed: true, target };
370
+ }
371
+ // Default: allow typing into text-like inputs that passed all safety checks above
372
+ // (not a textarea, not a content-composition field, not a non-text input type)
373
+ return { allowed: true, target };
374
+ }
375
+ export function evaluateResolvedActionSecurity(action, args, context, target) {
376
+ if (action === 'click' || action === 'hover') {
377
+ if (!target)
378
+ return { allowed: false, reason: 'Blocked by security policy: unresolved target.' };
379
+ return evaluateClickTarget(target, context);
380
+ }
381
+ if (action === 'type_text') {
382
+ if (!target) {
383
+ return {
384
+ allowed: false,
385
+ reason: 'Blocked by security policy: free-form typing without a known target input is not allowed.',
386
+ };
387
+ }
388
+ return evaluateTypingTarget(target, String(args.text || ''), context);
389
+ }
390
+ if (action === 'select_option') {
391
+ if (!target) {
392
+ return {
393
+ allowed: false,
394
+ reason: 'Blocked by security policy: selecting an option requires a known target control.',
395
+ };
396
+ }
397
+ if (!isSelectLikeTarget(target)) {
398
+ return {
399
+ allowed: false,
400
+ reason: 'Blocked by security policy: target control is not a select-like input.',
401
+ target,
402
+ };
403
+ }
404
+ const optionText = [
405
+ args.optionLabel,
406
+ args.optionValue,
407
+ typeof args.optionIndex === 'number' ? String(args.optionIndex) : '',
408
+ ]
409
+ .filter(Boolean)
410
+ .join(' ')
411
+ .toLowerCase();
412
+ if (!optionText) {
413
+ return {
414
+ allowed: false,
415
+ reason: 'Blocked by security policy: no dropdown option was specified.',
416
+ target,
417
+ };
418
+ }
419
+ if (optionText.length > MAX_TYPED_TEXT_LENGTH) {
420
+ return {
421
+ allowed: false,
422
+ reason: `Blocked by security policy: option text exceeds ${MAX_TYPED_TEXT_LENGTH} characters.`,
423
+ target,
424
+ };
425
+ }
426
+ if (DANGEROUS_RE.test(optionText) || MUTATING_RE.test(optionText)) {
427
+ return {
428
+ allowed: false,
429
+ reason: 'Blocked by security policy: selected option looks high-risk or mutating.',
430
+ target,
431
+ };
432
+ }
433
+ return { allowed: true, target };
434
+ }
435
+ if (action === 'safe_expand') {
436
+ if (!target) {
437
+ return {
438
+ allowed: false,
439
+ reason: 'Blocked by security policy: expanding a panel requires a known target control.',
440
+ };
441
+ }
442
+ const text = combineElementText(target);
443
+ const disclosureLike = isDisclosureLikeTarget(target);
444
+ const labeled = !!(target.text || target.ariaLabel);
445
+ if (target.href) {
446
+ return {
447
+ allowed: false,
448
+ reason: 'Blocked by security policy: safe_expand cannot be used on navigation links.',
449
+ target,
450
+ };
451
+ }
452
+ if (target.inputType === 'file') {
453
+ return {
454
+ allowed: false,
455
+ reason: 'Blocked by security policy: file upload controls are not allowed.',
456
+ target,
457
+ };
458
+ }
459
+ if (target.inputType === 'submit') {
460
+ return {
461
+ allowed: false,
462
+ reason: 'Blocked by security policy: submit controls cannot be used for safe_expand.',
463
+ target,
464
+ };
465
+ }
466
+ if (isToggleLike(target)) {
467
+ return {
468
+ allowed: false,
469
+ reason: 'Blocked by security policy: toggles, checkboxes, and radio controls are not allowed.',
470
+ target,
471
+ };
472
+ }
473
+ if (DANGEROUS_RE.test(text) || MUTATING_RE.test(text)) {
474
+ return {
475
+ allowed: false,
476
+ reason: 'Blocked by security policy: disclosure trigger looks mutating or high-risk.',
477
+ target,
478
+ };
479
+ }
480
+ if (!isButtonLike(target) && target.tag !== 'summary' && !disclosureLike) {
481
+ return {
482
+ allowed: false,
483
+ reason: 'Blocked by security policy: target is not a disclosure-like control.',
484
+ target,
485
+ };
486
+ }
487
+ if (!disclosureLike && !EXPAND_TRIGGER_RE.test(text)) {
488
+ return {
489
+ allowed: false,
490
+ reason: 'Blocked by security policy: target does not look like a menu or disclosure trigger.',
491
+ target,
492
+ };
493
+ }
494
+ if (!labeled && !disclosureLike) {
495
+ return {
496
+ allowed: false,
497
+ reason: 'Blocked by security policy: unlabeled disclosure trigger is not allowed.',
498
+ target,
499
+ };
500
+ }
501
+ return { allowed: true, target };
502
+ }
503
+ return { allowed: true, target };
504
+ }
505
+ export function evaluateActionSecurity(action, args, context) {
506
+ if (action === 'navigate_to') {
507
+ const url = args.url;
508
+ if (!url)
509
+ return { allowed: false, reason: 'Blocked by security policy: navigation target missing.' };
510
+ if (!isAllowedNavigation(url, context)) {
511
+ return { allowed: false, reason: 'Blocked by security policy: navigation outside the allowed site scope.' };
512
+ }
513
+ if (DANGEROUS_RE.test(url) || MUTATING_RE.test(url)) {
514
+ return { allowed: false, reason: 'Blocked by security policy: high-risk destination URL.' };
515
+ }
516
+ return { allowed: true };
517
+ }
518
+ if (action === 'press_key') {
519
+ const key = String(args.key || '');
520
+ if (ALLOWED_KEYS.has(key))
521
+ return { allowed: true };
522
+ if (key === 'Enter' && inferAuthContext(context))
523
+ return { allowed: true };
524
+ return { allowed: false, reason: `Blocked by security policy: keyboard key "${key}" is not allowed.` };
525
+ }
526
+ if (action === 'click') {
527
+ const target = resolveTargetFromAction(args, context.interactiveElements);
528
+ if ((args.selector || (args.x !== undefined && args.y !== undefined)) && !target) {
529
+ return {
530
+ allowed: false,
531
+ reason: 'Blocked by security policy: unresolved selector/coordinates are not allowed. Use a known interactive element.',
532
+ };
533
+ }
534
+ if (!target)
535
+ return { allowed: true };
536
+ return evaluateResolvedActionSecurity(action, args, context, target);
537
+ }
538
+ if (action === 'hover') {
539
+ const target = resolveTargetFromAction(args, context.interactiveElements);
540
+ if ((args.selector || (args.x !== undefined && args.y !== undefined)) && !target) {
541
+ return {
542
+ allowed: false,
543
+ reason: 'Blocked by security policy: unresolved selector/coordinates are not allowed. Use a known interactive element.',
544
+ };
545
+ }
546
+ if (!target)
547
+ return { allowed: true };
548
+ return evaluateResolvedActionSecurity(action, args, context, target);
549
+ }
550
+ if (action === 'type_text') {
551
+ const target = resolveTargetFromAction(args, context.interactiveElements);
552
+ return evaluateResolvedActionSecurity(action, args, context, target);
553
+ }
554
+ if (action === 'select_option') {
555
+ const target = resolveTargetFromAction(args, context.interactiveElements);
556
+ return evaluateResolvedActionSecurity(action, args, context, target);
557
+ }
558
+ if (action === 'safe_expand') {
559
+ const target = resolveTargetFromAction(args, context.interactiveElements);
560
+ return evaluateResolvedActionSecurity(action, args, context, target);
561
+ }
562
+ return { allowed: true };
563
+ }
564
+ export function describeSecurityTarget(target) {
565
+ if (!target)
566
+ return 'target element';
567
+ return target.text || target.ariaLabel || target.href || target.inputType || target.tag;
568
+ }
569
+ //# sourceMappingURL=security.js.map
@@ -0,0 +1,86 @@
1
+ import { Browser } from './browser.js';
2
+ import type { AgentContextEntry, AgentRunHint, LoginCredentials, SessionProfileAuthState, SessionValidationDiagnostic, SessionValidationDiagnosticConfidence, SessionProfileValidationStatus, ValidatedSessionProfile, VideoPageSignals } from './types.js';
3
+ export interface SessionValidationResult {
4
+ validationStatus: Exclude<SessionProfileValidationStatus, 'repaired'>;
5
+ authState: SessionProfileAuthState;
6
+ authMatches: boolean;
7
+ langMatches: boolean | null;
8
+ themeMatches: boolean | null;
9
+ urlMatches: boolean;
10
+ authDiagnostic: SessionValidationDiagnostic;
11
+ langDiagnostic: SessionValidationDiagnostic;
12
+ themeDiagnostic: SessionValidationDiagnostic;
13
+ urlDiagnostic: SessionValidationDiagnostic;
14
+ detectedLang: string | null;
15
+ detectedTheme: 'light' | 'dark' | null;
16
+ accountLabel: string | null;
17
+ issues: Array<'auth' | 'lang' | 'theme' | 'url'>;
18
+ summary: string;
19
+ }
20
+ export interface RequestedLanguageState {
21
+ requested: string | null;
22
+ detected: string | null;
23
+ active: boolean;
24
+ ambiguous: boolean;
25
+ confidence: SessionValidationDiagnosticConfidence;
26
+ reasons: string[];
27
+ }
28
+ export interface RequestedThemeState {
29
+ requested: 'light' | 'dark' | null;
30
+ detected: 'light' | 'dark' | null;
31
+ active: boolean;
32
+ ambiguous: boolean;
33
+ confidence: SessionValidationDiagnosticConfidence;
34
+ reasons: string[];
35
+ }
36
+ export interface DeterministicSelectorUpdate {
37
+ stepSignature: string;
38
+ selector: string;
39
+ source: 'deterministic';
40
+ success: boolean;
41
+ }
42
+ export declare function evaluateRequestedLanguageState(params: {
43
+ currentUrl: string;
44
+ requestedLang?: string;
45
+ signals: VideoPageSignals;
46
+ }): RequestedLanguageState;
47
+ export declare function evaluateRequestedThemeState(params: {
48
+ requestedTheme?: 'light' | 'dark';
49
+ signals: VideoPageSignals;
50
+ }): RequestedThemeState;
51
+ export declare function buildAgentRunHints(entries: AgentContextEntry[]): AgentRunHint[];
52
+ export declare function validateSessionProfile(params: {
53
+ currentUrl: string;
54
+ startUrl?: string;
55
+ requestedLang?: string;
56
+ requestedTheme?: 'light' | 'dark';
57
+ signals: VideoPageSignals;
58
+ profile?: ValidatedSessionProfile;
59
+ }): SessionValidationResult;
60
+ export declare function collectValidatedSessionProfile(browser: Browser, params: {
61
+ requestedLang?: string;
62
+ requestedTheme?: 'light' | 'dark';
63
+ startUrl?: string;
64
+ existingProfile?: ValidatedSessionProfile;
65
+ includeStorageSnapshots?: boolean;
66
+ }): Promise<{
67
+ profile: ValidatedSessionProfile;
68
+ validation: SessionValidationResult;
69
+ }>;
70
+ export declare function performDeterministicSessionRepair(browser: Browser, params: {
71
+ startUrl?: string;
72
+ requestedLang?: string;
73
+ requestedTheme?: 'light' | 'dark';
74
+ credentials?: LoginCredentials;
75
+ profile?: ValidatedSessionProfile;
76
+ selectorMemory?: Record<string, string[]>;
77
+ }): Promise<{
78
+ repaired: boolean;
79
+ validation: SessionValidationResult;
80
+ pathUsed: string | null;
81
+ updates: DeterministicSelectorUpdate[];
82
+ }>;
83
+ export declare function buildProfileSelectorUpdates(signals: VideoPageSignals, params: {
84
+ requestedLang?: string;
85
+ requestedTheme?: 'light' | 'dark';
86
+ }): DeterministicSelectorUpdate[];