title: Filters tags: [filters, security, middleware, compiled]
Filters
Compiled Rust middleware that gates agent behavior. Filters form a security boundary that the agent cannot modify at runtime.
Architecture
Filters are compiled into the cowboy harness (Zellij WASM plugin). They execute before/after tool calls and after API responses. The agent has no mechanism to alter filter behavior -- they are part of the trusted codebase.
Filter Trait
#![allow(unused)] fn main() { pub trait Filter: Send + Sync { fn name(&self) -> &str; fn description(&self) -> &str; fn before_tool(&self, tool_name: &str, args: &Value) -> FilterAction; fn after_tool(&self, tool_name: &str, result: &mut String) -> FilterAction; fn after_response(&self, response: &mut String) -> FilterAction; } pub enum FilterAction { Allow, Block(String), RequireApproval(String), Transform(Value), } }
Pipeline
Filters compose in sequence. First non-Allow action short-circuits:
#![allow(unused)] fn main() { pub struct FilterPipeline { filters: Vec<Box<dyn Filter>>, } }
For before_tool, Block and RequireApproval short-circuit immediately. Transform actions accumulate -- each subsequent filter sees the transformed arguments. If any transforms occurred, the pipeline returns the final Transform(new_args).
For after_tool and after_response, filters mutate the result/response string in place. First non-Allow action short-circuits.
Built-in Filters
SecurityFilter
Blocks dangerous commands and redacts secrets from output.
Dangerous patterns blocked:
rm -rf /,rm -fr /(recursive delete from root)- Fork bombs (
:(){ :|:& };:) - Direct disk operations (
dd if=,mkfs, writes to/dev/sd*) shred /,chmod -R 777 /, boot overwrites,iptables -F
Secret patterns redacted (replaced with [REDACTED]):
- Anthropic API keys (
sk-ant-*) - OpenAI API keys (
sk-proj-*,sk-*) - Discord bot tokens and webhook URLs
- Exa API keys (UUID format)
- GitHub tokens (
ghp_,gho_,ghu_,ghs_,ghr_) - AWS access keys (
AKIA*) and secrets - Generic
api_key=,secret=,token=,password=patterns - Bearer tokens
- PEM private keys
WorkspaceFilter
Restricts file operations to allowed paths. Default allowed paths: /home/agent, /tmp, /var/tmp. Relative paths are always allowed (assumed relative to cwd).
Path checking uses canonicalize() to resolve symlinks before comparison.
AuditFilter
Logs all tool calls to an append-only JSONL file. Entries include timestamp, tool name, arguments (for before_tool) and truncated result preview (for after_tool). API responses are also logged (truncated to 500 chars).
Default Pipelines
#![allow(unused)] fn main() { // Standard: security + audit fn default_pipeline() -> FilterPipeline; // Restricted: security + workspace + audit fn restricted_pipeline(allowed_paths: Vec<PathBuf>) -> FilterPipeline; }
Nix Configuration
Filters are configured declaratively in the cowboy NixOS module. The Nix layer generates filters.json and shell-script wrappers (agent-filter, agent-audit-log) installed to ~/.config/agent/ and ~/.local/bin/.
services.agent.filters = {
security = {
enable = true;
blockedPatterns = [ "rm\\s+-rf\\s+/" ... ];
secretPatterns = [ "ANTHROPIC_API_KEY" ... ];
};
workspace = {
enable = true;
allowedPaths = [ cfg.homeDirectory "/tmp" "/nix/store" ];
};
audit = {
enable = true;
logPath = "${cfg.homeDirectory}/audit";
retentionDays = 30;
logLevel = "standard"; # minimal | standard | verbose
};
};
Audit log cleanup runs as a daily systemd timer, removing JSONL files older than retentionDays.
Two-Library Split
Filters are part of cowboy (the platform). They are compiled into the harness binary and configured via NixOS modules. agent-pkgs (bridge services like discord, email) do not interact with filters directly -- their outbound messages pass through the proxy/approval layer instead (see security.md).
Why Compiled Rust
- Security boundary -- agent cannot modify its own constraints
- Performance -- no runtime interpretation overhead in the WASM plugin
- Reproducibility -- same binary = same behavior (Nix builds)
- Audit -- filter code is version-controlled in the cowboy repo