agentwall 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (33) hide show
  1. agentwall-0.1.0/.gitignore +5 -0
  2. agentwall-0.1.0/PKG-INFO +642 -0
  3. agentwall-0.1.0/PLAN.md +1023 -0
  4. agentwall-0.1.0/docs/README.md +612 -0
  5. agentwall-0.1.0/pyproject.toml +53 -0
  6. agentwall-0.1.0/setup.sh +154 -0
  7. agentwall-0.1.0/src/agentfirewall/__init__.py +3 -0
  8. agentwall-0.1.0/src/agentfirewall/agents/__init__.py +0 -0
  9. agentwall-0.1.0/src/agentfirewall/audit.py +97 -0
  10. agentwall-0.1.0/src/agentfirewall/cli.py +379 -0
  11. agentwall-0.1.0/src/agentfirewall/engine.py +199 -0
  12. agentwall-0.1.0/src/agentfirewall/hooks/__init__.py +0 -0
  13. agentwall-0.1.0/src/agentfirewall/hooks/shell.py +123 -0
  14. agentwall-0.1.0/src/agentfirewall/network/__init__.py +0 -0
  15. agentwall-0.1.0/src/agentfirewall/presets/__init__.py +102 -0
  16. agentwall-0.1.0/src/agentfirewall/process.py +78 -0
  17. agentwall-0.1.0/src/agentfirewall/sandbox.py +485 -0
  18. agentwall-0.1.0/src/agentfirewall/schema.py +246 -0
  19. agentwall-0.1.0/src/agentfirewall/watchers/__init__.py +0 -0
  20. agentwall-0.1.0/src/agentfirewall/watchers/filesystem.py +135 -0
  21. agentwall-0.1.0/test.sh +17 -0
  22. agentwall-0.1.0/tests/__init__.py +0 -0
  23. agentwall-0.1.0/tests/test_audit.py +127 -0
  24. agentwall-0.1.0/tests/test_cli.py +124 -0
  25. agentwall-0.1.0/tests/test_cli_phase2.py +83 -0
  26. agentwall-0.1.0/tests/test_cli_protect.py +173 -0
  27. agentwall-0.1.0/tests/test_engine.py +206 -0
  28. agentwall-0.1.0/tests/test_hooks.py +135 -0
  29. agentwall-0.1.0/tests/test_presets.py +39 -0
  30. agentwall-0.1.0/tests/test_process.py +119 -0
  31. agentwall-0.1.0/tests/test_sandbox.py +539 -0
  32. agentwall-0.1.0/tests/test_schema.py +166 -0
  33. agentwall-0.1.0/tests/test_watcher.py +178 -0
@@ -0,0 +1,5 @@
1
+ #ignore all pycaches in the repository
2
+ __pycache__/
3
+
4
+ # ignore the virtual environemt (calling it security-env for now)
5
+ security-env
@@ -0,0 +1,642 @@
1
+ Metadata-Version: 2.4
2
+ Name: agentwall
3
+ Version: 0.1.0
4
+ Summary: A dotfile-driven firewall that protects the OS from destructive LLM agent tool calls
5
+ Author: Wissam El-Labban, prad
6
+ License: MIT
7
+ Keywords: agent,ai-safety,firewall,llm,security
8
+ Classifier: Development Status :: 3 - Alpha
9
+ Classifier: Intended Audience :: Developers
10
+ Classifier: License :: OSI Approved :: MIT License
11
+ Classifier: Operating System :: MacOS
12
+ Classifier: Operating System :: POSIX :: Linux
13
+ Classifier: Programming Language :: Python :: 3
14
+ Classifier: Programming Language :: Python :: 3.10
15
+ Classifier: Programming Language :: Python :: 3.11
16
+ Classifier: Programming Language :: Python :: 3.12
17
+ Classifier: Topic :: Security
18
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
19
+ Requires-Python: >=3.10
20
+ Requires-Dist: click>=8.0
21
+ Requires-Dist: psutil>=5.9
22
+ Requires-Dist: pyyaml>=6.0
23
+ Requires-Dist: watchdog>=3.0
24
+ Provides-Extra: dev
25
+ Requires-Dist: pytest-cov>=4.0; extra == 'dev'
26
+ Requires-Dist: pytest>=7.0; extra == 'dev'
27
+ Provides-Extra: sandbox
28
+ Requires-Dist: fusepy>=3.0; extra == 'sandbox'
29
+ Description-Content-Type: text/markdown
30
+
31
+ # Agent Firewall — Technical Documentation
32
+
33
+ ## Overview
34
+
35
+ `agentfirewall` is a filesystem-level security tool that protects your OS from destructive LLM agent tool calls. It works as a hidden `.agentfirewall/` directory (similar to `.git/`) placed at the root of any project, defining rules that govern what commands, file operations, and network connections an agent is allowed to perform.
36
+
37
+ **Current status:** Phase 1 (static rule checker) and Phase 2 (real-time OS enforcement) are complete. The tool evaluates actions against YAML-defined rules, returns allow/deny/warn verdicts, monitors the filesystem in real time, intercepts shell commands before execution, and kills offending agent processes.
38
+
39
+ ---
40
+
41
+ ## How It Works — Two-Layer Defense
42
+
43
+ The firewall protects your project through two independent layers that work together:
44
+
45
+ ```
46
+ Agent tries something destructive
47
+
48
+ ┌───────▼────────┐
49
+ │ LAYER 1: │ Shell hooks intercept every command BEFORE it runs.
50
+ │ Prevention │ bash DEBUG trap / zsh preexec calls "agentfirewall check".
51
+ │ (hooks/) │ If denied → command never executes.
52
+ └───────┬────────┘
53
+ │ command allowed (or agent bypasses shell entirely,
54
+ │ e.g. Python os.remove(), Node fs.unlink())
55
+ ┌───────▼────────┐
56
+ │ LAYER 2: │ Filesystem watcher (watchdog) monitors all file events.
57
+ │ Detection & │ Evaluates each event against engine rules.
58
+ │ Retaliation │ If denied → kills the agent process via psutil.
59
+ │ (watchers/) │
60
+ └───────┬────────┘
61
+
62
+ ┌───────▼────────┐
63
+ │ Audit Log │ Both layers log every decision (allow/deny/warn)
64
+ │ (audit.py) │ as structured JSON to .agentfirewall/logs/firewall.log
65
+ └────────────────┘
66
+ ```
67
+
68
+ **Why two layers?** Shell hooks can only intercept commands typed into a shell. If an LLM agent uses a programming language's file APIs directly (e.g., `os.remove()` in Python, `fs.unlinkSync()` in Node), the shell never sees it. The filesystem watcher catches those operations at the OS level.
69
+
70
+ ---
71
+
72
+ ## Architecture
73
+
74
+ ```
75
+ ┌──────────────────────────────────────────────────────────┐
76
+ │ cli.py │
77
+ │ (Click commands: init, check, status, watch, hooks) │
78
+ └──┬────────┬────────┬──────────┬──────────┬───────────────┘
79
+ │ │ │ │ │
80
+ │ uses │ uses │ uses │ uses │ uses
81
+ │ │ │ │ │
82
+ ▼ │ ▼ ▼ ▼
83
+ presets/ │ hooks/ watchers/ process.py
84
+ __init__.py │ shell.py filesystem (kill agents)
85
+ (rule sets) │ (bash/zsh .py │
86
+ │ hooks) (watchdog) │
87
+ │ │ ┌─────┘
88
+ ▼ ▼ ▼
89
+ ┌──────────────────────────────────┐
90
+ │ engine.py │
91
+ │ (Rule evaluation — verdicts) │
92
+ │ ▲ │ │
93
+ │ uses │ logs │ │
94
+ │ │ ▼ │
95
+ │ schema.py audit.py │
96
+ │ (YAML config) (JSON logger) │
97
+ └──────────────────────────────────┘
98
+ ```
99
+
100
+ Dependencies flow **one direction only** — no circular imports:
101
+ - `schema.py` → foundation (no internal imports)
102
+ - `engine.py` → imports `schema`
103
+ - `audit.py` → imports `engine` (for `RuleResult` type)
104
+ - `process.py` → standalone (uses `psutil`)
105
+ - `hooks/shell.py` → standalone (reads `$SHELL`, writes RC files)
106
+ - `watchers/filesystem.py` → imports `engine`, `schema`, `process`
107
+ - `cli.py` → imports everything above
108
+
109
+ ---
110
+
111
+ ## `.agentfirewall/` Directory Structure
112
+
113
+ ```
114
+ .agentfirewall/
115
+ ├── config.yaml # Main configuration (rules, mode, sandbox settings)
116
+ ├── rules/ # Custom rule files (reserved for future use)
117
+ ├── logs/ # Audit logs (firewall.log lives here)
118
+ │ └── firewall.log # Structured JSON log — one entry per line
119
+ ├── hooks/ # Shell hook scripts (reserved for future use)
120
+ └── plugins/ # Extension plugins (reserved for future use)
121
+ ```
122
+
123
+ Created by `agentfirewall init`. The directory is discovered by walking up from the current working directory (like `.git/`).
124
+
125
+ ---
126
+
127
+ ## Core Files
128
+
129
+ ### `schema.py` — Data Model
130
+
131
+ Defines all data structures and handles YAML config parsing/validation/serialization.
132
+
133
+ **Enums:**
134
+ - `FirewallMode` — `ENFORCE` | `AUDIT` | `OFF`
135
+ - ENFORCE: deny actions that violate rules
136
+ - AUDIT: log violations as warnings but allow them
137
+ - OFF: disable all checks
138
+ - `DenyOperation` — `DELETE` | `CHMOD` | `MOVE_OUTSIDE_SANDBOX` | `WRITE`
139
+
140
+ **Dataclasses (the config tree):**
141
+ - `FirewallConfig` — top-level container (version, mode, and all sub-configs)
142
+ - `SandboxConfig` — defines the allowed working directory (`root`) and whether escaping is permitted
143
+ - `FilesystemConfig` — protected path patterns (globs) and which operations are denied on them
144
+ - `CommandsConfig` — blocklist and allowlist of shell command patterns
145
+ - `NetworkConfig` — allowed hosts, denied egress targets, max upload size
146
+ - `LoggingConfig` — log file location and level
147
+
148
+ **Constants:**
149
+ - `DOTFILE_NAME = ".agentfirewall"` — directory name
150
+ - `CONFIG_FILENAME = "config.yaml"` — config file within the directory
151
+ - `SUBDIRS = ["rules", "logs", "hooks", "plugins"]` — subdirectories created on init
152
+
153
+ **Key Functions:**
154
+ - `find_config(start_dir)` — walks up from `start_dir` (default: CWD) looking for a `.agentfirewall/` directory containing `config.yaml`. Returns the path to `config.yaml` or `None`.
155
+ - `load_config(path)` — reads and validates a YAML file into a `FirewallConfig`. Uses `yaml.safe_load` for security. Raises `ConfigError` on invalid input.
156
+ - `default_config()` — returns a `FirewallConfig` with sensible defaults (15 blocklist patterns, 3 allowed hosts).
157
+ - `config_to_yaml(config)` — serializes a `FirewallConfig` back to YAML text.
158
+
159
+ ---
160
+
161
+ ### `engine.py` — Rule Evaluation
162
+
163
+ The decision-making core. Takes a `FirewallConfig` and evaluates actions against it.
164
+
165
+ **Data Types:**
166
+ - `Verdict` enum — `ALLOW` | `DENY` | `WARN`
167
+ - `RuleResult` dataclass — contains `verdict`, `rule` (which rule triggered), `detail` (human-readable explanation), and a `blocked` property (True only for DENY)
168
+
169
+ **`Engine` class:**
170
+
171
+ Constructor takes a `FirewallConfig` and an optional `AuditLogger`. It pre-compiles all blocklist/allowlist patterns into regex objects for performance. When an `AuditLogger` is provided, every evaluation call automatically logs the decision.
172
+
173
+ Three evaluation methods:
174
+
175
+ 1. **`evaluate_command(command)`** — checks a shell command string
176
+ - If mode is OFF → ALLOW
177
+ - If allowlist is non-empty and command doesn't match → DENY
178
+ - If command matches any blocklist pattern → DENY
179
+ - Otherwise → ALLOW
180
+ - Allowlist takes priority (checked first)
181
+
182
+ 2. **`evaluate_file_operation(operation, path)`** — checks a filesystem operation
183
+ - If mode is OFF → ALLOW
184
+ - Sandbox boundary check: resolves the path against `sandbox.root` and verifies it stays within bounds. Relative paths are resolved against the sandbox root (not CWD) to prevent bypass.
185
+ - Protected paths check: if the operation is in `deny_operations` and the path matches any `protected_paths` glob → DENY
186
+ - Otherwise → ALLOW
187
+
188
+ 3. **`evaluate_network(host)`** — checks an outbound connection
189
+ - If mode is OFF → ALLOW
190
+ - If host is in `deny_egress_to` → DENY
191
+ - If `allowed_hosts` is set and host isn't listed → DENY
192
+ - Otherwise → ALLOW
193
+
194
+ **Internal helpers:**
195
+ - `_make_result()` — in AUDIT mode, downgrades DENY → WARN (with `[AUDIT]` prefix in detail)
196
+ - `_compile_command_pattern()` — converts glob-style patterns (e.g., `rm -rf *`) to regex by escaping everything except `*`, then replacing `*` with `.*`
197
+ - `_path_matches()` — matches paths against glob patterns using both the full path and just the filename
198
+
199
+ ---
200
+
201
+ ### `cli.py` — Command-Line Interface
202
+
203
+ Click-based CLI with 10 commands:
204
+
205
+ | Command | Description | Exit Code |
206
+ |---------|-------------|-----------|
207
+ | `protect` | One-command setup: init + install hooks + start background watcher | 0 or 1 |
208
+ | `unprotect` | One-command teardown: stop watcher + remove hooks | 0 |
209
+ | `init` | Create `.agentfirewall/` with config and subdirectories | 0 or 1 |
210
+ | `check <command>` | Dry-run: check if a shell command would be allowed | 0=allow, 1=deny |
211
+ | `check-file <path>` | Dry-run: check if a file operation would be allowed | 0=allow, 1=deny |
212
+ | `check-network <host>` | Dry-run: check if a network connection would be allowed | 0=allow, 1=deny |
213
+ | `status` | Show current firewall config, watcher state, and hooks state | 0 |
214
+ | `watch` | Start real-time filesystem monitoring (Ctrl+C to stop) | 0 or 1 |
215
+ | `install-hooks` | Install shell preexec hooks for command interception | 0 |
216
+ | `uninstall-hooks` | Remove shell preexec hooks | 0 |
217
+
218
+ **`protect` command details:**
219
+ - Accepts `--preset` (standard/strict/permissive), `--shell` (bash/zsh), and `--force` flags
220
+ - Runs `init`, installs shell hooks, and starts `watch` as a background process
221
+ - Saves the watcher PID to `.agentfirewall/watcher.pid` so `unprotect` can stop it later
222
+ - Errors if `.agentfirewall/` already exists (unless `--force`)
223
+
224
+ **`unprotect` command details:**
225
+ - Reads `.agentfirewall/watcher.pid` and sends SIGTERM to stop the background watcher
226
+ - Removes shell hooks from `~/.bashrc` or `~/.zshrc`
227
+ - `--remove-config` flag also deletes the `.agentfirewall/` directory entirely
228
+ - Handles gracefully if watcher is already stopped or PID file is missing
229
+
230
+ **`init` command details:**
231
+ - Accepts `--preset` (standard/strict/permissive) and `--force` flags
232
+ - Creates the `.agentfirewall/` directory + all subdirectories (`rules/`, `logs/`, `hooks/`, `plugins/`)
233
+ - Writes `config.yaml` from the selected preset
234
+ - Errors if directory already exists (unless `--force`)
235
+
236
+ **`watch` command details:**
237
+ - Loads the nearest `.agentfirewall/` config
238
+ - Creates an `AuditLogger`, wires it into an `Engine`, creates a `ProcessKiller`
239
+ - Starts a `FirewallObserver` that monitors the sandbox directory
240
+ - Runs in the foreground until Ctrl+C
241
+
242
+ **`install-hooks` / `uninstall-hooks` details:**
243
+ - Accept `--shell bash|zsh` (auto-detected if omitted)
244
+ - Install appends a guard-marked hook block to `~/.bashrc` or `~/.zshrc`
245
+ - Uninstall removes only the guard-marked block, preserving all other content
246
+
247
+ **`_load_engine()`** — shared helper that calls `find_config()` then `load_config()` then creates an `Engine`. Returns `None` if no config found.
248
+
249
+ ---
250
+
251
+ ### `audit.py` — Structured Audit Logging
252
+
253
+ Writes every firewall decision (allow, deny, warn) as a JSON log entry to `.agentfirewall/logs/firewall.log`.
254
+
255
+ **`AuditLogger` class:**
256
+ - Constructor: `AuditLogger(config: LoggingConfig, base_dir: Path)`
257
+ - Creates the log directory if it doesn't exist
258
+ - Uses Python's `RotatingFileHandler` (5 MB per file, 3 backup files)
259
+ - Uses a custom `_JsonFormatter` that outputs one JSON object per line
260
+
261
+ **Log entry format (one per line):**
262
+ ```json
263
+ {"timestamp": "2026-03-27T14:30:00Z", "action_type": "command", "target": "rm -rf /", "verdict": "deny", "rule": "blocklist", "detail": "Matches blocklist pattern: rm -rf /"}
264
+ ```
265
+
266
+ **Log level filtering:**
267
+ The `level` in `LoggingConfig` controls which verdicts are logged:
268
+ - `"info"` — logs everything (ALLOW + DENY + WARN)
269
+ - `"warn"` (default) — logs DENY and WARN only, skips ALLOW
270
+ - `"error"` — logs DENY only
271
+
272
+ If `logging.enabled` is `false`, the logger is created but writes nothing.
273
+
274
+ ---
275
+
276
+ ### `process.py` — Agent Process Killer
277
+
278
+ Identifies and terminates running LLM agent processes. Called by the filesystem watcher when a rule violation is detected.
279
+
280
+ **`ProcessKiller` class:**
281
+ - Constructor: `ProcessKiller(signatures: list[str] | None = None)` — custom signatures override defaults
282
+ - `find_agent_processes()` — scans all running processes via `psutil`
283
+ - `kill_agents()` — finds and terminates agent processes, returns count killed
284
+
285
+ **Agent identification — two tiers:**
286
+
287
+ *Exact signatures* (process name contains any of these):
288
+ `claude`, `cursor`, `copilot-agent`, `windsurf`, `aider`
289
+
290
+ *Broad signatures* (process name matches, but only if the full command line also contains an agent keyword):
291
+ `node`, `code` — these are too common on their own, so the killer checks the full command line for agent-related strings before killing.
292
+
293
+ **Safety guards:**
294
+ - Never kills PID 0 or PID 1 (init/kernel)
295
+ - Never kills its own process or its parent process
296
+ - Gracefully handles `NoSuchProcess` (process died between scan and kill)
297
+ - Gracefully handles `AccessDenied` (insufficient permissions)
298
+
299
+ ---
300
+
301
+ ### `watchers/filesystem.py` — Real-Time Filesystem Monitor
302
+
303
+ Uses the `watchdog` library to monitor directory trees for file events, then evaluates each event against the engine rules.
304
+
305
+ **`FirewallHandler(FileSystemEventHandler)` class:**
306
+ - Receives filesystem events from watchdog
307
+ - Maps events to engine operations:
308
+
309
+ | Watchdog Event | → | Engine DenyOperation |
310
+ |----------------|---|---------------------|
311
+ | `FileDeletedEvent` | → | `DELETE` |
312
+ | `FileModifiedEvent` | → | `WRITE` |
313
+ | `FileMovedEvent` | → | `MOVE_OUTSIDE_SANDBOX` |
314
+
315
+ - Converts absolute paths (from watchdog) to relative paths (for engine pattern matching) using the watch root directory
316
+ - Ignores all events inside `.agentfirewall/` itself (prevents self-triggering)
317
+ - Ignores directory-level events (only monitors files)
318
+ - On DENY verdict: prints a formatted violation alert to stderr (emoji, verdict, operation, path, rule, detail), calls `ProcessKiller.kill_agents()` to terminate the offending agent, and prints the kill count
319
+ - On ALLOW verdict: no output (silent pass-through)
320
+
321
+ **`FirewallObserver` class:**
322
+ - Wraps watchdog's `Observer` with agentfirewall-specific setup
323
+ - `start()` — begins watching the sandbox root recursively
324
+ - `stop()` — stops the observer
325
+ - `run_forever()` — starts and blocks until Ctrl+C (used by the `watch` CLI command)
326
+
327
+ ---
328
+
329
+ ### `hooks/shell.py` — Shell Preexec Hooks
330
+
331
+ Generates, installs, and removes shell hook scripts that intercept commands before they execute.
332
+
333
+ **How the hooks work:**
334
+ - **Bash:** Enables `extdebug` mode (`shopt -s extdebug`) and installs a `DEBUG` trap. Before every command, bash calls `agentfirewall check "$BASH_COMMAND"`. If the exit code is non-zero (denied), the hook prints `[agentfirewall] BLOCKED: <command>` to stderr and returns 1 — with `extdebug` enabled, a non-zero return from a DEBUG trap **prevents the command from executing**. Without `extdebug`, bash ignores the return value and runs the command anyway.
335
+ - **Zsh:** Uses `add-zsh-hook preexec`. Checks the command via `agentfirewall check "$1"`. If denied, prints the BLOCKED message and sends `kill -INT $$` (SIGINT to self) to abort the pending command. Zsh's `preexec` is a notification hook — `return 1` alone doesn't stop execution — so the self-interrupt is required.
336
+
337
+ **Guard markers:**
338
+ Hook blocks are wrapped in `# >>> agentfirewall >>>` and `# <<< agentfirewall <<<` markers (similar to how conda manages shell integration). This allows:
339
+ - **Idempotent install** — if markers already exist, `install_hook()` does nothing
340
+ - **Clean uninstall** — `uninstall_hook()` removes only the guarded block, preserving all other RC file content
341
+
342
+ **Functions:**
343
+ - `generate_bash_hook()` / `generate_zsh_hook()` — return the hook script strings
344
+ - `detect_shell()` — reads `$SHELL` env var, returns `"bash"` or `"zsh"`
345
+ - `install_hook(shell, rc_path)` — appends hook to `~/.bashrc` or `~/.zshrc`
346
+ - `uninstall_hook(shell, rc_path)` — removes the guard-marked block
347
+
348
+ ---
349
+
350
+ ### `presets/__init__.py` — Built-in Rule Sets
351
+
352
+ Three presets with increasing strictness:
353
+
354
+ **Common blocklist patterns (shared by all presets):**
355
+ `rm -rf /`, `rm -rf ~`, `rm -rf /*`, `dd if=*of=/dev/*`, `mkfs.*`, `:(){ :|:& };:`, `chmod -R 777`, `sudo rm*`, `git push --force`, `git push.*--force`, `git reset --hard`, `git clean -fd`, `kill -9`
356
+
357
+ | Feature | Standard | Strict | Permissive |
358
+ |---------|----------|--------|------------|
359
+ | Mode | ENFORCE | ENFORCE | AUDIT |
360
+ | Blocklist | 13 common patterns | 23 patterns (common + extra) | 13 common patterns |
361
+ | Protected paths | .git/**, .env, .ssh/** | + /etc/**, /boot/**, /usr/** | .git/**, .env, .ssh/** |
362
+ | Sandbox escape | Allowed | **Blocked** | Allowed |
363
+ | Deny operations | delete, move_outside | + chmod, write | delete, move_outside |
364
+ | Allowed hosts | github.com, OpenAI, Anthropic | Same | *(none — all allowed)* |
365
+ | Logging | Enabled (warn level) | Enabled (warn level) | Enabled (warn level) |
366
+
367
+ **Extra strict blocklist additions:** `curl*|*bash`, `wget*|*sh`, `eval *`, `exec *`, `python -c*`, `node -e*`, `nc *`, `ncat *`, `netcat *`, `telnet *`
368
+
369
+ **API:**
370
+ - `get_preset(name)` — returns a `FirewallConfig` for the named preset
371
+ - `list_presets()` — returns `["standard", "strict", "permissive"]`
372
+
373
+ ---
374
+
375
+ ## Config Schema (YAML)
376
+
377
+ ```yaml
378
+ version: 1
379
+ mode: enforce # enforce | audit | off
380
+
381
+ sandbox:
382
+ root: "." # Working directory boundary
383
+ allow_escape: false # Allow operations outside sandbox root
384
+
385
+ filesystem:
386
+ protected_paths: # Glob patterns for protected files
387
+ - ".git/**"
388
+ - ".env"
389
+ - ".ssh/**"
390
+ deny_operations: # Operations blocked on protected paths
391
+ - delete
392
+ - move_outside_sandbox
393
+
394
+ commands:
395
+ blocklist: # Shell command patterns to block (* = wildcard)
396
+ - "rm -rf /"
397
+ - "sudo rm*"
398
+ allowlist: [] # If non-empty, ONLY these commands are allowed
399
+
400
+ network:
401
+ allowed_hosts: # If non-empty, only these hosts permitted
402
+ - "github.com"
403
+ - "api.openai.com"
404
+ deny_egress_to: # Always blocked regardless of allowed_hosts
405
+ - "169.254.169.254"
406
+ - "metadata.google.internal"
407
+ max_upload_bytes: 10485760 # 10 MB (not yet enforced)
408
+
409
+ logging:
410
+ enabled: true
411
+ file: "logs/firewall.log"
412
+ level: warn
413
+ ```
414
+
415
+ ---
416
+
417
+ ## Decision Logic Summary
418
+
419
+ ```
420
+ Command Check:
421
+ mode=OFF? → ALLOW
422
+ allowlist non-empty AND command not in allowlist? → DENY
423
+ command matches blocklist? → DENY
424
+ else → ALLOW
425
+
426
+ File Operation Check:
427
+ mode=OFF? → ALLOW
428
+ path escapes sandbox (and allow_escape=false)? → DENY
429
+ operation in deny_operations AND path matches protected_paths? → DENY
430
+ else → ALLOW
431
+
432
+ Network Check:
433
+ mode=OFF? → ALLOW
434
+ host in deny_egress_to? → DENY
435
+ allowed_hosts non-empty AND host not in allowed_hosts? → DENY
436
+ else → ALLOW
437
+
438
+ In all cases: if mode=AUDIT, DENY is downgraded to WARN.
439
+ ```
440
+
441
+ ---
442
+
443
+ ## Test Structure
444
+
445
+ 109 tests across 10 files (all passing):
446
+
447
+ | File | Tests | What it covers |
448
+ |------|-------|----------------|
449
+ | `tests/test_schema.py` | 12 | Config loading, validation, YAML round-trip, find_config directory walking, error handling |
450
+ | `tests/test_engine.py` | 25 | Command evaluation (blocklist, allowlist, wildcards), file operations (sandbox boundary, protected paths), network checks, audit mode downgrade, mode=off bypass |
451
+ | `tests/test_cli.py` | 14 | CLI init (directory creation, presets, --force), check/check-file/check-network commands, status output, missing config errors |
452
+ | `tests/test_presets.py` | 5 | Preset content validation, list_presets, unknown preset error |
453
+ | `tests/test_audit.py` | 10 | Log file creation, JSON format, multiple entries, disabled logging, level filtering, timestamp format |
454
+ | `tests/test_process.py` | 14 | Agent detection (exact + broad signatures), PID safety guards, kill count, AccessDenied/NoSuchProcess handling |
455
+ | `tests/test_watcher.py` | 10 | Delete/modify/move event handling, protected path triggers, allow normal ops, process killer calls, .agentfirewall/ ignoring, directory event ignoring |
456
+ | `tests/test_hooks.py` | 14 | Hook generation (bash/zsh), shell detection, install to new/existing files, idempotency, uninstall with content preservation |
457
+ | `tests/test_cli_phase2.py` | 6 | watch command (starts observer, no-config error), install-hooks (bash/zsh), uninstall-hooks (remove/not-present) |
458
+
459
+ Run tests: `pytest -v` (from project root with venv activated)
460
+
461
+ ---
462
+
463
+ ## Quick Start
464
+
465
+ The fastest way to get everything set up (system deps + venv + Python packages):
466
+
467
+ ```bash
468
+ git clone <repo-url> && cd llm-security
469
+ ./setup.sh # Full setup including FUSE sandbox support
470
+ ./setup.sh --no-fuse # Skip FUSE if you don't need the sandbox
471
+ ```
472
+
473
+ Or install manually:
474
+
475
+ ```bash
476
+ # System dependencies (for FUSE sandbox — skip if not needed)
477
+ sudo apt install fuse3 libfuse-dev # Debian/Ubuntu
478
+ sudo dnf install fuse fuse-devel # Fedora/RHEL
479
+
480
+ # Install in development mode
481
+ pip install -e ".[dev]" # Core + tests
482
+ pip install -e ".[dev,sandbox]" # Core + tests + FUSE sandbox
483
+
484
+ # --- One-command setup (recommended) ---
485
+
486
+ # Protect your project (init + hooks + background watcher in one step)
487
+ cd /path/to/your/project
488
+ agentfirewall protect # standard preset
489
+ agentfirewall protect --preset strict # stricter rules
490
+ agentfirewall protect --preset permissive # warn-only mode
491
+
492
+ # Disable protection (stops watcher + removes hooks)
493
+ agentfirewall unprotect
494
+ agentfirewall unprotect --remove-config # also deletes .agentfirewall/
495
+
496
+ # View current status (shows mode, watcher state, hooks state)
497
+ agentfirewall status
498
+
499
+ # --- Manual setup (advanced) ---
500
+
501
+ # Initialize firewall in your project
502
+ agentfirewall init # standard preset
503
+ agentfirewall init --preset strict # stricter rules
504
+
505
+ # Check commands before executing
506
+ agentfirewall check "rm -rf /" # 🚫 DENY
507
+ agentfirewall check "ls -la" # ✅ ALLOW
508
+
509
+ # Check file operations
510
+ agentfirewall check-file .env -o delete # 🚫 DENY
511
+ agentfirewall check-file readme.md -o write # ✅ ALLOW
512
+
513
+ # Check network connections
514
+ agentfirewall check-network github.com # ✅ ALLOW
515
+ agentfirewall check-network 169.254.169.254 # 🚫 DENY
516
+
517
+ # Start real-time filesystem monitoring
518
+ agentfirewall watch # Ctrl+C to stop
519
+
520
+ # Install shell hooks (intercept commands before execution)
521
+ agentfirewall install-hooks # auto-detects bash/zsh
522
+ agentfirewall install-hooks --shell zsh
523
+
524
+ # Remove shell hooks
525
+ agentfirewall uninstall-hooks
526
+ ```
527
+
528
+ ---
529
+
530
+ ## Dependencies
531
+
532
+ ### System Requirements
533
+
534
+ | Package | Platform | Purpose | Required? |
535
+ |---------|----------|---------|----------|
536
+ | `libfuse-dev` | Debian/Ubuntu | Provides `libfuse.so.2` for FUSE sandbox (`agentfirewall sandbox`) | Only for sandbox feature |
537
+ | `fuse3` | Debian/Ubuntu | FUSE utilities (`fusermount`) | Only for sandbox feature |
538
+ | `fuse-devel` | Fedora/RHEL | Equivalent of `libfuse-dev` | Only for sandbox feature |
539
+ | `macfuse` | macOS | macOS FUSE support ([osxfuse.github.io](https://osxfuse.github.io/)) | Only for sandbox feature |
540
+
541
+ > **Note:** `fusepy` (the Python binding) requires FUSE 2 (`libfuse.so.2`), **not** FUSE 3. On systems with only `libfuse3`, install `libfuse-dev` to get the FUSE 2 library — both coexist safely.
542
+
543
+ ### Python Packages
544
+
545
+ | Package | Version | Purpose |
546
+ | `pyyaml` | ≥ 6.0 | Parse `.agentfirewall/config.yaml` (uses `safe_load` for security) |
547
+ | `click` | ≥ 8.0 | CLI framework (commands, options, argument parsing) |
548
+ | `watchdog` | ≥ 3.0 | Cross-platform filesystem monitoring (abstracts inotify/fsevents/ReadDirectoryChanges) |
549
+ | `psutil` | ≥ 5.9 | Cross-platform process scanning and termination (agent killing) |
550
+ | `fusepy` | ≥ 3.0 | FUSE filesystem bindings for sandbox (`pip install agentfirewall[sandbox]`) — *optional* |
551
+
552
+ Dev dependencies: `pytest` ≥ 7.0, `pytest-cov` ≥ 4.0
553
+
554
+ ---
555
+
556
+ ## Developer Onboarding
557
+
558
+ ### Reading Order
559
+
560
+ If you're new to the codebase, read the source files in this order — each one builds on the previous:
561
+
562
+ 1. **`schema.py`** — Start here. This is the data model. Learn the config structure (`FirewallConfig` and its sub-configs) and how YAML is parsed. No internal dependencies.
563
+ 2. **`engine.py`** — The decision maker. Takes a config and evaluates commands/files/network against it. Returns `RuleResult` with a verdict. Depends only on `schema.py`.
564
+ 3. **`presets/__init__.py`** — Three built-in configs (standard/strict/permissive). Shows how configs are constructed in code. Depends on `schema.py`.
565
+ 4. **`audit.py`** — JSON structured logging. Wraps Python's `logging` module. Depends on `engine.py` (for the `RuleResult` type).
566
+ 5. **`process.py`** — Agent process identification and killing. Standalone module — only uses `psutil`.
567
+ 6. **`watchers/filesystem.py`** — Filesystem monitoring. Connects watchdog events → engine evaluation → process killing. Depends on `engine.py`, `schema.py`, `process.py`.
568
+ 7. **`hooks/shell.py`** — Shell hook generation and RC file management. Standalone module — only uses `os` and `pathlib`.
569
+ 8. **`cli.py`** — The user interface. Ties everything together into Click commands. Read this last — it imports from all other modules.
570
+
571
+ ### Module Dependency Graph
572
+
573
+ ```
574
+ schema.py ◄──── engine.py ◄──── audit.py
575
+ ▲ ▲ │
576
+ │ │ │ (logged by)
577
+ │ │ ▼
578
+ presets/ watchers/ .agentfirewall/
579
+ __init__.py filesystem.py logs/firewall.log
580
+
581
+ ├──── process.py (standalone)
582
+
583
+
584
+ hooks/shell.py (standalone)
585
+
586
+
587
+ │ all wired together by
588
+
589
+ cli.py
590
+ ```
591
+
592
+ Arrows point from **dependent → dependency** (e.g., engine imports schema).
593
+
594
+ ### How to Add a New Rule Type
595
+
596
+ 1. Add a new evaluation method to `Engine` in `engine.py` (e.g., `evaluate_dns(hostname)`)
597
+ 2. If it needs new config fields, add a new dataclass to `schema.py` and wire it into `FirewallConfig` + the YAML parser
598
+ 3. Add the corresponding `action_type` string to audit log calls (e.g., `self._log("dns", hostname, result)`)
599
+ 4. Add a CLI command in `cli.py` (e.g., `check-dns`)
600
+ 5. Add tests in `tests/test_engine.py` and `tests/test_cli.py`
601
+
602
+ ### How to Add a New Watcher Event Type
603
+
604
+ 1. Add the new `DenyOperation` variant to the enum in `schema.py`
605
+ 2. Map the watchdog event to the new operation in `_EVENT_TO_OP` in `watchers/filesystem.py`
606
+ 3. Handle the event in `FirewallHandler` (implement the `on_*` method)
607
+ 4. Add tests in `tests/test_watcher.py`
608
+
609
+ ### Running Tests
610
+
611
+ ```bash
612
+ # Activate the virtual environment
613
+ source security-env/bin/activate
614
+
615
+ # Run all tests with verbose output
616
+ pytest -v
617
+
618
+ # Run a specific test file
619
+ pytest tests/test_engine.py -v
620
+
621
+ # Run with coverage
622
+ pytest --cov=agentfirewall --cov-report=term-missing
623
+
624
+ # Run only FUSE sandbox tests (requires libfuse-dev)
625
+ pytest tests/test_sandbox.py -v
626
+
627
+ # Run everything except FUSE sandbox tests
628
+ pytest -v --ignore=tests/test_sandbox.py
629
+ ```
630
+
631
+ > **Note:** FUSE sandbox tests (`test_sandbox.py`) require `libfuse-dev` to be installed. If only `libfuse3` is present, these tests will fail with `OSError: Unable to find libfuse`. Run `sudo apt install libfuse-dev` to fix, or use `./setup.sh` which handles this automatically.
632
+
633
+ ---
634
+
635
+ ## Roadmap
636
+
637
+ - **Phase 1** ✅ — Static rule checker (schema, engine, CLI dry-run commands, presets)
638
+ - **Phase 2** ✅ — Real-time OS enforcement (audit logging, filesystem watcher, process killer, shell hooks)
639
+ - **Phase 3** — Network interception (eBPF/iptables egress filtering, HTTP inspection)
640
+ - **Phase 4** — Agent protocol integration (MCP middleware, LangChain callbacks)
641
+ - **Phase 5** — Advanced features (ML anomaly detection, multi-agent policies)
642
+ - **Phase 6** — Web UI dashboard (Flask, live log viewer, config editor)