Can You Restrict Access to Folders in a GitHub Repo?
No, GitHub has no folder-level read permissions. Here's why, what GitHub does offer (CODEOWNERS, push rulesets), and the options that hide folders.
Short answer: No. GitHub has no way to let someone read one folder of a repository but not another. Every permission GitHub offers, from Read to Admin, applies to the entire repository and its full history. You can restrict who changes a folder, but hiding a folder requires a separate repository or a layer in front of GitHub that serves a filtered copy.
Below: what GitHub actually supports, why folder-level read access is hard in Git, and the workable alternatives.
What GitHub permissions cover
GitHub assigns repository roles per repository: Read, Triage, Write, Maintain and Admin. On GitHub Enterprise Cloud you can create custom repository roles, but they are combinations of repository-level permissions. None of them mention paths.
The same is true of everything else that grants access: organization teams, outside collaborators, deploy keys, GitHub Apps and fine-grained personal access tokens. They're all scoped to repositories.
People have asked for this for years. The GitHub community thread Request for Granular Folder-Level Access Control (opened January 2024) describes the monorepo version of the problem: "developers with access to the Mono repository gain visibility and access to all folders". The replies suggest branch protection, submodules and CODEOWNERS. Only one of those hides anything.
Why Git makes folder-level read access hard
A Git commit points to a tree, which points to every file in the project. Hashes cover all of it, so you can't hand someone a commit with a folder cut out and keep the same commit ID. Any filtered view is a different set of commits.
Git's documentation spells out the security consequence in the gitnamespaces SECURITY section: "The fetch and push protocols are not designed to prevent one side from stealing data from the other repository that was not intended to be shared." It describes two attacks where a client tricks the server into sending objects it shouldn't, one of them by asking for a delta against an object it claims to have. The recommendation: put private data in another repository.
That's also why the usual answer on other Git hosts is the same; GitLab's forum, for example, has threads asking to restrict access to sub-folders that end in "use separate repositories". Centralized systems like Subversion and Perforce can enforce path-based read rules because clients never hold the full history.
What GitHub does offer per path (all write-side)
CODEOWNERS
A CODEOWNERS file maps paths to people or teams. In GitHub's words, "Code owners are automatically requested for review when someone opens a pull request that modifies code that they own."
# .github/CODEOWNERS
/core/ @acme/core-team
/infra/ @acme/platform
*.sql @acme/data
With the branch protection option "Require review from Code Owners", a change to /core/ can't merge without approval from @acme/core-team. Code owners must have write permission. Nothing here stops anyone from reading /core/. More in CODEOWNERS is not access control.
Push rulesets
Push rulesets include "Restrict file paths", which can "prevent commits that include changes in specified file paths from being pushed to the repository". They apply to private and internal repositories and their fork network. Again, this blocks writes only.
Branch protection and rulesets on branches
These control who can merge to which branch and which checks must pass. They don't change what anyone can clone.
Ways to actually hide a folder
1. Move it to its own repository
Use git filter-repo --subdirectory-filter core/ to extract the folder with history, then restrict the new repository to the people who need it. This is the advice Git itself gives.
The cost: everything that imported core/ now has to consume it as a package or submodule, and people without access can't build those parts.
2. Make it a submodule
git rm -r core/pricing
git commit -m "Remove pricing from main repo"
git submodule add [email protected]:acme/pricing-core.git core/pricing
git commit -m "Add pricing as a private submodule"
Users without access to acme/pricing-core get an empty core/pricing directory when they clone. Note that removing the folder doesn't remove it from history; anyone with access to the old commits can still read it. If the folder must be hidden historically, you need to rewrite history (and every existing clone keeps the old copy).
3. Generate a filtered mirror
Tools like Copybara or a scheduled git filter-repo job publish a second repository with selected paths removed. People get the mirror, not the original. Contributions have to be moved back to the real repo, which is the hard part. This is also the classic setup for open-core projects that publish part of a private codebase; see Gitfence for open core.
4. Josh or a similar projection proxy
Josh ("Just One Single History") is an open-source Git proxy that presents subfolders of a monorepo as their own repositories, with history, and maps pushes back. It's designed for scaling monorepo workflows rather than as a permission system, so you'd build the authentication and policy layer yourself.
5. A path-aware access layer such as Gitfence
Gitfence does the projection and the permissions together, for GitHub repositories. You choose per folder or file who gets No access, Types only, Can read or Can edit. Each person or AI agent clones a view containing only what they're allowed to see, and their pushes come back as normal GitHub pull requests. The core team keeps working in GitHub with no changes.
What makes it different from a filtered mirror:
- Round trip. Pushes are validated (no writes into hidden paths, no symlinks into fences, no case or Unicode look-alikes of fenced paths) and applied onto the full upstream tree, then opened as a PR.
- Code still compiles. Types only gives generated TypeScript declarations for a hidden module, with literal values redacted, so code that imports it type-checks.
- No history rewrites when rules change. Views are forward-only. Granting or revoking access adds a commit; nobody is force-pushed or asked to re-clone.
- Hidden objects aren't in the served repo. We test this against shallow, partial and mirror clones and direct object fetches.
Limits: it protects from now on, not retroactively; commit messages of commits that touch both hidden and visible files are kept; it's GitHub-only today, and Types only covers TypeScript. See how it works and security for detail, or compare it with the approaches above.
What about tools that only hide folders from the editor?
.gitignore, .cursorignore, Copilot content exclusion, VS Code files.exclude and sparse checkout all change what a tool shows. None of them changes what's in the repository. If someone can clone it, they can read it. Sparse checkout is covered in detail in Git sparse checkout vs real access control.
For the monorepo version of this problem (several teams, one repository), see monorepo permissions for per-team access.
Summary
| Need | GitHub native? | Option |
|---|---|---|
| Require approval for changes to a folder | Yes | CODEOWNERS + branch protection |
| Block pushes that touch a folder | Yes (private/internal repos) | Push ruleset: restrict file paths |
| Hide a folder from some people | No | Separate repo, submodule, filtered mirror, or Gitfence |
| Hide a folder but let them compile against it | No | Gitfence Types only (TypeScript) |
FAQ
Does GitHub Enterprise support folder-level permissions?
No. GitHub Enterprise Cloud adds custom repository roles, but they're still scoped to a whole repository. There's no path-level read permission on any GitHub plan.
Can I hide a folder in a public GitHub repository?
No. Everything in a public repository's history is public. Move the folder to a private repository, and if it was ever committed, assume it has been copied.
Can a GitHub Action restrict who reads a folder?
No. Actions run after code is pushed and can enforce rules on changes, but anyone with read access can already clone every folder.
Is a submodule enough to protect a private folder?
It hides the folder's contents from people without access to the submodule's repository, which is real protection. It doesn't remove the folder from older commits of the parent repo if it used to live there.
Can I restrict a folder for AI coding agents only?
Editor-level ignore files reduce what an agent reads by default, but agents with a terminal can still read files directly. Giving the agent its own filtered clone is stronger; see least-privilege repo access for AI agents.