Forge Policy Engine Architecture: How File System Operations Are Restricted
Forge's policy engine architecture employs a three-layer defense system that converts every file system and network action into a PermissionOperation, evaluates it against declarative Policy rules, and returns Allow, Deny, or Confirm to gate potentially dangerous I/O behind explicit user approval or predefined constraints.
Forge, the open-source AI coding assistant from antinomyhq/forgecode, implements a declarative policy engine architecture to prevent unauthorized file modifications. Every potentially dangerous operation—whether reading source code, writing to disk, executing shell commands, or fetching remote URLs—is intercepted and evaluated against a configurable set of security policies. This system ensures that no file system access occurs without explicit permission logic defined in the PolicyConfig and evaluated by the PolicyEngine found in crates/forge_domain/src/policies/.
Three-Layer Policy Engine Architecture
The policy engine architecture operates through three distinct layers: operation description, policy definition, and engine evaluation.
Layer 1: Operation Description with PermissionOperation
Every dangerous action is first encoded as a PermissionOperation in crates/forge_domain/src/policies/operation.rs. This enum captures what the program intends to do, the target path or URL, and a human-readable message.
use forge_domain::PermissionOperation;
let op = PermissionOperation::Write {
path: std::path::PathBuf::from("src/main.rs"),
cwd: std::path::PathBuf::from("/repo"),
message: "Create/overwrite src/main.rs".into(),
};
The four operation variants are Read, Write, Execute, and Fetch. Each carries sufficient context—including current working directory and target paths—for the engine to apply glob-based matching rules.
Layer 2: Policy Definition and Rule Composition
Policies declare when an operation is permitted, denied, or requires confirmation. Defined in crates/forge_domain/src/policies/policy.rs, the Policy enum supports simple rules and logical composition.
use forge_domain::{Policy, Permission, Rule, WriteRule};
let policy = Policy::Simple {
permission: Permission::Allow,
rule: Rule::Write(WriteRule {
write: "*.rs".into(), // glob pattern
dir: None,
}),
};
The architecture supports complex logic through All, Any, and Not combinators:
let combined = Policy::All {
all: vec![
policy.clone(),
Policy::Simple {
permission: Permission::Deny,
rule: Rule::Write(WriteRule { write: "**/*.secret".into(), dir: None })
},
],
};
Concrete rule structs (ReadRule, WriteRule, ExecuteRule, Fetch) live in crates/forge_domain/src/policies/rule.rs and implement the glob-matching logic against operation paths, command strings, or hostnames.
Layer 3: Engine Evaluation and Decision
The PolicyEngine in crates/forge_domain/src/policies/engine.rs evaluates operations against a PolicyConfig (defined in crates/forge_domain/src/policies/config.rs). The engine traverses the policy set and applies specific resolution logic:
- Short-circuits on the first Deny or Confirm result
- Tracks the last Allow encountered when multiple policies permit the operation
- Returns Confirm when the policy set is empty or no rules match
use forge_domain::{PolicyEngine, PolicyConfig};
let policies = PolicyConfig::new().add_policy(policy);
let engine = PolicyEngine::new(&policies);
match engine.can_perform(&op) {
Permission::Allow => println!("✅ operation permitted"),
Permission::Deny => println!("❌ operation denied"),
Permission::Confirm => println!("⚠️ ask user for confirmation"),
}
How File System Restrictions Are Enforced
The policy engine architecture restricts I/O through pattern matching and default-deny semantics, ensuring Forge never performs unchecked operations.
Read and Write Restrictions
File system read and write operations are gated by glob patterns defined in ReadRule and WriteRule. If an operation's path does not match any rule, the engine defaults to Confirm, forcing a user prompt before proceeding.
Execute Command Sanitization
Shell commands are converted to glob patterns (e.g., "git push*") and matched against ExecuteRule definitions. Only commands matching an explicit Allow rule execute automatically; others trigger the confirmation flow.
Network Fetch Control
URL access is restricted by hostname matching (e.g., "example.com*"). The Fetch rule type in rule.rs evaluates network requests against these patterns, denying or confirming access to unauthorized domains.
Service Integration and Persistence
The ForgePolicyService in crates/forge_services/src/policy.rs provides the high-level integration layer. It loads a default policy collection from the embedded permissions.default.yaml resource and persists user-added policies to $HOME/.forge/policies.yml.
When the engine returns Confirm, the service prompts the user via the UserInfra trait. If the user selects Accept and Remember, the service automatically appends a new allowing policy to the user's configuration file, updating the PolicyConfig for future operations.
use std::sync::Arc;
use forge_services::ForgePolicyService;
#[tokio::main]
async fn main() -> anyhow::Result<()> {
let infra = Arc::new(LocalFileInfra::new()?);
let policy_service = ForgePolicyService::new(infra.clone());
let decision = policy_service.check_operation_permission(&op).await?;
println!("Allowed? {}", decision.allowed);
Ok(())
}
Practical Implementation Examples
Direct Engine Usage
use forge_domain::{
PermissionOperation, PolicyEngine, PolicyConfig,
Permission, Policy, Rule, WriteRule,
};
let write_op = PermissionOperation::Write {
path: std::path::PathBuf::from("src/lib.rs"),
cwd: std::path::PathBuf::from("/repo"),
message: "Write src/lib.rs".into(),
};
let config = PolicyConfig::new().add_policy(Policy::Simple {
permission: Permission::Allow,
rule: Rule::Write(WriteRule { write: "src/**/*.rs".into(), dir: None }),
});
let engine = PolicyEngine::new(&config);
assert_eq!(engine.can_perform(&write_op), Permission::Allow);
Complex Policy Logic
use forge_domain::{Policy, Permission, Rule, ReadRule, WriteRule};
let restrictive_policy = Policy::All {
all: vec![
Policy::Simple {
permission: Permission::Allow,
rule: Rule::Read(ReadRule { read: "src/**/*.rs".into(), dir: None }),
},
Policy::Simple {
permission: Permission::Deny,
rule: Rule::Write(WriteRule { write: "**/*.toml".into(), dir: None }),
},
],
};
Summary
- Forge's policy engine architecture isolates dangerous operations through a three-layer system:
PermissionOperationdescription,Policydefinition, andPolicyEngineevaluation. - The engine supports logical composition via
All,Any, andNotcombinators, allowing complex security rules incrates/forge_domain/src/policies/policy.rs. - File system restrictions rely on glob pattern matching in
rule.rs, with default behavior set toConfirmwhen no policies match. ForgePolicyServicemanages persistence between embedded defaults (permissions.default.yaml) and user configurations (~/.forge/policies.yml).- Every I/O operation returns one of three permissions: Allow, Deny, or Confirm, ensuring explicit authorization before any file system modification.
Frequently Asked Questions
What are the three possible outcomes when Forge's policy engine evaluates an operation?
According to the source code in crates/forge_domain/src/policies/engine.rs, the PolicyEngine returns an enum with three variants: Allow (permit the operation immediately), Deny (block the operation), or Confirm (suspend execution and prompt the user for explicit approval). This design ensures that potentially dangerous actions never proceed without explicit authorization when predefined rules do not apply.
How does Forge resolve conflicts when multiple policies apply to a single operation?
The evaluation logic in engine.rs implements specific precedence rules: the engine short-circuits on the first Deny or Confirm result it encounters. If multiple Allow policies match the same operation, the engine collapses them and uses the last Allow seen in the evaluation order. This deterministic resolution prevents ambiguous security states and ensures predictable behavior.
Where does Forge store user-defined security policies?
User-customized policies are persisted to $HOME/.forge/policies.yml by the ForgePolicyService in crates/forge_services/src/policy.rs. The service loads these at runtime alongside the embedded permissions.default.yaml, merging them into a single PolicyConfig that the engine evaluates against incoming operations.
What happens if no policy rules match a requested file system operation?
When the PolicyConfig contains no matching rules or is completely empty, the PolicyEngine::can_perform method defaults to returning Confirm. This fail-safe mechanism, implemented in crates/forge_domain/src/policies/engine.rs, forces the user interface to prompt for explicit approval rather than allowing unchecked file system access, maintaining the security invariant that all I/O must be explicitly permitted.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →