Background reading for Lesson 5 · 8 min read
GitHub Access Control
Can you make package.json read-only for some developers? Yes. Learning how GitHub does it is the fastest way to understand AWS IAM (Identity and Access Management, the AWS system that controls who may do what), because both use the same idea.
What this reading covers
The question
When a team grows, people often ask a sensible question:
“Some files should not be edited by everyone. Examples are package.json, the ESLint config (the settings of the code-style checker), tsconfig.json and, most of all, the .github folder that controls deployment. Can I make those files read-only for most developers?”
The answer is yes. But the way it works is not what most people expect.
The important correction
GitHub does not have read-only permissions for single files, like the Linux chmod command (a command that changes who may read or write a file). You cannot mark package.json as read-only directly.
Instead, GitHub controls the change. Anyone can edit the file on their own computer. But they cannot get that change merged (added to the main code) unless the right person approves it. The change travels in a pull request, which is a request to merge a set of changes.
You do not weld the store room door shut. You make a rule that nothing leaves the store room unless the manager signs the register. The door still opens, but nothing moves without approval.
This difference matters, because AWS works the same way. In AWS you do not lock a file either. You control who is allowed to perform an action.
Three ways to do it
1. CODEOWNERS — the standard answer
CODEOWNERS is a file that says which people or teams must review changes to which files. Create it at .github/CODEOWNERS in your repository. When two lines match a file, the last matching line wins.
# Default owner for anything not matched by a later line
* @your-handle
# Important config files: only you can approve changes
/package.json @your-handle
/package-lock.json @your-handle
/eslint.config.ts @your-handle
/tsconfig.json @your-handle
/next.config.js @your-handle
# The whole .github folder (the CI/CD workflows live here)
/.github/ @your-handle
# A whole folder, owned by a team instead of one person
/src/lib/auth/ @your-org/security-teamThen turn on branch protection. A branch is a separate line of work in Git, and main is the main one. Branch protection is a set of rules for a branch such as main. In the repository settings, add a rule for main (a “branch protection rule” under Branches, or a branch ruleset under Rules). Then switch on these two options:
Require a pull request before merging → Require review from Code Owners
Now suppose a developer changes package.json. Their pull request is blocked until you approve it. They can still read the file and propose changes. They just cannot merge without you.
2. Rulesets with push rules — the hard block
A ruleset is a newer and more flexible kind of rule set for a repository. It can include push rules. A push rule can restrict file paths. Then any commit that changes those paths is rejected outright. Push rules apply to private and internal repositories, and they need a paid GitHub plan such as Team.
Here is the difference. CODEOWNERS blocks a change at merge time. A push rule can block it at push time, before the change even reaches a pull request.
Use this when the answer must be “nobody, not even by accident”.
3. Branch protection basics
Turn these on whatever else you do:
- Require a pull request before merging — no direct pushes to
main - Require status checks to pass — automatic checks such as tests and lint must pass (show green)
- Require review from Code Owners — makes the CODEOWNERS file actually enforce something
- Do not allow bypassing the above settings — otherwise admins can skip all of these rules without anyone noticing
It becomes a real rule only when branch protection has “Require review from Code Owners” switched on. Many teams write the file, think they are protected, and are not.
Why this explains AWS IAM
Compare what you want in GitHub with what IAM does in AWS. It is the same list, written twice.
| In GitHub | In AWS IAM |
|---|---|
“Developers can read package.json but not change it” | Policy: Allow s3:GetObject, Deny s3:PutObject (S3 is the AWS file storage service; these two actions mean read a file and write a file) |
| The CODEOWNERS file listing who owns what | IAM Policy — the document listing what is allowed |
A GitHub team such as @security-team | IAM Group |
| A repository, a folder, a file | Resource — a thing you protect, such as an S3 bucket (a container for files) or an EC2 instance (a virtual server) |
“Nobody touches .github/ without my approval” | “Nobody deletes the production database” |
| A GitHub Actions workflow using a token to deploy | IAM Role — a machine, not a person, given temporary access |
| Nothing is allowed until a rule permits it | Deny by default — no policy means no access |
If you have set up branch protection, you have already thought like IAM. You used different words. AWS says policies, groups and roles for the same ideas. A policy is a document that lists what is allowed. A group is a set of users. A role is a set of permissions that a person or a machine can take on for a while.
The idea behind the first question was some files should be read-only for some people. That is the principle of least privilege: give each person or machine only the access it needs. This is the main idea of Lesson 5. Here you reached it from your own experience, not from a textbook.
Is this part of DevOps?
Yes, it is. It is called access governance: controlling who can do what. It applies to every tool in your stack:
| Layer | The question it answers |
|---|---|
| GitHub | Who can merge to main, who owns which files |
| AWS / IAM | Who can create, read or delete which resources |
| Database | Which users can read versus write which tables |
| CI/CD pipeline (the automatic steps that test and deploy your code) | Which secrets a build job is allowed to see |
The same principle applies in all four places. Give each person and each machine only what it needs, and nothing more.
A practical setup checklist
Protect the main branch
Require pull requests. Allow no direct pushes. This one change prevents most accidents.
Add a CODEOWNERS file
Start with only the risky files. These are the dependency lists (such as
package.json), config files and the.githubfolder.Turn on “Require review from Code Owners”
Without this step, the CODEOWNERS file is only a suggestion.
Require status checks
Tests and lint must pass before a merge. This is where the CI/CD pipeline from Lesson 14 connects.
Review who has admin rights
Admins can often bypass protections. Keep that list short. The reason is the same as for keeping the AWS root user (the all-powerful first account) locked away.
Background reading for Lesson 5 — AWS Account and IAM: Identity and Access.