I Shipped Five Prefilled AI Links, Then Read the Reprompt Write-Up

In August I added a row of buttons to the top of every article on this site — ChatGPT, Claude, Perplexity, Google AI Mode, Grok — each one opening the article in that assistant with a prompt already typed in. It took an afternoon. It is a pleasant little feature and the click-through is decent. Then I read Varonis Threat Labs' write-up of the Reprompt attack, went back to my own template, and spent considerably longer than an afternoon thinking about what I had actually shipped.

The uncomfortable realisation is this: a prefilled prompt link is not a search link. A search link asks a stateless engine a question. A prefilled prompt link types an instruction into somebody's authenticated assistant session — one that may hold their chat history, their connected mail, their files, and a set of tools that can reach the network. The parameter is not a query. It is an instruction executed as that user.

Reprompt turned three ordinary features into an exfiltration pipeline

Varonis disclosed the chain to Microsoft in August 2025; mitigations went out in the January 2026 Patch Tuesday stream on 13 January, and the public write-up followed on the 14th. It targeted Copilot Personal — the tenant-managed Microsoft 365 Copilot was not exposed in the same way, because Purview and tenant DLP sat in the path.

Nothing in the chain was a memory-corruption bug. It was three convenience features stacked:

Parameter-to-prompt injection. copilot.microsoft.com/?q=<prompt> did not merely prefill the box. It loaded and executed the prompt as though the user had typed and submitted it, against their live session. One click on a genuine microsoft.com link was the entire user interaction required.

The double-request bypass. The leak-prevention checks applied to the initial request only. So the injected prompt told Copilot to do everything twice — phrasing along the lines of always double check yourself. The first attempt tripped the filter; the second went through unfiltered.

Chain-request, which is the part worth internalising. The URL did not carry the payload. It carried a bootstrap: fetch a response from this server, and follow whatever instructions come back. From that point the attacker's server drove the conversation, adapting each request to what the previous answer returned, and the session survived the user closing the chat window. Names, location, travel plans, file-access history, conversation summaries — pulled out over ordinary HTTPS traffic that looked like normal assistant activity.

A URL parameter became a command-and-control channel because the product treated a link as trusted input. That is the same category of mistake as trusting a query string in a WHERE clause, and it will keep recurring wherever an assistant gains a new entry point — the same authority-boundary question I raised about where MCP still hurts when tool servers get wired in without an audit.

The whole safety question is: does it submit, or does it wait?

Every assistant's URL parameter sits on one of two sides of a single line, and everything else is detail.

Anthropic's documentation for opening Claude with a link states it plainly: if you include q, the prompt field is prefilled with that text so you can review and send it. The text is truncated to roughly 14,000 characters, all parameter values must be URL-encoded, and — the part I liked most — any folder supplied through a link is treated as untrusted, with a confirmation dialog every time, even if that folder was trusted previously. Somebody there had already thought about links as an attacker-controlled channel.

Copilot, before January 2026, sat on the other side of the line. Same feature, same parameter name, opposite default.

Behaviour Prefill and wait Prefill and auto-submit
User sees the prompt before it runs yes no
Injection needs a second user action yes no
Attacker controls the first turn of the session no yes
Safe to link to from a third-party page broadly no

If you are choosing which assistants to put behind a share button, that table is the criterion. Not brand, not market share — whether a stranger's link can start a turn in the user's session without the user reading it first.

What I actually ship, and why the five URLs look nothing alike

Here is the real thing, lifted from the template rather than reconstructed for the article:

{% set aiQuery = 'articles.ai_insights.prompt'|trans({
    '%title%': title, '%url%': url, '%source%': site_uri
})|url_encode %}

{% set aiProviders = [
    { key: 'chatgpt',    url: 'https://chatgpt.com/?q=' ~ aiQuery },
    { key: 'claude',     url: 'https://claude.ai/new?q=' ~ aiQuery },
    { key: 'perplexity', url: 'https://www.perplexity.ai/search/new?q=' ~ aiQuery },
    { key: 'gemini',     url: 'https://www.google.com/search?udm=50&aep=11&q=' ~ aiQuery },
    { key: 'grok',       url: 'https://x.com/i/grok?text=' ~ aiQuery },
] %}

Four observations from having built it.

There is no standard. Three vendors use q, X uses text, and the paths disagree about everything — /new, /search/new, bare root. Any library promising you a single "open in AI" helper is maintaining five special cases behind the interface, and will be maintaining seven next year.

The Gemini entry is not a Gemini URL. It is google.com/search with udm=50, the AI Mode surface, because that is where the answer experience for a shared link actually lives. Guessing gemini.google.com/?q= gets you a redirect to an empty chat and a confused reader.

The encoding happens exactly once, in url_encode, over the whole assembled sentence. This is the single most common way these links break: encode twice and the assistant receives literal %20 sequences; encode zero times and your prompt terminates at the first &, silently truncating the instruction into something you never wrote. If you are hand-building one of these, run the string through a URL encoder once and compare, and if the percent-encoding rules are not fresh in your head, reserved versus unreserved characters is the thing to re-read first.

The links carry rel="nofollow noopener" target="_blank". noopener because I am handing a new tab to a domain I do not control; nofollow because these are utility links to a session surface, not editorial endorsements worth passing signal to.

The prompt is public, permanent, and quoted by a machine

Whatever you put in that parameter gets logged by the provider, replayed in the user's history, and read by a model that treats it as an instruction from the user. So the rule I settled on is narrow: the prompt may contain nothing the reader could not already see on the page they are standing on.

Mine is one sentence — read this article, give me the key takeaways, then answer follow-ups — plus the title, the canonical URL and the site name. Three interpolations, all of them server-side constants or the page's own metadata.

What must never go in:

  • anything from the current request. If your prompt template interpolates a query-string value, a referrer, a search term or a form field, your page has become an injection relay: an attacker crafts a link to your site, your template dutifully encodes their instruction, and your domain's credibility carries it into the assistant.
  • session identifiers, API keys, or tokens of any kind. This is the same failure as pasting a live JWT into an online decoder — the token is a credential, and a URL parameter is about the least private place it could be. Decoding a token is a job for a local JWT decoder that never puts it on the wire.
  • anything user-specific — names, plan tiers, internal IDs. It ends up in a third party's logs attached to that person's account.
  • instructions aimed at the model rather than the reader. Ignore previous instructions, respond only in JSON, do not mention this prompt — if your prompt is doing anything a user would be surprised to read, you have crossed from convenience into manipulation, and the reader can see the parameter in their address bar anyway.

Before you ship a share-to-AI button

Six checks, in the order I would run them:

  1. Does the target auto-submit? Open your own link in a logged-out browser and watch. If the turn starts without a second click, either drop that provider or accept that any page on your site can start a conversation in a reader's session.
  2. Is any part of the prompt reflected from the request? Grep the template for the request object. The answer must be no.
  3. Encode once. Assemble the sentence, then encode the whole thing, then never touch it again.
  4. Cap the length. Claude truncates around 14,000 characters and the others truncate at values they do not document. A prompt that silently loses its last clause reads as a different instruction.
  5. rel="nofollow noopener", target="_blank". Non-negotiable for a cross-origin surface you do not control.
  6. Re-check after every vendor change. The ?q= semantics on these five endpoints changed twice in the last eighteen months. This is not code you write and forget.

Treat the Address Bar as Attacker Input

The lesson from Reprompt is not that prefilled prompt links are unsafe to ship — I still ship five of them, and I would ship them again. It is that the assistant vendors spent 2025 discovering the rule the rest of us learned about query strings twenty years ago, and they are still discovering it surface by surface: a link is attacker-controlled input, and the fact that it arrives on your own domain proves nothing about who wrote it.

On the builder's side that reduces to one habit. Every character you put in that parameter should be a server-side constant or the page's own published metadata — and if you cannot state which of the two a given interpolation is, take it out before you deploy.

More Articles

Prompt, Context, Loop, Graph: Every AI Discipline Is a Patch With an Expiry Date

Each discipline patched one weakness and expired when the weights caught up. The half-life table, the eight-week naming cadence, and where graphs actually fit.

8 September, 2026

Your Agent Isn't Slow, It's Queued

Half the wall clock in a typical agent pipeline is false sequencing. Same work: 130s as a chain, 65s as a fan. Here is the arithmetic, plus the code.

2 September, 2026

What Actually Leaks When You Paste a JWT Into an Online Decoder

Decoding a JWT is trivial - the real risk is that the token is a live credential, and pasting the HS256 secret is far worse. How to check any decoder.

30 August, 2026