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

  1. Security boundary -- agent cannot modify its own constraints
  2. Performance -- no runtime interpretation overhead in the WASM plugin
  3. Reproducibility -- same binary = same behavior (Nix builds)
  4. Audit -- filter code is version-controlled in the cowboy repo