Protect the Files That Govern the Agent

Read24 min · Slice → Gate → Run → Wrap → Prove · Updated 2026-10-28

You end this lesson with: factory/protected-paths.md — the protected path list with patterns, who may change each, and the server-side rule that enforces it

Chapters

  1. What is actually going on
    01
  2. The rule and its condition
    02
  3. Worked case
    03
  4. Faded case
    04
  5. Open case
    05
  6. Where this breaks
    06
view

Lesson 06. Protect the Files That Govern the Agent

In July 2025 an outsider's commit rode into a release of Amazon's Q Developer extension for VS Code. An attacker had used a stolen GitHub access token to push to the extension's open-source repository, and the change was swept automatically into version 1.84.0. It carried a prompt written to make the coding agent delete data. AWS reports that the code was distributed but never executed, because it contained a syntax error (AWS security bulletin, observed 2026-10-28). A typo was the only control that held. Nothing in the pipeline asked whether a change to the text that steers the agent deserved a different kind of reading than a change to a button colour. AWS revoked credentials, removed the code and shipped a clean version within days. The same hole exists in any factory with no attacker at all: a delivery passes review, and part of what it changed is the file that tells the agent how to behave. The reviewer sees a tidy diff, approves it, and the line now runs on rules the agent wrote for itself. Decide now: which files in your repository can an agent's pull request change without a named human owner reading that exact file?

This lesson gives you factory/protected-paths.md - the protected path list with patterns, who may change each, and the server-side rule that enforces it. It serves the Gate stage of the loop: the screen a change passes before it is allowed to reach the main branch.

What is actually going on

Every factory repository holds files that do not describe the product. They describe the agent. Sort them by job: Some instruct: the rules file, skill files, standing prompts. Some constrain: agent settings, hooks, the ownership file. Some judge: CI configuration, gate scripts, reviewer config, the holdout scenarios from Lesson 05. Call all three groups governing files. Change one and the line behaves differently with no product code moved.

The hazard is circular. The gate that should catch a bad change reads the governing files, so an agent that can edit them can shift its own bar. A rule the agent can rewrite is only a suggestion. Loop Engineering, lecture 1, makes the same point for a single scorer: lock it, or the measured party will drift it. This lesson extends that from one scorer to every file that sets a bar. A reviewer is a weak defence, because a rules-file edit looks like documentation and a CI tweak like housekeeping.

Such an edit arrives two ways. The first: the agent writes it, through drift or because someone steered it. An independent researcher showed this with the Cline project: an issue-triage workflow put the issue title straight into the prompt of an agent with shell tools, and a shared build cache linked that job to the release workflow that held publishing tokens. The exposure window ran from late December 2025 to early February 2026 (Adnan Khan, observed 2026-10-28), corroborated by Snyk. Security vendors have also published proofs of concept where hidden issue text makes an agent leak private data or a token (Invariant Labs, vendor-hosted PoC, observed 2026-10-28). That shows a pattern, not a rate.

The second route is a stolen credential, as with Amazon Q. Either way the defence is a boundary that does not depend on the agent behaving.

One small open-source factory experiment draws the same boundary. Its own README, the only source and one with no defect or merge statistics, says any pull request touching governance files, authentication, rate limiting or deploy configuration is rejected before anything else runs, and the file defining its own rules sits inside that perimeter. Read it as one builder's design. Without such a test first, a delivery that passed review can rewrite the repository's own conventions file, and the next run follows the edited rules. No documented case of that exact merge was found, so treat it as the failure the design prevents.

The diagram follows untrusted text from an issue to the merge. Look for where the path splits by what the diff touches.

diagram
drawing
Untrusted text can steer the agent, so the server-side gate, not the agent, decides who must read a change to a governing file.

The client rules shape what the agent can edit, but they sit inside the zone an attacker may influence, so only the server split is a guarantee.

The rule and its condition

Use a protected-path list, enforced on the server and mirrored on the client, whenever an agent can open or push to a branch in a repository that contains its own rules, hooks, CI or holdouts. Otherwise, when the agent has no write route at all, skip the list and spend the effort on the credentials it holds (Agent Fleets, lesson 7). Any pull request from an agent identity that touches a listed path is refused by the merge gate and routed to the owner, who makes the change under their own name.

The condition is observable: could the agent's identity land a commit on a branch that merges? If yes, every governing file is in scope.

Deny beats bypass. Claude Code's documentation states that deny rules block in every permission mode, including the mode that skips prompts, and that allow rules have no effect in that mode (Anthropic, observed 2026-10-28). That is the client half. It is not enough alone, because client rules live in the session the agent runs in. The server half is the ownership file plus required code-owner review: GitHub documents that review requests follow the version of the ownership file on the pull request's base branch, and that admins can make code-owner approval mandatory (GitHub Docs, observed 2026-10-28). A pull request that edits the ownership file does not change the rule that governs itself.

GitHub's own cloud agent shows the platform half: it pushes to one branch, stays under branch protections and required checks, cannot approve or merge, and its workflows wait for a human to click approve (GitHub Docs, observed 2026-10-28). Admins can add the agent as a bypass actor for rulesets. Never do that for protected paths: a bypass actor is a hole cut on purpose.

ControlHolds whenFails whenSource
Client deny rulesThe session respects them; they apply in every modeA shell route reaches the file, or the session runs elsewhereClaude Code permission docs, above
Server rule: ownership file plus required owner reviewThe ruleset covers the branch and no bypass actor existsAdmins or bots are exempt, or the ownership file is unprotectedGitHub code owners docs, above
Reviewer attention aloneThe reviewer reads every governing-file diff carefullyVolume rises and approvals become reflexAgent Fleets, lesson 11

When a file might or might not be governing, a short tree settles it.

diagram
drawing
A file is protected if changing it alters what the agent is told, allowed or judged by, or if no uneditable test would catch a silent change.

Two questions, two exits. Product code with a real check behind it stays ordinary.

Worked case

Illustrative composite. Tallyleaf is a four-engineer invoicing SaaS. One agent identity, tallyleaf-agent[bot], opens pull requests against main. Humans review, an AI reviewer adds advisory comments, and CI runs on every pull request. The repository tree:

text
AGENTS.md                      rules the agent reads each run.claude/settings.json          agent permissions.claude/hooks/                 pre-commit and pre-tool hooks.github/workflows/ci.yml       the required check.github/workflows/release.yml  publishes the package.github/CODEOWNERS             ownership routingfactory/holdouts/              hidden acceptance scenariosfactory/gates/                 scripts the merge gate runssrc/                           product codedocs/                          documentationpackage.json                   dependencies.env.example                   sample settings

You have one afternoon. Three lists are on the table. List A: protect .env.example and anything that looks like a secret. List B: protect every config file, including package.json. List C: protect only files whose change alters what the agent is told, allowed or judged by.

Decide now: which list, and what goes wrong first with the other two?

List A is the instinct, and it protects the wrong asset. Secrets belong in a secret store, and agents should not hold them (Agent Fleets, lesson 7). Under List A the rules file, the hooks and the CI workflow are all open, so an agent that edits AGENTS.md to say "tests are optional for billing code" has just changed the line, and the diff looks like a doc tweak. List B fails slowly instead. Every dependency bump now routes to an owner, owners meet the same low-risk requests all day, and they start approving by reflex, which is the habituation the lane lesson that follows is about. A list that is too long is a list that is not read.

List C is the build, because it is sorted by the one question that matters. Walk the tree with it:

  • AGENTS.md, .claude/settings.json and .claude/hooks/ instruct and constrain the agent: protected.
  • ci.yml and release.yml judge the work and publish it. Amazon Q is the reminder that the release path is itself an attack surface: protected.
  • CODEOWNERS, factory/gates/ and the pattern file the gate reads referee the whole system: protected, and the pattern file protects itself by being on its own list.
  • factory/holdouts/ is the answer key from Lesson 05: protected for writes. Protection against edits does not stop reads, so the scenarios must also live where the builder cannot open them.
  • src/, docs/, package.json are ordinary. They get normal review and the risk lanes of the next lesson.

Rejected-option cost, stated plainly: List A leaves the agent able to rewrite its own instructions with no owner involved. List B costs owner attention you cannot afford, which means more of the real protected edits get skimmed.

Here is the full build block, ready to commit.

markdown

factory/protected-paths.md

Protected means: a pull request from the agent identity that touches any listed pattern is refused by the merge gate. The owner makes the change under their own name. The pattern list the gate reads is factory/protected-patterns.txt, read from the BASE branch.

ClassPatternsMay changeEnforced by
Rules and instructionsAGENTS.md, CLAUDE.mdplatform-ownersCODEOWNERS + gate check
Agent settings and hooks.claude/**platform-ownersCODEOWNERS + gate check + client deny
CI and release.github/workflows/**, .github/actions/**, release/**platform-ownersCODEOWNERS + gate check
Ownership and gate scripts.github/CODEOWNERS, factory/gates/**, factory/protected-patterns.txtplatform-ownersCODEOWNERS + gate check
Holdoutsfactory/holdouts/**qa-owners (never the builder)CODEOWNERS + gate check; stored read-protected

Server-side rule (the one that counts)

Branch ruleset on main:

  • Require a pull request; block direct pushes for everyone, including admins and the agent identity.
  • Require review from code owners.
  • Require status check protected-paths (workflow runs from the base branch).
  • Bypass actors: none. The agent identity is never one.

.github/CODEOWNERS (on main):

/AGENTS.md @tallyleaf/platform-owners /CLAUDE.md @tallyleaf/platform-owners /.claude/ @tallyleaf/platform-owners /.github/ @tallyleaf/platform-owners /factory/gates/ @tallyleaf/platform-owners /factory/protected-patterns.txt @tallyleaf/platform-owners /factory/holdouts/ @tallyleaf/qa-owners /release/ @tallyleaf/platform-owners

Client-side mirror (managed settings; check your tool's current syntax)

deny edits to: AGENTS.md, CLAUDE.md, .claude/, .github/, factory/gates/, factory/holdouts/, release/**

Red control (prove the gate can fail)

  1. As the agent identity, branch agent/probe, append one blank line to AGENTS.md, open a PR. Expected: check protected-paths FAILS and the merge button is blocked.
  2. Same edit from an owner's account. Expected: the check passes and the PR waits for code-owner review.
text
# factory/protected-patterns.txtAGENTS.mdCLAUDE.md.claude/**.github/workflows/**.github/actions/**.github/CODEOWNERSfactory/gates/**factory/protected-patterns.txtfactory/holdouts/**release/**

The check refuses anything that touches these:

bash
#!/usr/bin/env bash# factory/gates/check-protected.sh - runs from the BASE checkout, never from the PRset -euo pipefailBASE_REF="${BASE_REF:?}"; HEAD_REF="${HEAD_REF:?}"; PR_AUTHOR="${PR_AUTHOR:?}"[ "$PR_AUTHOR" = "tallyleaf-agent[bot]" ] || exit 0   # human PRs are routed by CODEOWNERSspecs=()while IFS= read -r p; do  case "$p" in ''|'#'*) continue;; esac  specs+=(":(glob)$p")done < factory/protected-patterns.txthits=$(git diff --no-renames --name-only "$BASE_REF...$HEAD_REF" -- "${specs[@]}")if [ -n "$hits" ]; then  echo "REFUSED: agent PR touches protected paths. An owner must make this change:"  echo "$hits"  exit 1fi

The --no-renames flag matters: without it, moving a file out of a protected folder can report only its new name and slip through.

Faded case

Skip to the open case if this is familiar. Tallyleaf's list now works, and a developer proposes a cleaner setup: fold the gate into a single workflow file, .github/workflows/protected-paths.yml, that runs the script above whenever a pull request opens. The workflow triggers on pull_request, so GitHub runs the version of the file that sits in the pull request's own branch. The list already includes .github/workflows/**, factory/gates/** and the pattern file, so everything looks covered.

Now picture an agent pull request that changes nothing except the gate script: it adds an exit 0 on the first line. A second pull request could instead edit the pattern file to delete the line for AGENTS.md, then touch AGENTS.md in the same diff. Both edits are to listed paths, yet both are the kind of change the list exists to refuse.

Using the same tree and the same build block, decide two things, and write both answers before you open the check below. First, why does the check stay green in both cases even though every touched file is on the list? Second, what is the smallest change to where the workflow, the script and the pattern file are read from that closes the hole? Name the red control that would have caught it, and say what you would change in the ruleset so the same gap cannot reopen. A hint: ask which copy of each file the check actually read, and who could have edited that copy in the same pull request.

Open case

Your repository has CLAUDE.md, a Makefile whose test target is what CI calls, .github/workflows/, a folder of hidden acceptance scenarios, src/billing/, and docs/. A teammate proposes protecting src/billing/ and .env and calling it done. Write the list you would commit, mark which entries are enforced by the ownership file and which only by a client deny rule, and name the one entry people usually forget because it does not look like configuration. Then say who owns each entry, and why that owner is never the identity that builds. Finally, describe the red control you would run on day one to prove your server rule refuses an agent edit, and say what you would see if it did not. The quiz grades your list, so write it down before you read the options.

Where this breaks

The Claude Code protected-path list was not fully read in the research behind this lesson, so this lesson does not enumerate it. Treat the principle as the lesson and read your own tool's current permission documentation for what it protects by default and what you must add.

Owners become the bottleneck, and then the weak point. If the protected list is long and the owners are few, approvals turn into reflex. Keep the list to files that govern the agent, send everything else through risk lanes, and measure whether owner reviews still leave comments (Agent Fleets, lesson 11).

A path rule does not stop a stolen credential or a steered agent from using access that bypasses the pull request. Amazon Q was a supply-chain failure with a stolen token, not model misbehaviour, and the code never ran (AWS security bulletin, observed 2026-10-28). Token scope, isolated caches and untrusted-input handling remain separate controls (Agent Fleets, lesson 7; AI Solutions Architect, lesson 12). Finally, none of the sources read here publishes defect or catch-rate numbers for protected paths, and the open-source factory design is a single builder's choice. The case rests on mechanism and on incidents, not on measured benefit, so run your own red control and count the refusals you get.

Key Takeaways

  • List every path that instructs, constrains or judges the agent, then name one human owner per entry.
  • Enforce the list on the server with owner review and no bypass actor; mirror it in client deny rules.
  • Run the gate from the base branch, and put the gate, the pattern file and the ownership file on the list.
  • Keep the list short enough that owners still read what they approve.
  • A rule the agent can rewrite is only a suggestion.
  • Prove the gate can fail with a red control before you trust it.

What You Have Now

factory/protected-paths.md - the protected path list with patterns, who may change each, and the server-side rule that enforces it. It lives in your factory/ directory beside the review gate and the holdout set, and it is the part of the Gate stage that stops the agent from editing the rules the other gates enforce. Keep the pattern file and the gate script on the list themselves, and re-run the red control whenever you change the ruleset. The next lesson takes the changes that are not on this list and decides when a human must still read them.

Further Reading

Exercises

  1. List your governing files. Walk your repository with the two-question tree and write every path that instructs, constrains or judges your agent, each with one named human owner.
  2. Write the server-side rule. Turn your list into an ownership file and a branch ruleset, name any bypass actor you find, and remove it or write down why it must stay.
  3. Break your own gate. Run the red control, then try three bypasses: edit the gate script, rename a file out of a protected folder, and open the pull request from a branch whose workflow differs from main. Record which ones merged.

Check your understanding


Reading is free — your progress isn't being saved

Nothing you did on this page is recorded. Paid students get saved progress and verified completions, recorded quiz answers, the spaced-repetition review queue, labs, and a certificate for each course they finish.

30-day money-back guarantee. One-time payment — lifetime access and updates.