---
title: "Run the Shannon AI pentester on a disposable VM"
description: "Run Shannon, Keygraph's open-source AI pentester, on a throwaway exe.dev VM: the model from the LLM integration, the target an app you own, and nothing on your laptop."
url: https://exe-staging.dev/deploy/shannon
category: "Security"
updated: 2026-11-02
---

# Run the Shannon AI pentester on a disposable VM

An AI pentester for your own web apps, in a VM you delete afterwards.

- **Software:** Shannon (https://keygraph.io)
- **Source:** https://github.com/KeygraphHQ/shannon
- **License:** AGPL-3.0
- **Time:** 15 minutes, then 1-2 hours per scan
- **Example VM name:** `my-shannon` (yours will differ; on the VM, `hostname` prints it)
- **Machine-readable:** https://exe-staging.dev/deploy/shannon/manifest.json

## What you get

- Shannon 3.x installed, with its worker image pulled and Docker started on every boot
- Shannon using exe.dev's LLM integration, with no model key on the VM
- OWASP Juice Shop running on the VM as a practice target, restarted with the VM, with its source in `~/juice-shop`
- A scan config, `~/scans/juice-shop.yaml`, whose rules of engagement keep Shannon on that target
- A login check against Juice Shop that passes, and no scan started

Running on the VM:

- **Shannon CLI**: @keygraph/shannon 3.x on Node.js 24, data in `~/.shannon/config.toml`
- **Temporal**: temporalio/temporal in Docker, started by Shannon, port 8233, data in `Docker volume infra_temporal-data`
- **Shannon worker**: keygraph/shannon in Docker, one container per scan, data in `~/.shannon/workspaces`
- **OWASP Juice Shop**: bkimminich/juice-shop, a deliberately vulnerable practice target, port 3000, data in `~/juice-shop (its source, for Shannon to read)`

How it is reached:

- ssh my-shannon.exe.xyz (private): start scans, follow them, read reports
- https://my-shannon.exe.xyz:3000/ (private): the practice target, Juice Shop, in your browser
- http://localhost:8233/ through an SSH tunnel (private): Temporal's web UI, for a scan's progress in detail

## Deploy with Shelley

Open https://exe-staging.dev/new?prompt=Set%20up%20Shannon%20on%20this%20VM%20by%20following%20the%20exe.dev%20recipe%20at%20https%3A%2F%2Fexe-staging.dev%2Fdeploy%2Fshannon.md%20from%20top%20to%20bottom.%0A%0AThe%20recipe%27s%20example%20VM%20name%20is%20my-shannon.%20This%20VM%27s%20name%20is%20the%20output%20of%20%60hostname%60%3A%20use%20it%20wherever%20the%20recipe%20writes%20my-shannon.%20Skip%20the%20step%20that%20creates%20the%20VM%3B%20you%20are%20on%20it.%0A%0AYou%20are%20done%20when%20all%20of%20these%20are%20true.%20Check%20each%20one%20yourself%3A%0A-%20Shannon%203.x%20installed%2C%20with%20its%20worker%20image%20pulled%20and%20Docker%20started%20on%20every%20boot%0A-%20Shannon%20using%20exe.dev%27s%20LLM%20integration%2C%20with%20no%20model%20key%20on%20the%20VM%0A-%20OWASP%20Juice%20Shop%20running%20on%20the%20VM%20as%20a%20practice%20target%2C%20restarted%20with%20the%20VM%2C%20with%20its%20source%20in%20%60~%2Fjuice-shop%60%0A-%20A%20scan%20config%2C%20%60~%2Fscans%2Fjuice-shop.yaml%60%2C%20whose%20rules%20of%20engagement%20keep%20Shannon%20on%20that%20target%0A-%20A%20login%20check%20against%20Juice%20Shop%20that%20passes%2C%20and%20no%20scan%20started%0A%0ARun%20only%20the%20commands%20in%20%22Install%20by%20hand%22%2C%20from%20%22Install%20Shannon%22%20on.%0ANever%20start%20a%20pentest%20yourself%3A%20the%20only%20%60shannon%20start%60%20you%20run%20is%20the%0Aone%20with%20%60--validate-auth%60%20in%20%22Check%20the%20setup%22.%20Never%20point%20Shannon%20at%0Aanything%20but%20%60http%3A%2F%2Fhost.docker.internal%3A3000%60.%20If%20the%20login%20check%0Afails%2C%20show%20the%20owner%20the%20last%20lines%20of%20its%20output%20and%20stop.%20Finish%20by%0Atelling%20the%20owner%20to%20read%20%22Before%20you%20scan%22%20and%20then%20follow%20%22Run%20your%0Afirst%20scan%22.%0A%0AFinish%20with%20the%20URLs%20I%20should%20open.%0A to create a VM whose agent, Shelley, follows this recipe. From a terminal:

```sh
curl -fsSL https://exe-staging.dev/deploy/shannon/prompt.txt | ssh exe.dev new --prompt=/dev/stdin
```

Or follow the steps below by hand.

[Shannon](https://github.com/KeygraphHQ/shannon) is an open-source AI
pentester for web apps and APIs, from Keygraph. Give it a running app and
that app's source code, and an agent maps the code, looks for injection,
XSS, SSRF, and broken authentication and authorization, then attacks the
running app in a real browser and with command-line tools. It reports only
what it managed to exploit, as a PDF and Markdown report plus SARIF.
Version 3.5.0 came out on October 27, 2026; it's licensed AGPL-3.0, and you
pay only for the model.

Shannon's own [safety guide](https://github.com/KeygraphHQ/shannon/blob/main/docs/safety.md)
suggests running it in a disposable virtual machine, against a disposable
copy of the app. exe.dev VMs are both. The pentester gets a VM of its own,
isolated from your laptop's files and keys; the model comes from exe.dev's
[LLM integration](/docs/integrations-llm), so no API key ever lands on a
VM; the target is a practice app this page sets up on the same VM, or your
own staging app on [another VM of yours](/docs/integrations-vm-to-vm),
reachable privately with no credentials on either side. When the report is
safely off, you delete the VM.

> [!WARNING]
> **Test only what you own, or have written permission to test.** Shannon
> executes real exploits: it creates users, submits forms, changes and
> deletes data, and makes outbound requests. Never run it against
> production, someone else's site, other people's exe.dev VMs, or exe.dev
> itself. Read Keygraph's
> [safety guide](https://github.com/KeygraphHQ/shannon/blob/main/docs/safety.md)
> and exe.dev's [terms of service](/docs/terms-of-service) first.
>
> **Using a pentester from an exe account against an external system, or exe
> resources owned by another user will trigger our monitoring systems and
> may result in your account being locked. Only use this against your own
> exe resources.**

## Install

Deploy installs Shannon, starts Juice Shop as a practice target, and checks
that Shannon can log in to it. It doesn't start a scan: that's yours to
start. To do it yourself:

<details id="create-the-vm" data-manual-setup class="dp-details">
<summary>Install by hand</summary>

On your computer, create a VM and connect. Pick any free name:

```bash
ssh exe.dev new --name=my-shannon
ssh my-shannon.exe.xyz
```

### Install Shannon

On the VM, have Docker start on every boot, install Node.js 24 from
NodeSource, and install the Shannon CLI. Then pull the worker image that
matches it (about 3.6 GB on disk), so the first scan doesn't wait for it:

```bash
sudo systemctl enable --now docker.service
curl -fsSL https://deb.nodesource.com/setup_24.x | sudo -E bash -
sudo apt-get install -y nodejs
sudo npm install -g @keygraph/shannon@3
shannon version
docker pull keygraph/shannon:$(shannon version | awk '{print $2}')
```

`shannon version` prints `shannon 3.5.0` or later. Keygraph's docs run the
same commands as `npx @keygraph/shannon <command>`; the global install
pins the major version.

### Connect it to the LLM integration

Shannon reads its model and credentials from `~/.shannon/config.toml`.
Point it at exe.dev's [LLM integration](/docs/integrations-llm), which
speaks Anthropic's API at `https://llm.int.exe.xyz` and adds the real
credential outside the VM. Shannon insists on an API key, so give it a
placeholder:

```bash
mkdir -p ~/.shannon
install -m 600 /dev/null ~/.shannon/config.toml
cat > ~/.shannon/config.toml <<'EOF'
[core]
model = "anthropic:claude-sonnet-5"
base_url = "https://llm.int.exe.xyz"

[anthropic]
api_key = "exe-llm-integration"
EOF
```

Shannon refuses to read the file if anyone but you can, hence the `600`.
`claude-sonnet-5` is Shannon's default model and Keygraph's first
suggestion; see [Use your own model key](#use-your-own-model-key) for
OpenAI and other providers.

### Start a practice target

Run OWASP Juice Shop in Docker, restarted with the VM, and put the source
of the same version in `~/juice-shop` for Shannon to read. Its port, 3000,
is private: only you can open `https://my-shannon.exe.xyz:3000/`.

```bash
docker run -d --name juice-shop --restart unless-stopped -p 3000:3000 bkimminich/juice-shop:v20.3.0
git clone --depth 1 --branch v20.3.0 https://github.com/juice-shop/juice-shop.git ~/juice-shop
sleep 15
curl -s -X POST localhost:3000/rest/user/login -H 'Content-Type: application/json' \
  -d '{"email":"jim@juice-sh.op","password":"ncc-1701"}' | head -c 40; echo
```

The last command prints `{"authentication":{"token":...`: Juice Shop is
up, and Jim, one of the customer accounts it ships with, can log in.
Shannon logs in as Jim. Juice Shop rebuilds its database every time it
starts, so anything a scan changes is gone after `docker restart juice-shop`,
and accounts you add are too.

### Write the scan config

The config tells Shannon what the app is, how to log in, and the rules of
engagement it must follow. Inside its container, Shannon reaches apps on
the VM at `host.docker.internal`. The rules keep it on Juice Shop and away
from exe.dev's own addresses:

```bash
mkdir -p ~/scans
cat > ~/scans/juice-shop.yaml <<'EOF'
description: "OWASP Juice Shop v20.3.0, a deliberately vulnerable training app, in Docker on this disposable exe.dev VM. Test data only."
rules_of_engagement: |
  - The only target is http://host.docker.internal:3000. Send no requests to any other host.
  - Never target exe.dev infrastructure: no requests to *.exe.xyz, *.int.exe.xyz, *.exe.dev or 169.254.169.254.
  - For SSRF, prove impact against the target itself, not against third-party hosts.
authentication:
  login_type: form
  login_url: "http://host.docker.internal:3000/#/login"
  credentials:
    username: "jim@juice-sh.op"
    password: "ncc-1701"
  login_flow:
    - "If a 'Welcome to OWASP Juice Shop' dialog is shown, click 'Dismiss'"
    - "If a cookie banner is shown, click 'Me want it!'"
    - "Type $username into the Email field"
    - "Type $password into the Password field"
    - "Click the 'Log in' button"
  success_condition:
    type: url_contains
    value: "/search"
EOF
chmod 600 ~/scans/juice-shop.yaml
```

### Check the setup

Have Shannon start its Temporal service and do one real login, without
attacking anything. It takes about a minute and a few cents of model use:

```bash
shannon start -u http://host.docker.internal:3000 -r ~/juice-shop \
  -c ~/scans/juice-shop.yaml -w login-check --validate-auth
```

It ends with `Validation COMPLETED` and a check list whose
`Preflight (LLM credentials, target URL)` line says `OK`. That proves the
model answers through the integration, the worker container reaches the
target, and the login flow works.

</details>

## Before you scan

A scan is a real attack, so decide what it may touch:

- **The target is yours.** Juice Shop on this VM is built to be attacked.
  For your own app, see [Scan your own app](#scan-your-own-app).
- **Nothing else is in reach.** Shannon follows the rules of engagement in
  the config, but it's an agent, so also limit what the VM can reach. With
  the [egress policy](/docs/internet-access) set to `integrations`, the VM
  can reach exe.dev's integrations — the LLM one, and a target reached
  through a [VM-to-VM integration](/docs/integrations-vm-to-vm) — and
  nothing on the internet. On your computer:

  ```bash
  ssh exe.dev egress my-shannon integrations
  ```

  Traffic that never leaves the VM, like the practice target on
  `host.docker.internal`, is unaffected. Run the login check from
  "Check the setup" again after changing the policy to confirm. Set it
  back with `ssh exe.dev egress my-shannon all` when you need to install
  or update something.
- **The model will cooperate.** Anthropic and OpenAI apply
  [safeguards to security work](https://github.com/KeygraphHQ/shannon/blob/main/docs/ai-providers.md#cyber-safeguards-do-this-before-your-first-scan).
  Shannon checks before every scan and stops early if the provider
  declines, but a refusal can still end a scan partway.

## Run your first scan

On the VM:

```bash
shannon start -u http://host.docker.internal:3000 -r ~/juice-shop \
  -c ~/scans/juice-shop.yaml -w juice-1
```

Shannon runs its checks, starts the scan in a container of its own, and
returns you to the prompt. The scan takes an hour or more and carries on
when you log out. From any SSH session:

```bash
shannon status juice-1   # phases and agents, done and pending
shannon logs juice-1     # the live log; Ctrl-C stops following, not the scan
shannon scans            # every scan on this VM
shannon stop juice-1     # stop it
```

For more detail, open Temporal's web UI. It listens only on the VM's
localhost, so tunnel to it from your computer and open
`http://localhost:8233/`:

```bash
ssh -L 8233:localhost:8233 my-shannon.exe.xyz
```

If a scan is interrupted, run the same `shannon start` command with the same
`-w` name: Shannon resumes it and skips the agents that finished.

## Read the report

Each scan leaves a workspace in `~/.shannon/workspaces/<name>/`, with the
report at the top:

- `Security-Assessment-Report.pdf` and `Security-Assessment-Report.md`:
  each finding with the proof that it was exploited.
- `report.sarif`: the same findings for GitHub code scanning or any other
  SARIF reader.
- `.shannon/`: logs, prompts, screenshots, and each agent's notes.

On your computer, copy the workspace off the VM:

```bash
scp -r my-shannon.exe.xyz:.shannon/workspaces/juice-1 ./shannon-juice-1
```

The report contains working exploits for your app. Keep it like any other
secret. And review it: Keygraph says a model can still get details wrong.

## Scan your own app

Shannon needs your app's source and a running copy with test data. The
copy should be disposable: a staging or review deployment, or a throwaway
run on a VM. Two ways to place it:

**On this VM, next to Shannon** — the simplest, and the attack never
leaves the VM:

1. Clone your repository into `~/myapp`, for example through the
   [GitHub integration](/docs/integrations-github), so no token lands on
   the VM. Shannon mounts it read-only.
2. Start the app on the VM the way you run it locally, such as
   `docker compose up -d`, with test data and test accounts only.

**On another VM of yours** — the natural home for a staging app, like the
test and prod VMs in [Dev, Test, Prod](/deploy/dev-test-prod). Don't open
it to the internet for this: a [VM-to-VM integration](/docs/integrations-vm-to-vm)
gives the Shannon VM a private HTTPS address for it, with a generated key
injected at exe.dev's edge so neither VM holds a credential. Serve the app
on the target VM's port 8000, the web port, then on your computer:

```bash
ssh exe.dev integrations add http-proxy --name myapp --target https://my-shannon-target.exe.xyz/ --peer --attach vm:my-shannon
```

`my-shannon-target` stands for the target VM's own name. From the Shannon
VM, the app is now at `https://myapp.int.exe.xyz/`, and that address keeps
working with the egress policy set to `integrations`. (This page's own
end-to-end test ran the same shape — Shannon on one VM attacking Juice
Shop on a second over its HTTPS address — and got 29 proven findings,
including SQL injection with a forged admin session, in about an hour and
a half with `claude-sonnet-5`.)

Then, either way:

3. Copy `~/scans/juice-shop.yaml` to `~/scans/myapp.yaml` and `chmod 600`
   it, since it will hold a test account's password. Change
   `description`, the target in `rules_of_engagement` —
   `http://host.docker.internal:<port>` or
   `https://myapp.int.exe.xyz`, and that host only — and the login.
   Keygraph's [configuration guide](https://github.com/KeygraphHQ/shannon/blob/main/docs/configuration.md)
   covers TOTP, magic links, and `rules` to `avoid` or `focus` on paths.
4. Check the login, then scan:

   ```bash
   shannon start -u https://myapp.int.exe.xyz -r ~/myapp -c ~/scans/myapp.yaml -w myapp-check --validate-auth
   shannon start -u https://myapp.int.exe.xyz -r ~/myapp -c ~/scans/myapp.yaml -w myapp-1
   ```

## One VM per scan

For regular scans, keep this VM as a template and scan in copies. A copy
has Shannon, the worker image, and the config already, and keeps the
template's egress policy. On your computer:

```bash
ssh exe.dev cp my-shannon my-shannon-scan1
ssh my-shannon-scan1.exe.xyz
```

Run the scan in the copy, copy the workspace off it as in
[Read the report](#read-the-report), and delete it:

```bash
ssh exe.dev rm my-shannon-scan1
```

To script that, the same commands are in exe.dev's
[HTTPS API](/docs/https-api), the [Python SDK](/deploy/python-sandboxes),
and the [MCP server](/deploy/exe-mcp), so an agent can make the VM, run
the scan, and fetch the report for you.

## Use your own model key

The LLM integration bills model use to your exe.dev account, and the
provider's safeguards see exe.dev's account. To use your own Anthropic or
OpenAI organization, one that has done the provider's security
verification, add a separate [LLM integration](/docs/integrations-llm)
with your key, attached only to this VM. The VM can use the key but never
read it. On your computer:

```bash
printf '%s' "$ANTHROPIC_API_KEY" | ssh exe.dev integrations add llm --name shannon-llm --anthropic=byok --anthropic-key=- --openai=disabled --fireworks=disabled --attach vm:my-shannon
```

On the VM, point Shannon at it by changing one line in
`~/.shannon/config.toml`:

```toml
base_url = "https://shannon-llm.int.exe.xyz"
```

To use OpenAI instead, set `model = "openai:gpt-5.6-sol"`, move the
placeholder key to an `[openai]` section, and end `base_url` in `/v1`.
Keygraph's [AI providers guide](https://github.com/KeygraphHQ/shannon/blob/main/docs/ai-providers.md)
lists the models Shannon knows.

## Update

On the VM:

```bash
sudo npm install -g @keygraph/shannon@3
docker pull keygraph/shannon:$(shannon version | awk '{print $2}')
```

Shannon removes older worker images itself. Check
[the releases](https://github.com/KeygraphHQ/shannon/releases) first: they
sometimes change the config.

## Clean up

When you're done, keep the reports and delete the VM. On your computer:

```bash
ssh exe.dev rm my-shannon
```

To keep the VM but clear out old scans, `shannon stop --all` stops any that
are running, and `shannon reset` wipes Temporal's history. Workspaces are
plain directories in `~/.shannon/workspaces/`. To reset Juice Shop's data,
restart it: `docker restart juice-shop`. When the target was a whole VM of
its own, delete that too: `ssh exe.dev rm my-shannon-target`.

## Troubleshooting

- **`Your config file is readable by other users`**: run
  `chmod 600 ~/.shannon/config.toml`.
- **Preflight fails on the model**: check the integration from the VM with
  `curl -s https://llm.int.exe.xyz/v1/messages -H 'content-type: application/json' -H 'anthropic-version: 2023-06-01' -d '{"model":"claude-sonnet-5","max_tokens":20,"messages":[{"role":"user","content":"hi"}]}'`.
  A plain reply means the gateway is fine. Shannon checks model names
  against its own catalogue, so a name the integration knows may still be
  refused; use one from Keygraph's
  [list](https://github.com/KeygraphHQ/shannon/blob/main/docs/ai-providers.md#suggested-models).
- **The provider declined the security workload**: the scan stops with a
  link to the provider's verification program. Complete it, or use your
  own key as above.
- **The target can't be reached**: from inside a container, `localhost` is
  the container. Use `http://host.docker.internal:<port>` for apps on the
  VM, and make sure the app's port is published (`docker ps`, `ss -ltn`).
  For an app on another VM, check the integration address from the VM
  first: `curl -s https://myapp.int.exe.xyz/`.
- **The login check fails**: write `login_flow` the way you'd log in by
  hand in a private window, with the exact labels and button texts. Juice
  Shop's welcome dialog and cookie banner are the usual culprits.
- **A scan stalls or fails**: `shannon logs <name>` shows the combined
  log, `shannon logs <name> --list-agents` the per-agent logs, and
  Temporal's UI each step's error. Running the same `start` again resumes.
- **After a reboot**: Docker comes back with Juice Shop; Shannon starts
  Temporal again with the next `shannon start`.

## Frequently asked questions

### What am I allowed to scan with Shannon?

Only systems you own or have written permission to test, and not production. Shannon doesn't just scan: it runs real exploits, creates users, and changes data. Scanning anyone else's site or API without permission is illegal in most places, and it breaks Keygraph's [terms](https://github.com/KeygraphHQ/shannon/blob/main/docs/safety.md) and exe.dev's [terms of service](/docs/terms-of-service).

### Does exe.dev allow security testing from a VM?

exe.dev's [terms of service](/docs/terms-of-service) have no section on security testing. They forbid breaking the law or anyone's rights, and attacking exe.dev's own servers. So: test your own apps, keep the target on the same VM or on another VM of yours, and never aim Shannon at exe.dev itself, other people's VMs, or the internet at large.

### How much does a scan cost?

The VM is part of your exe.dev plan; the model is the cost. Keygraph's own [benchmark](https://github.com/KeygraphHQ/shannon/blob/main/docs/shannon-xbow-aikido-benchmark.md) put one app's scan at $6 to $115 in tokens depending on the model, and Keygraph says a full run takes 1 to 1.5 hours. Through the [LLM integration](/docs/integrations-llm) it's billed to your exe.dev account; with your own key, by your provider. Our own end-to-end test — Shannon on one VM, Juice Shop on a second — cost 15 cents for the login check and about $45 for the full scan with `claude-sonnet-5`.

### Will Anthropic or OpenAI refuse to run a pentest?

They might. Both apply safeguards to security work, and a refusal stops a scan partway. Shannon checks before every scan; through exe.dev's gateway that check passed with `claude-sonnet-5` in our test, and a full scan against a practice app ran without a refusal. For regular scans, consider your own provider account that has completed [Anthropic's](https://support.claude.com/en/articles/14604842-real-time-cyber-safeguards-on-claude-opus-and-sonnet) or [OpenAI's](https://chatgpt.com/cyber) verification, through an LLM integration so the key stays off the VM. See [Use your own model key](#use-your-own-model-key).

### Can Shannon test an app without its source code?

Not the open-source version: it reads the source to find attack paths, then proves them against the running app. Black-box testing is part of Keygraph's paid editions.

### Why a VM rather than my laptop or CI?

Shannon's own safety guide suggests a disposable VM for the most isolation. A scan runs exploit code, a browser, and an agent that reads untrusted input; on an exe.dev VM none of that is near your laptop's files or keys, and you delete the VM when the report is out. For CI, Keygraph has a [GitHub Action](https://github.com/KeygraphHQ/shannon-action), which can run on a [self-hosted runner](/deploy/github-actions).

### Can I point it at an app on another VM of mine?

Yes, and that's the natural setup for a staging app: a [VM-to-VM integration](/docs/integrations-vm-to-vm) gives the Shannon VM a private HTTPS address for the other VM, with the credential injected at exe.dev's edge so neither VM holds a key. See [Scan your own app](#scan-your-own-app).


## Related

- [Deploy a Docker Compose app from GitHub](https://exe-staging.dev/deploy/docker-compose.md): Your compose.yaml on a VM of its own. git push to deploy.
- [Dev, Test, Prod: Choose One, Two, or Three](https://exe-staging.dev/deploy/dev-test-prod.md): You can build, test, and deploy on exe.dev.
- [Create sandboxes from Python](https://exe-staging.dev/deploy/python-sandboxes.md): Full VMs as sandboxes. A with block creates one, runs your code, and deletes it.
- [Give any agent a sandbox over MCP](https://exe-staging.dev/deploy/exe-mcp.md): One URL gives your agent real VMs to run code on, host apps from, and hand work to.
- [Give agents API access without giving them the keys](https://exe-staging.dev/deploy/agent-secrets.md): Your agents call your APIs. The keys stay with exe.dev, outside the VM.
- [Self-host n8n](https://exe-staging.dev/deploy/n8n-2.md): Connect your apps. Let the boring bits run themselves.
