{"slug": "ai-agent-guardrails-look-simple-until-you-actually-code-them", "title": "AI Agent Guardrails Look Simple Until You Actually Code Them", "summary": "A developer implementing file access guardrails for an AI agent discovered that the seemingly simple task of restricting directory access required careful handling of path resolution, revealing a common gap between conceptual understanding and practical implementation. The developer encountered bugs such as double-looping over directory listings and mixing up path variables, highlighting the complexity of building secure AI agent tools.", "body_md": "I have spent the last year working around [AI security and governance](/posts/one-year-ai-security-governance-see-ai-differently/), so I already understood the high level idea behind guardrails.\n\nAgents should have boundaries. Tools should have scopes. Read access and write access should mean different things. A system that can touch files, APIs, CRMs, Slack, browsers, or internal docs should probably have a stronger plan than “hope it behaves nicely.”\n\nOkay, that last sentence already sounds like I am about to sell you a whitepaper.\n\nAnyways, I was doing a Boot.dev module around building an AI agent and the task was simple: let the agent list files from a working directory, but keep it inside the directory it was allowed to use.\n\nIt’s technically a simple small function that checks if the agent is in the permitted directory, if yes, extract the details of all the items in that directory.\n\nAnd having worked through that function I came out way more confident in my understanding of what goes behind guardrails, especially when it comes to local file access.\n\n## The annoying part was a path check[#](#the-annoying-part-was-a-path-check)\n\nThe function looked like this at one point:\n\n``` php\ndef get_files_info(working_directory: str, directory: str = \".\") -> str:\n    try:\n        working_dir = os.path.abspath(working_directory)\n        full_target_path = os.path.normpath(os.path.join(working_dir, directory))\n        if not os.path.commonpath([working_dir, full_target_path]) == working_dir:\n            return f'Error: Cannot list \"{directory}\" as it is outside the permitted working directory'\n        elif not os.path.isdir(full_target_path):\n            return f'Error: \"{directory}\" is not a directory'\n        else:\n            results = []\n            for item in os.listdir(full_target_path):                \n                full_path = os.path.join(full_target_path, item)\n                if os.path.isdir(full_path):\n                    isdir = True\n                else:\n                    isdir = False\n                file_size = os.path.getsize(full_path)\n                for item in os.listdir(full_target_path):\n                    results.append(f\"- {item}: file_size={file_size}, is_dir={isdir}\")\n            return \"\\n\".join(results)\n```\n\nThere is a bug in the listing part here.\n\nI loop over `os.listdir(full_target_path)`\n\ntwice, so the size and `is_dir`\n\nvalue can get tied to the wrong item. Very elegant. 10/10 production-grade software lol.\n\nAnyways, the part that I want to share with you is:\n\n```\nworking_dir = os.path.abspath(working_directory)\nfull_target_path = os.path.normpath(os.path.join(working_dir, directory))\nif not os.path.commonpath([working_dir, full_target_path]) == working_dir:\n    return f'Error: Cannot list \"{directory}\" as it is outside the permitted working directory'\n```\n\nThis is the base guardrail.\n\nThe agent gets a working directory. The user can ask for a directory. The code builds the real target path and checks whether that target still is inside the allowed working directory or not.\n\nIf the target is outside, the function returns an error.\n\nSmall thing, very boring, also the whole point.\n\n## I knew the idea, but coding it makes a difference[#](#i-knew-the-idea-but-coding-it-makes-a-difference)\n\nThis is where I felt the difference between understanding a concept and implementing it.\n\nIn my head, the rule was:\n\nThe agent should only inspect files inside the permitted directory.\n\nI had to keep track of `working_directory`\n\n, `working_dir`\n\n, `directory`\n\n, and `full_target_path`\n\n. One is the function argument. One is the absolute version. One is what the user asked for. One is the thing the agent might actually inspect.\n\nThose sound small until you mix them up.\n\nAnd I did mix them up.\n\nI passed the wrong thing to `abspath`\n\n. I confused `working_directory`\n\nand `working_dir`\n\n. I had to slow down and understand what `commonpath`\n\nwas comparing. I also checked whether something was a directory using the wrong version of the path at one point.\n\nAll this through the process felt like, “why is this test still failing, I swear I understood this five minutes ago.”\n\nBut in hindsight, the mistake was useful because it showed me where the guardrail actually does its work.\n\nDetails are tehcnically in the boring exact-ness.\n\nWhich path is allowed?\n\nWhich path was requested?\n\nWhich path will the tool actually touch?\n\nThose three questions have to line up. If they do not, the guardrail thing is out the window.\n\n## The boundary has to become executable[#](#the-boundary-has-to-become-executable)\n\nIn AI security and governance, we use a lot of clean words.\n\nControls. Policies. Permissions. Least privilege. Data boundaries. Tool access. Monitoring. Audit trails.\n\nThese words are useful. I am not making fun of them. Okay, some of them do start sounding like a compliance team discovered a thesaurus.\n\nBut they are useful.\n\nThe problem is that the words can stay at a comfortable distance from the implementation.\n\n“The agent should only access approved resources” sounds fine.\n\nThen you write the code and realize the approved resource is a path, the path can be relative, the filesystem has its own perks, and your variable names can be a part of the security model.\n\nThe boundary eventually has to become executable.\n\nA function has to say yes or no.\n\nA tool has to reject the request.\n\nA path has to be normalized before it gets compared.\n\nThe model can be instructed but the tool has to enforce.\n\n## A prompt is a request and code has to become the wall[#](#a-prompt-is-a-request-and-code-has-to-become-the-wall)\n\nYou can tell an agent, “Only read files inside this folder.”\n\nPrompts matter, obviously. Half of the current AI world is people discovering that words have consequences and then pretending this is a new branch of engineering.\n\nBut the prompt is still a request.\n\nThe model can follow it, misunderstand it, ignore it in some edge case, or get pulled away from it by some other piece of text. I am not even saying this in a dramatic jailbreak way. Sometimes systems fail because the input is weird, the instruction is vague, or the person using the tool asks for something the builder did not think about.\n\nThe tool code is where the request becomes a wall.\n\nThis was less obvious when I was staring at `commonpath`\n\nand asking myself whether I was comparing the actual path the agent would touch or some nice variable that made me feel safe.\n\nAnd that is the part that is important when it comes to agents.\n\nA chatbot can be wrong inside the chat window and irritate you. An agent with tools can read files, call APIs, update records, send messages, [browse websites](/posts/why-you-should-not-use-an-ai-browser-today/), or run commands. The error gets a body. It can move around inside your system and do things.\n\nSo when people say “we added a guardrail”, I now have a much more annoying follow up question in my head.\n\nWhere?\n\nIn the prompt? In the tool function? In the permissions layer? In the UI? In some policy doc that everyone agrees with and nobody has implemented properly?\n\nYes, I have become this person.\n\n## Agents make small engineering details matter[#](#agents-make-small-engineering-details-matter)\n\nThis also connects to the automation article I have been writing. (haven’t published it yet, will do that soon)\n\nThe more a workflow touches real data and real systems, the less it feels like a productivity trick. Its a small system someone has to understand, trust, maintain, debug, and then maybe apologize for in a Slack thread that begins with “quick question.”\n\nAgents make this more important because the path is less fixed.\n\nA regular script usually has a shape. And it can still break in absurd ways because computers are deeply committed to comedy, but at least you can usually point to the path through the code.\n\nWith an agent, the path can be looser. It can choose the tool. It can choose the argument. It can read a file and then decide the next thing to inspect. It can call the right function with the wrong target and look completely normal while doing it.\n\nThat is where boring engineering starts looking less boring.\n\nPath checks, allowlists, read-only modes, write confirmations, logs, dry runs, smaller scopes, clear errors.\n\nI used to think of these things as the furniture around the interesting part.\n\nNow I think they might be the interesting part.\n\n## The main work needs to be done before the actual code[#](#the-main-work-needs-to-be-done-before-the-actual-code)\n\nThe path check enforces a boundary, but it does not decide the boundary by itself.\n\nSomeone has to decide what the agent should be allowed to see.\n\nThe whole project? One folder? Only source files? Hidden files? Logs? `.env`\n\n? Generated files? Test fixtures? A scratch folder where it can make a mess without ruining anything important?\n\nThe same question comes up with writing access. Reading a file is one thing. Editing it is another. Deleting it is a separate category of “please tell me a human approved this before the agent got crazy.”\n\nThis is where the AI governance side and the software engineering side start becoming the same conversation for me.\n\nA policy can say the agent should only access approved resources. Good. Very adult.\n\nThen the code has to decide what an approved resource is at 11:37 PM when a user passes `../`\n\ninto a function.\n\nThat is the gap I am more interested in now.\n\nThe governance sentence and the code path have to meet somewhere. If they do not meet, the boundary is mostly vibes, and I have distrust of “vibes” now.\n\n## What I learnt here[#](#what-i-learnt-here)\n\nI did not learn that permissions matter. I already knew that.\n\nIt was more specific than that.\n\nI started respecting the gap between agreeing with a guardrail and implementing one.\n\nBefore writing this function, I could say “restrict the agent to the working directory” and feel like the idea was clear enough. After writing it, I had to care about the exact path being checked, the exact path being used, and the exact point where the tool says no.\n\nSlightly more irritating. Probably more useful.\n\nAnd honestly, this is becoming a pattern in how I learn technical things now. The concept makes sense at the top level, then the code makes me pay attention to the small details where my understanding was still fuzzy. I hate this process during the process, obviously, because I am a normal person. But after the tests pass and the confusion goes down a bit, that annoying detail usually becomes the part I remember.\n\nIn this case, the thing I remember is simple: a guardrail is only as real as the place where the system enforces it.\n\n## The small things I care about more[#](#the-small-things-i-care-about-more)\n\nThere are a few questions I now cannot ignore when looking at agents.\n\nWhat can the agent read?\n\nWhat can it write?\n\nWhere is that enforced?\n\nCan it touch secrets by accident?\n\nCan it call an external API?\n\nCan it write somewhere permanent?\n\nCan I see what tool calls happened?\n\nCan I run it in a dry-run mode first?\n\nWhat happens when it asks for something outside scope?\n\nWho owns the mess if the agent does the wrong thing successfully?\n\nA lot of software problems happen because the system did exactly what someone allowed it to do. The risky part is not always failure. Sometimes it is success inside a badly thought out boundary.\n\nThis is why I am starting to like boring controls more than I expected.\n\nI still like the agent part. I still like the feeling of giving a system tools and watching it do something useful. I am very much the target audience for this stuff, which is maybe why I am also suspicious of it.\n\nBecause the useful version and the dangerous version can look weirdly similar in the beginning.\n\n## Anyways, the guardrail was just a path check[#](#anyways-the-guardrail-was-just-a-path-check)\n\nThat is the funny thing.\n\nThe guardrail was tiny. A few lines around `abspath`\n\n, `normpath`\n\n, and `commonpath`\n\n. It made me scrtch my head at variable names longer than I wanted, which is one of the least glamorous ways to spend your learning time.\n\nAnd still, it changed how I think about this stuff a little.\n\nAI agent safety often gets discussed through prompts, evals, jailbreaks, model behavior, and governance frameworks. All of that matters. I work around this world, so I know those conversations are necessary.\n\nBut once an agent gets tools, safety also becomes ordinary software engineering.\n\nA function decides what the agent can touch. A check decides whether the request goes through. An implementation detail becomes the difference between a boundary and a suggestion.\n\nNow, I respect the annoying code behind the guardrails a lot more.", "url": "https://wpnews.pro/news/ai-agent-guardrails-look-simple-until-you-actually-code-them", "canonical_source": "https://codebynight.dev/posts/ai-agent-guardrails-look-simple-until-you-code-them/", "published_at": "2026-07-02 00:00:00+00:00", "updated_at": "2026-07-11 11:16:27.895944+00:00", "lang": "en", "topics": ["ai-safety", "ai-agents", "ai-tools", "developer-tools"], "entities": ["Boot.dev"], "alternates": {"html": "https://wpnews.pro/news/ai-agent-guardrails-look-simple-until-you-actually-code-them", "markdown": "https://wpnews.pro/news/ai-agent-guardrails-look-simple-until-you-actually-code-them.md", "text": "https://wpnews.pro/news/ai-agent-guardrails-look-simple-until-you-actually-code-them.txt", "jsonld": "https://wpnews.pro/news/ai-agent-guardrails-look-simple-until-you-actually-code-them.jsonld"}}