It started as a friendly challenge on X.

A developer I follow, Riza, was raving about pointing Claude Code at his own codebase. It spun up dozens of subagents and handed him a stack of real security and performance fixes. I replied, “Now let me give it a scan :)”.

He pointed me at SandyWP, his new side project. Real WordPress sandboxes in seconds. Then he mentioned the catch. SandyWP is not open source. My WordPress audits usually start by reading the code. This time there was none to read. Challenge accepted.

He gave me the go-ahead. I was not expecting to find anything. A few hours later I sent back a detailed report of what I found. I uncovered a way to sign into every sandbox on the platform, completely unauthorized! 🏆

His reply. Damn, not even need the codebase?

Let me start by saying SandyWP is genuinely a good product, and you should try it. Then we’ll dig into how my Claude Code completely broke through security measures harden by Claude Ultracode.

SandyWP homepage shown in a browser window: WordPress sandbox, instantly.
SandyWP. A fresh WordPress sandbox in seconds.

SandyWP spins up a throwaway WordPress site in seconds.

You pick a PHP version, a WordPress version, and a debug preset. A minute later you have a live WordPress site at a friendly URL. A passwordless magic-login link drops you straight into wp-admin. You get root SSH into your own container. There is a built-in file manager and database tools. The site expires on its own when you are done.

It is the kind of tool I reach for constantly. I build throwaway WordPress sites all day, to reproduce a bug or test a plugin. SandyWP makes that a single click. I liked it immediately.

I went in blind, and let Claude Code do the work.

I had no source code. This was a black box. So I drove the whole test with Claude Code and gave it everything I could legitimately get my hands on.

The setup was simple. I created a sandbox. I turned on SSH. I handed Claude Code the SSH login and the database details. Then I opened Chrome’s Network tab and watched the real requests fly by as I clicked around the dashboard. The request that creates a site. The request that mints a magic-login link. I copied those as curl and pasted them in.

That last part is the trick. Once Claude Code had the create-site request as curl, it held a live session cookie. It could spin up fresh sandboxes on demand, whenever it needed a clean target to test against. No browser in the loop. I gave it the keys to my own account and let it run.

Claude Code Session
Austin
I am testing SandyWP for the owner. Here is the create-site request as curl with my session cookie, a magic-login link, and SSH access to a sandbox. It is gated by cookies, which is a good sign. Probe it for holes. Maybe audit the mu-plugin, or anything else we can find.
Claude
Found a critical one, and it is the passwordless login. SSH lands me as root in the sandbox, where one must-use plugin, sandywp-helper.php, verifies the magic-login token with an HMAC secret read from the environment, SANDYWP_MAGIC_SECRET. The verification itself is careful. Constant-time compare, expiry enforced, no algorithm header to trick. The weak point is the secret.
Austin
Is that secret unique to each sandbox, or the same everywhere?
Claude
Same everywhere. I created a second sandbox, never read its secret, and forged an admin token for it using the first sandbox’s secret. The second site accepted it. HTTP 302 straight into its wp-admin. One sandbox is enough to log into anyone’s.

One secret signs every sandbox.

The magic-login link is a signed token. Two parts, a payload and a signature. The signature is an HMAC over the payload, keyed with SANDYWP_MAGIC_SECRET, which the plugin reads from the container environment.

The cryptography here is textbook. Constant-time comparison. Expiry checked. No algorithm field to confuse. Honestly, better than a lot of what I see. The one flaw is the key itself. It is identical in every sandbox on the platform.

function magic_secret(): string {
    return getenv('SANDYWP_MAGIC_SECRET') ?: '';   // the same value in every sandbox
}

// signature = HMAC-SHA256(payload, magic_secret())
// hash_equals() compare, exp enforced, no alg header.
// The crypto is fine. The key is shared.

Every user gets root SSH into their own sandbox. So every user can read the secret. Once you have it, you can sign a valid magic-login token for any other customer’s sandbox. Set the slug to their site. Set the user to admin. Sign it. Open the link. You are in their wp-admin.

One secret, every sandbox
Tokens are bound to the target hostname, which sounds protective. It is not. The attacker supplies the hostname in the token and sends the request to the victim’s own site. With one key signing every sandbox, the binding only restates the target. It never proves you are allowed to be there.

There is a second token that is worse. The same plugin accepts a filesystem token with no WordPress login at all. With it you can read and write any file in the sandbox. Writing a PHP file is remote code execution. The same shared secret signs it.

What the shared secret unlocks on any sandbox
Admin takeover
Forge a magic-login token
Sign an admin token for any slug and open the link. You land in that sandbox’s wp-admin. From there, install a plugin and you have code execution anyway.
Detect302 redirect into /wp-admin/ on a sandbox you do not own
Unauthenticated RCE
Forge a filesystem token
No WordPress login required. Write a PHP file anywhere under the web root through the file API. That is a web shell.
DetectPOST to the fs/write endpoint carrying a Bearer token
Data theft
Read any file
Pull wp-config.php, database credentials, and any customer content out of a sandbox that is not yours.
Detectfs/read returning file contents to an outside caller

I proved it across two sandboxes I owned.

I made two sandboxes under my own account, on two different SandyWP domains. I read the secret from the first one. I never read the second one’s secret. Then I signed an admin token for the second site using the first one’s key.

It worked on the first try.

# token signed with sandbox A's secret, presented to sandbox B
GET https://probe-two.sandywp.cc/?sandywp_magic=<forged-token>
  -> HTTP 302   Location: https://probe-two.sandywp.cc/wp-admin/

The owner later confirmed the same thing from his side. A token signed by one sandbox signs into another. That is every sandbox on the platform.

And the sandboxes are trivial to find.

Forging a token needs the target’s hostname. So how many hostnames are there to guess? SandyWP names each sandbox with a friendly word pair. breezy-wave. coral-wave. I found the generator sitting in the dashboard JavaScript.

const adjectives = ["breezy","salty","sunny","sandy","tidal","shelly",
                    "misty","cozy","driftwood","coral","reefy","lazy"];   // 12
const nouns      = ["crab","lagoon","cove","dune","wave","shore",
                    "pier","kelp","gull","shell","tide","pail"];          // 12
const name = pick(adjectives) + "-" + pick(nouns);   // 144 possibilities

Twelve adjectives. Twelve nouns. That is 144 names, with a number tacked on only when two collide. And a plain static file like readme.html returns 200 on a live sandbox and 404 on a dead one, with no login required. So you can sweep the entire space and watch which names answer.

0
Lines of source code I started with
144
Possible sandbox names
47s
To sweep the live fleet
1
Shared secret behind all of it

I ran the sweep. 2,880 candidate hostnames. 47 seconds. It came back with a few live sandboxes that were not mine. Chain the two findings together and a single free signup can find other live sandboxes and walk into them with a short script.

Here is the part SandyWP got right.

To be fair, everything else looked pretty locked down.

WHAT SANDYWP GETS RIGHT
A hardened container and real database isolation

The container drops Linux capabilities tighter than Docker’s own defaults. Seccomp is on. There is no Docker socket and no host mounts. Each sandbox gets its own MySQL user that can only see its own database. I tried to reach another database from inside a sandbox and got a flat access-denied. The token verification code itself is the careful kind. This was not a sloppy build. It was one shared key in an otherwise solid design.

The fix is small. The exposure was total.

The repair is per-sandbox keys. Derive a unique secret for each container from its immutable site id. The server signs with that same per-site key when it mints a link. Now reading your own key buys you nothing against anyone else. Rotate the shared secret too, because it is burned. And add some real randomness to the sandbox names while you are at it.

Keep the good verification code. Change only the key. That one move closes the whole thing.

This was authorized and coordinated
I tested with the owner’s permission. Everything I did was read-only and confined to sandboxes. I wrote the whole thing up and sent it over. The owner was responsive and confirmed the finding.

So two things are true at the same time. SandyWP is a great tool that I will keep using. And for a few hours Claude Code handed me the keys to every sandbox on it. 🤣

This was a fun one.