Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -607,6 +607,7 @@ This is useful for multimodal AI models that can reason about visual layout, unl
| `--proxy <url>` | Proxy server URL with optional auth (or `AGENT_BROWSER_PROXY` env) |
| `--proxy-bypass <hosts>` | Hosts to bypass proxy (or `AGENT_BROWSER_PROXY_BYPASS` env) |
| `--ignore-https-errors` | Ignore HTTPS certificate errors (useful for self-signed certs) |
| `--ca-cert <path>` | Trust a specific CA certificate for HTTPS interception proxies (or `AGENT_BROWSER_CA_CERT` env) |
| `--allow-file-access` | Allow file:// URLs to access local files (Chromium only) |
| `-p, --provider <name>` | Cloud browser provider (or `AGENT_BROWSER_PROVIDER` env) |
| `--device <name>` | iOS device name, e.g. "iPhone 15 Pro" (or `AGENT_BROWSER_IOS_DEVICE` env) |
Expand Down Expand Up @@ -704,7 +705,8 @@ Create an `agent-browser.json` file to set persistent defaults instead of repeat
"proxy": "http://localhost:8080",
"profile": "./browser-data",
"userAgent": "my-agent/1.0",
"ignoreHttpsErrors": true
"ignoreHttpsErrors": true,
"caCert": "/etc/ssl/certs/proxy-ca.crt"
}
```

Expand Down
2 changes: 2 additions & 0 deletions cli/src/commands.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2350,6 +2350,7 @@ mod tests {
user_agent: None,
provider: None,
ignore_https_errors: false,
ca_cert: None,
allow_file_access: false,
device: None,
auto_connect: false,
Expand All @@ -2362,6 +2363,7 @@ mod tests {
cli_user_agent: false,
cli_proxy: false,
cli_proxy_bypass: false,
cli_ca_cert: false,
cli_allow_file_access: false,
cli_annotate: false,
cli_download_path: false,
Expand Down
4 changes: 4 additions & 0 deletions cli/src/connection.rs
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,7 @@ pub struct DaemonOptions<'a> {
pub proxy_username: Option<&'a str>,
pub proxy_password: Option<&'a str>,
pub ignore_https_errors: bool,
pub ca_cert: Option<&'a str>,
pub allow_file_access: bool,
pub profile: Option<&'a str>,
pub state: Option<&'a str>,
Expand Down Expand Up @@ -266,6 +267,9 @@ fn apply_daemon_env(cmd: &mut Command, session: &str, opts: &DaemonOptions) {
if opts.ignore_https_errors {
cmd.env("AGENT_BROWSER_IGNORE_HTTPS_ERRORS", "1");
}
if let Some(ca) = opts.ca_cert {
cmd.env("AGENT_BROWSER_CA_CERT", ca);
}
if opts.allow_file_access {
cmd.env("AGENT_BROWSER_ALLOW_FILE_ACCESS", "1");
}
Expand Down
56 changes: 56 additions & 0 deletions cli/src/flags.rs
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ pub struct Config {
pub provider: Option<String>,
pub device: Option<String>,
pub ignore_https_errors: Option<bool>,
pub ca_cert: Option<String>,
pub allow_file_access: Option<bool>,
pub cdp: Option<String>,
pub auto_connect: Option<bool>,
Expand Down Expand Up @@ -116,6 +117,7 @@ impl Config {
provider: other.provider.or(self.provider),
device: other.device.or(self.device),
ignore_https_errors: other.ignore_https_errors.or(self.ignore_https_errors),
ca_cert: other.ca_cert.or(self.ca_cert),
allow_file_access: other.allow_file_access.or(self.allow_file_access),
cdp: other.cdp.or(self.cdp),
auto_connect: other.auto_connect.or(self.auto_connect),
Expand Down Expand Up @@ -221,6 +223,7 @@ fn extract_config_path(args: &[String]) -> Option<Option<String>> {
"--screenshot-quality",
"--screenshot-format",
"--idle-timeout",
"--ca-cert",
"--model",
];
let mut i = 0;
Expand Down Expand Up @@ -285,6 +288,7 @@ pub struct Flags {
pub user_agent: Option<String>,
pub provider: Option<String>,
pub ignore_https_errors: bool,
pub ca_cert: Option<String>,
pub allow_file_access: bool,
pub device: Option<String>,
pub auto_connect: bool,
Expand Down Expand Up @@ -319,6 +323,7 @@ pub struct Flags {
pub cli_user_agent: bool,
pub cli_proxy: bool,
pub cli_proxy_bypass: bool,
pub cli_ca_cert: bool,
pub cli_allow_file_access: bool,
pub cli_annotate: bool,
pub cli_download_path: bool,
Expand Down Expand Up @@ -384,6 +389,7 @@ pub fn parse_flags(args: &[String]) -> Flags {
provider: env::var("AGENT_BROWSER_PROVIDER").ok().or(config.provider),
ignore_https_errors: env_var_is_truthy("AGENT_BROWSER_IGNORE_HTTPS_ERRORS")
|| config.ignore_https_errors.unwrap_or(false),
ca_cert: env::var("AGENT_BROWSER_CA_CERT").ok().or(config.ca_cert),
allow_file_access: env_var_is_truthy("AGENT_BROWSER_ALLOW_FILE_ACCESS")
|| config.allow_file_access.unwrap_or(false),
device: env::var("AGENT_BROWSER_IOS_DEVICE").ok().or(config.device),
Expand Down Expand Up @@ -455,6 +461,7 @@ pub fn parse_flags(args: &[String]) -> Flags {
cli_user_agent: false,
cli_proxy: false,
cli_proxy_bypass: false,
cli_ca_cert: false,
cli_allow_file_access: false,
cli_annotate: false,
cli_download_path: false,
Expand Down Expand Up @@ -586,6 +593,13 @@ pub fn parse_flags(args: &[String]) -> Flags {
i += 1;
}
}
"--ca-cert" => {
if let Some(s) = args.get(i + 1) {
flags.ca_cert = Some(s.clone());
flags.cli_ca_cert = true;
i += 1;
}
}
"--allow-file-access" => {
let (val, consumed) = parse_bool_arg(args, i);
flags.allow_file_access = val;
Expand Down Expand Up @@ -801,6 +815,7 @@ pub fn clean_args(args: &[String]) -> Vec<String> {
"--screenshot-quality",
"--screenshot-format",
"--idle-timeout",
"--ca-cert",
"--model",
];

Expand Down Expand Up @@ -1419,6 +1434,47 @@ mod tests {
assert_eq!(merged.extensions, Some(vec!["/ext2".to_string()]));
}

#[test]
fn test_parse_ca_cert_flag() {
let flags = parse_flags(&args("--ca-cert /path/to/ca.crt open example.com"));
assert_eq!(flags.ca_cert, Some("/path/to/ca.crt".to_string()));
}

#[test]
fn test_parse_ca_cert_flag_no_value() {
let flags = parse_flags(&args("--ca-cert"));
assert_eq!(flags.ca_cert, None);
}

#[test]
fn test_clean_args_removes_ca_cert() {
let cleaned = clean_args(&args("--ca-cert /path/to/ca.crt open example.com"));
assert_eq!(cleaned, vec!["open", "example.com"]);
}

#[test]
fn test_config_merge_ca_cert() {
let user = Config {
ca_cert: Some("/user/ca.crt".to_string()),
..Config::default()
};
let project = Config::default();
let merged = user.merge(project);
assert_eq!(merged.ca_cert, Some("/user/ca.crt".to_string()));

// Project overrides user
let user = Config {
ca_cert: Some("/user/ca.crt".to_string()),
..Config::default()
};
let project = Config {
ca_cert: Some("/project/ca.crt".to_string()),
..Config::default()
};
let merged = user.merge(project);
assert_eq!(merged.ca_cert, Some("/project/ca.crt".to_string()));
}

#[test]
fn test_no_auto_dialog_flag() {
let flags = parse_flags(&args("open example.com --no-auto-dialog"));
Expand Down
34 changes: 34 additions & 0 deletions cli/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -802,6 +802,26 @@ fn main() {
return;
}

// Validate --ca-cert file exists early, before spawning the daemon.
if let Some(ref ca_path) = flags.ca_cert {
if !std::path::Path::new(ca_path).exists() {
let msg = format!("CA certificate file not found: {}", ca_path);
if flags.json {
print_json_error(&msg);
} else {
eprintln!("{} {}", color::warning_indicator(), msg);
}
exit(1);
}
}

if flags.ca_cert.is_some() && flags.ignore_https_errors && !flags.json {
eprintln!(
"{} --ca-cert has no effect when --ignore-https-errors is set (all certificate errors are already ignored)",
color::warning_indicator()
);
}

// Parse proxy URL to separate server from credentials for the daemon.
let (proxy_server, proxy_username, proxy_password) = if let Some(ref proxy_str) = flags.proxy {
let parsed = parse_proxy(proxy_str);
Expand All @@ -821,6 +841,7 @@ fn main() {
proxy_username: proxy_username.as_deref(),
proxy_password: proxy_password.as_deref(),
ignore_https_errors: flags.ignore_https_errors,
ca_cert: flags.ca_cert.as_deref(),
allow_file_access: flags.allow_file_access,
profile: flags.profile.as_deref(),
state: flags.state.as_deref(),
Expand Down Expand Up @@ -893,6 +914,7 @@ fn main() {
None
},
flags.ignore_https_errors.then_some("--ignore-https-errors"),
flags.cli_ca_cert.then_some("--ca-cert"),
flags.cli_allow_file_access.then_some("--allow-file-access"),
flags.cli_download_path.then_some("--download-path"),
flags.cli_headed.then_some("--headed"),
Expand Down Expand Up @@ -976,6 +998,10 @@ fn main() {
launch_cmd["ignoreHTTPSErrors"] = json!(true);
}

if let Some(ref ca) = flags.ca_cert {
launch_cmd["caCert"] = json!(ca);
}

if let Some(ref cs) = flags.color_scheme {
launch_cmd["colorScheme"] = json!(cs);
}
Expand Down Expand Up @@ -1072,6 +1098,10 @@ fn main() {
launch_cmd["ignoreHTTPSErrors"] = json!(true);
}

if let Some(ref ca) = flags.ca_cert {
launch_cmd["caCert"] = json!(ca);
}

if let Some(ref cs) = flags.color_scheme {
launch_cmd["colorScheme"] = json!(cs);
}
Expand Down Expand Up @@ -1214,6 +1244,10 @@ fn main() {
launch_cmd["ignoreHTTPSErrors"] = json!(true);
}

if let Some(ref ca) = flags.ca_cert {
launch_cmd["caCert"] = json!(ca);
}

if flags.allow_file_access {
launch_cmd["allowFileAccess"] = json!(true);
}
Expand Down
6 changes: 6 additions & 0 deletions cli/src/native/actions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1634,6 +1634,7 @@ fn launch_options_from_env() -> LaunchOptions {
ignore_https_errors: env::var("AGENT_BROWSER_IGNORE_HTTPS_ERRORS")
.map(|v| v == "1" || v == "true")
.unwrap_or(false),
ca_cert: env::var("AGENT_BROWSER_CA_CERT").ok(),
color_scheme: env::var("AGENT_BROWSER_COLOR_SCHEME").ok(),
download_path: env::var("AGENT_BROWSER_DOWNLOAD_PATH").ok(),
use_real_keychain: false,
Expand Down Expand Up @@ -1736,6 +1737,11 @@ async fn handle_launch(cmd: &Value, state: &mut DaemonState) -> Result<Value, St
.get("ignoreHTTPSErrors")
.and_then(|v| v.as_bool())
.unwrap_or(false),
ca_cert: cmd
.get("caCert")
.and_then(|v| v.as_str())
.map(String::from)
.or_else(|| env::var("AGENT_BROWSER_CA_CERT").ok()),
color_scheme: cmd
.get("colorScheme")
.and_then(|v| v.as_str())
Expand Down
3 changes: 3 additions & 0 deletions cli/src/native/browser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,9 @@ fn validate_lightpanda_options(options: &LaunchOptions) -> Result<(), String> {
if !options.headless {
return Err("Headed mode is not supported with Lightpanda (headless only)".to_string());
}
if options.ca_cert.is_some() {
return Err("--ca-cert is not supported with Lightpanda (Chromium only)".to_string());
}
if !options.args.is_empty() {
return Err(
"Custom Chrome arguments (--args) are not supported with Lightpanda".to_string(),
Expand Down
Loading
Loading