# When Documentation Becomes an Execution Surface

> Coding agents turned trusted vendor documentation into package-manager commands. The exploit shows why AI security is no longer just about malicious prompts: it is about controlling the point where information becomes action.

- Author: Kostas Karolemeas
- Published: 2026-08-28
- Topics: AI security, Coding agents, Software supply chain, llms.txt, Agent governance
- Canonical URL: [https://www.voxelperfect.com/writing/when-documentation-becomes-an-execution-surface](https://www.voxelperfect.com/writing/when-documentation-becomes-an-execution-surface)

A coding agent reads an installation command in a vendor's official documentation.

The package name does not exist.

An attacker claims it.

The next agent that follows the same documentation downloads and runs the attacker's code.

That is the unnerving simplicity behind the experiment [reported by Ars Technica](https://arstechnica.com/security/2026/08/claude-codex-and-hermes-installed-unowned-code-inside-corporate-networks/). Researchers scanned 6,214 domains belonging to major technology companies, defence contractors and Fortune 500 businesses. Ars reports that 120 of the `llms.txt` or `llms-full.txt` files they found contained 227 references to unregistered packages or domains. After the researchers claimed a small number of those empty names and attached harmless beacons, callbacks arrived from corporate environments. Parent-process data associated some executions with Claude, OpenAI Codex and Nous Research's Hermes.

This was not an AI system inventing a new exploit. It was a familiar software-supply-chain weakness connected to a new execution path.

The important change is not that language models can read documentation. It is that an agent can convert what it reads into a shell command without a hard, independently verified boundary between **information** and **authority**.

## How the exploit works

`llms.txt` is a proposed convention for giving agents concise, machine-readable guidance about a website. Its [current specification](https://llmstxt.org/) describes a Markdown file that summarises a project and points agents toward useful documentation. It is especially relevant to software documentation, where coding agents retrieve API references and setup instructions while working.

That makes the file useful. It also makes it influential.

The attack chain has six steps:

1. A legitimate company publishes documentation on a trusted domain. An `llms.txt`, `llms-full.txt`, README or ordinary setup guide contains an installation command such as `pip install package-name` or `npm install package-name`.
2. The package name or linked domain is unregistered. It may have been mistyped, hallucinated, copied from stale documentation or abandoned later. No attacker needs to be present when the instruction is written.
3. An attacker discovers and claims the empty package namespace or expired domain.
4. A coding agent retrieves the legitimate documentation while completing a developer's task. Because the source looks authoritative, the agent treats the installation line as operational guidance.
5. The agent invokes a package manager through its shell tool. The registry serves the attacker's newly claimed package under the name the documentation already recommends.
6. Installation hooks, an executed binary or a later import runs with the agent process's effective permissions. The code can now read whatever the environment exposes and make whatever network calls its egress policy permits.

![A trusted document points to an unclaimed package name, which is captured and executed through a coding agent](/writing/when-documentation-becomes-an-execution-surface-linkedin-attack-path.webp)

The dangerous property is **time separation**. The documentation can be authentic and benign on Monday. The referenced namespace can be captured on Friday. Nothing on the trusted website needs to change for the old instruction to become an attack path.

This is related to package hallucination, dependency confusion and prompt injection, but it is not identical to any one of them.

[Research on package hallucinations](https://arxiv.org/abs/2501.19012) has already shown that coding models can recommend dependencies that do not exist, creating names an attacker may later register. Dependency-confusion attacks exploit a resolver's choice between expected internal packages and attacker-controlled public packages. Indirect prompt injection places adversarial instructions inside content a model retrieves.

Here, the instruction itself may be neither adversarial nor model-generated. It can be an ordinary, human-authored line in real vendor documentation. The exploit emerges from the combination of a stale or empty namespace, transitive trust and an agent authorised to execute.

## Why `npx` made the Clerk example especially dangerous

Ars highlights a concrete case in Clerk's documentation. An instruction referred to:

`npx clerk-next-fix-auth-protection`

The unscoped npm package name was available for someone else to claim. The [OSV record for `MAL-2026-11069`](https://osv.dev/vulnerability/MAL-2026-11069) says versions `7.7.7` and `8.8.8` of the package were malicious. Amazon Inspector found that install hooks sent the installer's username, hostname, working directory and a timestamp to an attacker-controlled endpoint.

`npx` raises the stakes because it is an execution mechanism, not merely a dependency declaration. [npm's documentation](https://docs.npmjs.com/cli/v8/commands/npx/) explains that when the requested package is not already present locally, it can be fetched into npm's cache, added to the process path and executed. It normally prompts before installing a missing package, but that protection can be suppressed, pre-approved or weakened by habitual approval.

Clerk's current migration guide now binds the executable to the legitimate scoped package:

`npx --package @clerk/eslint-plugin clerk-next-fix-auth-protection`

The [updated Clerk documentation](https://clerk.com/docs/guides/development/upgrading/upgrade-guides/migrate-from-create-route-matcher) also recommends pinning the experimental package and reviewing the changes it makes. That is a materially safer instruction because it removes ambiguity about which package supplies the binary, although package ownership, version integrity and execution policy still need verification.

This distinction is easy for a busy developer to miss. It is even easier for an agent to collapse when its objective is “make the project work” and the shortest path is to run the command in front of it.

## The security risk is larger than one malicious package

The immediate consequences are conventional and serious:

- credential and token theft from environment variables, configuration files and local credential helpers;
- source-code or proprietary-data exfiltration;
- modification of the repository, build scripts or generated artefacts;
- compromise of CI credentials, cloud accounts and package-publishing identities;
- persistence through dependencies, developer tooling or startup hooks;
- ransomware or destructive commands within the agent's reachable environment;
- lateral movement when the agent can reach internal services or privileged local sockets.

The agent does not create these privileges. The organisation gives them to the process.

That is why [OWASP's description of excessive agency](https://genai.owasp.org/llmrisk/llm062025-excessive-agency/) is useful. The root cause is normally some combination of excessive functionality, permissions and autonomy. A shell tool is open-ended functionality. A developer workstation full of credentials supplies permissions. An unattended install supplies autonomy.

The model can fail in several ways, but the blast radius is determined by the system around it.

## Why familiar controls may not notice

To endpoint protection or a corporate proxy, the sequence can look almost completely legitimate:

- a company-approved coding agent launches the process;
- the command uses a standard package manager;
- the package comes from an allowed public registry over HTTPS;
- the developer asked the agent to configure a real product;
- the instruction came from the product's official domain.

There may be no exploit payload crossing an unusual domain, no unsigned binary downloaded directly by `curl` and no obvious privilege escalation. The failure happens earlier, when documentation is promoted into executable authority.

Traditional security controls often ask, “Is this process allowed to run?”

Agentic systems force a second question: “Was the instruction that caused this allowed process to run independently verified?”

That is a much harder event to reconstruct unless the organisation connects the agent's retrieved context, tool call, package-manager process, resolved package identity and resulting network activity in one audit trail.

## AI makes security a graph problem

Enterprise security was already complex. Coding agents add several new dimensions at once.

### Authority can be laundered through context

HTTPS proves that content came from a domain. It does not prove that every package name inside the content belongs to the domain owner. A standard format proves that a document can be parsed. It does not prove that its instructions are safe to execute.

An agent can compress those separate signals into one intuition: official-looking documentation is trusted. The source's reputation is then transferred to a package registry entry the source does not control.

### Every readable surface can influence action

A website, issue, README, generated log, source comment, MCP response or retrieved PDF was once mostly data for a developer to interpret. For an agent with tools, any of them can become input to a decision that changes files, invokes a command or calls an API.

The attack surface is therefore not just the code the organisation runs. It includes the information the agent is permitted to treat as a reason to run code.

### Risk changes without a code change

Package ownership expires. Domains lapse. Maintainers change. Documentation generators copy old examples. A safe reference can become unsafe while the repository, the documentation and the agent configuration remain byte-for-byte identical.

Security review can no longer be only a point-in-time approval of artefacts. It has to monitor the relationships between them.

### Non-determinism meets approval fatigue

Two agent runs may retrieve different context or choose different setup paths. Meanwhile, humans who repeatedly approve package-manager commands learn to click through the one control intended to stop the attack.

The goal cannot be to ask for more approvals. It has to be to make rare, high-signal approvals appear at the right boundary.

## How to harden coding agents

No single configuration closes this class of vulnerability. A defensible setup puts independent controls at the instruction, resolution, execution and observation layers.

### 1. Treat retrieved instructions as untrusted input

Add an explicit agent policy: documentation may inform a plan, but it cannot independently authorise dependency installation or remote code execution.

Require a separate verification step before any new package, executable installer, `npx`/`npm exec`, `pip`/`uv` tool, `curl | sh`, PowerShell download cradle or binary release is run. The verification result should identify the exact package coordinate, registry, publisher or organisation, version, age and expected executable.

Do not ask the same model to read a document and then merely “double-check” its own conclusion. Put enforcement in a deterministic tool, proxy policy or approval service that can fail closed.

### 2. Make new dependency installation a controlled capability

An agent that can edit application code does not automatically need permission to install arbitrary software.

For OpenAI Codex, the [official sandbox guidance](https://learn.chatgpt.com/docs/sandboxing) recommends a lower-risk local automation posture of `workspace-write` with `on-request` approvals, rather than unrestricted full access with approvals disabled. Current [Codex permission profiles](https://learn.chatgpt.com/docs/permissions) can also separate filesystem and network policy, deny sensitive files and default network access to off.

For Claude Code, use narrow allow and deny rules, keep install-capable Bash commands approval-gated and avoid `--dangerously-skip-permissions`. Anthropic's [CLI reference](https://docs.anthropic.com/en/docs/claude-code/cli-usage) exposes `--allowedTools`, `--disallowedTools`, plan mode and permission-prompt integration for this purpose.

The safe default is simple: an agent may propose an install command; a policy layer decides whether that exact command is permitted.

### 3. Restrict network egress

If an agent does not need the public internet, turn it off.

If it needs dependencies, send package traffic through an internal registry proxy or repository manager and allow only the minimum destinations. OpenAI's [Codex internet-access controls](https://learn.chatgpt.com/docs/cloud/internet-access) support per-environment domain allowlists and restricted HTTP methods. Anthropic documents routing Claude Code through a [corporate proxy](https://docs.anthropic.com/en/docs/claude-code/corporate-proxy) for security, monitoring and allowlisting.

Allowing all of npmjs.org or pypi.org is convenient, but it still allows first-seen attacker packages. The registry gateway should enforce organisation policy, not merely relay bytes.

### 4. Verify package identity before version resolution

Version pinning is necessary but insufficient if the package name itself is wrong.

For every first-time dependency, verify:

- that the package is linked from the vendor's canonical, version-controlled source;
- that its namespace or scope belongs to the expected publisher;
- that the project has a credible history rather than appearing minutes ago;
- that the requested binary is actually exported by that package;
- that the version and integrity hash match the approved lockfile or internal catalogue;
- that known-malware, provenance and vulnerability checks pass.

Unknown or newly created packages should be quarantined, not automatically resolved. Where the ecosystem permits it, require signed provenance and maintain an approved-component catalogue.

### 5. Separate installation from execution

Resolve and inspect dependencies in an ephemeral environment without production secrets. Disable lifecycle scripts during the initial inspection where the package manager supports it, then run only reviewed scripts in a later stage. Be aware that suppressing install hooks is not a complete defence: a malicious library can execute when imported or when its binary is invoked.

Avoid ambiguous one-line execution. Prefer an explicit, pinned package coordinate over an unqualified `npx some-command`. Commit lockfiles and verify hashes in CI.

### 6. Reduce the blast radius

Run coding agents as non-root users inside disposable workspaces or containers. Mount only the repository they need. Do not expose broad home-directory access, production credentials, SSH keys or cloud-admin tokens. Do not attach the Docker socket unless the task genuinely requires host-equivalent control.

Use short-lived, task-scoped credentials. Separate read, edit, install, deploy and publish identities instead of letting one developer token do everything.

### 7. Audit the documentation supply chain

Vendors should generate `llms.txt` from version-controlled canonical documentation, review it like code and validate every package and domain reference in CI. Package names should be claimed before publication. Domain and namespace ownership should be monitored continuously, not only at launch.

Consumers should scan retrieved setup material for unregistered packages, expired domains and ambiguous executable names. This should cover READMEs, examples and generated documentation—not only `llms.txt`.

### 8. Join agent telemetry to endpoint telemetry

Record the chain:

`retrieved source → interpreted instruction → tool call → resolved package → child process → network activity`

Alert on first-seen packages, unexpected install hooks, package managers launched by agents, direct public-registry access that bypasses the internal proxy and outbound connections during installation.

This context turns “npm made a request” into “an agent followed this exact third-party instruction and installed a package the organisation had never approved.”

### 9. Test the boundary

Seed controlled evaluation documents with nonexistent package names, stale domains and plausible but unauthorised install commands. A hardened agent should stop, explain the uncertainty and request or invoke independent verification. Run the test across interactive agents, CI agents and non-interactive automation because their approval paths often differ.

## What teams should do now

Start with a focused incident check:

1. Inventory corporate `llms.txt`, `llms-full.txt`, setup guides and generated documentation.
2. Extract referenced packages and domains, then verify ownership and registration status.
3. Search agent and endpoint logs for the identified names and for unexpected package-manager child processes.
4. Block known malicious packages, remove cached copies and inspect affected hosts.
5. If a suspicious package executed, rotate any credentials it could read and investigate downstream use rather than assuming uninstalling the package is enough.
6. Move install commands behind an enforceable approval and package-validation policy.

Do not wait for every coding-agent vendor to solve instruction trust inside the model. The durable controls belong at the capability boundary where a proposed action becomes a real one.

## Security now governs the conversion of meaning into action

The Ars report is easy to frame as another story about agents behaving recklessly.

That framing is too small.

Humans wrote or published the documentation. Organisations granted the shell and network access. Package registries accepted the names. Security tools trusted the package-manager traffic. The agent connected those systems at machine speed.

Responsibility follows that whole chain.

In the pre-agent web, a stale package name in a documentation page was usually a broken instruction. In the agentic web, the same line can become a latent execution path waiting for someone to claim the missing name.

That is the larger shift. AI security is not only about protecting models from malicious prompts or protecting code from vulnerable dependencies. It is about governing how context becomes intent, how intent becomes a tool call and how a tool call inherits real authority.

Once documentation can cause execution, documentation needs controls that look much more like software-supply-chain security.

## Sources

- Dan Goodin's [Ars Technica report](https://arstechnica.com/security/2026/08/claude-codex-and-hermes-installed-unowned-code-inside-corporate-networks/) supplies the researchers' scan, proof-of-concept callbacks, process attribution and description of the Clerk case. The researchers' [public summary](https://www.linkedin.com/in/alon-hertz/) states that the packages used for their own test contained harmless phone-home beacons and that findings were responsibly disclosed.
- The [llms.txt v2 proposal](https://llmstxt.org/) defines the file's purpose, format and use by coding agents. It is a documentation convention, not an integrity or execution-authorisation standard.
- The [OSV malicious-package record](https://osv.dev/vulnerability/MAL-2026-11069), sourced from OpenSSF Package Analysis and Amazon Inspector, documents the malicious `clerk-next-fix-auth-protection` versions and observed install-hook exfiltration. Clerk's [current migration guide](https://clerk.com/docs/guides/development/upgrading/upgrade-guides/migrate-from-create-route-matcher) shows the corrected scoped-package invocation.
- npm's [`npx` documentation](https://docs.npmjs.com/cli/v8/commands/npx/) explains how missing packages can be fetched into the npm cache and executed. Krishna et al., [_Importing Phantoms_](https://arxiv.org/abs/2501.19012), provides the package-hallucination research discussed here.
- [OWASP's Excessive Agency guidance](https://genai.owasp.org/llmrisk/llm062025-excessive-agency/) supplies the functionality, permissions and autonomy model. Current vendor controls are documented in OpenAI's [Codex sandbox](https://learn.chatgpt.com/docs/sandboxing), [permission-profile](https://learn.chatgpt.com/docs/permissions) and [internet-access](https://learn.chatgpt.com/docs/cloud/internet-access) documentation and Anthropic's [Claude Code CLI](https://docs.anthropic.com/en/docs/claude-code/cli-usage) and [corporate-proxy](https://docs.anthropic.com/en/docs/claude-code/corporate-proxy) documentation.
