browser-use 0.6.0 → 0.7.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 (85) hide show
  1. package/README.md +29 -18
  2. package/dist/actor/element.js +24 -3
  3. package/dist/actor/mouse.js +21 -3
  4. package/dist/actor/page.js +33 -11
  5. package/dist/agent/gif.js +28 -3
  6. package/dist/agent/message-manager/service.js +2 -22
  7. package/dist/agent/message-manager/utils.js +15 -2
  8. package/dist/agent/message-manager/views.d.ts +7 -7
  9. package/dist/agent/message-manager/views.js +1 -0
  10. package/dist/agent/prompts.d.ts +3 -0
  11. package/dist/agent/prompts.js +22 -12
  12. package/dist/agent/service.d.ts +9 -1
  13. package/dist/agent/service.js +215 -81
  14. package/dist/agent/system_prompt.md +12 -11
  15. package/dist/agent/system_prompt_anthropic_flash.md +6 -5
  16. package/dist/agent/system_prompt_no_thinking.md +12 -11
  17. package/dist/agent/views.d.ts +2 -0
  18. package/dist/agent/views.js +48 -36
  19. package/dist/browser/extensions.js +20 -10
  20. package/dist/browser/profile.d.ts +4 -0
  21. package/dist/browser/profile.js +107 -4
  22. package/dist/browser/session.d.ts +28 -1
  23. package/dist/browser/session.js +1436 -528
  24. package/dist/browser/watchdogs/default-action-watchdog.js +32 -3
  25. package/dist/browser/watchdogs/downloads-watchdog.d.ts +4 -0
  26. package/dist/browser/watchdogs/downloads-watchdog.js +105 -9
  27. package/dist/browser/watchdogs/har-recording-watchdog.d.ts +1 -0
  28. package/dist/browser/watchdogs/har-recording-watchdog.js +54 -2
  29. package/dist/browser/watchdogs/permissions-watchdog.d.ts +5 -0
  30. package/dist/browser/watchdogs/permissions-watchdog.js +106 -3
  31. package/dist/browser/watchdogs/recording-watchdog.d.ts +2 -0
  32. package/dist/browser/watchdogs/recording-watchdog.js +54 -2
  33. package/dist/browser/watchdogs/security-watchdog.d.ts +1 -0
  34. package/dist/browser/watchdogs/security-watchdog.js +47 -7
  35. package/dist/browser/watchdogs/storage-state-watchdog.d.ts +6 -0
  36. package/dist/browser/watchdogs/storage-state-watchdog.js +206 -14
  37. package/dist/cli.d.ts +13 -2
  38. package/dist/cli.js +188 -8
  39. package/dist/code-use/namespace.js +52 -7
  40. package/dist/code-use/notebook-export.js +18 -2
  41. package/dist/code-use/service.js +1 -0
  42. package/dist/config.js +27 -5
  43. package/dist/controller/action-timeout.d.ts +9 -0
  44. package/dist/controller/action-timeout.js +95 -0
  45. package/dist/controller/registry/service.d.ts +1 -0
  46. package/dist/controller/registry/service.js +28 -1
  47. package/dist/controller/registry/views.d.ts +2 -0
  48. package/dist/controller/registry/views.js +44 -17
  49. package/dist/controller/service.d.ts +2 -1
  50. package/dist/controller/service.js +494 -329
  51. package/dist/filesystem/file-system.js +38 -8
  52. package/dist/integrations/gmail/service.js +30 -6
  53. package/dist/llm/browser-use/chat.js +2 -2
  54. package/dist/llm/codex/auth.d.ts +118 -0
  55. package/dist/llm/codex/auth.js +599 -0
  56. package/dist/llm/codex/chat.d.ts +70 -0
  57. package/dist/llm/codex/chat.js +392 -0
  58. package/dist/llm/codex/index.d.ts +2 -0
  59. package/dist/llm/codex/index.js +2 -0
  60. package/dist/llm/google/chat.js +18 -1
  61. package/dist/logging-config.js +22 -11
  62. package/dist/mcp/client.d.ts +1 -0
  63. package/dist/mcp/client.js +12 -10
  64. package/dist/mcp/redaction.d.ts +3 -0
  65. package/dist/mcp/redaction.js +132 -0
  66. package/dist/mcp/server.d.ts +2 -0
  67. package/dist/mcp/server.js +64 -22
  68. package/dist/observability.js +1 -1
  69. package/dist/screenshots/service.js +25 -2
  70. package/dist/skill-cli/direct.d.ts +4 -1
  71. package/dist/skill-cli/direct.js +260 -64
  72. package/dist/skill-cli/server.d.ts +1 -0
  73. package/dist/skill-cli/server.js +115 -25
  74. package/dist/skill-cli/tunnel.d.ts +1 -0
  75. package/dist/skill-cli/tunnel.js +16 -4
  76. package/dist/sync/auth.js +22 -9
  77. package/dist/telemetry/service.js +21 -2
  78. package/dist/telemetry/views.js +31 -8
  79. package/dist/tokens/custom-pricing.js +2 -2
  80. package/dist/tokens/openrouter-pricing.d.ts +11 -0
  81. package/dist/tokens/openrouter-pricing.js +102 -0
  82. package/dist/tokens/service.js +20 -16
  83. package/dist/utils.d.ts +3 -1
  84. package/dist/utils.js +4 -2
  85. package/package.json +75 -33
@@ -14,12 +14,19 @@ You excel at following tasks:
14
14
  </language_settings>
15
15
  <input>
16
16
  At every step, your input will consist of:
17
- 1. <agent_history>: A chronological event stream including your previous actions and their results.
18
- 2. <agent_state>: Current <user_request>, summary of <file_system>, <todo_contents>, and <step_info>.
19
- 3. <browser_state>: Current URL, open tabs, interactive elements indexed for actions, and visible page content.
20
- 4. <browser_vision>: Screenshot of the browser with bounding boxes around interactive elements. If you used screenshot before, this will contain a screenshot.
21
- 5. <read_state> This will be displayed only if your previous action was extract or read_file. This data is only shown in the current step.
17
+ 1. <user_request>: Your ultimate objective.
18
+ 2. <agent_history>: A chronological event stream including your previous actions and their results.
19
+ 3. <agent_state>: Summary of <file_system>, <todo_contents>, and other current agent context.
20
+ 4. <browser_state>: Current URL, open tabs, interactive elements indexed for actions, and visible page content.
21
+ 5. <browser_vision>: Screenshot of the browser with bounding boxes around interactive elements. If you used screenshot before, this will contain a screenshot.
22
+ 6. <read_state> This will be displayed only if your previous action was extract or read_file. This data is only shown in the current step.
22
23
  </input>
24
+ <user_request>
25
+ USER REQUEST: This is your ultimate objective and always remains visible.
26
+ - This has the highest priority. Make the user happy.
27
+ - If the user request is very specific - then carefully follow each step and dont skip or hallucinate steps.
28
+ - If the task is open ended you can plan yourself how to get it done.
29
+ </user_request>
23
30
  <agent_history>
24
31
  Agent history will be given as a list of step information as follows:
25
32
  <step_{{step_number}}>:
@@ -30,12 +37,6 @@ Action Results: Your actions and their results
30
37
  </step_{{step_number}}>
31
38
  and system messages wrapped in <sys> tag.
32
39
  </agent_history>
33
- <user_request>
34
- USER REQUEST: This is your ultimate objective and always remains visible.
35
- - This has the highest priority. Make the user happy.
36
- - If the user request is very specific - then carefully follow each step and dont skip or hallucinate steps.
37
- - If the task is open ended you can plan yourself how to get it done.
38
- </user_request>
39
40
  <browser_state>
40
41
  1. Browser State will be given as:
41
42
  Current URL: URL of the page you are currently viewing.
@@ -101,11 +101,12 @@ BEFORE calling `done` with `success=true`, you MUST perform this verification:
101
101
  </task_completion_rules>
102
102
  <input>
103
103
  At every step, your input will consist of:
104
- 1. <agent_history>: A chronological event stream including your previous actions and their results.
105
- 2. <agent_state>: Current <user_request>, summary of <file_system>, <todo_contents>, and <step_info>.
106
- 3. <browser_state>: Current URL, open tabs, interactive elements indexed for actions, and visible page content.
107
- 4. <browser_vision>: Screenshot of the browser with bounding boxes around interactive elements. This is your GROUND TRUTH.
108
- 5. <read_state> This will be displayed only if your previous action was extract or read_file. This data is only shown in the current step.
104
+ 1. <user_request>: Your ultimate objective.
105
+ 2. <agent_history>: A chronological event stream including your previous actions and their results.
106
+ 3. <agent_state>: Summary of <file_system>, <todo_contents>, and other current agent context.
107
+ 4. <browser_state>: Current URL, open tabs, interactive elements indexed for actions, and visible page content.
108
+ 5. <browser_vision>: Screenshot of the browser with bounding boxes around interactive elements. This is your GROUND TRUTH.
109
+ 6. <read_state> This will be displayed only if your previous action was extract or read_file. This data is only shown in the current step.
109
110
  </input>
110
111
  <agent_history>
111
112
  Agent history will be given as a list of step information as follows:
@@ -14,12 +14,19 @@ You excel at following tasks:
14
14
  </language_settings>
15
15
  <input>
16
16
  At every step, your input will consist of:
17
- 1. <agent_history>: A chronological event stream including your previous actions and their results.
18
- 2. <agent_state>: Current <user_request>, summary of <file_system>, <todo_contents>, and <step_info>.
19
- 3. <browser_state>: Current URL, open tabs, interactive elements indexed for actions, and visible page content.
20
- 4. <browser_vision>: Screenshot of the browser with bounding boxes around interactive elements. If you used screenshot before, this will contain a screenshot.
21
- 5. <read_state> This will be displayed only if your previous action was extract or read_file. This data is only shown in the current step.
17
+ 1. <user_request>: Your ultimate objective.
18
+ 2. <agent_history>: A chronological event stream including your previous actions and their results.
19
+ 3. <agent_state>: Summary of <file_system>, <todo_contents>, and other current agent context.
20
+ 4. <browser_state>: Current URL, open tabs, interactive elements indexed for actions, and visible page content.
21
+ 5. <browser_vision>: Screenshot of the browser with bounding boxes around interactive elements. If you used screenshot before, this will contain a screenshot.
22
+ 6. <read_state> This will be displayed only if your previous action was extract or read_file. This data is only shown in the current step.
22
23
  </input>
24
+ <user_request>
25
+ USER REQUEST: This is your ultimate objective and always remains visible.
26
+ - This has the highest priority. Make the user happy.
27
+ - If the user request is very specific - then carefully follow each step and dont skip or hallucinate steps.
28
+ - If the task is open ended you can plan yourself how to get it done.
29
+ </user_request>
23
30
  <agent_history>
24
31
  Agent history will be given as a list of step information as follows:
25
32
  <step_{{step_number}}>:
@@ -30,12 +37,6 @@ Action Results: Your actions and their results
30
37
  </step_{{step_number}}>
31
38
  and system messages wrapped in <sys> tag.
32
39
  </agent_history>
33
- <user_request>
34
- USER REQUEST: This is your ultimate objective and always remains visible.
35
- - This has the highest priority. Make the user happy.
36
- - If the user request is very specific - then carefully follow each step and dont skip or hallucinate steps.
37
- - If the task is open ended you can plan yourself how to get it done.
38
- </user_request>
39
40
  <browser_state>
40
41
  1. Browser State will be given as:
41
42
  Current URL: URL of the page you are currently viewing.
@@ -13,6 +13,8 @@ export interface StructuredOutputParser<T = unknown> {
13
13
  model_json_schema?: () => unknown;
14
14
  schema?: unknown;
15
15
  }
16
+ type SensitiveDataMap = Record<string, string | Record<string, string>>;
17
+ export declare const redactSensitiveDataFromString: (value: string, sensitive_data: SensitiveDataMap | null) => string;
16
18
  export interface ActionResultInit {
17
19
  is_done?: boolean | null;
18
20
  success?: boolean | null;
@@ -8,6 +8,45 @@ import { DEFAULT_INCLUDE_ATTRIBUTES, } from '../dom/views.js';
8
8
  import { MessageManagerState } from './message-manager/views.js';
9
9
  // Re-export ActionModel for agent/service.ts
10
10
  export { ActionModel };
11
+ export const redactSensitiveDataFromString = (value, sensitive_data) => {
12
+ if (!sensitive_data) {
13
+ return value;
14
+ }
15
+ const placeholders = {};
16
+ for (const [keyOrDomain, content] of Object.entries(sensitive_data)) {
17
+ if (typeof content === 'string' && content) {
18
+ placeholders[keyOrDomain] = content;
19
+ }
20
+ else if (content && typeof content === 'object') {
21
+ for (const [key, val] of Object.entries(content)) {
22
+ if (val) {
23
+ placeholders[key] = val;
24
+ }
25
+ }
26
+ }
27
+ }
28
+ const entries = Object.entries(placeholders).sort(([, left], [, right]) => right.length - left.length);
29
+ if (!entries.length) {
30
+ return value;
31
+ }
32
+ let filtered = value;
33
+ for (const [key, secret] of entries) {
34
+ filtered = filtered.split(secret).join(`<secret>${key}</secret>`);
35
+ }
36
+ return filtered;
37
+ };
38
+ const chmodPrivateFile = (filePath) => {
39
+ if (process.platform !== 'win32') {
40
+ fs.chmodSync(filePath, 0o600);
41
+ }
42
+ };
43
+ const ensurePrivateDirectoryIfCreated = (dirPath) => {
44
+ const existed = fs.existsSync(dirPath);
45
+ fs.mkdirSync(dirPath, { recursive: true, mode: 0o700 });
46
+ if (!existed && process.platform !== 'win32') {
47
+ fs.chmodSync(dirPath, 0o700);
48
+ }
49
+ };
11
50
  const parseStructuredOutput = (schema, value) => {
12
51
  if (!schema) {
13
52
  return null;
@@ -539,30 +578,7 @@ export class AgentHistory {
539
578
  return elements;
540
579
  }
541
580
  static _filterSensitiveDataFromString(value, sensitive_data) {
542
- if (!sensitive_data) {
543
- return value;
544
- }
545
- const placeholders = {};
546
- for (const [keyOrDomain, content] of Object.entries(sensitive_data)) {
547
- if (typeof content === 'string' && content) {
548
- placeholders[keyOrDomain] = content;
549
- }
550
- else if (content && typeof content === 'object') {
551
- for (const [key, val] of Object.entries(content)) {
552
- if (val) {
553
- placeholders[key] = val;
554
- }
555
- }
556
- }
557
- }
558
- if (!Object.keys(placeholders).length) {
559
- return value;
560
- }
561
- let filtered = value;
562
- for (const [key, secret] of Object.entries(placeholders)) {
563
- filtered = filtered.split(secret).join(`<secret>${key}</secret>`);
564
- }
565
- return filtered;
581
+ return redactSensitiveDataFromString(value, sensitive_data);
566
582
  }
567
583
  static _filterSensitiveDataFromDict(data, sensitive_data) {
568
584
  if (!sensitive_data) {
@@ -594,16 +610,8 @@ export class AgentHistory {
594
610
  return filtered;
595
611
  }
596
612
  toJSON(sensitive_data = null) {
597
- let modelOutput = this.model_output?.toJSON() ?? null;
598
- if (modelOutput &&
599
- Array.isArray(modelOutput.action) &&
600
- sensitive_data) {
601
- modelOutput.action = modelOutput.action.map((action) => Object.prototype.hasOwnProperty.call(action, 'input')
602
- ? AgentHistory._filterSensitiveDataFromDict(action, sensitive_data)
603
- : action);
604
- }
605
- return {
606
- model_output: modelOutput,
613
+ const payload = {
614
+ model_output: this.model_output?.toJSON() ?? null,
607
615
  result: this.result.map((r) => r.toJSON()),
608
616
  state: this.state.to_dict(),
609
617
  metadata: this.metadata
@@ -616,6 +624,9 @@ export class AgentHistory {
616
624
  : null,
617
625
  state_message: this.state_message,
618
626
  };
627
+ return sensitive_data
628
+ ? AgentHistory._filterSensitiveDataFromDict(payload, sensitive_data)
629
+ : payload;
619
630
  }
620
631
  }
621
632
  export class AgentHistoryList {
@@ -863,8 +874,9 @@ export class AgentHistoryList {
863
874
  }
864
875
  save_to_file(filepath, sensitive_data = null) {
865
876
  const dir = path.dirname(filepath);
866
- fs.mkdirSync(dir, { recursive: true });
867
- fs.writeFileSync(filepath, JSON.stringify(this.toJSON(sensitive_data), null, 2), 'utf-8');
877
+ ensurePrivateDirectoryIfCreated(dir);
878
+ fs.writeFileSync(filepath, JSON.stringify(this.toJSON(sensitive_data), null, 2), { encoding: 'utf-8', mode: 0o600 });
879
+ chmodPrivateFile(filepath);
868
880
  }
869
881
  static load_from_file(filepath, outputModel) {
870
882
  const content = fs.readFileSync(filepath, 'utf-8');
@@ -13,6 +13,22 @@ import { Readable } from 'node:stream';
13
13
  import extract from 'extract-zip';
14
14
  import { createLogger } from '../logging-config.js';
15
15
  const logger = createLogger('browser_use.extensions');
16
+ const chmodPrivatePath = (targetPath, mode) => {
17
+ if (process.platform === 'win32') {
18
+ return;
19
+ }
20
+ try {
21
+ fs.chmodSync(targetPath, mode);
22
+ }
23
+ catch {
24
+ /* best effort */
25
+ }
26
+ };
27
+ const createPrivateDirectory = (dirPath) => {
28
+ fs.mkdirSync(dirPath, { recursive: true, mode: 0o700 });
29
+ chmodPrivatePath(dirPath, 0o700);
30
+ };
31
+ const createPrivateWriteStream = (filePath) => createWriteStream(filePath, { mode: 0o600 });
16
32
  /**
17
33
  * Generate extension ID from unpacked path
18
34
  * Chrome uses SHA256 hash of the directory path
@@ -42,14 +58,11 @@ async function downloadCrx(crxUrl, crxPath) {
42
58
  logger.warning(`[⚠️] Failed to download extension: ${response.statusText}`);
43
59
  return false;
44
60
  }
45
- // Ensure directory exists
46
61
  const dir = path.dirname(crxPath);
47
- if (!fs.existsSync(dir)) {
48
- fs.mkdirSync(dir, { recursive: true });
49
- }
50
- // Download file
51
- const fileStream = createWriteStream(crxPath);
62
+ createPrivateDirectory(dir);
63
+ const fileStream = createPrivateWriteStream(crxPath);
52
64
  await pipeline(Readable.fromWeb(response.body), fileStream);
65
+ chmodPrivatePath(crxPath, 0o600);
53
66
  logger.info(`[✅] Downloaded to ${crxPath}`);
54
67
  return true;
55
68
  }
@@ -63,10 +76,7 @@ async function downloadCrx(crxUrl, crxPath) {
63
76
  */
64
77
  async function unpackCrx(crxPath, unpackedPath) {
65
78
  try {
66
- // Ensure unpacked directory exists
67
- if (!fs.existsSync(unpackedPath)) {
68
- fs.mkdirSync(unpackedPath, { recursive: true });
69
- }
79
+ createPrivateDirectory(unpackedPath);
70
80
  // Extract zip file (CRX is essentially a ZIP with extra header)
71
81
  await extract(crxPath, { dir: path.resolve(unpackedPath) });
72
82
  // Verify manifest exists
@@ -4,6 +4,7 @@ export declare const DOMAIN_OPTIMIZATION_THRESHOLD = 100;
4
4
  export declare const CHROME_DISABLED_COMPONENTS: string[];
5
5
  export declare const CHROME_HEADLESS_ARGS: string[];
6
6
  export declare const CHROME_DOCKER_ARGS: string[];
7
+ export declare const CHROME_PROFILE_TRANSIENT_FILE_PATTERNS: string[];
7
8
  export declare const CHROME_DISABLE_SECURITY_ARGS: string[];
8
9
  export declare const CHROME_DETERMINISTIC_RENDERING_ARGS: string[];
9
10
  export declare const CHROME_DEFAULT_ARGS: string[];
@@ -181,6 +182,9 @@ export declare class BrowserProfile {
181
182
  private warnStorageStateUserDataDirConflict;
182
183
  private warnUserDataDirNonDefault;
183
184
  private warnDeterministicRenderingWeirdness;
185
+ private copyChromeProfileToTempIfNeeded;
186
+ private isChromeBackedProfile;
187
+ private isBrowserUseManagedProfile;
184
188
  private ensureDefaultDownloadsPath;
185
189
  private getDefaultArgsList;
186
190
  private getWindowSizeArgs;
@@ -12,6 +12,21 @@ import { log_pretty_path, uuid7str } from '../utils.js';
12
12
  const logger = createLogger('browser_use.browser.profile');
13
13
  export const CHROME_DEBUG_PORT = 9242;
14
14
  export const DOMAIN_OPTIMIZATION_THRESHOLD = 100;
15
+ const chmodPrivatePath = (targetPath, mode) => {
16
+ if (process.platform === 'win32') {
17
+ return;
18
+ }
19
+ try {
20
+ fs.chmodSync(targetPath, mode);
21
+ }
22
+ catch {
23
+ /* best effort */
24
+ }
25
+ };
26
+ const createPrivateDirectory = (dirPath) => {
27
+ fs.mkdirSync(dirPath, { recursive: true, mode: 0o700 });
28
+ chmodPrivatePath(dirPath, 0o700);
29
+ };
15
30
  export const CHROME_DISABLED_COMPONENTS = [
16
31
  'AcceptCHFrame',
17
32
  'AutoExpandDetailsElement',
@@ -54,6 +69,13 @@ export const CHROME_DOCKER_ARGS = [
54
69
  '--no-zygote',
55
70
  '--disable-site-isolation-trials',
56
71
  ];
72
+ export const CHROME_PROFILE_TRANSIENT_FILE_PATTERNS = [
73
+ 'Singleton*',
74
+ '*.lock',
75
+ '*-journal',
76
+ 'LOCK',
77
+ 'LOCKFILE',
78
+ ];
57
79
  export const CHROME_DISABLE_SECURITY_ARGS = [
58
80
  '--disable-site-isolation-trials',
59
81
  '--disable-web-security',
@@ -381,6 +403,30 @@ const argsAsList = (args) => Object.entries(args).map(([key, value]) => value
381
403
  ? `--${key.replace(/^-+/, '')}=${value}`
382
404
  : `--${key.replace(/^-+/, '')}`);
383
405
  const cloneDefaultOptions = () => JSON.parse(JSON.stringify(DEFAULT_BROWSER_PROFILE_OPTIONS));
406
+ const isChromeProfileTransientFile = (name) => name.startsWith('Singleton') ||
407
+ name.endsWith('.lock') ||
408
+ name.endsWith('-journal') ||
409
+ name === 'LOCK' ||
410
+ name === 'LOCKFILE';
411
+ const isChromeProfileLockError = (error) => {
412
+ if (!(error instanceof Error)) {
413
+ return false;
414
+ }
415
+ const code = String(error.code ?? '');
416
+ const message = error.message;
417
+ return (code === 'EACCES' ||
418
+ code === 'EPERM' ||
419
+ code === 'EBUSY' ||
420
+ message.includes('WinError 32') ||
421
+ message.includes('being used by another process'));
422
+ };
423
+ const pathContains = (candidate, parent) => {
424
+ const relative = path.relative(path.resolve(parent), path.resolve(candidate));
425
+ return (relative === '' ||
426
+ (relative !== '' &&
427
+ !relative.startsWith('..') &&
428
+ !path.isAbsolute(relative)));
429
+ };
384
430
  const normalizeDomainEntry = (entry) => String(entry ?? '')
385
431
  .trim()
386
432
  .toLowerCase();
@@ -443,6 +489,7 @@ export class BrowserProfile {
443
489
  this.warnStorageStateUserDataDirConflict();
444
490
  this.warnUserDataDirNonDefault();
445
491
  this.warnDeterministicRenderingWeirdness();
492
+ this.copyChromeProfileToTempIfNeeded();
446
493
  }
447
494
  toString() {
448
495
  return `BrowserProfile#${this.options.id.slice(-4)}`;
@@ -555,6 +602,59 @@ export class BrowserProfile {
555
602
  logger.warning('⚠️ BrowserSession(deterministic_rendering=True) is NOT RECOMMENDED. It breaks many sites and increases chances of getting blocked by anti-bot systems. It hardcodes the JS random seed and forces browsers across Linux/Mac/Windows to use the same font rendering engine so that identical screenshots can be generated.');
556
603
  }
557
604
  }
605
+ copyChromeProfileToTempIfNeeded() {
606
+ const userDataDir = this.options.user_data_dir;
607
+ if (!userDataDir) {
608
+ return;
609
+ }
610
+ const userDataDirText = String(userDataDir);
611
+ if (userDataDirText.toLowerCase().includes('browser-use-user-data-dir-')) {
612
+ return;
613
+ }
614
+ if (this.isBrowserUseManagedProfile(userDataDirText)) {
615
+ return;
616
+ }
617
+ if (!this.isChromeBackedProfile(userDataDirText)) {
618
+ return;
619
+ }
620
+ const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'browser-use-user-data-dir-'));
621
+ const originalProfileDir = path.join(userDataDirText, this.options.profile_directory);
622
+ const tempProfileDir = path.join(tempDir, this.options.profile_directory);
623
+ try {
624
+ if (fs.existsSync(originalProfileDir)) {
625
+ fs.cpSync(originalProfileDir, tempProfileDir, {
626
+ recursive: true,
627
+ filter: (source) => !isChromeProfileTransientFile(path.basename(source)),
628
+ });
629
+ const localStateSource = path.join(userDataDirText, 'Local State');
630
+ if (fs.existsSync(localStateSource)) {
631
+ fs.copyFileSync(localStateSource, path.join(tempDir, 'Local State'));
632
+ }
633
+ logger.info(`Copied profile (${this.options.profile_directory}) and Local State to temp directory: ${tempDir}`);
634
+ }
635
+ else {
636
+ fs.mkdirSync(tempProfileDir, { recursive: true });
637
+ logger.info(`Created new profile (${this.options.profile_directory}) in temp directory: ${tempDir}`);
638
+ }
639
+ this.options.user_data_dir = tempDir;
640
+ }
641
+ catch (error) {
642
+ fs.rmSync(tempDir, { recursive: true, force: true });
643
+ if (isChromeProfileLockError(error)) {
644
+ throw new Error(`Unable to copy Chrome profile "${this.options.profile_directory}" because one or more files are locked. ` +
645
+ 'Close any Chrome windows using this profile, or start browser-use with --cdp-url to connect to an already-running browser instead of copying the profile.');
646
+ }
647
+ throw error;
648
+ }
649
+ }
650
+ isChromeBackedProfile(userDataDirText) {
651
+ const normalized = userDataDirText.toLowerCase();
652
+ return normalized.includes('chrome') || normalized.includes('chromium');
653
+ }
654
+ isBrowserUseManagedProfile(userDataDirText) {
655
+ const profilesDir = path.dirname(CONFIG.BROWSER_USE_DEFAULT_USER_DATA_DIR);
656
+ return pathContains(userDataDirText, profilesDir);
657
+ }
558
658
  ensureDefaultDownloadsPath() {
559
659
  if (this.options.downloads_path) {
560
660
  return;
@@ -563,7 +663,7 @@ export class BrowserProfile {
563
663
  while (fs.existsSync(downloadsPath)) {
564
664
  downloadsPath = path.join(os.tmpdir(), `browser-use-downloads-${randomUUID().slice(0, 8)}`);
565
665
  }
566
- fs.mkdirSync(downloadsPath, { recursive: true });
666
+ createPrivateDirectory(downloadsPath);
567
667
  this.options.downloads_path = downloadsPath;
568
668
  }
569
669
  getDefaultArgsList() {
@@ -613,7 +713,7 @@ export class BrowserProfile {
613
713
  }
614
714
  async ensureDefaultExtensionsDownloaded() {
615
715
  const cacheDir = CONFIG.BROWSER_USE_EXTENSIONS_DIR;
616
- await fsp.mkdir(cacheDir, { recursive: true });
716
+ createPrivateDirectory(cacheDir);
617
717
  const extensionPaths = [];
618
718
  const loadedNames = [];
619
719
  for (const ext of DEFAULT_EXTENSIONS) {
@@ -674,7 +774,10 @@ export class BrowserProfile {
674
774
  response.on('data', (chunk) => chunks.push(chunk));
675
775
  response.on('end', async () => {
676
776
  try {
677
- await fsp.writeFile(outputPath, Buffer.concat(chunks));
777
+ await fsp.writeFile(outputPath, Buffer.concat(chunks), {
778
+ mode: 0o600,
779
+ });
780
+ chmodPrivatePath(outputPath, 0o600);
678
781
  resolve();
679
782
  }
680
783
  catch (error) {
@@ -689,7 +792,7 @@ export class BrowserProfile {
689
792
  if (fs.existsSync(extractDir)) {
690
793
  await fsp.rm(extractDir, { recursive: true, force: true });
691
794
  }
692
- await fsp.mkdir(extractDir, { recursive: true });
795
+ createPrivateDirectory(extractDir);
693
796
  const buffer = await fsp.readFile(crxPath);
694
797
  try {
695
798
  const zip = new AdmZip(buffer);
@@ -95,6 +95,8 @@ export declare class BrowserSession {
95
95
  private _watchdogs;
96
96
  private _defaultWatchdogsAttached;
97
97
  private _captchaWatchdog;
98
+ private _scopedExtraHeadersRouteContext;
99
+ private _scopedExtraHeadersRouteHandler;
98
100
  readonly RECONNECT_WAIT_TIMEOUT = 54;
99
101
  private _reconnecting;
100
102
  private _reconnectTask;
@@ -157,7 +159,10 @@ export declare class BrowserSession {
157
159
  private _waitWithAbort;
158
160
  private _withAbort;
159
161
  private _toPlaywrightOptions;
162
+ private _assertHttpCredentialsScoped;
160
163
  set_extra_headers(headers: Record<string, string>): Promise<void>;
164
+ private _removeScopedExtraHeadersRoute;
165
+ private _installScopedExtraHeaders;
161
166
  private _applyConfiguredExtraHttpHeaders;
162
167
  private _usesRemoteBrowserConnection;
163
168
  private _connectToConfiguredBrowser;
@@ -244,6 +249,12 @@ export declare class BrowserSession {
244
249
  set_auto_download_pdfs(enabled: boolean): void;
245
250
  auto_download_pdfs(): boolean;
246
251
  static get_unique_filename(directory: string, filename: string): Promise<string>;
252
+ private _cancel_download_best_effort;
253
+ private _assert_download_url_allowed;
254
+ private static _sanitize_download_filename;
255
+ private static _redact_url_for_logging;
256
+ private static _redact_urls_in_text;
257
+ private static _same_origin;
247
258
  get_selector_map(options?: BrowserActionOptions): Promise<SelectorMap>;
248
259
  static is_file_input(node: DOMElementNode | null): boolean;
249
260
  is_file_input(node: DOMElementNode | null): boolean;
@@ -255,7 +266,9 @@ export declare class BrowserSession {
255
266
  /**
256
267
  * Get all cookies from the current browser context
257
268
  */
258
- get_cookies(): Promise<Array<Record<string, any>>>;
269
+ get_cookies(options?: {
270
+ include_blocked?: boolean;
271
+ }): Promise<Array<Record<string, any>>>;
259
272
  /**
260
273
  * Save cookies to a file (deprecated, use save_storage_state instead)
261
274
  * @deprecated Use save_storage_state() instead
@@ -274,10 +287,17 @@ export declare class BrowserSession {
274
287
  * Load storage state (cookies, localStorage, sessionStorage) from a file
275
288
  */
276
289
  load_storage_state(filePath?: string): Promise<void>;
290
+ private _apply_storage_state_origins;
291
+ private _normalize_storage_entries;
292
+ private _sanitize_storage_state_for_save;
293
+ private _filter_storage_state_cookies;
294
+ private _filter_cookies_for_exposure;
295
+ private _get_cookie_access_denial_reason;
277
296
  /**
278
297
  * Execute JavaScript in the current page context
279
298
  */
280
299
  execute_javascript(script: string): Promise<any>;
300
+ validate_page_after_action(page: Page | null, signal?: AbortSignal | null): Promise<void>;
281
301
  /**
282
302
  * Get comprehensive page information (size, scroll position, etc.)
283
303
  */
@@ -384,6 +404,7 @@ export declare class BrowserSession {
384
404
  private _is_ip_address_host;
385
405
  private _get_domain_variants;
386
406
  private _setEntryMatchesUrl;
407
+ private _domainCollectionHasEntries;
387
408
  /**
388
409
  * Check if page is displaying a PDF
389
410
  */
@@ -423,7 +444,13 @@ export declare class BrowserSession {
423
444
  private _get_url_access_denial_reason;
424
445
  private _is_url_allowed;
425
446
  private _formatDomainCollection;
447
+ private _has_url_access_restrictions;
448
+ private _sanitize_tab_for_exposure;
426
449
  private _assert_url_allowed;
450
+ private _rollback_disallowed_navigation;
451
+ private _replace_disallowed_current_page;
452
+ private _assert_page_url_allowed_or_rollback;
453
+ private _get_disallowed_page_error_after_navigation_error;
427
454
  /**
428
455
  * Navigate helper with URL validation
429
456
  */