Path-Scoped Write Permissions for AI Coding Agents Without Repo-Wide Trust
2 Jul 2026 · 4 min read
- AI Agents
- Repository Security
- CODEOWNERS
- Policy Enforcement
- Developer Workflow
Hook
Most AI coding agents do not need blanket write access to an entire repository. They need to touch a bounded set of files for one task, then stop. The problem is that many tool stacks still treat “can edit files” as a repo-wide capability.
That feels convenient until a bug fix drifts into deployment YAML, a refactor updates generated code nobody meant to review, or an agent decides the best place to help is a secrets-related config nearby.
This post walks through a cleaner model: path-scoped write permissions. Let the agent read broadly if you want, but only allow writes inside approved paths, with a hard pre-apply gate and a review lane for escalations.
Why this matters
Permission boundaries for AI coding agents usually focus on shell access, network access, or whether the agent can push a branch. Those matter, but repository boundaries matter too.
In real repos, the dangerous failures are often local and plausible: a config file that should not have changed, a migration script pulled into scope by pattern matching, or a docs update that quietly edits product policy text.
Architecture or workflow overview
- Start each run with a task manifest that names the write scope, not just the goal.
- Resolve that scope against repo policy, CODEOWNERS, and no-go directories.
- Let the agent draft changes normally, but treat every patch as provisional.
- Before applying or committing, compare changed files against the approved path set.
- If the patch crosses the boundary, fail closed and produce an escalation packet.
Implementation details
1) Define write lanes as explicit policy, not vibes
version: 1
lanes:
ui-copy-fix:
allow:
- web/src/**
- web/public/**
deny:
- web/src/generated/**
- infra/**
- .github/workflows/**
docs-update:
allow:
- docs/**
- README.md
deny:
- docs/policies/**
I prefer deny rules even when allow rules look sufficient. They make sharp edges visible, and they help when broad path globs would otherwise catch generated code, deploy configs, or legal text.
2) Resolve task scope against CODEOWNERS and sensitive paths
import picomatch from "picomatch";
import { parseCodeowners } from "./codeowners.js";
export function resolveWritablePaths(taskLane, policy, repoFiles) {
const allow = policy.lanes[taskLane].allow.map(picomatch);
const deny = policy.lanes[taskLane].deny.map(picomatch);
const codeowners = parseCodeowners('.github/CODEOWNERS');
return repoFiles.filter((file) => {
const allowed = allow.some((match) => match(file));
const blocked = deny.some((match) => match(file));
const ownerLane = codeowners.ownerGroupFor(file);
return allowed && !blocked && ownerLane !== 'security-admins';
});
}
3) Fail closed at patch time, not after commit time
#!/usr/bin/env bash
set -euo pipefail
ALLOWED_FILE=.run/allowed-paths.txt
PATCH_FILE=.run/candidate.patch
violations=$(git apply --numstat "$PATCH_FILE" | awk '{print $3}' | grep -v -x -f "$ALLOWED_FILE" || true)
if [[ -n "$violations" ]]; then
echo "OUT_OF_SCOPE_WRITE"
echo "$violations"
exit 42
fi
git apply "$PATCH_FILE"
$ ./scripts/apply-scoped-patch.sh
OUT_OF_SCOPE_WRITE
infra/prod/deploy.yaml
.github/workflows/release.yml
Patch blocked. Escalation bundle written to .run/escalations/2026-07-02T120100Z.json
4) Produce reviewer evidence instead of a vague denial
{
"taskId": "run_01jz8v9m0n",
"lane": "ui-copy-fix",
"allowedPaths": ["web/src/**", "web/public/**"],
"blockedFiles": ["infra/prod/deploy.yaml"],
"reason": "Agent attempted to edit deployment config while fixing frontend asset path",
"suggestedAction": "Require explicit approval or split into separate task"
}
What went wrong / tradeoffs
| Choice | What it helps | What it costs |
|---|---|---|
| Broad allow globs | Higher task completion rate on first try | More accidental cross-service edits |
| Strict deny lists | Protects dangerous paths reliably | Needs maintenance when repos move |
| CODEOWNERS alignment | Matches human ownership models | Breaks if CODEOWNERS is stale theater |
| Hard pre-apply gate | Fail-closed mutation control | More escalations for multi-file fixes |
- Do not confuse read scope with write scope.
- Expect stale policy to become the main operational failure mode.
- Avoid inferring write scope entirely from the prompt.
- Prefer explicit multi-lane escalation for legitimate cross-boundary fixes.
Practical checklist
- Define per-task or per-lane write scopes in repo-local policy.
- Keep hard deny zones for secrets, infra, workflows, generated code, and policy text.
- Resolve scope against CODEOWNERS or another ownership map.
- Check changed files immediately before apply, commit, or push.
- Fail closed on out-of-scope files, with no partial mutation.
- Emit an escalation bundle with blocked paths and rationale.
- Review lane churn monthly so the policy does not rot into friction.
Conclusion
Path-scoped write permissions are one of the simplest ways to make AI coding agents safer without making them useless. The model can still reason broadly. It just cannot mutate broadly by default.
If a task truly needs more reach, make that visible and deliberate. Repo trust should expand by evidence, not by accident.