file-guard

View source on GitHub

Deterministic file protection plugin that intercepts file-access tool calls and enforces path-based, multi-level policies. Companion to channel-guard , content-guard — file-guard covers the file system attack surface using pattern matching instead of ML.

How it works

Hooks into before_tool_call for file tools (read, write, edit, apply_patch) and shell tools (exec, bash):

Tool call --> before_tool_call --> path normalize --> pattern match --> allow/block
  1. Extracts file paths from tool parameters (or parses bash commands for indirect file access)
  2. Normalizes paths (resolve ~, ../, symlinks, relative paths)
  3. Matches against protection patterns using picomatch globs
  4. Returns block verdict if a protected path is accessed inappropriately

Protection levels

LevelReadWrite/EditDeleteUse case
no_accessBlockedBlockedBlockedSecrets, credentials, keys
read_onlyAllowedBlockedBlockedLock files, generated files
no_deleteAllowedAllowedBlockedGit internals, LICENSE, README

Bash command parsing

Detects indirect file access via shell commands:

CategoryCommands detected
Readcat, head, tail, less, more, grep, rg, < (input redirect)
Writesed -i, tee, >, >> (output redirects), cp/mv (destination)
Deleterm, unlink, shred
Copy/Movecp, mv — source classified as read, destination as write

Shell operators (|, &&, ||, ;) are split before parsing. Quoted paths are handled.

Known limitations:

  • Language-level file access (python3 -c "open('.env').read()") is not detected
  • file:// protocol, tar/zip archival, find -exec are not covered
  • Shell variable expansion ($HOME/.env) cannot be resolved statically
  • cp/mv detection is heuristic — complex flag combinations may confuse argument parsing

Install

cd extensions/file-guard
npm install

No model download — this plugin uses deterministic pattern matching only.

Configuration

Add to your openclaw.json:

{
  "plugins": {
    "entries": {
      "file-guard": {
        "enabled": true,
        "config": {
          "failOpen": false,
          "configPath": "./file-guard.json",
          "logBlocks": true
        }
      }
    }
  }
}

Config reference

OptionTypeDefaultDescription
configPathstring"./file-guard.json"Path to protection patterns JSON (relative to plugin dir or absolute)
failOpenbooleanfalseAllow access when config is malformed. Default: block
logBlocksbooleantrueLog blocked access attempts to gateway console
agentOverridesobject{}Per-agent config overrides. Key = agent ID, value = { configPath }

Protection config file

External JSON file defining protected patterns. Default location: file-guard.json in plugin directory.

{
  "protection_levels": {
    "no_access": {
      "description": "Blocked from all access",
      "patterns": [
        "**/.env", "**/.env.*",
        "**/.ssh/*",
        "**/.aws/credentials", "**/.aws/config",
        "**/credentials.json", "**/credentials.yaml",
        "**/*.pem", "**/*.key",
        "**/.kube/config",
        "**/secrets.yml", "**/secrets.yaml"
      ]
    },
    "read_only": {
      "description": "Read allowed, write/edit blocked",
      "patterns": [
        "**/package-lock.json", "**/yarn.lock",
        "**/pnpm-lock.yaml", "**/Cargo.lock",
        "**/poetry.lock", "**/go.sum"
      ]
    },
    "no_delete": {
      "description": "Read/edit allowed, deletion blocked",
      "patterns": [
        "**/.git/*", "**/LICENSE", "**/README.md"
      ]
    }
  }
}

Default patterns

LevelPatterns
no_access.env, .env.*, .ssh/*, .aws/credentials, .aws/config, credentials.json, credentials.yaml, *.pem, *.key, .kube/config, secrets.yml, secrets.yaml
read_onlypackage-lock.json, yarn.lock, pnpm-lock.yaml, Cargo.lock, poetry.lock, go.sum
no_delete.git/*, LICENSE, README.md

When no config file exists, these defaults are used. When config is malformed and failOpen: false, all file access is blocked.

Self-protection

The plugin hardcodes protection of its own directory and config file at no_access level for write/edit/delete operations. Read access is allowed (the plugin needs to read its own config). This prevents an agent from modifying file-guard to disable protections.

Self-protection patterns are not configurable — they are always enforced regardless of the external config file.

Per-agent overrides

Different agents can have additional protection rules:

{
  "file-guard": {
    "enabled": true,
    "config": {
      "configPath": "./file-guard.json",
      "agentOverrides": {
        "search": {
          "configPath": "./file-guard-search.json"
        }
      }
    }
  }
}

Merge semantics: Agent override configs are merged additively with the base config. Each protection level’s patterns array is unioned. Agent overrides can add patterns but cannot remove base patterns.

Guard plugin family

channel-guardcontent-guardfile-guardnetwork-guardcommand-guard
Hookmessage_receivedbefore_tool_callbefore_tool_callbefore_tool_callbefore_tool_call
MethodLLM via OpenRouterLLM via OpenRouterDeterministic patternsDeterministic regex + globRegex patterns
ProtectsInbound channelsInter-agent boundaryFile systemNetwork accessShell execution
Latency~100-500ms~500ms-2s<10ms<5ms<5ms

Testing

cd extensions/file-guard
npm install
npm test

No model download needed — tests run in <1s.

Security notes

  • Fail-closed by default — if config is malformed and failOpen: false, all file access is blocked
  • Self-protection — plugin directory and config file are hardcoded as protected; cannot be disabled via config
  • Path normalization — resolves ~, ../, and symlinks to prevent bypass via path manipulation
  • Case sensitivity — uses case-insensitive matching on macOS (case-insensitive filesystem)
  • No network calls — pure local pattern matching, no dependencies beyond picomatch
Last updated on