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:
@@ -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
|
||||||
|
|||||||
@@ -1212,6 +1212,7 @@ mod tests {
|
|||||||
args: None,
|
args: None,
|
||||||
user_agent: None,
|
user_agent: None,
|
||||||
provider: None,
|
provider: None,
|
||||||
|
ignore_https_errors: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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
@@ -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
@@ -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);
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
```
|
||||||
|
|||||||
@@ -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,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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', () => {
|
||||||
|
|||||||
@@ -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({
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
Reference in New Issue
Block a user