feat: align browser callback runtime and export flows
Consolidate the browser task runtime around the callback path, add safer artifact opening for Zhihu exports, and cover the new service/browser flows with focused tests and supporting docs. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -25,6 +25,8 @@ pub struct PipeActionRules {
|
||||
pub blocked: Vec<String>,
|
||||
}
|
||||
|
||||
const LOCAL_DASHBOARD_EXPECTED_DOMAIN: &str = "__sgclaw_local_dashboard__";
|
||||
|
||||
impl MacPolicy {
|
||||
pub fn load_from_path(path: impl AsRef<Path>) -> Result<Self, SecurityError> {
|
||||
let contents = fs::read_to_string(path)?;
|
||||
@@ -91,6 +93,64 @@ impl MacPolicy {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn validate_local_dashboard_presentation(
|
||||
&self,
|
||||
action: &Action,
|
||||
expected_domain: &str,
|
||||
presentation_url: &str,
|
||||
output_path: &str,
|
||||
) -> Result<(), SecurityError> {
|
||||
let action_name = action.as_str();
|
||||
if self
|
||||
.pipe_actions
|
||||
.blocked
|
||||
.iter()
|
||||
.any(|blocked| blocked == action_name)
|
||||
{
|
||||
return Err(SecurityError::ActionNotAllowed(action_name.to_string()));
|
||||
}
|
||||
|
||||
if !self
|
||||
.pipe_actions
|
||||
.allowed
|
||||
.iter()
|
||||
.any(|allowed| allowed == action_name)
|
||||
{
|
||||
return Err(SecurityError::ActionNotAllowed(action_name.to_string()));
|
||||
}
|
||||
|
||||
if action != &Action::Navigate {
|
||||
return Err(SecurityError::InvalidLocalDashboard(
|
||||
"local dashboard open only supports navigate".to_string(),
|
||||
));
|
||||
}
|
||||
if expected_domain != LOCAL_DASHBOARD_EXPECTED_DOMAIN {
|
||||
return Err(SecurityError::InvalidLocalDashboard(
|
||||
"local dashboard expected_domain is invalid".to_string(),
|
||||
));
|
||||
}
|
||||
if !presentation_url.starts_with("file:///") {
|
||||
return Err(SecurityError::InvalidLocalDashboard(
|
||||
"local dashboard presentation_url must be file:///".to_string(),
|
||||
));
|
||||
}
|
||||
if !output_path.to_ascii_lowercase().ends_with(".html") {
|
||||
return Err(SecurityError::InvalidLocalDashboard(
|
||||
"local dashboard output_path must point to .html".to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
let normalized_output = normalize_local_dashboard_path(output_path);
|
||||
let normalized_presentation = normalize_local_dashboard_file_url(presentation_url)?;
|
||||
if normalized_output != normalized_presentation {
|
||||
return Err(SecurityError::InvalidLocalDashboard(
|
||||
"local dashboard presentation_url does not match output_path".to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn privileged_surface_metadata(&self) -> ExecutionSurfaceMetadata {
|
||||
let mut metadata = ExecutionSurfaceMetadata::privileged_browser_pipe("mac_policy");
|
||||
metadata.allowed_domains = self.domains.allowed.clone();
|
||||
@@ -130,3 +190,19 @@ fn normalize_domain(raw: &str) -> String {
|
||||
.unwrap_or_default()
|
||||
.to_ascii_lowercase()
|
||||
}
|
||||
|
||||
fn normalize_local_dashboard_path(raw: &str) -> String {
|
||||
raw.trim().replace('\\', "/").to_ascii_lowercase()
|
||||
}
|
||||
|
||||
fn normalize_local_dashboard_file_url(raw: &str) -> Result<String, SecurityError> {
|
||||
let path = raw
|
||||
.trim()
|
||||
.strip_prefix("file:///")
|
||||
.ok_or_else(|| {
|
||||
SecurityError::InvalidLocalDashboard(
|
||||
"local dashboard presentation_url must be file:///".to_string(),
|
||||
)
|
||||
})?;
|
||||
Ok(normalize_local_dashboard_path(path))
|
||||
}
|
||||
|
||||
@@ -14,6 +14,8 @@ pub enum SecurityError {
|
||||
ActionNotAllowed(String),
|
||||
#[error("domain is not allowed: {0}")]
|
||||
DomainNotAllowed(String),
|
||||
#[error("invalid local dashboard request: {0}")]
|
||||
InvalidLocalDashboard(String),
|
||||
#[error("invalid rules: {0}")]
|
||||
InvalidRules(String),
|
||||
#[error("hmac error: {0}")]
|
||||
|
||||
Reference in New Issue
Block a user