Feature Proposal: `.gitallow` — A native allowlist for Git tracking

9 min read Original article ↗

Component: git-core / gitignore
Type: Feature Request
Priority: Medium-High


Summary

Introduce a new .gitallow file that acts as a native allowlist for Git tracking. While .gitignore defines what to exclude, .gitallow would explicitly define what is permitted to be tracked — shifting the paradigm from "everything is allowed unless excluded" to "nothing is tracked unless explicitly permitted."

This proposal is motivated by the rise of agentic AI development, where coding agents autonomously generate large numbers of intermediate files that have no place in version history.


Motivation

The .gitignore was designed for a human-only world

Git's current model is a deny-list (blacklist): everything is tracked by default, and .gitignore patterns exclude specific files. This worked when humans were the sole actors creating and committing code.

The agentic development era has changed this fundamentally. Tools like Claude Code, Cursor, Windsurf, GitHub Copilot, Cline, and Aider generate a considerable volume of working files during a session: feature specifications, context files, project constitutions, architecture plans, drafts, benchmark results, logs… None of these belong in a Git repository, but a single careless git add . can commit them to history — permanently.

The blocklist approach is structurally insufficient

With a classic .gitignore, you must anticipate every sensitive or unwanted file pattern in advance. This is a losing game:

  • AI agents create new file types and directory structures unpredictably (.claude/, .cursor/, CONSTITUTION.md, feature-spec-auth.md, context directories…)
  • One forgotten .env.production, one credentials.json, one *.pem and secrets leak into history forever
  • The .gitignore requires constant maintenance as the ecosystem of AI tooling evolves — each new agent brings its own set of working files

The question is no longer "what should we exclude?" but "what should we permit?"

Developers already do this — painfully

The community has long recognized the value of an allowlist approach. The current workaround uses .gitignore with inverted logic:

# Block everything
*

# Re-allow directory traversal
!*/

# Whitelist specific patterns
!**/*.py
!src/
!src/**

This pattern is powerful but deeply flawed as a user experience:

  1. The !*/ trick is poorly documented and confuses developers for years. Without it, child directories are silently blocked even if their contents are whitelisted.
  2. Parent directories must be explicitly re-allowed, one by one. !test/fixtures/*.jl won't work without !test/ and !test/fixtures/ declared beforehand — a trap that catches even experienced developers.
  3. Order of rules matters in non-obvious ways. A parent directory ignored by one pattern silently overrides a child un-ignore pattern.
  4. The semantics are inverted from the file's name. You're writing "allow" rules in a file called "ignore" — cognitive dissonance that makes maintenance harder.

A dedicated .gitallow file would make this first-class, intuitive, and maintainable.

The .gitignore as a security perimeter — not just a convenience

In agentic development, the .gitignore is no longer a convenience file — it is the security policy of the repository. Consider what happens without proper protection:

  • An agent generates a file containing temporary API tokens or test credentials
  • An agent creates a context file that embeds sensitive architectural details
  • An agent modifies the .gitignore itself to "unblock" a file it needs — in good faith, but bypassing your protection strategy
  • Recent reports show AI coding agents reading .env files despite exclusion rules in tool-specific ignore files (.claudeignore, .cursorignore…)

Git itself should provide the last line of defense — a layer that no agent, no tool, and no accidental git add . can bypass.


Proposed solution

New file: .gitallow

When a .gitallow file is present at the root of a repository (or in subdirectories), it inverts the default behavior: nothing is tracked unless it matches a pattern in .gitallow.

The 4-layer architecture

The .gitallow naturally organizes into four complementary layers, each serving a distinct purpose. This mirrors the best practices already developed by the community for inverted .gitignore files:

Layer 1 — The global lock: allow nothing by default

This is implicit when .gitallow exists. No file is tracked unless explicitly listed. This single behavior change protects against every unknown file an AI agent might create.

Layer 2 — Explicit exceptions: what MUST be versioned

# Project documentation
README.md
LICENSE.md
CHANGELOG.md
CONTRIBUTING.md
SECURITY.md

# Source code
src/**

# Tests
test/**/*.jl
test/fixtures/**

# Project configuration
Project.toml
.github/**

# Examples and benchmarks
examples/**
benchmark/**

Each line is a conscious decision: "yes, this file is part of the deliverable." The .gitallow becomes living documentation — by reading it, you understand exactly what constitutes the project.

Layer 3 — Fine-grained exclusions within allowed paths

Even within allowed directories, some build or test artifacts should remain excluded. This is where .gitignore complements .gitallow:

# Benchmark results (code yes, results no)
benchmark/*.json

# Build artifacts
*.jl.cov
*.jl.mem
*.o
*.so
*.dylib

# Generated documentation
docs/build/

Layer 4 — Environment and tooling exclusions

The classic layer, still necessary for IDE, OS, and CI-specific files:

.DS_Store
.idea/
.vscode/

Resolution order

1. .gitallow (if present) → defines the universe of trackable files
2. .gitignore             → further excludes from that universe
3. .git/info/exclude      → local overrides (unchanged)
4. core.excludesFile      → global overrides (unchanged)

A file must pass both filters: allowed by .gitallow AND not excluded by .gitignore.

Syntax

Identical to .gitignore (glob patterns, ** recursive wildcards, directory markers with /, comments with #). No new syntax to learn.

# .gitallow — Only these files can be tracked

# Source code
**/*.py
**/*.js
**/*.ts
**/*.rs
**/*.go

# Configuration (safe)
**/pyproject.toml
**/package.json
**/Cargo.toml

# Documentation
**/*.md
LICENSE
CHANGELOG

# CI/CD
.github/**

# The allowlist and ignorelist themselves
.gitallow
.gitignore

Behavior matrix

.gitallow present? .gitignore present? Result
No No All files tracked (current default)
No Yes Current behavior, unchanged
Yes No Only matching files tracked
Yes Yes Intersection: allowed AND not ignored

Why this matters for agentic development

Protection by default. When an agent creates feature-spec-auth.md, a context directory (.claude/, .cursor/, .copilot/), or a CONSTITUTION.md at the root, these files are automatically invisible to Git. No need to update any ignore file for each new artifact.

Intentionality. Every versioned file is an explicit choice. You don't suffer your version control — you drive it. This is critical when an agent can create dozens of files in a single work session.

Separation of concerns. The context given to the agent (project constitution, detailed feature specs, prompt files) lives in the working directory without polluting Git history. The agent works with rich local context; the remote repository stays clean.

Security. Configuration files containing tokens, temporary API keys, or test credentials cannot leak by accident. The "deny-all" model is fundamentally safer than an "allow-all" model where you hope you've thought of everything.

Errors become loud, not silent. The main risk of an allowlist is forgetting to include a new file or directory. But this produces a build failure — a loud error resolved in 5 minutes. The alternative (a sensitive file leaking into Git history) is a silent error that haunts you for years. Loud errors are always preferable to silent ones.


Protecting the .gitallow itself

The .gitallow becomes a critical security file. An AI agent might modify it to "unblock" a file it needs — in good faith, but bypassing the protection strategy. Multiple defense layers can address this:

  1. Agent instructions. Most coding agents read persistent directive files (CLAUDE.md, .cursor/rules, AGENTS.md, .github/copilot-instructions.md). An explicit rule — "Never modify .gitallow without explicit user approval" — covers most accidental modifications.

  2. File system permissions. chmod 444 .gitallow or chattr +i .gitallow (Linux) makes the file read-only. Modification becomes a deliberate act requiring explicit unlocking.

  3. CI/CD enforcement. A pre-commit hook or pipeline check can detect any modification to .gitallow and require explicit validation (specific PR label, designated maintainer approval).

  4. Git-native protection (new). Git could support a core.protectAllowFile config option that prevents modification of .gitallow without a --force flag, similar to how protected branches work.


Configuration

# Enable .gitallow processing (default: auto — use if file exists)
git config core.allowFile .gitallow

# Disable entirely (ignore .gitallow even if present)
git config core.allowFile false

# Strict mode — require .gitallow, refuse to track without it
git config core.allowFile strict

# Protect .gitallow from accidental modification
git config core.protectAllowFile true

Migration path

For existing projects

# 1. Generate .gitallow from currently tracked files
git ls-files | sed 's|[^/]*$||' | sort -u | sed 's|^|!|; s|$|**|' > .gitallow.draft

# 2. Or more precisely:
git ls-files > /tmp/tracked.txt
# Review and convert to glob patterns

# 3. Verify nothing is lost
git status  # Should show no unexpected changes

For Git itself

  1. Phase 1 — Introduce .gitallow as opt-in. Add git init --allow template option.
  2. Phase 2 — Add git allow subcommand for managing patterns (add, remove, check).
  3. Phase 3 — Provide git allow --generate to auto-create a .gitallow from currently tracked files.
  4. Phase 4 — Consider core.allowFile strict as a recommended setting for security-sensitive projects.

Alternatives considered

Alternative Why it's insufficient
* + ! negation in .gitignore Counter-intuitive syntax, parent directory trap, order-dependent, cognitive dissonance (writing "allow" in "ignore")
Pre-commit hooks (gitleaks, trufflehog) External dependency, detects secrets after staging, can be bypassed with --no-verify
git add discipline Relies on human vigilance — exactly what AI agents lack
.git/info/exclude Not shared, not enforceable across a team
GitHub Push Protection Server-side only, limited to known secret patterns, doesn't prevent local commits
Tool-specific ignore files (.claudeignore, .cursorignore) Fragmented ecosystem, each tool has its own syntax and enforcement (or lack thereof), not a Git-level guarantee

Backward compatibility

  • Repositories without .gitallow behave exactly as today — zero breaking changes.
  • .gitallow is opt-in and additive.
  • The file is versioned and shared, like .gitignore.
  • Existing .gitignore files continue to work, complementing .gitallow as a secondary filter.

References


The safest .gitignore is one that trusts nothing by default. It's time for Git to make this a first-class feature.