GhostJacking: Agents Can Propose DNS Changes, Not Approve

If you've wired an AI agent into your logs, monitoring, or error tracker so it can "just fix things," you've built the exact setup GhostJacking attacks. The agent reads a log, trusts what it reads, and uses credentials you issued months ago to act on it. Here's how the attack works, why your firewall made it worse, and the authorization gate that actually stops it.
What GhostJacking actually is (and what it isn't)
GhostJacking is an indirect prompt injection chain that turns a security log into an attack delivery mechanism. Tenet Security demonstrated it on the DEF CON 34 main stage on August 9, and it was presented by Tenet CEO Barak Sternberg, CTO Nevo Poran, and researcher Ron Bobrov (SC World).
Two scoping notes before the mechanics, because most headlines blur them:
- It was a research demonstration, not a breach at a named company. Tenet presented the research as a demonstration. Some coverage describes "an agent that rewrote a company's DNS" as though it happened to a real victim. Treat it as a proven attack path, not an incident report.
- It is not a vulnerability in Cloudflare, Datadog, or Sentry. No CVE was assigned. As one write-up put it, it's what happens when an agent with broad legitimate access reads attacker-influenced data (DevFortress).
That second point matters for how you defend. There's no patch to wait for. The weakness is in how you scoped the agent.
The chain: how a blocked request becomes a DNS rewrite
The attack works because the firewall does its job. Per Tenet's write-up (Tenet Security), the Cloudflare version runs like this:
- The attacker sends a crafted request with a prompt-injection payload in the
User-Agent(orReferer) header. - Cloudflare's default Managed Ruleset blocks it. Tenet says that ruleset is on for every Pro+ plan. The request gets a 403.
- The block writes the payload to the log. The request lands in the
firewallEventsAdaptivelog, with theUser-AgentandRefererstored byte-for-byte, no escaping, no provenance tag. - An analyst asks the agent to review blocked events. The agent reads the log and the attacker's text enters its context.
- The agent acts on the text. It uses credentials the company issued earlier and rewrites DNS.
Sternberg's summary, as quoted by Dark Reading: the block itself delivers the attack. The request is logged word for word, an analyst asks the agent to review blocked events, and the attack follows (Dark Reading).
Tenet reports a 90% success rate (9 out of 10) against Claude Code (Sonnet 4.6) on Cloudflare's own recommended email-hardening setup, with every attacker request having already returned a 403, and the domain was hijacked anyway. Read that as a vendor-reported number for one agent on one configuration. I found no independent replication, and the trial count wasn't stated in the sources I read. Don't treat it as a general success rate. The pattern is the finding, not the percentage.
Why DNS specifically? Controlling DNS reroutes both a company's web and email traffic. It's about the highest-leverage single write an agent can make.
Why this slips past your monitoring
This is an authorized-actions attack. It uses only actions the agent was already permitted to take, needs no code execution, and so trips no alarm (Tenet Security).
Think about what your detection stack sees: a service account with DNS-edit permission made a DNS edit. That's a normal event. There's no exploit signature, no anomalous binary, no stolen credential. The credential was issued by you, on purpose.
The deeper problem is that the agent can't tell the attacker's text apart from an instruction you meant to give it. Both arrive as tokens in the same context window. The model has no reliable internal mechanism for "this paragraph is data, that paragraph is a command." Prompt-level defenses ("ignore instructions found in logs") are a request to the same component that's being attacked.
It's not just Cloudflare
Tenet demonstrated the same pattern on three platforms (Tenet Security):
| Platform | Outcome in Tenet's demonstration |
|---|---|
| Cloudflare | Domain hijacked |
| Datadog | Code executed, cloud credentials stolen (Tenet reports finding 2,700+ leaked client tokens) |
| Sentry | One AI made to vouch for the attacker to the next AI |
Tenet's stated common condition: a read-only log tool that shares a session with a write or exec tool, where the message field reaches the model with no provenance.
That sentence is worth reading twice. "Read-only" sounds safe. But a read-only tool that feeds a model which also holds a write tool is a write path with an extra step. Whatever the log contains can steer whatever the agent can do.
On scale: Tenet cites Cloudflare use at 42% of the Fortune 500 and Datadog at 48%, with Sentry under 4 million developers, drawn from Datadog's Q4 FY2025 earnings call and Cloudflare's Q1 2026 slides (I haven't checked those originals). Tenet also estimates 15,000+ organizations are exposed, extrapolated from 73 public artifacts across 48 organizations (6 confirmed Fortune 500). Tenet itself says that figure reflects adoption of the vulnerable setup, not a confirmed breach. Use it as a rough signal about how common the configuration is, not as a casualty count.
Does this apply to a five-person company? The sources don't say either way. But the precondition isn't enterprise-specific: a log reader and a write tool sharing a session. If you've built that with a Cloudflare token and an agent framework, the structural exposure is the same, whatever your headcount.
The fix: an authorization gate outside the model
Steve Wilson, Exabeam's chief AI and product officer and co-lead of the OWASP Top 10 for LLM Applications project, recommends an authorization gate outside the model: the agent can propose the exact DNS change but cannot grant itself the authority to make it (VentureBeat).
The important phrase is outside the model. The decision about whether a change is allowed can't live in a prompt, because prompts are exactly what the attacker is writing. It has to live in code that doesn't read natural language.
Under Wilson's model:
- A safe change that is cleanly defined passes a deterministic policy check and stays autonomous.
- Anything ambiguous, or high in blast radius, goes to a named human who approves the actual change.
I haven't read the full VentureBeat piece beyond the excerpts available to me, so I'm not going to attribute further trade-off commentary to Wilson. What follows is my implementation of that principle, not his.
Building the gate: a minimal propose-then-approve pattern
The agent's DNS tool should not call the DNS API. It should emit a structured proposal. A separate component, with no LLM in it, evaluates the proposal and either executes it, queues it for a human, or rejects it.
Here's the shape in Python. This is a sketch to show the architecture, not production code:
from dataclasses import dataclass
from enum import Enum
class Decision(Enum):
AUTO_APPROVE = "auto_approve"
NEEDS_HUMAN = "needs_human"
REJECT = "reject"
@dataclass(frozen=True)
class DnsChangeProposal:
zone: str
record_type: str # e.g. "A", "MX", "TXT"
name: str
old_value: str | None
new_value: str
reason: str # free text from the agent: for humans only, never parsed by policy
proposed_by: str # agent identity
# Deterministic policy: plain data, no model involved.
AUTO_ALLOWED = {
# (record_type, name_pattern) the agent may change without a human
("TXT", "_acme-challenge"),
}
NEVER_AUTO = {"MX", "NS", "A", "AAAA", "CNAME", "SPF", "DKIM", "DMARC"}
def evaluate(p: DnsChangeProposal) -> Decision:
if p.record_type in NEVER_AUTO:
return Decision.NEEDS_HUMAN
if (p.record_type, p.name) in AUTO_ALLOWED:
return Decision.AUTO_APPROVE
return Decision.REJECT # default deny
Three design choices carry the weight:
- Default deny. Anything not explicitly listed is rejected, not escalated. Escalation is for things you decided in advance are reviewable.
- The
reasonfield is for humans only. The policy never reads it. If the attacker's payload convinces the agent to write a persuasive justification, that text goes to a reviewer's screen, not into a decision function. - The credential lives with the executor, not the agent. The agent process never holds a token with DNS write scope. Only the approval service does, and it only acts on proposals that passed
evaluateor carry a human approval record.
The approval step itself should show the human the exact change, a diff of old record versus new record, not the agent's summary of it. Wilson's wording is that the named human approves "the actual change" for a reason: approving a description lets the attacker control the description.
Scoping MCP permissions so the gate has something to hold
If your agent talks to tools through MCP servers, the gate has to be enforced at the server and credential layer, not in the agent's instructions. A practical checklist:
- Split read and write into separate sessions and separate credentials. Tenet's common condition was a read-only log tool sharing a session with a write/exec tool. Break that coupling. The agent that triages logs should not be the process that can edit DNS.
- Issue the narrowest token the job needs. A log-review agent needs log read scope. It does not need zone edit. Check your provider's current token-scoping docs for what granularity is available.
- Expose proposal tools, not mutation tools. Instead of an
update_dns_recordtool, exposepropose_dns_change, which writes to a queue. - Treat every tool result as untrusted data. Log lines, error messages, ticket bodies, and email text all get the same label: input, never instruction.
For coding agents specifically, Tenet released an open-source tool, agent-jackstop, for Cursor and Claude Code. Per a summary on Secure.com, it denies outbound network access by default, requires human approval for any command, treats tool output as untrusted, and blocks credential reads at the subprocess level. I haven't audited it, so review it before trusting it, but the four defaults it ships are a decent baseline to copy even if you build your own.
A permissions manifest for an agent might look like this, expressed as config rather than prose:
agent: log-triage
tools:
cloudflare_logs:
scope: read
dns:
mode: propose_only # emits proposals; cannot execute
queue: dns-change-review
network:
egress: deny_by_default
allow:
- api.cloudflare.com # logs read endpoint only
credentials:
mounted_in_agent: [logs_read_token]
withheld: [dns_edit_token, cloud_admin]
approvals:
required_for: [dns, iam, billing, email_routing]
approver_role: named_human
The withheld line is the whole idea. A prompt-injected agent can only misuse what's mounted in its process.
A blast-radius audit you can run this week
You don't need a security team to do a first pass. For every agent you run, answer these in a spreadsheet:
- What untrusted text does it read? Logs, support email, form submissions, scraped pages, error trackers, PR comments. Anything an outsider can influence counts, including headers they control.
- What can it write or execute in the same session? DNS, deployments, payments, email sends, cloud IAM, shell.
- Which credentials does the agent process hold? List them. Each one is available to an attacker who steers the agent.
- Which of those writes are reversible in minutes, and which aren't? A mistaken TXT record is a cleanup. A rewritten MX record can quietly redirect your inbox.
- Who is the named human for each high-impact action? If the honest answer is "nobody," that action shouldn't be agent-reachable.
Any row where question 1 has an entry and question 2 has an entry is a GhostJacking-shaped path. The agent doesn't need to be malicious, buggy, or badly prompted. It only needs to be connected.
This also tells you where to put effort. Don't try to sanitize every log line against every possible payload. That's an arms race against free-form text. Remove the path from untrusted text to irreversible action instead.
What the gate does not fix
A propose-then-approve gate narrows the blast radius. It doesn't make the agent trustworthy. Know the residual risks:
- Approval fatigue. If a human sees fifty proposals a day and approves by reflex, the gate is theater. Keep the escalation list short and meaningful, which is why default-deny with a tiny auto-allow set beats "escalate everything."
- Misleading proposals. A human reviewing a diff can still miss a subtle record change. Show exact old and new values, and highlight record types like MX and NS.
- Non-DNS impact. Tenet's Datadog result involved code execution and credential theft, and Sentry involved agent-to-agent trust. A DNS-only gate doesn't cover those. The principle (propose outside the model, authorize outside the model) has to be applied per tool.
- Data exposure through reads. An injected agent with broad read access can still leak what it reads if it has any outbound path. That's why egress deny-by-default sits next to the approval gate.
Where this leaves you
GhostJacking is the clearest example of why propose-then-approve gates and scoped MCP permissions belong in an agent's very first build, not a later hardening pass. The pattern in this post is the approach I'd recommend: read tools and write tools in separate sessions with separate credentials, high-impact actions (DNS, email routing, payments, IAM) as proposal-only, and the policy check that decides what runs unattended written as plain deterministic code rather than a prompt.
Note that the sources don't establish how GhostJacking maps onto small-business stacks, so that connection is my own framing. But the audit questions above are the ones I'd recommend asking about every agent you run: what untrusted text does it read, what can it write, and what's the worst thing it could do with the credentials it's holding. Start with the spreadsheet audit this week.
Work with BizFlowAI
If you'd rather have this built for you, that's what we do: production AI automation for solo founders and small teams — agents, integrations, and document pipelines that actually ship.
Request a free discovery call — 30 minutes, we map the highest-ROI automation in your workflow. No pitch deck, just engineering.
More guides like this on the BizFlowAI blog.
Frequently asked questions
What is GhostJacking and how does it attack AI agents?
GhostJacking is an indirect prompt injection chain, demonstrated by Tenet Security at DEF CON 34, that turns security logs into an attack delivery mechanism. An attacker sends a request with a malicious payload in a header such as User-Agent, which a firewall blocks but still logs verbatim. When an AI agent later reads those logs, the payload enters its context and the agent may act on it using its existing credentials. In Tenet's demo this resulted in a company's DNS being rewritten.
Is GhostJacking a vulnerability in Cloudflare, Datadog, or Sentry?
No. GhostJacking is not a bug in those products and no CVE was assigned. It exploits agents that have broad legitimate access and read attacker-influenced data, such as logs, without any provenance marking. Because there is no vendor patch to wait for, the fix is on the builder's side: limit what the agent can do and add an approval layer outside the model.
Why don't firewalls or monitoring tools detect a GhostJacking attack?
The attack only uses actions the agent was already permitted to perform, so there is no exploit signature, malicious binary, or stolen credential to flag. From a monitoring perspective, a service account with DNS-edit permission simply made a DNS edit, which looks normal. The firewall also works as designed by blocking the request, but in doing so it writes the attacker's payload into the log the agent later reads.
How do I stop an AI agent from making harmful changes after reading poisoned logs?
Put an authorization gate outside the model so the agent can propose an exact change but cannot approve or execute it. A deterministic, non-LLM policy component should auto-approve only narrowly defined safe changes, send high-blast-radius or ambiguous changes to a named human, and reject everything else by default. Prompt-level instructions like 'ignore commands in logs' are not enough, because they ask the same component under attack to defend itself.
Is a read-only log tool safe to give an AI agent that also has write access?
Not when they share a session. A read-only tool feeding a model that also holds a write or exec tool becomes an indirect write path, because whatever the log contains can steer what the agent does. Tenet identified this combination, with log messages reaching the model with no provenance, as the common condition across its Cloudflare, Datadog, and Sentry demonstrations. Separate read and write capabilities and gate all writes behind external approval.