Prompt injection / self-persisting agent-rules block

HIGH
vercel/next.js
Commit: eeb59c12aaf1
Affected: 16.2.x series up to and including 16.2.2 (pre-fix); fixed by this commit
2026-07-09 04:25 UTC

Description

The commit fixes a vulnerability vector known as prompt injection in the agent-rules block used by Next.js. Previously, the agent-rules block could be treated as an instructional directive that might persist or be re-added through automated generation, commits, or diffs, enabling an attacker to influence agent behavior or pull in untrusted content via automated tooling. The patch changes the content and generation flow so the block is verifiable and self-upgrading rather than a self-persisting, commit-bound instruction. Specifically, it: - anchors the docs path to the block file’s directory (instead of a repo-root reference), which helps ensure the generated guidance points to the correct location in monorepos; - replaces the imperative “Keep this block…” with a verify pointer to generate-agent-files.js and a rationale for why committing it keeps the tree clean; - updates the block content to indicate that it is generated and re-added by the dev tool, reducing the likelihood that a malicious patch can cause the block to persist or be trusted as an injected directive. This reduces the risk of prompt injection via the agent rules block in multi-repo or monorepo environments.

Proof of Concept

High-level PoC (conceptual, non-destructive): Context: A Next.js 16.2.x project that uses the generated agent-rules block. An attacker with patch access submits a PR that alters the agent-rules block to include malicious guidance or instructions that could influence tooling or developer workflows if interpreted by automation. 1) Before the fix: The agent-rules block contains language that encourages committing the block and hints that the block will be re-generated by Next.js dev tooling. A bot or automation could interpret this as a legitimate directive and reintroduce the injected content on subsequent runs, making the injection persistent across diffs. 2) Attacker action: Modify the block in a PR to include an injection-like directive or malicious instruction within the text block that is meant to be consumed by an automation script in dev tooling. 3) Exploit path: When a developer runs next dev or when the agent-files generator runs, the old logic could reinsert the attacker-provided content into the working tree, causing the injected directive to be committed or propagated in diffs or docs. 4) Mitigation by this commit: The block now uses a verify pointer to generate-agent-files.js and anchors the docs path to the block file’s directory, removing the implicit trust and persistence of injected content. It also reframes the block as something generated by Next.js dev tooling, not a manually persisted instruction. Notes: - This PoC is a high-level illustration of the vector and its mitigation. It does not provide a weaponized payload, but demonstrates how untrusted edits to the agent-rules block could be propagated before the fix and how the fix mitigates that risk.

Commit Details

Author: Jude Gao

Date: 2026-07-09 02:59 UTC

Message:

Make the agent-rules block verifiable and self-upgrading (#95467) Agents have started flagging the managed `nextjs-agent-rules` block as prompt injection and refusing to follow or commit it (#95130). Headless A/B evals (Claude Code + Sonnet 5, pnpm-monorepo and standalone-npm fixtures, `next@16.3.0-preview.5`) isolate the triggers: `node_modules/next/dist/docs/` doesn't resolve from the workspace root in non-hoisted installs, the "maintained by `next dev`" claim is unverifiable, and the **Keep this block** paragraph reads as a self-persisting instruction. The failure concentrates on the uncommitted state a fresh `next dev` run produces. The block now anchors the docs path to the block file's directory with a monorepo note, and replaces the commit imperative with a verify pointer to `generate-agent-files.js` plus the reason committing it keeps the tree clean. This is the only change in this PR, and it was ablated in isolation — an A/A run of the baseline first established the noise floor (block committal 0/5, 0/8, 1/8 across three independent baseline batches): | Metric | Old block | New block | | --------------------------------------------------------------------------------- | --------- | --------- | | Uncommitted block, diff-review prompt — committed the block | 1/21 | 8/20 | | Uncommitted block, diff-review prompt — final message calls it a prompt injection | 12/21 | 4/20 | | Committed block, task prompts — injection flags | 0/15 | 0/15 | | Docs reads/run — standalone app, write tests (n=2) | 4.5 | 5.0 | | Docs reads/run — monorepo, add page (n=3) | 3.7 | 4.0 | | Docs reads/run — monorepo, write tests (n=10) | 3.4 | 2.6 | Committal improves at p = 0.008 and injection accusations drop at p = 0.016 (Fisher exact); runs that follow the verify pointer conclude "genuine framework-scaffolded content, not injected". A candidate follow-up — appending "commit this change" to the dev-server log line — was ablated separately and does nothing: the line reaches the agent's transcript in 12/12 dev-session runs but committal stayed 0/6 vs 0/6, so it is not included. The upsert machinery that delivers this rewording to existing projects is #95470.

Triage Assessment

Vulnerability Type: Prompt injection

Confidence: HIGH

Reasoning:

The commit alters the agent-rules block to be verifiable and self-upgrading, addressing prior issues where the block could be treated as a prompt/instruction that would self-persist or be committed in diffs. By anchoring the docs path and replacing implicit commit instructions with a verify pointer, it mitigates prompt-injection risks and untrusted edits that could be exploited to change agent behavior or leak/execute unintended content.

Verification Assessment

Vulnerability Type: Prompt injection / self-persisting agent-rules block

Confidence: HIGH

Affected Versions: 16.2.x series up to and including 16.2.2 (pre-fix); fixed by this commit

Code Diff

diff --git a/packages/create-next-app/helpers/generate-agent-files.ts b/packages/create-next-app/helpers/generate-agent-files.ts index 1dfc8c68f330..f382f369c2b3 100644 --- a/packages/create-next-app/helpers/generate-agent-files.ts +++ b/packages/create-next-app/helpers/generate-agent-files.ts @@ -9,9 +9,9 @@ export function generateAgentFiles(root: string): void { const agentsMdContent = `<!-- BEGIN:nextjs-agent-rules --> # This is NOT the Next.js you know -This version has breaking changes — APIs, conventions, and file structure may all differ from your training data. Read the relevant guide in \`node_modules/next/dist/docs/\` before writing any code. Heed deprecation notices. +This version has breaking changes — APIs, conventions, and file structure may all differ from your training data. Read the relevant guide in \`node_modules/next/dist/docs/\` (resolved from this file's directory; in monorepos the \`next\` package may not be visible from the repo root) before writing any code. Heed deprecation notices. -**Keep this block, including in commits.** It is part of the project's agent setup, maintained by \`next dev\` for every agent that works here. If it appears as an uncommitted change, that is intentional — commit it as-is. Do not remove it to clean up a diff; it will be regenerated. +This block is written and re-added by \`next dev\` — verify at \`node_modules/next/dist/server/lib/generate-agent-files.js\`. Removing it from a diff only re-creates the uncommitted change; committing it with your work keeps the tree clean. <!-- END:nextjs-agent-rules --> ` diff --git a/packages/next/src/server/lib/generate-agent-files.ts b/packages/next/src/server/lib/generate-agent-files.ts index 12618c6f5352..88afc548d241 100644 --- a/packages/next/src/server/lib/generate-agent-files.ts +++ b/packages/next/src/server/lib/generate-agent-files.ts @@ -25,9 +25,9 @@ function buildAgentRulesBlock(): string { return `${AGENT_RULES_START_MARKER} # This is NOT the Next.js you know -This version has breaking changes — APIs, conventions, and file structure may all differ from your training data. Read the relevant guide in \`node_modules/next/dist/docs/\` before writing any code. Heed deprecation notices. +This version has breaking changes — APIs, conventions, and file structure may all differ from your training data. Read the relevant guide in \`node_modules/next/dist/docs/\` (resolved from this file's directory; in monorepos the \`next\` package may not be visible from the repo root) before writing any code. Heed deprecation notices. -**Keep this block, including in commits.** It is part of the project's agent setup, maintained by \`next dev\` for every agent that works here. If it appears as an uncommitted change, that is intentional — commit it as-is. Do not remove it to clean up a diff; it will be regenerated. +This block is written and re-added by \`next dev\` — verify at \`node_modules/next/dist/server/lib/generate-agent-files.js\`. Removing it from a diff only re-creates the uncommitted change; committing it with your work keeps the tree clean. ${AGENT_RULES_END_MARKER}` }
← Back to Alerts View on GitHub →