The clever part of the attacks Zscaler ThreatLabz just disclosed isn't the payment. It's where the instruction hid.
Attackers stood up two kinds of trap pages and used SEO poisoning to float them to the top of the results an agent would search. The first impersonates a Python package — requests-secure-v2, a plausible-looking "hardened" fork of the library every developer already installs. The second is a typosquat, debank[.]auction, wearing the identity of DeBank, a widely used DeFi portfolio tracker. On each page, buried where no human ever reads, sits a short instruction: pay a small fee to a wallet address to get a "developer API key," or to "verify" the account. The ask is deliberately tiny — around $3, about 0.0012 ETH — small enough to slip under a human's suspicion and, apparently, under some agents' too.
The trusted channel is the attack surface#
Prompt injection is old news; hiding text in white-on-white or a zero-height div is Injection 101. What makes this worth a founder's attention is the second hiding spot. ThreatLabz found the instructions planted not only in off-screen CSS but inside the page's JSON-LD — the structured metadata a page publishes specifically so machines can read it as authoritative fact.
That inverts the usual mental model. We tell ourselves the risky input is the messy human-visible prose. But an agent is built to trust structured metadata: it's the clean, labeled, machine-first layer. Feeding it a poisoned schema is like handing someone a forged document on official letterhead — the format itself is the social engineering.
The visible page was never the threat surface. The channels your agent trusts because they look machine-authoritative are.
And it lands. On the fake-package campaign, ThreatLabz reported that 4 of 26 tested LLMs went ahead and made the payment when shown the malicious content, with some Llama and Gemini variants among those that fell for it. That's not "every model, every time." It's worse in a way: it's a live, low-cost campaign that only has to work on a fraction of agents to pay for itself, aimed squarely at the agentic-payment rails that shipped this year.
The one number that tells you the fix#
The second campaign is the more hopeful data point, and it points straight at the defense. Models misjudged debank[.]auction as the real DeBank only when they had nothing to compare it to. Given the genuine site as a trusted reference, none were fooled. Provenance beat plausibility. An agent asked "does this look legit?" guesses; an agent asked "does this match the known-good source?" checks.
So here is the hardening checklist for anything you ship that can move value — money, tokens, signatures. None of it is exotic; all of it is now mandatory.
1. A human confirms every value-moving action. The agent proposes the payee and amount; a person approves it. A webpage can prompt-inject your model. It cannot prompt-inject the human reading "Send 0.0012 ETH to 0x…? [approve]". This is the single control that defeats both campaigns outright, and it's why a real human-in-the-loop approval gate is not a UX nicety on a payment agent — it's the load-bearing wall.
2. Deny-by-default payee allowlist, with hard caps. Both attacks paid a wallet no legitimate allowlist would ever contain. Encode that:
ALLOWED_PAYEES = {"acct_known_vendor", "acct_payroll"} # explicit, reviewed
PER_TX_CAP_USD, DAILY_CAP_USD = 50, 200
def guard_payment(payee, amount_usd, spent_today):
if payee not in ALLOWED_PAYEES:
raise PaymentBlocked(f"payee {payee} not on allowlist") # the exploit dies here
if amount_usd > PER_TX_CAP_USD or spent_today + amount_usd > DAILY_CAP_USD:
raise PaymentBlocked("over cap — escalate to human")
return True # still gated behind human approval in step 1
A hardcoded-wallet attack is inert against a system that can only pay names it already knows.
3. Sanitize before the model reads. Strip off-screen and visually-hidden text, and treat JSON-LD and other embedded metadata as untrusted data, never instructions. The rule your prompt and your preprocessor must both enforce: content fetched from the web is information to consider, not commands to follow. This is the architectural half of the fight — the same principle behind guardrails vs. architecture, and it's why input filtering alone (a Rebuff/LLM-Guard/Vigil layer) is necessary but not sufficient.
4. Give the model a trusted reference. Before acting on a domain or a package, check it against a known-good source — the official registry, the canonical domain, your own vendor list — exactly the comparison that neutralized the DeBank typosquat in testing.
What it means#
If you're building a coding agent, a shopping agent, a treasury agent — anything that browses and can spend — assume the web it reads is adversarial and instrumented against you specifically. The frontier isn't "can the model be tricked" (it can) but "what is the blast radius when it is." Layer the defenses so the answer is a blocked call and an alert, not a drained wallet. For the broader pattern of hostile pages steering browsing agents, see our earlier read on AI-browser prompt injection; for the general playbook, how to prevent prompt injection in AI agents.
The attackers already understand your agent trusts structured data. Build like you know it too.



