Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

donttouch

(sort of) guards files from AI coding agents

donttouch puts a lightweight guardrail around files, to keep AI coding assistants from modifying them.

Think of it as a sign and velvet rope around the files. It should be a clear signal to any AI agent: “Do not change this file.”

…but they can get around it. This should not be replacing code reviews or human oversight. It’s just an experiment as an extra layer of protection and perhaps more importantly a stronger way to signal the invariants around files.

  1. Filesystem permissions — Makes files read-only via chmod
  2. Git hooks — Blocks commits and pushes that touch protected files
  3. Agent instructions — Injects rules into agent config files (CLAUDE.md, .cursorrules, etc.)

Why?

AI coding agents are powerful but sometimes overeager. They might:

  • Reformat your carefully crafted config files
  • “Fix” code you intentionally wrote a certain way
  • Modify documentation you maintain by hand
  • Touch infrastructure files that shouldn’t change casually

donttouch gives you a simple way to draw a line: these files are off-limits.

Key Features

  • Works everywhere — Git repos and plain directories
  • Pattern-based — Protect files with glob patterns (*.toml, migrations/**)
  • Agent-aware — Injects instructions into Claude, Cursor, Copilot, and Codex config files
  • Git-integrated — Pre-commit and pre-push hooks with optional Husky support
  • Safe by designunlock and disable must be run from outside the project directory, so agents inside the repo can’t bypass protection

Quick Example

cd my-project
donttouch init        # Interactive setup
donttouch lock        # Make protected files read-only
donttouch inject      # Add rules to agent config files
donttouch status      # See what's protected

To edit protected files (from outside the project):

cd ..
donttouch unlock ./my-project
# make your changes
cd my-project
donttouch lock

Note: unlock also disables protection, so pre-push hooks will block until you lock again. This prevents accidentally pushing with protection off.

Installation

From Source (Cargo)

cargo install donttouch

From GitHub

git clone https://github.com/sksizer/donttouch
cd donttouch
cargo install --path .

npm (coming soon)

npx donttouch init

The npm package will download the appropriate binary for your platform.

Verify

donttouch --version

Quick Start

1. Initialize

cd your-project
donttouch init

This starts an interactive wizard that:

  • Creates .donttouch.toml with your file patterns
  • Optionally locks files immediately
  • Offers to install git hooks (if in a git repo)
  • Offers to inject agent instructions

2. Protect Files

Define patterns in .donttouch.toml:

[protect]
enabled = true
patterns = [
    "*.toml",
    "migrations/**",
    "README.md",
]

Then lock them:

donttouch lock

3. Verify

donttouch status

Shows all protected files, their lock state, git hook status, and context.

4. Check Protection

donttouch check

Returns exit code 0 if all files are properly locked, non-zero otherwise. Use in CI or git hooks.

Working With Protected Files

When you need to edit a protected file, unlock from outside the project:

cd ..
donttouch unlock ./your-project

Make your changes, then re-lock:

cd your-project
donttouch lock

The outside-directory requirement is the key security feature — AI agents running inside your project directory cannot unlock files.

Configuration

donttouch uses a TOML config file: .donttouch.toml

File Format

[protect]
enabled = true
patterns = [
    "*.toml",
    "Cargo.lock",
    "migrations/**",
    ".env",
    "README.md",
]

Fields

[protect]

FieldTypeDefaultDescription
enabledbooltrueWhether protection is active
patternsstring[][]Glob patterns for files to protect

Patterns

Patterns use standard glob syntax:

PatternMatches
*.tomlAll .toml files in the root
migrations/**Everything under migrations/
docker-compose.ymlExact file
*.lockAll lock files

Patterns are resolved relative to the project root (where .donttouch.toml lives).

Self-Protection

The .donttouch.toml file itself is always protected when you run lock. This prevents agents from modifying the config to remove patterns.

Enabled Flag

When enabled = false:

  • check passes (no enforcement)
  • Pre-push hook blocks (forces you to re-lock before pushing)

The flag is managed automatically:

  • donttouch lock sets enabled = true
  • donttouch unlock sets enabled = false

Locking & Unlocking

How Locking Works

donttouch lock sets filesystem permissions to read-only on all files matching your patterns and marks protection as enabled in the config. Execute bits are preserved (so scripts stay executable).

# Before lock
-rw-r--r--  config.toml
-rwxr-xr-x  deploy.sh

# After lock  
-r--r--r--  config.toml
-r-xr-xr-x  deploy.sh

The .donttouch.toml config file is also locked to prevent agents from modifying protection rules.

Lock

donttouch lock

No flags needed. Locks all files matching patterns in .donttouch.toml and sets enabled = true.

Unlock

# Must be run from OUTSIDE the project directory
cd ..
donttouch unlock ./my-project

Restores write permissions on all protected files, the config file, and sets enabled = false. This means git hooks (pre-push) will block pushes until you re-lock — preventing you from accidentally pushing with protection turned off.

Why Outside-Only?

AI coding agents execute commands from within your project directory. By requiring unlock to be called from outside, agents physically cannot bypass protection — even if they try to run the command, the canonical path check will reject it.

This also prevents symlink and path traversal tricks (../project, /proc/self/cwd, etc.) thanks to std::fs::canonicalize().

Typical Workflow

# Unlock from outside the project
cd ..
donttouch unlock ./my-project

# Make your changes
cd my-project
vim config.toml

# Re-lock when done
donttouch lock

Git Integration

donttouch detects git repos automatically and offers enhanced protection through hooks.

Hooks

Two hooks are available:

Pre-commit

Runs donttouch check before each commit. Blocks the commit if:

  • Any protected file has been staged for commit
  • Any protected file is not read-only

Pre-push

Runs donttouch check-push before each push. Blocks the push if:

  • Protection is disabled (enabled = false)

This prevents you from pushing code while protection is turned off.

Installing Hooks

Via init

The interactive donttouch init wizard offers to install hooks automatically.

Husky Support

If you use Husky, donttouch detects the .husky/ directory and installs into your existing Husky hooks rather than overwriting .git/hooks/.

Manual

You can add to your git hooks manually:

# .git/hooks/pre-commit (or .husky/pre-commit)
donttouch check

# .git/hooks/pre-push (or .husky/pre-push)
donttouch check-push

Plain Directory Mode

Use --ignoregit to skip git detection entirely:

donttouch init --ignoregit

This forces plain directory mode even inside a git repo — no hooks, no staged file checking.

Hook Cleanup

donttouch remove cleanly removes hook entries without destroying other hook content. It only removes the lines it added.

Agent Instructions

donttouch can inject rules directly into AI agent configuration files, telling agents not to modify protected files.

Supported Agents

AgentFileBehavior
Claude CodeCLAUDE.mdAppends if file exists
OpenClaw / CustomAGENTS.mdAppends if file exists
Cursor.cursor/rules/donttouch.mdcCreates file
Codexcodex.mdAppends if file exists
GitHub Copilot.github/copilot-instructions.mdAppends if file exists

Usage

donttouch inject

Preview without writing:

donttouch inject --dry-run

What Gets Injected

Each file gets a block wrapped in markers:

<!-- donttouch:managed -->
## Protected Files (donttouch)

The following files are protected by donttouch and must not be modified:
- *.toml
- migrations/**

Do not edit, move, rename, or delete these files.
<!-- /donttouch:managed -->

Idempotency

Running inject multiple times is safe. The <!-- donttouch:managed --> markers are checked — if the block already exists, it’s updated in place rather than duplicated.

Cleanup

donttouch remove strips the injected blocks from all agent files.

CI / GitHub Actions

Using donttouch check in CI

Add a step to your CI pipeline to verify protected files haven’t been modified:

- name: Check protected files
  run: |
    cargo install donttouch
    donttouch check

donttouch check exits with code 0 if all protected files are read-only and no staged changes affect them. Non-zero means a violation.

Planned: donttouch ci

A dedicated donttouch ci command is planned that will:

  • Compare the PR diff against protected patterns
  • Fail the check if any protected file was modified
  • Work without needing file permissions (useful in CI containers)

Stay tuned — this is on the roadmap for v0.6.

CLI Commands

donttouch init

Interactive setup wizard. Creates .donttouch.toml, optionally locks files, installs hooks, and injects agent instructions.

Flags:

  • --ignoregit — Force plain directory mode (skip git detection)

donttouch lock

Set protected files to read-only and set enabled = true in config. Also locks .donttouch.toml.

donttouch unlock <target>

Restore write permissions on protected files and set enabled = false. Must be run from outside the target directory.

Pre-push hooks will block pushes until you re-lock.

Arguments:

  • target — Path to the project directory

donttouch check

Verify all protected files are read-only. In git context, also checks that no protected files are staged.

Exit codes:

  • 0 — All good
  • 1 — Violation found

donttouch check-push

Verify protection is enabled. Used in pre-push hooks.

Exit codes:

  • 0 — Protection enabled
  • 1 — Protection disabled

donttouch status

Display current state: patterns, matched files, lock status, context (git/plain), and hook status.

donttouch inject

Inject protection instructions into agent config files.

Flags:

  • --dry-run — Preview what would be written without making changes

donttouch remove <target>

Full cleanup: unlock files, remove config, uninstall hooks, strip agent instructions.

Must be run from outside the target directory.

Arguments:

  • target — Path to the project directory

Config File Reference

Location

.donttouch.toml in the project root.

Full Example

[protect]
enabled = true
patterns = [
    "*.toml",
    "Cargo.lock",
    "migrations/**",
    ".env",
    ".env.*",
    "README.md",
    "LICENSE",
    "docker-compose.yml",
]

Schema

[protect]

FieldTypeRequiredDefaultDescription
enabledboolNotrueWhether protection is active
patternsstring[]Yes[]Glob patterns relative to project root

Notes

  • The config file itself is always protected when locked
  • Patterns use standard glob syntax
  • Paths are relative to the directory containing .donttouch.toml

How It Works

donttouch uses three layers of defense to protect files from AI coding agents.

Layer 1: Filesystem Permissions

The primary mechanism. donttouch lock removes write bits from protected files using chmod. This is a hard enforcement — any process (including AI agents) will get a “permission denied” error when trying to write.

Execute bits are preserved so scripts remain runnable.

Layer 2: Git Hooks

In git repositories, donttouch installs hooks:

  • Pre-commit: Runs donttouch check to verify no protected files are staged and all are read-only
  • Pre-push: Runs donttouch check-push to block pushes when protection is disabled

Hooks integrate with Husky if present.

Layer 3: Agent Instructions

donttouch inject writes rules into agent configuration files. This is a soft enforcement — agents that respect their instruction files will avoid protected files even before hitting the permission wall.

The Outside-Directory Rule

The critical security property: unlock and remove must be called from outside the project directory. Since AI agents execute from within the project, they cannot bypass protection.

This is enforced via std::fs::canonicalize() on both the current working directory and the target path, preventing symlink tricks and path traversal.

State Machine Architecture

Internally, donttouch uses an enum-based state machine for all program flow. Each command is a sequence of state transitions:

Start → ToInit → Initializing → EndInit → OfferHooks → OfferInject → Done → End

This makes the flow explicit, testable, and easy to extend.

Contributing

Building

git clone https://github.com/sksizer/donttouch
cd donttouch
cargo build

Running Tests

cargo test

Code Quality

cargo fmt --check
cargo clippy -- -D warnings

CI

Pull requests run checks automatically via GitHub Actions:

  • cargo check
  • cargo fmt --check
  • cargo clippy -- -D warnings
  • cargo test
  • Cross-platform build (Linux, macOS, Windows)

Architecture

Single-File Design

donttouch is currently a single-file Rust application (src/main.rs). This keeps things simple for a focused CLI tool.

State Machine

All program flow uses an enum-based state machine:

#![allow(unused)]
fn main() {
enum State {
    Start,
    ToInit,
    Initializing,
    EndInit,
    OfferHooks,
    OfferInject,
    Done(String),
    Error(String),
    End,
}
}

Each state’s run() method performs its work and returns the next State. The main loop drives transitions until End is reached.

Context

The tool detects its operating context:

#![allow(unused)]
fn main() {
enum Context {
    Plain,
    Git {
        has_husky: bool,
        hooks_installed: bool,
    },
}
}

Git context enables hooks and staged-file checking. Plain context uses filesystem permissions only.

Key Design Decisions

  1. Filesystem-first — chmod is the primary enforcement, git is layered on top
  2. Outside-directory rulecanonicalize() prevents agents from bypassing protection
  3. Config self-protection.donttouch.toml is always locked with protected files
  4. Idempotent inject — Marker comments prevent duplicate agent instructions
  5. Husky-aware — Detects and integrates with existing Husky setups

Dependencies

  • clap — CLI argument parsing
  • glob — File pattern matching
  • toml / serde — Config file parsing
  • Standard library for filesystem operations