DEV Community

Quinn Zhu
Quinn Zhu

Posted on

Build a Path Deny List Before Your First Agent PR

Your first agent patch will wander into the wrong files. Freeze those paths before you write a prompt. A junior needs a deny list on day one.

Agents will chase a green local run. They do not share your merge risk. Secrets, lockfiles, and CI configs look like easy edits. They are the edits that wake the whole team.

Why this gate exists

You joined a new repo this morning. You can clone it and run tests. You cannot yet judge blast radius well.

An agent will refresh a lockfile without asking. It will rewrite a workflow file next. It will open env examples and guess values. None of that belongs in your first PR.

You need a written freeze you can grep. Your memory fails fast under time pressure. A written file does not fail that way.

The deny list in plain terms

Treat every path as allowed or frozen. Frozen paths never enter the agent context window. Those frozen paths must never enter git add.

Use this decision table on day one. Copy it, then rename patterns for your repo.

Path pattern Freeze? Why you freeze it
.env, .env.*, **/*.pem Yes Secrets leak through prompts and diffs.
**/credentials*, **/*secret* Yes The names already signal production risk.
package-lock.json, yarn.lock, pnpm-lock.yaml Yes Lock churn hides the real change.
Cargo.lock, go.sum, poetry.lock Yes Same problem in other ecosystems.
.github/workflows/**, .gitlab-ci.yml Yes CI edits skip your reviewer set.
CODEOWNERS Yes Ownership files change who must sign.
**/migrations/** Yes Data rewrites are not first-PR work.
infra/**, terraform/**, k8s/** Yes Cloud diffs need a different owner.
README.md, CONTRIBUTING.md No Docs are safe if you read them.
Source under the ticket path No That is the work you were assigned.

The table is a proposal, not team policy. Your reviewers may freeze even more paths. Ask them before you treat this as law.

Step 1: Inventory the tree before you prompt

Do this work in a clean clone. Do not open a chat window yet.

git clone git@example.com:your-org/your-repo.git
cd your-repo
git status
git log --oneline -n 15
Enter fullscreen mode Exit fullscreen mode

You should list the dangerous file names yourself. Do not ask a model to guess them.

find . -type f \( \
  -name '.env' -o \
  -name '.env.*' -o \
  -name '*.pem' -o \
  -name 'CODEOWNERS' -o \
  -name '*lock.json' -o \
  -name '*lock.yaml' -o \
  -name 'Cargo.lock' -o \
  -name 'go.sum' -o \
  -name '.gitlab-ci.yml' \
\) -not -path './.git/*' | sort
Enter fullscreen mode Exit fullscreen mode

Save that output as your first artifact. You produced that list without an agent. If find is noisy, narrow the names.

ls -la
ls -la .github/workflows 2>/dev/null || true
ls -la infra terraform k8s 2>/dev/null || true
Enter fullscreen mode Exit fullscreen mode

Read CODEOWNERS now if it exists. Note who owns workflows and infra paths. You will not stage those trees today.

Step 2: Write a deny file you can grep

Create .agent-untouchable at your repo root. Keep the deny file boring and short. You should use only one pattern per line.

# proposal: junior path freeze, not a security boundary
.env
.env.*
*.pem
**/credentials*
**/*secret*
package-lock.json
yarn.lock
pnpm-lock.yaml
Cargo.lock
go.sum
poetry.lock
.github/workflows/**
.gitlab-ci.yml
CODEOWNERS
**/migrations/**
infra/**
terraform/**
k8s/**
.agent-untouchable
check-untouchable.sh
Enter fullscreen mode Exit fullscreen mode

Keep the file local until the team wants it. Copy it outside the worktree as well.

cp .agent-untouchable "$HOME/.agent-untouchable.$(basename "$PWD")"
Enter fullscreen mode Exit fullscreen mode

A bad reset then cannot eat your freeze list. You still own the source of truth.

Step 3: Install a checker that fails loud

This checker script is a proposal only. Run it only on your local machine. Do not treat it as production policy.

It compares staged names to the deny list. It exits one when a frozen path is staged.

#!/usr/bin/env bash
# check-untouchable.sh
# proposal: local gate for a junior's first agent PR
set -euo pipefail
shopt -s globstar nullglob

DENY="${1:-.agent-untouchable}"
if [[ ! -f "$DENY" ]]; then
  echo "missing deny file: $DENY" >&2
  exit 2
fi

mapfile -t staged < <(git diff --cached --name-only)
if [[ ${#staged[@]} -eq 0 ]]; then
  echo "nothing staged"
  exit 0
fi

fail=0
while IFS= read -r pat; do
  [[ -z "$pat" || "$pat" =~ ^# ]] && continue
  for f in "${staged[@]}"; do
    if [[ "$f" == $pat ]]; then
      echo "FROZEN path staged: $f (pattern $pat)" >&2
      fail=1
    fi
  done
done < "$DENY"

if [[ "$fail" -ne 0 ]]; then
  echo "unstage frozen paths before you request review" >&2
  exit 1
fi

echo "staged paths are outside the deny list"
Enter fullscreen mode Exit fullscreen mode

Make it executable before you rely on it.

chmod +x check-untouchable.sh
Enter fullscreen mode Exit fullscreen mode

Wire a local hook if you want friction. Skip shared repo hooks on day one. You do not own hook policy yet.

mkdir -p .git/hooks
cat > .git/hooks/pre-commit <<'EOF'
#!/usr/bin/env bash
exec "$(git rev-parse --show-toplevel)/check-untouchable.sh"
EOF
chmod +x .git/hooks/pre-commit
Enter fullscreen mode Exit fullscreen mode

A local hook protects your index only. It does not police the remote branch. That is enough for a first PR.

Step 4: Prove the checker with a fake diff

Never trust a gate you have not failed. Stage one frozen file on purpose.

echo "# probe" >> README.md
# pick a real frozen file if it exists
git add package-lock.json 2>/dev/null || git add .github/workflows 2>/dev/null || true
./check-untouchable.sh || true
git reset HEAD
Enter fullscreen mode Exit fullscreen mode

You want a red line from the checker. A silent pass teaches you nothing useful. Record the exact command you just ran.

If the pattern language misses, tighten the lines. Bash globbing is not the gitignore language. Enable globstar if you need recursive stars.

# already in the script; re-run after edits
shopt -s globstar nullglob
./check-untouchable.sh || true
Enter fullscreen mode Exit fullscreen mode

Retest the fake diff after that change. Keep the failing output in your notes. You now have proof the gate can fire.

Step 5: Bound the prompt after the freeze

Only now may you talk to an agent. Paste the ticket path and the deny file. Do not paste env files or keys.

Use a prompt that states the bound twice. This block is a template, not a transcript.

Edit only files under app/billing/.
Do not read or write paths in .agent-untouchable.
Do not refresh lockfiles.
Do not edit workflows.
Return a file list before any patch.
Enter fullscreen mode Exit fullscreen mode

You must demand the full file list first. Then you may accept a bounded patch. Then run the checker, then known tests.

# replace with the test command from README
python -m pytest -q
# or
npm test --silent
Enter fullscreen mode Exit fullscreen mode

If tests are unknown, stop the patch. You do not edit a repo you cannot run. Keep this workflow on path control only.

A worked example for a billing ticket

You were assigned ticket BILL-214 this morning. The ticket names app/billing/invoice.py only. That is the only product path you will stage.

Your agent now returns four changed files. You read the names before any patch.

app/billing/invoice.py
app/billing/invoice_test.py
package-lock.json
.github/workflows/ci.yml
Enter fullscreen mode Exit fullscreen mode

Two of those four names are frozen. You reject the whole patch at once. You do not "fix" the workflow as a favor.

Restate the bound in one short prompt.

Return a patch for app/billing/invoice.py
and app/billing/invoice_test.py only.
Enter fullscreen mode Exit fullscreen mode

Stage only those two files after that. Run the checker on the index. Run the tests you already know well.

This is slower than accepting the full diff. Slower is the point on day one. You are learning the repo, not farming merges.

What you paste, and what you never paste

You may paste the deny file itself. You may paste the ticket path next. You may paste a failing test name.

You never paste a .env file. You never paste id_rsa or pem files. You never paste production URLs with tokens.

If a README shows a sample key, stop. Replace it with REDACTED before any chat. The agent does not need real credentials to edit billing math.

This rule is stronger than the checker. The checker only sees git state. The chat sees whatever you paste in.

Where a free coding agent fits

You can draft the deny list by hand. You should draft that list yourself first. That inventory work is a junior skill.

You may still want a model for extra patterns. Keep all secrets out of that chat. Disclosure: This article was prepared as part of MonkeyCode's product outreach.

MonkeyCode is an open source coding-agent project. It offers free model access and a free server option. This article does not list model names or quotas. Those details change and must be checked on the project page.

Want a free model for extra pattern suggestions? MonkeyCode's free model access covers that narrow step. Use a free server only as a scratch runner. Run check-untouchable.sh there if your laptop is busy. Do not upload env files or production keys.

The deny list exists to stop that habit. After the freeze file exists, the rest of the work is git.

Failure modes you should expect

The checker will miss renamed files often. A move plus git add -A can sneak past a stale pattern.

The checker will also miss generated files. A build may write a lockfile you never meant to stage.

The checker will not stop a paste into a chat box. Frozen paths in git are not frozen in your clipboard.

If the agent rewrites the deny file, you lost. Protect the deny file itself from edits.

Add those lines to the deny list. Then restage nothing from that frozen set.

Limitations

This local workflow is not authorization at all. It is not a secret scanning tool. It is not a CODEOWNERS file replacement.

Bash globbing is a weak matcher here. It will not parse gitignore files correctly. It will not understand submodules or sparse checkout.

A determined agent can still edit a frozen file on disk. Your hook only sees git add events. Those unstaged edits still remain on disk.

The table above is only a proposal. It is not your company's written policy. Ask your reviewer which paths are actually sacred.

Do not publish a deny file that names internal hosts. Keep every pattern generic and boring instead.

Who should not use this

Skip this workflow if you were hired to change CI. Skip it if the ticket is a lockfile bump. Skip it during an incident when the frozen path is the fix.

Staff engineers with merge rights may need those files. This gate is for a junior's first agent PR. It is not a repo-wide standard until the team says so.

Do not use a remote agent host for an uncleared private repo. A free server is not a legal review.

Keep the freeze, then request review

Write the deny list before the first prompt. Fail the checker on purpose once today. Keep frozen paths out of the index.

The agent can still write code you understand. It cannot wander into secrets, locks, and CI today.

Ask your reviewer to confirm the freeze list. Then open the PR with the two files you can name.

Top comments (0)