file-guard
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- Extracts file paths from tool parameters (or parses bash commands for indirect file access)
- Normalizes paths (resolve
~,../, symlinks, relative paths) - Matches against protection patterns using
picomatchglobs - Returns block verdict if a protected path is accessed inappropriately
Protection levels
| Level | Read | Write/Edit | Delete | Use case |
|---|---|---|---|---|
no_access | Blocked | Blocked | Blocked | Secrets, credentials, keys |
read_only | Allowed | Blocked | Blocked | Lock files, generated files |
no_delete | Allowed | Allowed | Blocked | Git internals, LICENSE, README |
Bash command parsing
Detects indirect file access via shell commands:
| Category | Commands detected |
|---|---|
| Read | cat, head, tail, less, more, grep, rg, < (input redirect) |
| Write | sed -i, tee, >, >> (output redirects), cp/mv (destination) |
| Delete | rm, unlink, shred |
| Copy/Move | cp, 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/ziparchival,find -execare not covered- Shell variable expansion (
$HOME/.env) cannot be resolved statically cp/mvdetection is heuristic — complex flag combinations may confuse argument parsing
Install
cd extensions/file-guard
npm installNo 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
| Option | Type | Default | Description |
|---|---|---|---|
configPath | string | "./file-guard.json" | Path to protection patterns JSON (relative to plugin dir or absolute) |
failOpen | boolean | false | Allow access when config is malformed. Default: block |
logBlocks | boolean | true | Log blocked access attempts to gateway console |
agentOverrides | object | {} | 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
| Level | Patterns |
|---|---|
no_access | .env, .env.*, .ssh/*, .aws/credentials, .aws/config, credentials.json, credentials.yaml, *.pem, *.key, .kube/config, secrets.yml, secrets.yaml |
read_only | package-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-guard | content-guard | file-guard | network-guard | command-guard | |
|---|---|---|---|---|---|
| Hook | message_received | before_tool_call | before_tool_call | before_tool_call | before_tool_call |
| Method | LLM via OpenRouter | LLM via OpenRouter | Deterministic patterns | Deterministic regex + glob | Regex patterns |
| Protects | Inbound channels | Inter-agent boundary | File system | Network access | Shell execution |
| Latency | ~100-500ms | ~500ms-2s | <10ms | <5ms | <5ms |
Testing
cd extensions/file-guard
npm install
npm testNo 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