Frontier coding models generate vulnerable code even when they solve the feature request.
We ran Opus 5 with xhigh reasoning on 184 feature-request tasks from the SusVibes benchmark. The tasks come from real open-source projects and are scored for both functionality and security.
Opus 5 passed 177 functional checks, or 96.2%.
It passed 51 security checks, or 27.7%. It generated vulnerable code on the other 133 tasks, or 72.3% of the benchmark.

The model knows security, but not your security
Frontier models have learned common secure-coding patterns. They can parameterize a SQL query, escape output, validate input, and avoid familiar dangerous APIs. They are getting better at these pattern-based flaws.
They have not learned the private rules that define your application:
- which tenant owns a resource;
- which role may perform an action;
- which plan includes an entitlement;
- which approval must happen before a state change;
- which actor may initiate, approve, or receive a payment.
The current rules of a private application were not in the model's training data. They may not be written down anywhere. Parts live in code, architecture documents, threat models, old pentest findings, and the heads of the engineers who built the product.
The agent sees a local coding task. The security decision often depends on context outside that task.
The same feature request, with and without context

Consider a simple request to a coding agent:
Add an authenticated API route that returns a report by ID.
The agent could produce this Next.js route:
export async function GET(_request: Request,{ params }: { params: Promise<{ reportId: string }> },) {const session = await getSession();const { reportId } = await params;if (!session) {return Response.json({ error: 'Unauthorized' }, { status: 401 });}const report = await db.report.findUnique({where: { id: reportId },});if (!report) {return Response.json({ error: 'Not found' }, { status: 404 });}return Response.json(report);}
The route is authenticated. It handles missing records. It returns the requested report. A functional test using a report owned by the current user will pass.
It also lets any authenticated user request a report from another tenant if they can obtain or guess its ID.
There is no obviously dangerous function for the model or a scanner to flag. The vulnerability is the missing tenant constraint. Authentication answers who the user is. It does not prove that the user may access this report.
Now give the agent one piece of private application context:
Security invariant: every read of a tenant-owned report must be scoped to the tenant in the authenticated session.
The implementation changes:
export async function GET(_request: Request,{ params }: { params: Promise<{ reportId: string }> },) {const session = await getSession();const { reportId } = await params;if (!session) {return Response.json({ error: 'Unauthorized' }, { status: 401 });}const report = await db.report.findFirst({where: {id: reportId,tenantId: session.user.tenantId,},});if (!report) {return Response.json({ error: 'Not found' }, { status: 404 });}return Response.json(report);}
The feature request did not change. The model did not need a broader lesson about authorization. It needed the specific rule that connects reports, tenants, and authenticated users in this application.
Business-logic vulnerabilities look like valid code
Pattern-based vulnerabilities usually contain something a security tool can recognize: an unsafe function, untrusted input reaching a sink, or a dependency with a known CVE.
A missing business rule has no equivalent signature. The code compiles. Authentication is present. The database query is valid. The route can pass unit tests and code review because every visible line looks reasonable.
Generic SAST remains useful for recognizable patterns. It cannot reliably infer that this product requires tenant scoping on every report read, or that only billing administrators may change a payment method. The same limitation applies to a coding model unless someone supplies that context.
Human review does not automatically solve the problem either. Reviewers need the same private knowledge, and that knowledge is often scattered across teams and documents. More generated code makes the gap easier to hit, but code volume is not the root cause. Missing context is.
Put the application context where the decision happens
Copying a long security document into every agent session is a rough workaround. Static instructions help with broad conventions, but they drift, consume context when irrelevant, and depend on the agent reading and following them.
Konvu Guardrails maps code, configuration, business context, internal docs, threat models, and optional security history into a Security Context Graph. It derives concrete security invariants that AppSec teams can review. For this route, that means connecting every report read to the requirement that the caller belongs to the owning tenant.
Through MCP or the CLI, Guardrails gives the coding agent the relevant invariant before it edits the code. A payment change receives the payment rules. An entitlement change receives the rules for that entitlement. The agent gets the context relevant to its task instead of the entire security model.
Guardrails also checks relevant changes in CI, in report-only or blocking mode. Teams do not need to standardize on one coding agent, and Guardrails does not replace SAST or the scanners they already use.
Guardrails will not prevent every business-logic vulnerability. It puts the application's private security rule in front of the agent when the code is written, then checks the same rule again before the change ships. Guardrails is in early access.
See how Konvu Guardrails brings application-specific security context into coding agents and CI.