Files are the most common thing an agent is given access to and the easiest thing for it to damage. The failures are not exotic — no clever attack required — and they follow from two ordinary facts: file contents compete with everything else for context, and a model asked to reproduce something long will reproduce most of it.
The two rules
Read narrowly. File contents stay in the context window for the rest of the session. Search or read ranges rather than pulling whole files in.
Edit, do not rewrite. A model regenerating a whole file will silently lose parts of it. Targeted replacements cannot lose what they never touched.
There is no filesystem access
Worth restating because it determines where every control belongs: the agent has no more access to your disk than it has to your database. It emits a request; your handler does the work.
So "can the agent delete my home directory" is not a question about the agent. It is a question about what your delete_file handler permits. If you did not write one, the answer is no; if you wrote one without a path check, the answer is yes.
Reading: the context problem
Every byte returned occupies the context window and is re-sent on every subsequent turn. A 4,000-line file is not a one-off cost — it is a permanent tax on the rest of the session.
Three things make reads behave:
- Offer a line range parameter and describe it in the tool definition, so the model knows partial reads are available. Models default to reading everything when the schema does not suggest otherwise.
- Give it a search tool that returns matching lines with numbers. Find-then-read is dramatically cheaper than read-then-scan, and it is the pattern an experienced engineer uses too.
- Cap the response, with a visible truncation notice. Silent truncation is worse than the original problem — the model reasons confidently about a file it only half received.
Writing: why rewrites lose content
This is the failure that costs people real work, and its cause is worth stating plainly. A whole-file write asks the model to generate the complete new contents. Generation is not copying. Everything it did not carefully attend to is reconstructed from an imperfect internal picture, and reconstruction drops things.
🚨 The placeholder that eats your file
The signature failure of whole-file rewrites:
The model wrote a comment describing the remaining content instead of reproducing it, and your handler faithfully wrote that to disk. Four hundred lines are gone. This is not rare, it is not a quirk of one model, and it is entirely prevented by never offering a whole-file write for edits.
The alternative is a targeted edit tool: the model supplies the exact existing text and its replacement, and your handler substitutes one for the other.
Two rules make this reliable, and both belong in your handler rather than the prompt:
- Require the file to have been read this session. An edit written against a remembered version of a file will not match, and if you match loosely it will match the wrong place.
- Fail on ambiguity. If
oldappears zero times or more than once, refuse and say so. Never guess which occurrence was meant — silently editing the wrong one produces a bug nobody will trace back to this.
⚠️ Whitespace is where edits die
Exact-match editing fails constantly on invisible differences: tabs against spaces, trailing whitespace, CRLF against LF. The model reproduces what it saw in the conversation, which may not be what is on disk byte for byte.
Return file contents with a consistent, documented representation, and when a match fails, say why — "found the line but indentation differs" is actionable; "no match" sends the model guessing.
Path containment
Every path arriving from a model is untrusted input, whether or not the model meant any harm. Content it read can steer it, and even without that, a confused agent working from a relative path can wander.
Resolving first is the whole point: it collapses .. segments, follows symlinks and normalises separators, so the comparison happens on the real destination rather than the requested spelling. A symlink inside your workspace pointing at /etc passes any string check and fails this one.
Worth denying beyond the root check:
- Dotfiles and credential paths —
.env,.git/, SSH keys, cloud credentials. These are the highest-value targets and almost never legitimately needed. - Anything outside an extension allowlist, if your use case permits one.
- Files above a size limit, which protects the context window as much as the filesystem.
- Writes outside a designated output directory, where the task allows that separation.
Deletion and other irreversible acts
Reads are recoverable. Edits are recoverable if the file is in version control. Deletion is not, and it is the one operation where the model's occasional confident wrongness has no undo.
| Operation | Reversible? | Sensible default |
|---|---|---|
| Read | n/a | Allow, within the root |
| Create new file | Yes | Allow, in the output directory |
| Edit existing | With VCS | Allow; require a prior read |
| Overwrite whole file | With VCS | Do not offer |
| Delete | No | Confirm, or move to quarantine |
| Execute | No | Confirm every time |
The quarantine pattern is worth the ten lines it takes: delete_file moves the file to a timestamped holding directory and reports success. The agent's view is identical, the operation is reversible for as long as you keep the directory, and a confused agent's damage becomes a cleanup task rather than an incident.
💡 Version control is the real safety net
Every control here reduces the chance of damage. A clean git working tree before the agent starts makes damage reversible, which is a categorically stronger position.
If an agent will touch a codebase, commit first. It costs one command and converts the worst realistic outcome from lost work into git checkout ..
Files carry instructions
An agent that reads files is an agent that reads whatever is in them, and file contents enter its context with the same standing as your instructions. A comment in a source file, a line in a README, a string in a config — any of these can be written to address the model.
This is indirect prompt injection through the file channel, and it is the reason read access plus write access plus network access is a qualitatively different risk from any one alone. The containment is architectural: no credentials in the agent's reachable paths, no arbitrary outbound requests, confirmation on anything that leaves the machine.
Inspecting files before an agent touches them?
View, convert and clean files entirely in your browser — nothing is uploaded to a server.
Browse all tools →Summary
- The agent has no filesystem access — your handler does, and that is where every control belongs.
- Reads are permanent context cost. Offer ranges and search; cap responses visibly.
- Never offer a whole-file write for edits. Models write "rest of file unchanged" and mean it literally.
- Targeted edits cannot lose untouched content. Require a prior read; fail on ambiguous matches.
- Resolve paths before checking them. String matching on
..is not containment. - Deny dotfiles and credential paths explicitly.
- Quarantine instead of deleting, and confirm anything irreversible.
- File contents can address the model. Read plus write plus network is the dangerous combination.
Frequently Asked Questions
How does an AI agent read a file?
Through a tool your code provides. The agent emits a request naming a path, your handler reads it and returns the contents as text in the conversation. The agent has no filesystem access of its own — everything passes through the tool you wrote, which is also where every restriction has to live.
Why do agents read files in chunks?
Because file contents consume context. A large file read in full can fill most of the available window, leaving no room for the actual work, and the cost is paid again on every subsequent turn since the content stays in the conversation. Reading ranges or searching first keeps the window usable.
Why is rewriting a whole file risky?
Because the model regenerates the entire content from what it has in context. Anything it did not read, misremembered or silently dropped is gone from the written result. The classic damage is a rewritten file where unrelated functions have vanished or been replaced by a comment saying the rest is unchanged.
How do I stop an agent writing outside its working directory?
Resolve every path to an absolute canonical form in your handler, then verify it sits inside the permitted root before touching it. Checking the string for '..' is not sufficient — symlinks, encoded separators and absolute paths all bypass it. Resolve first, then compare.
Should an agent be allowed to delete files?
Rarely, and never silently. Deletion is irreversible and the model can be induced into it by content it read, so it belongs behind explicit confirmation. A safer default is moving to a quarantine directory, which is recoverable and looks identical from the agent's perspective.