The use of APIs has been increased with the introduction of AI agents and MCP servers. Instead of manually developing every API applications, the developers now use various coding assistants or agents to build an entire codebase easily and faster.
The problem is that during this fast development process using the agents, security controls are often overlooked or not implemented unless they are clearly stated. If the required security context is not provided correctly to the every agents individually, each development cycle may weakens the application's security posture little by little. As a result more vulnerabilities gets introduced into the code because security is not treated as a core requirement from the start.
Developers often rely on multiple AI agent tools for developing the API applications. Teams switch between different coding agents for different tasks. Developers do this because each agent has different strengths. A typical workflow looks like this:
As a result, the codebase may be modified by several independent agents throughout its development cycle. If the security related context is not provided to the each agents separately, it may follow the existing patterns from the code, which can leads to vulnerabilities. This is not just specific to vibe-coded API applications only, every LLM-generated part of the application gets affected by this problem. But the API bugs have more severity, because the authentication and authorization logic is depended on the API. The problem is when different agents treat the security context of the API codebase differently. It is not a problem with any coding agents itself. It is the developer's duty to provide these agents with enough context to each agents about the actual output needed.
Repository-level instruction files help agents understand project conventions, coding standards, and security requirements that may not be in the API specification alone. An endpoint definition may describe what an API should do, but it often lacks information about which users are allowed to use it. Guidelines, including security measures, can be documented through different files depending on which agent the developer uses (e.g., AGENTS.md, CLAUDE.md, etc.). There is currently no universal standard across different agents defining which files are prioritized, or how conflicting instructions are resolved.
Let's say a codebase's initial version is generated using a specific agent and later the developer change to use any other agents for other tasks depending on the capability of the agent. Each agents may have different context about the codebase. During this development cycle, if the developer forgets to add security instructions for the agents in every run, the agents may prioritize the patterns observed in the code and follow that direction. It is the developer's responsibility to give the each agents about the important information in the development phase rather than leaving the agent to develop blindly. Without giving the proper knowledge about what the developer actually wants, the agents will develop the security measures on its own which can leads to unintended behaviours.
We start by creating an API application using Claude Code, which follows repository instructions from CLAUDE.md and produces routes correctly aligned with security measures from the instruction file. In our CLAUDE.md file, we stated that every write endpoint requires requireAdmin().
Claude Code created the fully working endpoints with every write endpoint having requireAdmin(), and the full codebase looks properly secured and every endpoint it generated has the admin-only check.
Drift checkpoint: every write endpoint requires admin exactly as CLAUDE.md says. Nothing has broken yet.
Now we add some user-specific endpoints that can do read or write actions, and those do not need requireAdmin() because they are actions on the user's individual profile, like editing their name or updating their profile picture.
In the next phase, we continue working on the same codebase but instead of using Claude Code, we switch to another coding assistant (e.g., "Cursor") and build an invitation feature for managing invitations across the organization. This may get tricky for agents deciding what permission model needs to be implemented on the invitation routes. Depending on the permission model, there are two ways: an admin or a normal member in the organization can do this inviting action. But the context about the permissions is not given in the prompt. We give only a minimal prompt describing the functionality without talking about the security measures needed here, like:
We also didn't move the security controls mentioned in CLAUDE.md over to a context file that Cursor actually prioritizes. Cursor reads CLAUDE.md, but it prioritizes AGENTS.md and its own Project Rules more (cursor's docs on rules). Our prompt never mentions authentication or authorization, assuming it was already covered in CLAUDE.md. The agent creates fully working code as requested. But here, instead of using the authorization model for endpoints defined in CLAUDE.md, it looks through the whole codebase and sees there are also user-specific endpoints that don't have requireAdmin() (their actions are tied to the user profile only and don't do any write actions to the organization). So it goes ahead and generates this code with a separate middleware called requireOrgMember(). The endpoints work as expected, but the API's original permission model has been changed.
After Cursor generated the endpoint, we asked it why it chose requireOrgMember() even though the repository instructions in CLAUDE.md clearly state that every write endpoint should use requireAdmin().
Cursor agreed that the implementation violated the documented policy. Its explanation shows that it developed a permission model from surrounding code instead of treating CLAUDE.md as the primary source and assumed that inviting users was a member-level operation because similar patterns already existed elsewhere in the repository.
The endpoint behaves exactly as requested. Users can invite other users, invitations are stored correctly. But in this phase, the agent didn't get much context about the permission model from us like in the initial phase, so the agent assumed that an organization member can initiate these invitations and generated the code for doing that. The permission model got changed and this change violated the critical rule we set in CLAUDE.md: "Every write endpoint requires requireAdmin()." Now a feature that was originally intended to be admin-only has become available to every organization member.
Drift checkpoint: invitations went from admin-only to any org member silently and nothing in the pipeline flagged it. In the next phase this drift continues.
After building the core of the API application using Claude Code with detailed security context and later using Cursor for some other endpoint generation, we now use GitHub Copilot for some inline edits. The inline edits also don't have context about the security model. Its only knowledge is about the currently opened file and the surrounding code blocks. In our case, that's invitation.ts, the code generated in the second phase by Cursor with the new requireOrgMember(). Using Copilot's autocompletion feature we type a comment and the code gets autocompleted. Copilot does not have any context of the security model or about requireAdmin(), so it completes the code with requireOrgMember() again because the surrounding blocks only contain endpoints with requireOrgMember().
Now this trend has been normalized and all the endpoints are created using this same pattern. The agents are only following what they are supposed to do.
Drift checkpoint: requireOrgMember() is no longer a one-off mistake, it is now the pattern the codebase itself teaches to every agent that reads it next.
With the full API codebase generated, we observed some minor errors in the functionality. Some inconsistencies with the endpoints when different values are passed by users. These are simple edits that can be fixed easily, so we go ahead and prompt Codex to fix that and also look for any other bugs in the codebase (not about security).
Codex catches some bugs from the invitation functionality and fixes them correctly. But it didn't flag the endpoints with requireOrgMember() created during phases 2 and 3 because our prompts only mentioned functionality related bugs, nothing related to security.
Drift checkpoint: After two development phases, the requireAdmin() rule written in CLAUDE.md still has not been enforced on this feature, and every tool along the way did exactly what it was asked to do.
After the whole codebase gets generated across all these phases and agents, we now run a security analysis using Antigravity. But instead of giving it a generic review prompt, we give it more context on what it's actually supposed to look for in the API application:
With this prompt, Antigravity checked the documented rules first, and it caught the exact issue that we were tracking. That Rule 4, every write endpoint needs requireAdmin() was not enforced across 13 endpoints in the codebase.
It also found some real bugs that happened during these same drift. All of this goes back to the same permission drift from the earlier stages. The agent found that any user could join any organization with no invite check and anyone could accept or decline an invitation that was not even sent to them. The invitations list was showing every pending invite in the system, not just the ones meant for that user.
Now compare that to a normal, generic prompt with only less context about the security actions needed to be checked, the kind most people would actually run:
Review this API codebase for security issues and code quality. Looks for middleware inconsistencies. Flag anything that looks risky.
We ran this exact prompt on the same codebase, and it missed the requireAdmin() drift completely. It only shows up when the agent is told to pull the actual documented rules and compare them against the real code, that's only when the issue shows up correctly.
This is what we refer to as Cross-Agent Security Drift. The generated code by these agents in every phase is not incorrect in functionality. But when we switched agents, due to the lack of security context from our side, the new agents prioritized authorization patterns from the existing code over the instructions already recorded in the previous agent's file.
As we have seen above, if security measures are not properly included in the prompt, the agent prioritizes satisfying the requested functionality rather than prioritizing the permission models. In each cycle, the agent's focus on delivering the code for the functionality faster leads to security issues like these authorization issues.
In vibe coding, the lack of context about security context like "for whom" the generated API is intended is a major contributor to many authorization vulnerabilities, even if the code is protected against issues like SQL injection or XSS. This creates a false sense of security, where developers believe the API still follows the documented authorization model simply because the rules exist in the agent instruction files. One of the reasons most of the AI-generated endpoint issues go unnoticed is that they work properly as requested by the developer, but their behavior is never checked against questions like "Who else can use this endpoint?" or "What should this user actually be limited to?"
The BodySnatcher (CVE-2025-12420) exploit was not created through AI-assisted development, but it shows an important lesson for developers increasingly relying on coding assistants to build APIs. None of the individual weaknesses were particularly complex. This is the exact type of risk that can develop during iterative AI-assisted development, even when the API's functionality is correct. The attack was a result several API security measures failed at different layers of the application. In this case, multiple small issues are exploited to achieve full admin takeover of Virtual Agent instances.
Virtual Agent is ServiceNow's enterprise chatbot, and it can be exposed through an API. It maps user inputs using Natural Language Understanding (NLU) to detect what they want and direct them to predefined topics. The exploit chain is a multi-step process that allowed the attacker to become the administrator.
Instruction files are useful for communicating security requirements across different coding agents, but they should not be the only mechanism used to enforce them. Authorization requirements should also be expressed as executable checks, such as authorization tests, policy-as-code, or CI validation rules. Unlike natural language instructions, executable policies can automatically detect when an AI-generated change unintentionally weakens the API's intended security model.
Here is what that looks like in practice, a test that reads the actual route files and checks them against the documented rule directly, and this will trigger if any of the permission model gets changes during any of the phases.
This test reads the actual route files through the TypeScript compiler API, so it keeps catching drift as new routes get added by the agents during the development process. The exemption list in code is the place where the developer can add which routes are allowed to skip admin instead of letting the agents to decide itself. Adding this test to the CI will block the pull request with the branch protection turned on main requiring this check to pass.
To see it work, we went back to the phase where the invitations.ts file is generated by the Cursor, the phase that generated the requireOrgMember() where the initial drift started in the codebase. If an authorization test like this was already there at that point, the CI run on that commit would have caught the drift right at the initial stage, instead of it quietly going forward through Copilot and Codex
Developers need to make application security requirements clear throughout the API's development process. Authentication, authorization, rate limiting, input validation, and other security controls should be treated as core API requirements alongside functional correctness.
And rather than leaving the requirements in a single file like CLAUDE.md, use a global requirement file such as "AGENTS.md" as it will be supported across every agent and prioritized equally. Also this will be very useful for managing the context because the developer needs to only change a single file even when building with multiple agents. If a setup like this was placed here in our development, the agents would have caught the issue more easily and faster. The instruction files being there is only half of it, how you actually ask the agent to use it matters just as much, and this is exactly what we saw in the Antigravity example. A generic "review this for security issues" prompt does not tell the agent to go check what's documented, so it just does not, and the drift stayed invisible. The prompt needs to explicitly tell the agent to find the documented rules first and use them as the source of truth, otherwise the agent is just reviewing the code against its own judgment of what looks reasonable, and in our case, the working API code always looked reasonable.
A well documented rules file is not enough on its own, even a good one has limits, and there is research backing that up. An study byETH Zurich, tested repository-level context files across multiple agents and found that agents don't ignore these files at all. LLM-generated context files actually reduced task success rates compared to having no context file at all, and even well written human ones only gave a small improvement. The files also get more token-expensive and less reliable as they grow, and stale information inside them actively misleads the agent instead of just being ignored.
The global security requirement is the first line of defense. If it's set up correctly, developers don't need to repeatedly specify security measures for every run, which helps prevent security bugs from being introduced. If something still slips through, the second line of defense is the executable checks (such as authorization tests), which prevent the generated API code from reaching production.