Release binaries / Build macOS ARM64 (push) Has been cancelled
Release binaries / Build macOS x64 (push) Has been cancelled
Release binaries / Build Linux ARM64 (push) Has been cancelled
Release binaries / Build Linux musl ARM64 (push) Has been cancelled
Release binaries / Build Linux musl x64 (push) Has been cancelled
Release binaries / Build Linux x64 (push) Has been cancelled
Release binaries / Build Windows x64 (push) Has been cancelled
Release binaries / Attach binaries to GitHub Release (push) Has been cancelled
Standalone product rename across the whole repo (issue: project identity): - Binary/package/repo/skill/docs: agent-browser[-stealth] → chrome-use (single binary name `chrome-use`; old aliases agent-browser/abs dropped). - Version: 0.27.0-fork.51 → 1.0.0 (drop the upstream-fork counter). - Native-messaging host: com.agent_browser.connect → com.leeguoo.chrome_use (CLI + ab-connect extension in lockstep — this is a breaking handshake change, extension bumped 0.4.2 → 0.5.0, needs a Web Store republish). - Config dir: ~/.agent-browser → ~/.chrome-use. - README/zh: reframed from "stealth fork of agent-browser" to a standalone product with a small `originally based on vercel-labs/agent-browser` credit. - Kept AGENT_BROWSER_* env vars working (63 vars across the codebase; renaming them would break every existing script/skill for no user-facing gain). Build green, 802 unit tests pass, fmt + clippy clean. Upstream attribution to vercel-labs/agent-browser preserved.
168 lines
5.7 KiB
Rust
168 lines
5.7 KiB
Rust
//! Check security posture: encryption key presence / permissions, saved
|
|
//! state file age, and the optional action policy file.
|
|
|
|
use std::env;
|
|
use std::fs;
|
|
use std::path::PathBuf;
|
|
use std::time::{Duration, SystemTime};
|
|
|
|
#[cfg(unix)]
|
|
use std::os::unix::fs::PermissionsExt;
|
|
|
|
use super::helpers::parse_json_file;
|
|
use super::{Check, Status};
|
|
use crate::native::state::{get_sessions_dir, get_state_dir};
|
|
|
|
pub(super) fn check(checks: &mut Vec<Check>) {
|
|
let category = "Security";
|
|
|
|
let key_env = env::var("AGENT_BROWSER_ENCRYPTION_KEY").ok();
|
|
let key_file = get_state_dir().join(".encryption-key");
|
|
if let Some(hex) = &key_env {
|
|
if hex.len() == 64 && hex.chars().all(|c| c.is_ascii_hexdigit()) {
|
|
checks.push(Check::new(
|
|
"security.encryption_key",
|
|
category,
|
|
Status::Pass,
|
|
"AGENT_BROWSER_ENCRYPTION_KEY set (64-char hex)",
|
|
));
|
|
} else {
|
|
checks.push(
|
|
Check::new(
|
|
"security.encryption_key",
|
|
category,
|
|
Status::Fail,
|
|
"AGENT_BROWSER_ENCRYPTION_KEY is not a 64-char hex string",
|
|
)
|
|
.with_fix("export AGENT_BROWSER_ENCRYPTION_KEY=$(openssl rand -hex 32)"),
|
|
);
|
|
}
|
|
} else if key_file.exists() {
|
|
let mut msg = format!("Encryption key file present: {}", key_file.display());
|
|
let mut status = Status::Pass;
|
|
let mut fix: Option<String> = None;
|
|
#[cfg(unix)]
|
|
if let Ok(meta) = fs::metadata(&key_file) {
|
|
let mode = meta.permissions().mode() & 0o777;
|
|
if mode & 0o077 != 0 {
|
|
status = Status::Warn;
|
|
msg = format!(
|
|
"Encryption key file is too permissive ({:o}): {}",
|
|
mode,
|
|
key_file.display()
|
|
);
|
|
fix = Some(format!("chmod 600 {}", key_file.display()));
|
|
}
|
|
}
|
|
let mut check = Check::new("security.encryption_key", category, status, msg);
|
|
if let Some(f) = fix {
|
|
check = check.with_fix(f);
|
|
}
|
|
checks.push(check);
|
|
} else {
|
|
checks.push(
|
|
Check::new(
|
|
"security.encryption_key",
|
|
category,
|
|
Status::Info,
|
|
"No encryption key set (will be auto-generated on first auth save)",
|
|
)
|
|
.with_fix("export AGENT_BROWSER_ENCRYPTION_KEY=$(openssl rand -hex 32)"),
|
|
);
|
|
}
|
|
|
|
let sessions_dir = get_sessions_dir();
|
|
if sessions_dir.exists() {
|
|
let expire_days = env::var("AGENT_BROWSER_STATE_EXPIRE_DAYS")
|
|
.ok()
|
|
.and_then(|s| s.parse::<u64>().ok())
|
|
.unwrap_or(30);
|
|
let cutoff = SystemTime::now()
|
|
.checked_sub(Duration::from_secs(expire_days * 86_400))
|
|
.unwrap_or(SystemTime::UNIX_EPOCH);
|
|
let mut total = 0usize;
|
|
let mut old = 0usize;
|
|
if let Ok(entries) = fs::read_dir(&sessions_dir) {
|
|
for entry in entries.flatten() {
|
|
if entry.file_type().map(|t| t.is_file()).unwrap_or(false) {
|
|
total += 1;
|
|
if let Ok(meta) = entry.metadata() {
|
|
if let Ok(modified) = meta.modified() {
|
|
if modified < cutoff {
|
|
old += 1;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if total == 0 {
|
|
checks.push(Check::new(
|
|
"security.state_count",
|
|
category,
|
|
Status::Info,
|
|
"No saved state files",
|
|
));
|
|
} else if old > 0 {
|
|
checks.push(
|
|
Check::new(
|
|
"security.state_count",
|
|
category,
|
|
Status::Warn,
|
|
format!(
|
|
"{} state file(s) older than {} days ({} total)",
|
|
old, expire_days, total
|
|
),
|
|
)
|
|
.with_fix(format!(
|
|
"chrome-use state clean --older-than {}",
|
|
expire_days
|
|
)),
|
|
);
|
|
} else {
|
|
checks.push(Check::new(
|
|
"security.state_count",
|
|
category,
|
|
Status::Pass,
|
|
format!("{} saved state file(s)", total),
|
|
));
|
|
}
|
|
}
|
|
|
|
if let Ok(policy_path) = env::var("AGENT_BROWSER_ACTION_POLICY") {
|
|
let p = PathBuf::from(&policy_path);
|
|
if !p.exists() {
|
|
checks.push(
|
|
Check::new(
|
|
"security.action_policy",
|
|
category,
|
|
Status::Fail,
|
|
format!(
|
|
"AGENT_BROWSER_ACTION_POLICY points to missing file: {}",
|
|
policy_path
|
|
),
|
|
)
|
|
.with_fix("update or unset AGENT_BROWSER_ACTION_POLICY"),
|
|
);
|
|
} else {
|
|
match parse_json_file(&p) {
|
|
Ok(_) => checks.push(Check::new(
|
|
"security.action_policy",
|
|
category,
|
|
Status::Pass,
|
|
format!("Action policy: {}", policy_path),
|
|
)),
|
|
Err(e) => checks.push(
|
|
Check::new(
|
|
"security.action_policy",
|
|
category,
|
|
Status::Fail,
|
|
format!("Action policy: {}: {}", policy_path, e),
|
|
)
|
|
.with_fix(format!("edit {}", policy_path)),
|
|
),
|
|
}
|
|
}
|
|
}
|
|
}
|