Skip to content

Latest commit

 

History

13 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Cage Guard v1.2

File integrity monitor (FIM) for AI-agent workflows. Catch silent config tampering before you review agent work.

1. What this is

Cage Guard is a standalone, multi-project file integrity monitor (FIM) and manual validation layer. It protects the rule files your coding agents are most tempted to change: ESLint and Prettier configs, build configs, CI workflows, and architecture docs. It hashes them, locks them read-only, and later verifies that their bytes have not changed.

Why it exists: AI coding agents that hit strict local guardrails sometimes quietly rewrite config files to make their own code pass, instead of refactoring to match your design. Cage Guard catches that before you waste time evaluating compromised work. You run it yourself, outside the project it protects. Green means intact. Yellow means a file is not captured or not locked. Red means a protected file was modified or deleted.

It is project-blind: it does not know what any file is or what any project does. It checks if bytes changed. That is all. Zero dependencies. Node.js built-ins only. A low-friction alternative to enterprise file-integrity suites for teams running autonomous coding agents.

2. Requirements

  • Node.js 18 or newer installed and available in your system PATH.
  • Verify with: node --version

Nothing to install. No npm install. The tool uses Node.js built-ins only.

3. Quick start

Double-click first

On Windows, you do not need a terminal for daily use.

  1. Double-click gate.bat.
  2. Pick your project from the list. Each project shows its live lock state, e.g. 12 files locked.
  3. Choose an action:
Action Meaning
Lock Capture. Re-hashes every protected file and re-locks them read-only. Run this after YOU changed files on purpose.
Unlock Opens an edit window. Removes read-only from the protected files and the config. Edit your files, then Lock again.
Check Integrity scan. Re-hashes and compares against the stored hashes. Green = safe to review. Red = stop.
Status Cheap state query. No hashing. Shows whether files are locked and when they were last captured.

Lock and Unlock ask for confirmation before they act. Check and Status run immediately. Press B to go back to the project list, Q to quit.

First-time setup

The hub needs at least one project. Set one up once in a terminal:

node guard.mjs init "C:\Users\You\dev\my-project"

The tool scans the project, detects known config files, and suggests a list. Press Y to accept, or type edit to customize. Then open gate.bat, pick the project, and choose Lock to capture the first baseline.

From then on the daily loop is: agent finishes work, double-click check-all.bat (or Check in the hub), review only when green.

4. The three launchers

All three live in the cage-guard folder. Each is a thin launcher over one command, and each pauses at the end so the window stays open.

Launcher Runs What it does
gate.bat node guard.mjs menu The interactive hub: pick a project, then Lock / Unlock / Check / Status.
check-all.bat node guard.mjs check Checks every configured project and prints a combined summary.
protect-tool.bat node guard.mjs protect-tool Prompts Lock or Unlock for the TOOL's own source: guard.mjs, everything under src/, everything under configs/. Prints a verification total. hashes/ and reports/ stay writable.

Create desktop shortcuts to these files. Do NOT copy the .bat files elsewhere. %~dp0 resolves to the batch file's own directory, so they only work from inside the cage-guard folder.

Run protect-tool.bat and choose Lock after setup. This makes the tool's source read-only. Project files are managed by capture, not by this command.

5. Command reference

Every command has the form node guard.mjs <command> [args].

init

node guard.mjs init <path>

Scans the project folder against detection profiles, shows what it found grouped by profile, suggests folder patterns, and writes a config. A relative path is resolved against your current directory; the resolved absolute path is stored in the config. If a config already exists it asks before overwriting. Interactive: expects a real console. Exit code 0 when a config is written or the run is aborted cleanly.

capture

node guard.mjs capture <name>

Hashes every file matched by the project's protected patterns, sets each file read-only, writes the hash store to hashes/<name>.hashes.json, and locks configs/<name>.json. This is "Lock" in the hub. Safe to re-run any time: it re-hashes and re-locks everything. Missing files are skipped with a warning. Exit code 0.

Since v1.2 the hash store is written as version: 2 and mirrors eligible file contents into a snapshots/ directory beside hashes/. Snapshots are what let check show you WHAT changed. They are auto-managed: the last 3 generations are kept per project, and files over 1 MiB or containing binary bytes are hashed only. Version 1 stores keep working; checks against them report changes without diffs until you re-capture.

check

node guard.mjs check <name>
node guard.mjs check

With a name: verifies one project against its hash store. Without a name: checks every project in configs/ and prints a combined summary. A failing project or a broken config is reported per-project; the remaining projects still run.

Violations (red) cause exit code 1. Warnings (yellow) do not.

Every text-mode check writes two report files, even when everything is green: reports/<project>-<timestamp>.log (plain text) and reports/<project>-<timestamp>.html (a styled, self-contained page — no JavaScript, works offline). After writing them, the tool prints their paths:

Full report: reports\My-Project-2026-08-22-1412.log · reports\My-Project-2026-08-22-1412.html

A single-project run writes one pair; an all-projects run writes one pair per checked project. When violations exist, the tool also prints an agent-paste block:

=== CAGE GUARD REPORT ===
Project:   My Project
...
MODIFIED FILES:
  src/a.js
    expected: abc123def456
    actual:   def456abc123
    ```
    @@ -1,3 +1,3 @@
      unchanged line
    -old line
    +new line
    ```
...
ACTION REQUIRED:
  Revert ALL modifications to your last known good state.
=== END REPORT ===

Copy everything between the === lines. Paste it into your AI agent's conversation. The agent sees exactly what changed — each modified file carries a fenced diff against the stored snapshot — and what to do. You do not need to explain anything. When no diff can be shown (legacy store, binary file), the block says so in one line instead.

The WARNINGS section of the paste block is collapsed so a flood of unlocked files cannot bury the violations: one summary line per status type with the fix command, at most three filenames, then …and N more — full list in reports/<logfile>. Violations are never collapsed. The written .log and .html reports always keep the complete list.

Reading the per-file output:

Symbol Color Meaning Action
Green INTACT — unchanged and locked None. All good.
Red MODIFIED — content changed Revert or re-capture.
Red MISSING — file deleted Restore or re-capture.
! Yellow NOT LOCKED — hash matches but read-only removed Run capture to re-lock.
! Yellow NOT CAPTURED — in config but never hashed Run capture.

When a content snapshot exists, MODIFIED lines also end with a change tally like (+12 −5) — added and removed line counts. The full diff lives in the report files, not the terminal.

If you see many yellow and zero red, you unlocked to edit files but forgot to re-capture. Run capture.

status

node guard.mjs status <name>

Reports the project's current state without re-hashing anything: whether the config is LOCKED or UNLOCKED, the last capture time, the number of protected patterns, and how many files are in the hash store. A cheap "are my files locked right now?" query. Exit code 0.

unlock

node guard.mjs unlock <name>

Removes the read-only flag from every protected file and from the config. This is the daily friction point: when YOU need to edit protected files, unlock first instead of fighting attrib or chmod by hand. Then edit, then re-lock with node guard.mjs capture <name> (or Lock in the hub). Exit code 0.

unprotect

node guard.mjs unprotect <name> <pattern>

Removes one pattern from the project's protected list and rewrites the config atomically. Refuses to remove the last remaining pattern: a config must protect at least one. If the pattern is not in the list it says so and exits 0. After removing a pattern, run node guard.mjs capture <name> to update the hash store. Exit code 0.

menu

node guard.mjs menu

The same interactive hub as gate.bat: a project picker with live lock counts, branching into Lock (capture), Unlock, Check, and Status. With exactly one project it skips the picker. With no projects it prints onboarding. Exit code 0 when you quit.

Running bare node guard.mjs with no command opens this same hub. It is the default command.

protect-tool

node guard.mjs protect-tool [lock|unlock]

Locks or unlocks the tool's own source: guard.mjs, everything under src/, and everything under configs/. hashes/ and reports/ are never touched. With no argument it prompts [L]ock or [U]nlock or (Q)uit. It prints a green total ("Locked 42 files") and a red warning for any path it could not change. Exit code 0.

The --json flag

Add --json anywhere on the command line to get machine-readable output. Designed for check, capture, and status:

node guard.mjs --json check my-project
node guard.mjs check my-project --json

The contract:

  • stdout carries exactly ONE JSON document per invocation, ending in a newline.
  • Every document has a versioned schema field and an exitCode field that mirrors the process exit code. Consumers never parse stdout text to determine status.
  • Schemas: cage-guard/check@1 (one project), cage-guard/check-all@1 (all projects), cage-guard/capture@1, cage-guard/status@1.
  • Warnings, messages, and errors go to stderr, so stdout stays parseable.
  • No report files are written and no agent-paste block is printed in JSON mode.

Exit codes: 0 = success (no violations). 1 = violations found, a project errored, or the command failed. Any thrown error exits 1 with the message on stderr.

6. Config format

Each project is one JSON file in configs/, e.g. configs/my-project.json:

{
  "version": 1,
  "name": "My Project",
  "root": "C:/absolute/path/to/project",
  "protected": [
    "package.json",
    "tsconfig.json",
    "eslint.config.ts",
    ".prettierrc",
    "scripts/**",
    "src/core/contracts/**",
    ".github/workflows/**"
  ]
}
Field Type Rule
version number Schema version. Must be 1. Older configs without a version field are treated as v1.
name string Human-readable name, used in report headers.
root string Absolute path. Must exist.
protected string[] Non-empty list of paths/patterns to protect. Duplicates and empty strings are removed automatically.

protected patterns:

  • Exact path — e.g. package.json, .prettierrc — protects that single file.
  • folder/* — protects files directly inside the folder (depth 1).
  • folder/** — protects the folder recursively (any depth).
  • *.ext — wildcard in a filename segment, e.g. tsconfig*.json, vite.config.*, src/*.ts.

A pattern that matches zero files produces a warning, not an error. Common folders like node_modules, dist, and .git are always skipped during pattern expansion.

The config file is set read-only after capture. To change it, use unprotect (removes a pattern safely), or unlock <name> (unlocks the config too), edit, then capture <name>.

7. Detection profiles

Profiles are data in src/data/detection-profiles.mjs. There are 13. To add a profile, edit that file: no code changes elsewhere.

  • node-core
  • typescript
  • linters-formatters
  • bundlers-build
  • testing
  • ci-cd
  • build-scripts
  • governance-docs
  • python
  • rust
  • go
  • docker
  • version-control

8. Multi-project

Place one JSON config per project in configs/. Running node guard.mjs check with no name checks every config and prints a combined summary. A failing project or a config error is reported per-project; the remaining projects still run. gate.bat lists every project and lets you switch between them without retyping names.

9. Git workflow note

Protected files are read-only. git pull, git checkout, git merge may fail if they touch protected files. Before git operations: temporarily remove read-only (attrib -r <file> on Windows, chmod u+w <file> on Unix, or node guard.mjs unlock <name> for the whole project). After: node guard.mjs capture <name>.

10. WARNING — agent workspace scope

This tool assumes your AI agent's workspace is restricted to the project folder. If your agent has broad file system access (PowerShell, CLI, bash), it can modify this tool's files. After setup, run protect-tool.bat. File attributes stop direct writes. They do not stop an agent with explicit shell access from running attrib or icacls. If your agent has unrestricted shell access, this tool is not sufficient protection.

11. Testing

The test suite uses Node.js built-in test runner. No framework. Zero dependencies.

node --test

Current suite: 74 tests across 16 test files in test/. All green means the tool behaves as documented.

12. Troubleshooting

Error Fix
node is not recognized Install Node.js 18+. Add it to your system PATH.
Config not found Check the name matches the filename in configs/ (without .json).
Project root not found The path in config.root does not exist. Edit the config.
No configs found in configs/ No projects set up yet. Run node guard.mjs init <path> first.
EACCES on capture Hash store is read-only. Capture handles this automatically. If it persists, manually remove read-only: attrib -r hashes/<name>.hashes.json (Windows) or chmod u+w hashes/<name>.hashes.json (Unix).
Cannot read <file>: locked by another process Close the program using the file. Re-run check.
Pattern not supported in v1 ** in the middle of a path is not supported. Use folder/** or folder/*.
Hash store corrupted Delete hashes/<name>.hashes.json. Run capture to regenerate.
Invalid JSON in configs/ The config file has a syntax error. Open it and fix the JSON.
All files show NOT LOCKED You removed read-only but didn't re-capture. Run: node guard.mjs capture <name>
edit mode isn't what I expected edit accepts remove <n>, add <pattern>, then done. For a plain text edit, press Y and edit configs/<name>.json manually in your preferred editor.
Menu or prompt hangs when input is piped Interactive commands (menu, init, protect-tool, bare node guard.mjs) expect a real console. Double-click the .bat launcher instead of piping input.

13. Limitations

  • The read-only attribute is advisory, not security. A determined tool can bypass it. The hash check is the real detection: even if something unlocks and edits a file, check will catch the changed bytes.
  • File attributes stop direct writes, not shell commands (attrib -r, icacls).
  • ** in the middle of a path (src/**/*.ts) is not supported. Use src/** or src/*.
  • reports/ accumulates one log per check. There is no automatic cleanup or retention schedule yet. Delete old logs whenever you like.
  • Very large files are loaded entirely into memory for hashing.
  • Symlinks are followed to their target. Broken symlinks are skipped silently.
  • Windows-first. The core works anywhere Node.js runs, but the double-click launchers are Windows .bat files.

About

A lightweight, external file integrity monitor (FIM) to protect your project architecture and guardrails from silent overwrites by autonomous coding agents.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages