use super::*;
pub const SOURCE_DEFAULT_SENSITIVE_PATH: &str = "default_sensitive_path";
pub const SOURCE_DEFAULT_PATH_GUARD: &str = "default_path_guard";
pub const SOURCE_DEFAULT_EXTERNAL_PATH: &str = "default_external_path";
pub const SOURCE_NET_POLICY: &str = "harn.net_policy";
pub fn denial_gate_for_source(source: Option<&str>) -> crate::agent_events::DenialGate {
use crate::agent_events::DenialGate;
match source {
Some(SOURCE_DEFAULT_SENSITIVE_PATH) => DenialGate::SensitivePath,
Some(SOURCE_NET_POLICY) => DenialGate::NetworkPolicy,
Some(SOURCE_DEFAULT_PATH_GUARD) | Some(SOURCE_DEFAULT_EXTERNAL_PATH) => {
DenialGate::WorkspaceBoundary
}
_ => DenialGate::ApprovalPolicy,
}
}
pub(super) fn default_guard(
policy: &ToolApprovalPolicy,
ctx: &EvaluationContext,
) -> Option<Candidate> {
if !policy.allow_sensitive_paths {
if let Some(path) = sensitive_paths::first_candidate(policy, &ctx.path_candidates) {
let path = sensitive_paths::bounded_evidence(&path);
return Some(Candidate {
source: SOURCE_DEFAULT_SENSITIVE_PATH.to_string(),
source_rank: PolicyRuleSource::Policy,
index: None,
id: Some("sensitive_path".to_string()),
action: PolicyAction::Deny,
reason: format!("path '{path}' is denied by the sensitive-path default"),
approval: ApprovalShape::default(),
risk_labels: vec!["sensitive_path".to_string()],
denied_paths: vec![path],
});
}
}
if !policy.allow_external_paths {
for entry in &ctx.path_entries {
if matches!(entry.kind, WorkspacePathKind::Invalid) {
return Some(Candidate {
source: SOURCE_DEFAULT_PATH_GUARD.to_string(),
source_rank: PolicyRuleSource::Policy,
index: None,
id: Some("invalid_path".to_string()),
action: PolicyAction::Deny,
reason: entry
.reason
.clone()
.unwrap_or_else(|| format!("path '{}' is invalid", entry.display_path())),
approval: ApprovalShape::default(),
risk_labels: vec!["invalid_path".to_string()],
denied_paths: vec![entry.display_path().to_string()],
});
}
if entry.workspace_path.is_none()
&& entry
.host_path
.as_ref()
.is_some_and(|path| !under_external_root(path, &policy.external_roots))
{
return Some(Candidate {
source: SOURCE_DEFAULT_EXTERNAL_PATH.to_string(),
source_rank: PolicyRuleSource::Policy,
index: None,
id: Some("external_path".to_string()),
action: PolicyAction::Deny,
reason: format!(
"path '{}' is outside the workspace and no external root allows it",
entry.display_path()
),
approval: ApprovalShape::default(),
risk_labels: vec!["external_path".to_string()],
denied_paths: vec![entry.display_path().to_string()],
});
}
}
}
None
}
impl PolicyEvaluation {
pub fn denial_gate(&self) -> crate::agent_events::DenialGate {
denial_gate_for_source(self.matched_rule.as_ref().map(|rule| rule.source.as_str()))
}
pub fn terminal_denial(&self) -> crate::agent_events::ToolDenial {
let mut denial = crate::agent_events::ToolDenial::terminal(
self.denial_gate(),
None,
self.reason.clone(),
);
denial.denied_paths = self.denied_paths.clone();
denial.denied_network_targets = self.denied_network_targets.clone();
denial
}
}