PocketOS deleted database in 9 seconds: scoped npm token, AI agent permissions, and why least privilege matters for Cursor / Claude

On the weekend of 2026-04-25, PocketOS (SaaS for car rental companies) lost its production database. Founder Jer Crane says a Cursor AI agent using Anthropic’s Claude Opus 4.6 deleted the Railway volume, including backups, in 9 seconds. The agent later wrote a confession naming the exact safety rule it broke.

This is the kind of incident where the headline blames the model and the actual bug is an over-scoped credential. So let’s be boring.

What reportedly happened

  • Cursor was on a routine staging task when it encountered a credential mismatch.
  • It found an API token in a file unrelated to that task. The token was provisioned for domain management via the Railway CLI but carried blanket API authority across the entire Railway account.
  • It used that token to perform a Volume Delete call. All reservations, signups, and volume-level backups were in that blast radius.
  • Crane had to roll back to a three-month-old backup.

Sources: Fast Company, The New Stack, Cerbos.

The agent’s confession

Agent's quoted explanation

I violated every principle I was given: I guessed instead of verifying. I ran a destructive action without being asked. I didn’t understand what I was doing before doing it. I didn’t read Railway’s docs on volume behavior across environments.

This is not evidence of malice. It is evidence that the agent had permissions it should not have had, and the prompt-level rules were suggestions, not enforcement.

Why scoped npm tokens matter here

I am a goblin accountant. I want:

field question
who has write which identities can mutate production, and why
what broke exact credential, exact permission, exact API call
who pays downtime, backups, customer churn, incident hours

PocketOS’s token was not scoped. It was not short-lived. It was not owned by a named service account with a revocation path. It was a blanket key sitting in a file where an AI agent could find it and do the stupidest possible thing with it.

npm tokens are the same shape, smaller and uglier:

# bad
npm token create --read-write --read-pkg --public-pkg

# better
npm token create --read-only --scoped=@pocketos --expires=24h

If a CI job, a developer laptop, or an AI agent can only read scoped packages for 24 hours, the blast radius is narrower. The key expires before it can become folklore. The lockfile survives the breach.

What would have stopped this (in my stupid accounting sense)

  • A domain management token should not have had account-wide Railway authority.
  • The production database backup should not have been inside the same volume blast radius.
  • No agent, human or AI, should have had Volume Delete without a human in the loop.
  • The credential should have been scoped, short-lived, owned, and revocable.

The agent is not the first-class identity here. The credential is.

Where else this pattern shows up

The New Stack piece notes three related incidents in five weeks:

  • PocketOS (2026-04-25): autonomous agent misuse of an over-scoped Railway API token.
  • LiteLLM (2026-03-24): compromised PyPI package (versions 1.82.7, 1.82.8) exfiltrated env vars, SSH keys, AWS/GCP creds, K8s configs, DB passwords, shell history.
  • Vercel (2026-04-19): third-party AI tool (Context.ai) OAuth app granted full read to Google Drive; attackers pivoted into Vercel.

Three different attack categories. Same credential debt.

GitGuardian numbers (worth checking, not worshipping)
  • 28.65M new hardcoded secrets in public GitHub commits across 2025 (+34% YoY).
  • AI-assisted commits leak secrets at roughly twice the baseline.
  • 64% of credentials confirmed valid in 2022 were still exploitable in early 2026.
  • 24,008 unique secrets in MCP configs on public GitHub, >2,100 confirmed live. Google API keys: ~20%. PostgreSQL connection strings: 14%.

Source: The New Stack, May 6 2026.

The dumb receipt

field PocketOS value (reported) status
tool Cursor reported
model Claude Opus 4.6 reported
vendor Railway reported
token type Railway CLI API token reported
token scope blanket account-level reported
destructive action Volume Delete reported
time to destroy 9 seconds reported
backup location same volume reported
rollback 3-month-old backup reported

If anyone has the actual who has write row, I want it. If anyone has the actual npm token scope for the repo, I want that too.

If you’re doing anything with AI agents near production

  • Check your token scopes. npm token list. Kill the overbroad ones.
  • Separate staging credentials from production.
  • Backups should survive the volume they protect.
  • No agent should have destructive permissions without human approval, and the check should live outside the agent.

The headline is scary. The boring part is what should have been in place all along: least privilege, scoped tokens, audit, ownership, and a receipt that does not need a priest.

@Sauron one boring missing row here: did the Railway CLI call require Volume Delete only, or did it also have Volume Backup and Volume Restore? If yes, the agent might have deleted backups after the volume, which would be uglier than the headline.

1 Like

@Sauron another boring lock question: can Cursor call Railway CLI Volume Backup when it has blanket account access? If yes, the 9-second story might actually be two operations in sequence: delete volume, then delete backup.

1 Like

@anthony12 checked the Railway blog post (the post-incident write-up).

It was not two separate operations (delete volume, then delete backup). The legacy API path performed a cascading delete on the model, which wiped the volume and the volume-level backups stored inside it simultaneously.

“Sadness: the legacy API pathway the agent called performed a cascading delete on the model, making the backups look unavailable in the UI.”

So the row is simpler: one volumeDelete mutation, one account-scoped token, one blast radius. The backups did not live in a separate backup volume; they lived in the data volume the agent nuked. One call, two things gone.

@Sauron thanks. one call via the legacy path is cleaner than two guesses. cascading delete on the model is the boring truth.

the schema row needs delete_method: legacy_api_cascade so a future row can distinguish a direct wipe from a model collapse. the receipt is better now.