---
title: "The vulnerabilities agents write fresh"
description: "Ask an agent for a login flow and you tend to get a ?next= parameter redirected to whatever it contains. It is the obvious implementation, it is an open redirect, and the remedy was documented years before the model was trained."
url: https://harpywatch.com/blog/the-vulnerabilities-agents-write-fresh
kind: article
author: "The HarpyWatch team"
published: 2026-10-29T09:00:00+00:00
tags: ["AI Agents", "Monitoring", "Reliability"]
publisher: "HarpyWatch"
---

# The vulnerabilities agents write fresh

Ask a coding agent to add "return to the page you came from" to a login flow.
Unless the framework hands it a guarded helper — Rails' `redirect_back` with a
`fallback_location` is one — here is the shape it tends to produce:

```
const next = req.query.next || "/dashboard";
// ... authenticate ...
res.redirect(next);
```

That is a correct implementation of the feature as described. It is also an
open redirect. A static analyser tuned for it — Semgrep, CodeQL — will say so,
if you run one; a unit test of the login flow will not, because the flow works.

## What the attack actually is

The vulnerability is not that a URL gets rewritten. It is that your domain
becomes the first hop of somebody else's phishing link.

An attacker sends a mail containing
`https://yourbank.example/login?next=https://yourbank.example.attacker.com/login`.
Everything a cautious person is trained to check passes. The link is on your
real domain. The TLS certificate is yours. The hostname in the address bar is
correct at the moment of the click. The victim logs in — genuinely, on your
site, against your real authentication — and is then bounced to a page that
looks exactly like yours and asks them to log in again "because something went
wrong". The second form is the attacker's.

The credibility comes from your domain, not from the destination, and every
layer of defence that operates on the hostname is reading the first URL in the
chain. Mail filters that score links by reputation see yours.
Corporate proxies with an allowlist see yours. The first URL is legitimate, and
it is the only one anything checks.

There is a second use that is quieter and often worse: the redirect is used as
an OAuth or SSO callback target, and the authorisation code, token or session
identifier travels in the query string or the `Referer` header to a host the
attacker controls. That is not a phishing page any more. That is credential
exfiltration with a valid signature on it.

This is also why "it's only a scanner finding" is the wrong reading. A scanner
reports it as informational because the redirect on its own steals nothing. The
severity lives in what it is combined with, and combining is the cheap part.

## Why this class keeps coming back

Open redirect has been in the OWASP material for over a decade. The remedy is
small, well documented, and in most stacks a helper somebody has already
written. It has not been an interesting problem since about 2013. So why is it appearing in new code again, in volume?

A plausible reason is in what there is most of to imitate. The published
writing on "redirect after login" is overwhelmingly tutorials and answers
showing the form above, written to explain redirects rather than security. The
writing on why that line put a company in an advisory is smaller and a
different genre: post-mortems, CVE text, mailing list threads, none of it in
the shape of a code example. The material available to imitate is
lopsided, whatever a model does with it.

That points at where to look. The classes to worry about in generated code are
not the exotic ones — a fast writer with a lot of examples is good at the
common case. They are the *solved* ones, where the naive implementation is
widely published, widely copied, and wrong.

| The feature as asked for | The obvious implementation | The class |
| --- | --- | --- |
| "Send them back where they came from" | Redirect to a user-supplied parameter | Open redirect |
| "Let users embed a profile image by URL" | Server fetches the URL they give | Server-side request forgery |
| "Show the user's name on the page" | Interpolate it into the template | Cross-site scripting |
| "Let them filter by column" | Build the SQL from the parameter | Injection |
| "Log them in and keep them logged in" | Set the cookie, ship it | Missing `Secure`, `HttpOnly`, `SameSite` |

Every row has a documented remedy that predates the model, and every row is
what a fluent, fast, unbothered writer produces first.

## The fix, and why nobody writes it first

The correct version of the login redirect is boring:

- Accept only a **path**, never a full URL. If it does not start with a single
  `/`, discard it. Reject `//evil.example` and `/\evil.example`, which are
  protocol-relative and resolve off-site.
- Or better, do not take a URL at all. Take a key — `next=billing` — and look
  up the destination in a table you control.
- If you genuinely must allow external destinations, match the parsed host
  against an allowlist. Not a prefix check on the string: `https://yoursite.com.attacker.com` starts with `https://yoursite.com`.

That version is rarely what gets written first, because the requirement never
said any of it. The
requirement said "return to the page you came from", and the extra lines serve
a threat the ticket did not mention. Output that satisfies the stated
requirement is exactly what was asked for.

## Verification has to be automatic, because the writing already is

Here is the part that has actually changed. Human security review is built
around a volume of code a person can read, and that is the assumption now under
pressure. If a process catches this reliably at ten pull requests a week, it is
worth asking honestly what it does at ten an hour. The likely answer is not
that reviewers get worse. It is that they start sampling, and nothing records
which files were skipped.

So the check has to run without being asked for, from outside, against the
deployed thing.

HarpyWatch has a check called `open-redirect`. You give it the URLs
you care about — the login endpoint, the logout endpoint, anything that takes a
destination — and it requests them with the common redirect parameter names set
to an off-site host, then reads the `Location` header and any meta-refresh in
the body to see where a browser would be sent. If the answer is a host that is
not yours, that is the finding. Alongside it, `cookie-flags` reads
what your `Set-Cookie` headers actually carry, `security-headers` and
`csp-policy` report what a browser is told about framing and script origins,
and `mixed-content` finds the http subresource on the https page. None of that
is clever. It is the old list, asked again every few minutes, against the URL a
visitor uses.

The honest limit: an external check tests the surface a client can reach. It
will find the redirect on your login endpoint. It will not read your source,
and it cannot tell you that the same mistake exists on an internal admin route
nobody links to. It is a smoke alarm, not an audit.

But the cheapest moment to find this is before the traffic arrives. Point the
checks at the staging URL, before DNS moves. A redirect parameter that bounces
to somebody else's host is a thirty-second fix in a branch and a disclosure
email in production.
