pub struct SecurityPolicy {Show 22 fields
pub autonomy: AutonomyLevel,
pub workspace_dir: PathBuf,
pub workspace_only: bool,
pub allowed_commands: Vec<String>,
pub forbidden_paths: Vec<String>,
pub allowed_roots: Vec<PathBuf>,
pub allowed_roots_read_only: Vec<PathBuf>,
pub allowed_roots_write_only: Vec<PathBuf>,
pub max_actions_per_hour: u32,
pub max_cost_per_day_cents: u32,
pub require_approval_for_medium_risk: bool,
pub block_high_risk_commands: bool,
pub shell_env_passthrough: Vec<String>,
pub shell_timeout_secs: u64,
pub allowed_tools: Option<Vec<String>>,
pub excluded_tools: Option<Vec<String>>,
pub auto_approve: Vec<String>,
pub always_ask: Vec<String>,
pub sandbox_enabled: Option<bool>,
pub sandbox_backend: Option<String>,
pub firejail_args: Vec<String>,
pub tracker: PerSenderTracker,
}Expand description
Security policy enforced on all tool executions.
Three cross-agent allowlist tiers drive the multi-agent design:
allowed_roots: read AND write. Populated fromRiskProfileConfig.allowed_rootsand fromAccessMode::ReadWritegrants inagent.workspace.access.allowed_roots_read_only: read but NOT write. Populated fromAccessMode::Readgrants.allowed_roots_write_only: write but NOT read. Populated fromAccessMode::Writegrants. The bot can append/overwrite under the path butfile_read/pdf_read/glob_search/content_searchreject it.
Read-side tools call SecurityPolicy::is_resolved_path_readable,
which sees allowed_roots ∪ allowed_roots_read_only plus the
universal POSIX device files. Write-side tools call
SecurityPolicy::is_resolved_path_allowed, which sees
allowed_roots ∪ allowed_roots_write_only. The two tiers stay
disjoint by construction so AccessMode::Write and
AccessMode::Read grant exactly what they say.
Fields§
§autonomy: AutonomyLevel§workspace_dir: PathBuf§workspace_only: bool§allowed_commands: Vec<String>§forbidden_paths: Vec<String>§allowed_roots: Vec<PathBuf>Directories the agent can read AND write under. Includes
RiskProfileConfig.allowed_roots plus any cross-agent
AccessMode::ReadWrite grants resolved from
agent.workspace.access at policy construction time.
allowed_roots_read_only: Vec<PathBuf>Directories the agent can read but NOT write under. Populated
from cross-agent AccessMode::Read grants at policy
construction time. Empty when no read-only cross-agent access
is configured.
allowed_roots_write_only: Vec<PathBuf>Directories the agent can write but NOT read under. Populated
from cross-agent AccessMode::Write grants at policy
construction time. Empty when no write-only cross-agent access
is configured. Read-side tools (file_read, pdf_read,
glob_search, content_search) ignore this list; write-side
tools (file_write, file_edit, git_operations) honor it.
max_actions_per_hour: u32§max_cost_per_day_cents: u32§require_approval_for_medium_risk: bool§block_high_risk_commands: bool§shell_env_passthrough: Vec<String>§shell_timeout_secs: u64§allowed_tools: Option<Vec<String>>Tool name allowlist. None is unrestricted (default for agents
without an explicit risk_profile.allowed_tools setting).
Some(vec![]) denies every tool. Some(list) admits only the
listed names. Enforced at the agent loop’s tool-dispatch site.
excluded_tools: Option<Vec<String>>Tool name denylist. Subtracts from the allowed set (whether the
allowed set comes from allowed_tools or from the unrestricted
default). None and Some(vec![]) both mean “exclude nothing”.
auto_approve: Vec<String>Tools that never require approval in this profile. Mirrors
RiskProfileConfig.auto_approve.
always_ask: Vec<String>Tools that always require approval in this profile. Mirrors
RiskProfileConfig.always_ask.
sandbox_enabled: Option<bool>Whether the sandbox is enabled for this profile. None
inherits the global default at the call site.
sandbox_backend: Option<String>Sandbox backend identifier (e.g. "firejail", "landlock").
None inherits the global default.
firejail_args: Vec<String>Extra arguments forwarded to firejail when sandbox_backend
resolves to "firejail".
tracker: PerSenderTrackerImplementations§
Source§impl SecurityPolicy
impl SecurityPolicy
Sourcepub fn is_tool_allowed(&self, name: &str) -> bool
pub fn is_tool_allowed(&self, name: &str) -> bool
True when name is admissible under the current policy.
allowed_tools = None is unrestricted; Some(list) is the
allowlist. excluded_tools always subtracts.
Source§impl SecurityPolicy
impl SecurityPolicy
Sourcepub fn command_risk_level(&self, command: &str) -> CommandRiskLevel
pub fn command_risk_level(&self, command: &str) -> CommandRiskLevel
Classify command risk. Any high-risk segment marks the whole command high.
Sourcepub fn validate_command_execution(
&self,
command: &str,
approved: bool,
) -> Result<CommandRiskLevel, String>
pub fn validate_command_execution( &self, command: &str, approved: bool, ) -> Result<CommandRiskLevel, String>
Validate full command execution policy (allowlist + risk gate).
Sourcepub fn is_command_allowed(&self, command: &str) -> bool
pub fn is_command_allowed(&self, command: &str) -> bool
Check if a shell command is allowed.
Validates the entire command string, not just the first word:
- Blocks subshell operators (
`,$() that hide arbitrary execution - Splits on command separators (
|,&&,||,;, newlines) and validates each sub-command against the allowlist - Blocks single
&background chaining (&&remains supported) - Blocks shell redirections (
<,>,>>) that can bypass path policy - Blocks dangerous arguments (e.g.
find -exec,git config)
Sourcepub fn forbidden_path_argument(&self, command: &str) -> Option<String>
pub fn forbidden_path_argument(&self, command: &str) -> Option<String>
Return the first path-like argument blocked by path policy.
This is best-effort token parsing for shell commands and is intended as a safety gate before command execution.
Sourcepub fn is_path_allowed(&self, path: &str) -> bool
pub fn is_path_allowed(&self, path: &str) -> bool
Check if a file path is allowed (no path traversal, within workspace)
Sourcepub fn is_resolved_path_readable(&self, resolved: &Path) -> bool
pub fn is_resolved_path_readable(&self, resolved: &Path) -> bool
Validate that a resolved path is readable by the current
security policy. Used by read-side tools (file_read,
pdf_read, glob_search, content_search) that should honor
the read-write allowed_roots AND the read-only
allowed_roots_read_only lists, plus the universal POSIX
device files (/dev/null, /dev/zero, /dev/random,
/dev/urandom) that operators legitimately use for shell-
idiom CLI commands and standard input/output redirection.
Importantly: this method does NOT consult
allowed_roots_write_only. AccessMode::Write grants write
access without read access; surfacing those paths through a
read-side tool would silently elevate the grant.
Write-side tools (file_write, file_edit,
git_operations, shell write paths) call
Self::is_resolved_path_allowed instead.
Sourcepub fn is_resolved_path_allowed(&self, resolved: &Path) -> bool
pub fn is_resolved_path_allowed(&self, resolved: &Path) -> bool
Validate that a resolved path is inside the workspace or an
allowed root for write-side tools. Call this AFTER joining
workspace_dir + relative path and canonicalizing.
Sees allowed_roots (read+write) AND
allowed_roots_write_only (write-only). Read-only allowlist
entries are NOT honored; that’s the read-side tier.
pub fn is_runtime_config_path(&self, resolved: &Path) -> bool
pub fn runtime_config_violation_message(&self, resolved: &Path) -> String
pub fn resolved_path_violation_message(&self, resolved: &Path) -> String
Sourcepub fn enforce_tool_operation(
&self,
operation: ToolOperation,
operation_name: &str,
) -> Result<(), String>
pub fn enforce_tool_operation( &self, operation: ToolOperation, operation_name: &str, ) -> Result<(), String>
Enforce policy for a tool operation.
Read operations are always allowed by autonomy/rate gates. Act operations require non-readonly autonomy and available action budget.
Sourcepub fn record_action(&self) -> bool
pub fn record_action(&self) -> bool
Record an action for the current sender and check if rate-limited.
Returns true if allowed, false if budget exhausted.
Sourcepub fn is_rate_limited(&self) -> bool
pub fn is_rate_limited(&self) -> bool
Check if the current sender would be rate-limited without recording.
Sourcepub fn resolve_tool_path(&self, path: &str) -> PathBuf
pub fn resolve_tool_path(&self, path: &str) -> PathBuf
Resolve a user-provided path for tool use.
Expands ~ prefixes and resolves relative paths against the workspace
directory. This should be called after is_path_allowed to obtain
the filesystem path that the tool actually operates on.
Sourcepub fn is_under_allowed_root(&self, path: &str) -> bool
pub fn is_under_allowed_root(&self, path: &str) -> bool
Check whether the given raw path (before canonicalization)
falls under an allowed_roots (read+write) OR
allowed_roots_write_only entry. Tilde expansion is applied to
the path before comparison. This is useful for tool-level
pre-checks that want to allow absolute paths the policy
explicitly permits to write.
Write-side semantics. Use this from write-side tools
(file_write, git_operations, shell). Read-side tools
should use Self::is_under_any_allowed_root so a cross-agent
AccessMode::Read grant allows the read.
Sourcepub fn is_under_read_only_allowed_root(&self, path: &str) -> bool
pub fn is_under_read_only_allowed_root(&self, path: &str) -> bool
Check whether the given raw path falls under a read-only allowed
root. Returns false for the read-write list; callers that want
the union should use Self::is_under_any_allowed_root.
Populated for multi-agent: an agent’s workspace.access
entries with AccessMode::Read become read-only roots on the
policy.
Sourcepub fn is_under_any_allowed_root(&self, path: &str) -> bool
pub fn is_under_any_allowed_root(&self, path: &str) -> bool
Check whether the given raw path falls under
allowed_roots (rw), allowed_roots_read_only, OR
allowed_roots_write_only. Read-side tools (file_read,
pdf_read, glob_search, content_search) call
Self::is_resolved_path_readable for the resolved-path form,
which intentionally excludes the write-only tier. This raw-path
helper is the union of all three, used where read+write tools
share an entry point and the resolved-path check splits the
directionality afterward.
Sourcepub fn ensure_no_escalation_beyond(
&self,
parent: &SecurityPolicy,
) -> Result<(), EscalationViolation>
pub fn ensure_no_escalation_beyond( &self, parent: &SecurityPolicy, ) -> Result<(), EscalationViolation>
Verify this policy does not escalate any permission beyond
parent (SubAgent inheritance subset check).
Subset rules:
- Every
allowed_rootsentry onselfmust appear onparent.allowed_roots. (Read+write grants can never be wider than the parent’s read+write list.) - Every
allowed_roots_read_onlyentry onselfmust appear onparent.allowed_rootsOR onparent.allowed_roots_read_only. (A SubAgent can downgrade a parent’s rw root to read-only, but it cannot grant read access to a path the parent could not even read.) - Every
allowed_commandsentry onselfmust appear onparent.allowed_commands. self.workspace_onlymust betruewheneverparent.workspace_onlyistrue. A SubAgent cannot disable workspace_only when the parent enforces it.self.max_actions_per_hour <= parent.max_actions_per_hourandself.max_cost_per_day_cents <= parent.max_cost_per_day_cents. A SubAgent cannot raise the parent’s rate or cost ceiling.
Returns Err(EscalationViolation) describing the first
violation found. Callers should reject the spawn on Err so
a misconfigured override never lands as a constructed policy.
Sourcepub fn from_risk_profile(
risk_profile: &RiskProfileConfig,
workspace_dir: &Path,
) -> Self
pub fn from_risk_profile( risk_profile: &RiskProfileConfig, workspace_dir: &Path, ) -> Self
Legacy entry point: build a SecurityPolicy from a risk profile
without a runtime profile. Budget caps default to zero (interpreted
as “no enforcement”). Tests and pre-multi-agent callsites use this;
production code should call from_profiles or for_agent so the
runtime profile’s budget caps actually take effect.
Sourcepub fn from_profiles(
risk_profile: &RiskProfileConfig,
runtime_profile: Option<&RuntimeProfileConfig>,
workspace_dir: &Path,
) -> Self
pub fn from_profiles( risk_profile: &RiskProfileConfig, runtime_profile: Option<&RuntimeProfileConfig>, workspace_dir: &Path, ) -> Self
Build a SecurityPolicy from a resolved risk + runtime profile pair.
Authorization fields (autonomy level, allowlists, sandbox) come from
the risk profile. Budget caps (max_actions_per_hour,
max_cost_per_day_cents, shell_timeout_secs) come from the
runtime profile but are enforced with parent-subset discipline on
SubAgent spawn (see ensure_no_escalation_beyond).
Sourcepub fn for_agent(config: &Config, agent_alias: &str) -> Result<Self>
pub fn for_agent(config: &Config, agent_alias: &str) -> Result<Self>
Resolve the risk + runtime profiles owned by agent_alias and build
a SecurityPolicy. Bails when the agent isn’t configured or when its
risk_profile field doesn’t name a configured profile — there is no
global fallback, every security context is per-agent. Missing
runtime_profile falls back to zero budgets (treated as “inherit /
no enforcement”), matching the previous default when the budget
fields lived on the risk profile.
Sourcepub fn prompt_summary(&self) -> String
pub fn prompt_summary(&self) -> String
Render a human-readable summary of the active security constraints suitable for injection into the LLM system prompt.
Giving the LLM visibility into these constraints prevents it from wasting tokens on commands / paths that will be rejected at runtime. See issue #2404.
Trait Implementations§
Source§impl Clone for SecurityPolicy
impl Clone for SecurityPolicy
Source§fn clone(&self) -> SecurityPolicy
fn clone(&self) -> SecurityPolicy
1.0.0 · Source§fn clone_from(&mut self, source: &Self)
fn clone_from(&mut self, source: &Self)
source. Read moreSource§impl Debug for SecurityPolicy
impl Debug for SecurityPolicy
Auto Trait Implementations§
impl Freeze for SecurityPolicy
impl !RefUnwindSafe for SecurityPolicy
impl Send for SecurityPolicy
impl Sync for SecurityPolicy
impl Unpin for SecurityPolicy
impl UnsafeUnpin for SecurityPolicy
impl !UnwindSafe for SecurityPolicy
Blanket Implementations§
Source§impl<T> BorrowMut<T> for Twhere
T: ?Sized,
impl<T> BorrowMut<T> for Twhere
T: ?Sized,
Source§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
Source§impl<T> CloneToUninit for Twhere
T: Clone,
impl<T> CloneToUninit for Twhere
T: Clone,
§impl<T> Instrument for T
impl<T> Instrument for T
§fn instrument(self, span: Span) -> Instrumented<Self>
fn instrument(self, span: Span) -> Instrumented<Self>
§fn in_current_span(self) -> Instrumented<Self>
fn in_current_span(self) -> Instrumented<Self>
Source§impl<T> IntoEither for T
impl<T> IntoEither for T
Source§fn into_either(self, into_left: bool) -> Either<Self, Self>
fn into_either(self, into_left: bool) -> Either<Self, Self>
self into a Left variant of Either<Self, Self>
if into_left is true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read moreSource§fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
self into a Left variant of Either<Self, Self>
if into_left(&self) returns true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read more