# An agent was talked into sending $150,000 in Morse code

> No exploit and no stolen key, just a message the model agreed to act on. Prompt injection is not a bug you patch, so what can a persuaded model express?

- Source: https://defiloops.com/blog/talked-into-sending-150000
- Published: 2026-09-01
- Category: Security
- Tags: security, prompt-injection, agents, permissions
- Author: DeFiLoops

---
In May 2026 somebody [took $150,000 out of an AI-integrated wallet](https://www.giskard.ai/knowledge/how-grok-got-prompt-injected-an-x-user-drained-150-000-from-an-ai-wallet) without finding a single
bug.

They sent a membership NFT to the wallet, which silently moved it into a higher permission
tier. Then they posted a reply on X with an instruction hidden inside Morse code. The agent
— Grok, wired into a trading bot — decoded the Morse, read the instruction, decided it was
a legitimate command, and authorised the transfer.

No exploit. No stolen key. No compromised server. The model was simply asked nicely, in a
format nobody had thought to distrust.

## Two failures, and only one of them gets discussed

Both have names. They are [OWASP LLM01:2025 and LLM06:2025](https://genai.owasp.org/llm-top-10/) — the first and sixth
entries in the standard list of ways language-model applications fail.

<Compare left="Prompt injection" right="Excessive agency" verdict>
  <Fragment slot="left">
    The model could not tell an instruction *from its principal* apart from an instruction
    *in its input*. Everyone is working on this. Nobody has solved it.
  </Fragment>
  <Fragment slot="right">
    Once persuaded, the model was able to express "send everything to this address."

    This one is not a model problem at all. It is a question about what the surrounding
    system permits to be written down.
  </Fragment>
</Compare>

The first failure is the one that gets the headlines. The second is the one that decided
how much it cost.

<Callout type="warn" title="Treat injection as a given">
  Encoded content makes this concrete: any text passing through a translation or decoding
  layer should be treated as potentially injected. Morse code worked. So will base64, so
  will an image, so will a language the guardrails were not evaluated in. You cannot
  enumerate the encodings.
</Callout>

If injection is going to happen, the design question is not *how do we stop the model being
fooled*. It is **what can a fooled model actually do**.

## The industry's answer, and why it stops short

Read a dozen vendor pages on agentic wallet security and you get the same list: least
privilege, scoped session keys, per-transaction and daily caps, a recipient allowlist,
human confirmation for anything irreversible, keys isolated from the reasoning process.

All of it is good. Some of it is not happening — **[45.6% of teams still share one API key](https://www.gravitee.io/blog/state-of-ai-agent-security-2026-report-when-adoption-outpaces-control)
across their agents**, which makes it close to impossible to trace or stop an agent that has
turned. The reported dollar totals for agent breaches are worth treating carefully — the headline
Q1 2026 figure is dominated by a single incident that primary reporting attributes to
compromised executive laptops rather than to anything about agents. The failure mode that
is well evidenced is narrower and more useful: attacks on agents' memory and on the
protocols connecting them to their tools.

But the deeper issue is that the whole list shares an assumption:

<Quote>
  Permission scopes must be defined precisely, but defining them precisely is hard, because
  agents act on language-based intent and language is ambiguous.
</Quote>

That sentence, or one almost exactly like it, appears across the field. It is treated as a
hard problem to be managed with ever-tighter scopes.

It is a hard problem. It is only unavoidable if the model is composing calls.

## We took the field out instead

The agent here does not write instructions. It fills in a fixed form of labelled boxes,
drawn from a closed catalogue of operations.

<Spec title="What the form can carry" rows={[
  ['amount', "A quantity, in the asset's own scale"],
  ['asset', "A symbol from the network's list — never an address"],
  ['network', 'One of the supported networks'],
  ['role', 'A named destination, such as "the owner\'s account" — never a raw address'],
  ['market', 'A curated market name, never an identifier'],
  ['basis points, deadline, price, tick', 'Bounded numeric kinds'],
]} />

<Callout type="danger" title="There is deliberately no “raw bytes” kind and no “anything” kind">
  Either one would let a caller smuggle an instruction through a field, and that would undo
  the entire property. There is no box for a destination address and no box for a payload.
</Callout>

So run the Morse code attack against this. The model is persuaded — assume that completely.
It now wants to send your funds to an attacker.

It cannot write that down. Destinations are **roles**, resolved from your account at the
moment the step runs. There is no field where an attacker's address could go, so nothing
has to detect the attempt.

<Steps>
  <Step title="An operation not on the list cannot be named">
    A plan naming anything outside the catalogue is refused when it is written, not
    attempted and failed halfway.
  </Step>
  <Step title="Anything produced goes to you">
    The destination is a role looked up from your account, never a value the plan supplies.
  </Step>
  <Step title="Caps are fixed in your account">
    Per asset, per period. Nothing on our side can raise them.
  </Step>
  <Step title="The agent is a contract, not a key">
    A key can be stolen and used by whoever holds it. A contract can only be appointed or
    not — and the appointment is one-way, so an account can never be re-pointed at a
    different agent.
  </Step>
</Steps>

## Where this stops being comfortable

Everything above is real and none of it makes us safe in general. Three things you should
weigh against it:

<Callout type="warn" title="The guard is a denylist">
  The thing stopping the agent misbehaving bans specific actions rather than permitting only
  known-good ones. It completely blocks a takeover — the agent can never become the owner or
  hand your account to somebody else. It does not block every way value could move through
  an action nobody thought to ban.

  **We have proven four such attacks against ourselves**, on a copy of the real Base network,
  in the same shape as production.
</Callout>

Those are reachable in two situations: a permissive mode we do not run in by default, and
anywhere somebody holds the keeper key directly and therefore never goes through our signing
service. In normal operation that service will not produce the shape of instruction that
reaches them. That is a real protection and it is not the same as the hole being closed.

Second: **there is no external audit.** Every assurance here is our own testing.

Third: the service holding the keeper key refuses to start in a production setting, on
purpose, because it currently keeps that key in memory.

## What to actually take from the Morse code story

Not that one product was careless. The specific mistake — an NFT transfer silently raising a
permission tier — is unusual, but the shape is not.

The shape is: **a system where being persuaded and being able to act are the same event.**

<Quote cite="The only structural defence we know">
  Injection you cannot prevent. Expressiveness you can remove.
</Quote>

If you are evaluating any agent that touches money, that is the question worth asking, and
it is answerable without trusting anybody's security page. Ask what the agent is able to
write down. If the answer includes an address, every other control is a filter standing
between a persuadable model and your funds — and filters are evaluated against the attacks
somebody already thought of.

Ours does not include an address. We would still rather you assumed we had missed something,
because on four occasions we had.