feat: add support for ignoring HTTPS certificate errors (#93)

* feat: add support for ignoring HTTPS certificate errors

* fix: update warning message for already running daemon to include ignore HTTPS errors option

* docs: add documentation for --ignore-https-errors option in README and SKILL.md

* feat: initialize ignore_https_errors flag in command context

* fix: change launch_cmd to mutable for cdp value handling
This commit is contained in:
Zhiwei Li
2026-01-24 23:54:33 -06:00
committed by GitHub
parent 60534dfd63
commit 53187a603c
12 changed files with 65 additions and 2 deletions
+1
View File
@@ -334,6 +334,7 @@ agent-browser snapshot -i -c -d 5 # Combine options
| `--exact` | Exact text match | | `--exact` | Exact text match |
| `--headed` | Show browser window (not headless) | | `--headed` | Show browser window (not headless) |
| `--cdp <port>` | Connect via Chrome DevTools Protocol | | `--cdp <port>` | Connect via Chrome DevTools Protocol |
| `--ignore-https-errors` | Ignore HTTPS certificate errors (useful for self-signed certs) |
| `--debug` | Debug output | | `--debug` | Debug output |
## Selectors ## Selectors
+1
View File
@@ -1212,6 +1212,7 @@ mod tests {
args: None, args: None,
user_agent: None, user_agent: None,
provider: None, provider: None,
ignore_https_errors: false,
} }
} }
+9
View File
@@ -194,6 +194,7 @@ pub fn ensure_daemon(
user_agent: Option<&str>, user_agent: Option<&str>,
proxy: Option<&str>, proxy: Option<&str>,
proxy_bypass: Option<&str>, proxy_bypass: Option<&str>,
ignore_https_errors: bool,
) -> Result<DaemonResult, String> { ) -> Result<DaemonResult, String> {
if is_daemon_running(session) && daemon_ready(session) { if is_daemon_running(session) && daemon_ready(session) {
return Ok(DaemonResult { return Ok(DaemonResult {
@@ -266,6 +267,10 @@ pub fn ensure_daemon(
cmd.env("AGENT_BROWSER_PROXY_BYPASS", pb); cmd.env("AGENT_BROWSER_PROXY_BYPASS", pb);
} }
if ignore_https_errors {
cmd.env("AGENT_BROWSER_IGNORE_HTTPS_ERRORS", "1");
}
// Create new process group and session to fully detach // Create new process group and session to fully detach
unsafe { unsafe {
cmd.pre_exec(|| { cmd.pre_exec(|| {
@@ -321,6 +326,10 @@ pub fn ensure_daemon(
cmd.env("AGENT_BROWSER_PROXY_BYPASS", pb); cmd.env("AGENT_BROWSER_PROXY_BYPASS", pb);
} }
if ignore_https_errors {
cmd.env("AGENT_BROWSER_IGNORE_HTTPS_ERRORS", "1");
}
// CREATE_NEW_PROCESS_GROUP | DETACHED_PROCESS // CREATE_NEW_PROCESS_GROUP | DETACHED_PROCESS
const CREATE_NEW_PROCESS_GROUP: u32 = 0x00000200; const CREATE_NEW_PROCESS_GROUP: u32 = 0x00000200;
const DETACHED_PROCESS: u32 = 0x00000008; const DETACHED_PROCESS: u32 = 0x00000008;
+4 -1
View File
@@ -16,6 +16,7 @@ pub struct Flags {
pub args: Option<String>, pub args: Option<String>,
pub user_agent: Option<String>, pub user_agent: Option<String>,
pub provider: Option<String>, pub provider: Option<String>,
pub ignore_https_errors: bool,
} }
pub fn parse_flags(args: &[String]) -> Flags { pub fn parse_flags(args: &[String]) -> Flags {
@@ -40,6 +41,7 @@ pub fn parse_flags(args: &[String]) -> Flags {
args: env::var("AGENT_BROWSER_ARGS").ok(), args: env::var("AGENT_BROWSER_ARGS").ok(),
user_agent: env::var("AGENT_BROWSER_USER_AGENT").ok(), user_agent: env::var("AGENT_BROWSER_USER_AGENT").ok(),
provider: env::var("AGENT_BROWSER_PROVIDER").ok(), provider: env::var("AGENT_BROWSER_PROVIDER").ok(),
ignore_https_errors: false,
}; };
let mut i = 0; let mut i = 0;
@@ -115,6 +117,7 @@ pub fn parse_flags(args: &[String]) -> Flags {
i += 1; i += 1;
} }
} }
"--ignore-https-errors" => flags.ignore_https_errors = true,
_ => {} _ => {}
} }
i += 1; i += 1;
@@ -127,7 +130,7 @@ pub fn clean_args(args: &[String]) -> Vec<String> {
let mut skip_next = false; let mut skip_next = false;
// Global flags that should be stripped from command args // Global flags that should be stripped from command args
const GLOBAL_FLAGS: &[&str] = &["--json", "--full", "--headed", "--debug"]; const GLOBAL_FLAGS: &[&str] = &["--json", "--full", "--headed", "--debug", "--ignore-https-errors"];
// Global flags that take a value (need to skip the next arg too) // Global flags that take a value (need to skip the next arg too)
const GLOBAL_FLAGS_WITH_VALUE: &[&str] = &[ const GLOBAL_FLAGS_WITH_VALUE: &[&str] = &[
"--session", "--session",
+15 -1
View File
@@ -200,6 +200,7 @@ fn main() {
flags.user_agent.as_deref(), flags.user_agent.as_deref(),
flags.proxy.as_deref(), flags.proxy.as_deref(),
flags.proxy_bypass.as_deref(), flags.proxy_bypass.as_deref(),
flags.ignore_https_errors,
) { ) {
Ok(result) => result, Ok(result) => result,
Err(e) => { Err(e) => {
@@ -223,6 +224,7 @@ fn main() {
flags.user_agent.as_ref().map(|_| "--user-agent"), flags.user_agent.as_ref().map(|_| "--user-agent"),
flags.proxy.as_ref().map(|_| "--proxy"), flags.proxy.as_ref().map(|_| "--proxy"),
flags.proxy_bypass.as_ref().map(|_| "--proxy-bypass"), flags.proxy_bypass.as_ref().map(|_| "--proxy-bypass"),
flags.ignore_https_errors.then(|| "--ignore-https-errors"),
] ]
.into_iter() .into_iter()
.flatten() .flatten()
@@ -235,6 +237,10 @@ fn main() {
ignored_flags.join(", ") ignored_flags.join(", ")
); );
} }
if flags.ignore_https_errors {
eprintln!("{} --ignore-https-errors ignored: daemon already running. Use 'agent-browser close' first to restart with this option.", color::warning_indicator());
}
} }
// Validate mutually exclusive options // Validate mutually exclusive options
@@ -261,7 +267,7 @@ fn main() {
// Connect via CDP if --cdp flag is set // Connect via CDP if --cdp flag is set
// Accepts either a port number (e.g., "9222") or a full URL (e.g., "ws://..." or "wss://...") // Accepts either a port number (e.g., "9222") or a full URL (e.g., "ws://..." or "wss://...")
if let Some(ref cdp_value) = flags.cdp { if let Some(ref cdp_value) = flags.cdp {
let launch_cmd = if cdp_value.starts_with("ws://") let mut launch_cmd = if cdp_value.starts_with("ws://")
|| cdp_value.starts_with("wss://") || cdp_value.starts_with("wss://")
|| cdp_value.starts_with("http://") || cdp_value.starts_with("http://")
|| cdp_value.starts_with("https://") || cdp_value.starts_with("https://")
@@ -317,6 +323,10 @@ fn main() {
}) })
}; };
if flags.ignore_https_errors {
launch_cmd["ignoreHTTPSErrors"] = json!(true);
}
let err = match send_command(launch_cmd, &flags.session) { let err = match send_command(launch_cmd, &flags.session) {
Ok(resp) if resp.success => None, Ok(resp) if resp.success => None,
Ok(resp) => Some( Ok(resp) => Some(
@@ -401,6 +411,10 @@ fn main() {
cmd_obj.insert("args".to_string(), json!(args_vec)); cmd_obj.insert("args".to_string(), json!(args_vec));
} }
if flags.ignore_https_errors {
launch_cmd["ignoreHTTPSErrors"] = json!(true);
}
if let Err(e) = send_command(launch_cmd, &flags.session) { if let Err(e) = send_command(launch_cmd, &flags.session) {
if !flags.json { if !flags.json {
eprintln!("{} Could not configure browser: {}", color::warning_indicator(), e); eprintln!("{} Could not configure browser: {}", color::warning_indicator(), e);
+1
View File
@@ -1535,6 +1535,7 @@ Options:
e.g., --proxy "http://user:pass@127.0.0.1:7890" e.g., --proxy "http://user:pass@127.0.0.1:7890"
--proxy-bypass <hosts> Bypass proxy for these hosts (or AGENT_BROWSER_PROXY_BYPASS) --proxy-bypass <hosts> Bypass proxy for these hosts (or AGENT_BROWSER_PROXY_BYPASS)
e.g., --proxy-bypass "localhost,*.internal.com" e.g., --proxy-bypass "localhost,*.internal.com"
--ignore-https-errors Ignore HTTPS certificate errors
-p, --provider <name> Cloud browser provider (or AGENT_BROWSER_PROVIDER env) -p, --provider <name> Cloud browser provider (or AGENT_BROWSER_PROVIDER env)
--json JSON output --json JSON output
--full, -f Full page screenshot --full, -f Full page screenshot
+7
View File
@@ -347,3 +347,10 @@ Usage:
./templates/authenticated-session.sh https://app.example.com/login ./templates/authenticated-session.sh https://app.example.com/login
./templates/capture-workflow.sh https://example.com ./output ./templates/capture-workflow.sh https://example.com ./output
``` ```
## HTTPS Certificate Errors
For sites with self-signed or invalid certificates:
```bash
agent-browser open https://localhost:8443 --ignore-https-errors
```
+2
View File
@@ -883,6 +883,7 @@ export class BrowserManager {
extraHTTPHeaders: options.headers, extraHTTPHeaders: options.headers,
userAgent: options.userAgent, userAgent: options.userAgent,
...(options.proxy && { proxy: options.proxy }), ...(options.proxy && { proxy: options.proxy }),
ignoreHTTPSErrors: options.ignoreHTTPSErrors ?? false,
} }
); );
this.isPersistentContext = true; this.isPersistentContext = true;
@@ -910,6 +911,7 @@ export class BrowserManager {
extraHTTPHeaders: options.headers, extraHTTPHeaders: options.headers,
userAgent: options.userAgent, userAgent: options.userAgent,
...(options.proxy && { proxy: options.proxy }), ...(options.proxy && { proxy: options.proxy }),
ignoreHTTPSErrors: options.ignoreHTTPSErrors ?? false,
}); });
} }
+2
View File
@@ -248,6 +248,7 @@ export async function startDaemon(options?: { streamPort?: number }): Promise<vo
} }
: undefined; : undefined;
const ignoreHTTPSErrors = process.env.AGENT_BROWSER_IGNORE_HTTPS_ERRORS === '1';
await browser.launch({ await browser.launch({
id: 'auto', id: 'auto',
action: 'launch' as const, action: 'launch' as const,
@@ -257,6 +258,7 @@ export async function startDaemon(options?: { streamPort?: number }): Promise<vo
args, args,
userAgent: process.env.AGENT_BROWSER_USER_AGENT, userAgent: process.env.AGENT_BROWSER_USER_AGENT,
proxy, proxy,
ignoreHTTPSErrors: ignoreHTTPSErrors,
}); });
} }
+21
View File
@@ -510,6 +510,27 @@ describe('parseCommand', () => {
const result = parseCommand(cmd({ id: '1', action: 'launch', cdpPort: 'invalid' })); const result = parseCommand(cmd({ id: '1', action: 'launch', cdpPort: 'invalid' }));
expect(result.success).toBe(false); expect(result.success).toBe(false);
}); });
it('should parse launch with ignoreHTTPSErrors true', () => {
const result = parseCommand(cmd({ id: '1', action: 'launch', ignoreHTTPSErrors: true }));
expect(result.success).toBe(true);
if (result.success) {
expect(result.command.ignoreHTTPSErrors).toBe(true);
}
});
it('should parse launch with ignoreHTTPSErrors false', () => {
const result = parseCommand(cmd({ id: '1', action: 'launch', ignoreHTTPSErrors: false }));
expect(result.success).toBe(true);
if (result.success) {
expect(result.command.ignoreHTTPSErrors).toBe(false);
}
});
it('should reject launch with non-boolean ignoreHTTPSErrors', () => {
const result = parseCommand(cmd({ id: '1', action: 'launch', ignoreHTTPSErrors: 'true' }));
expect(result.success).toBe(false);
});
}); });
describe('mouse actions', () => { describe('mouse actions', () => {
+1
View File
@@ -45,6 +45,7 @@ const launchSchema = baseCommandSchema.extend({
args: z.array(z.string()).optional(), args: z.array(z.string()).optional(),
userAgent: z.string().optional(), userAgent: z.string().optional(),
provider: z.string().optional(), provider: z.string().optional(),
ignoreHTTPSErrors: z.boolean().optional(),
}); });
const navigateSchema = baseCommandSchema.extend({ const navigateSchema = baseCommandSchema.extend({
+1
View File
@@ -27,6 +27,7 @@ export interface LaunchCommand extends BaseCommand {
args?: string[]; args?: string[];
userAgent?: string; userAgent?: string;
provider?: string; provider?: string;
ignoreHTTPSErrors?: boolean;
} }
export interface NavigateCommand extends BaseCommand { export interface NavigateCommand extends BaseCommand {