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, onecredentials.json, one*.pemand secrets leak into history forever - The
.gitignorerequires 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:
- The
!*/trick is poorly documented and confuses developers for years. Without it, child directories are silently blocked even if their contents are whitelisted. - Parent directories must be explicitly re-allowed, one by one.
!test/fixtures/*.jlwon't work without!test/and!test/fixtures/declared beforehand — a trap that catches even experienced developers. - Order of rules matters in non-obvious ways. A parent directory ignored by one pattern silently overrides a child un-ignore pattern.
- 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
.gitignoreitself to "unblock" a file it needs — in good faith, but bypassing your protection strategy - Recent reports show AI coding agents reading
.envfiles 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:
-
Agent instructions. Most coding agents read persistent directive files (
CLAUDE.md,.cursor/rules,AGENTS.md,.github/copilot-instructions.md). An explicit rule — "Never modify.gitallowwithout explicit user approval" — covers most accidental modifications. -
File system permissions.
chmod 444 .gitalloworchattr +i .gitallow(Linux) makes the file read-only. Modification becomes a deliberate act requiring explicit unlocking. -
CI/CD enforcement. A pre-commit hook or pipeline check can detect any modification to
.gitallowand require explicit validation (specific PR label, designated maintainer approval). -
Git-native protection (new). Git could support a
core.protectAllowFileconfig option that prevents modification of.gitallowwithout a--forceflag, 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
- Phase 1 — Introduce
.gitallowas opt-in. Addgit init --allowtemplate option. - Phase 2 — Add
git allowsubcommand for managing patterns (add, remove, check). - Phase 3 — Provide
git allow --generateto auto-create a.gitallowfrom currently tracked files. - Phase 4 — Consider
core.allowFile strictas 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
.gitallowbehave exactly as today — zero breaking changes. .gitallowis opt-in and additive.- The file is versioned and shared, like
.gitignore. - Existing
.gitignorefiles continue to work, complementing.gitallowas a secondary filter.
References
- Jason Stitt — "Effective .gitignore Whitelisting Patterns" — demonstrates the complexity of current workarounds
- izissise — "Allowlist for .gitignore" — real-world monorepo allowlist with dedicated generation script
- How-To Geek — "How to Set Up .gitignore As a Whitelist" — tutorial on the current workaround
- Brian Gershon — "Securing AI Coding Tools" — security analysis of AI coding agents and ignore files
- The Register — "Claude Code ignores ignore rules meant to block secrets" — real-world example of agent-level ignore failures
- Gitleaks — Feature request: use .gitignore as allowlist
- git-scm.com — gitignore documentation
- GitHub gitignore templates
The safest .gitignore is one that trusts nothing by default. It's time for Git to make this a first-class feature.