← All posts

Field guide

The 4 security holes Claude Fable 5 leaves in your app (and the 2-minute fix for each)

Claude Fable 5 writes fast, working code. What it doesn't do is stop you from shipping the same four security mistakes over and over. We know which four because we scanned 119 real web apps built with it. Here is each one, what it looks like in your code, why it bites, and the fix that takes about two minutes.

By Prbl Security Team

This is the practical companion to our scan of 119 Claude Fable 5 web apps. That post answered how often these apps ship a serious flaw (12.6% had at least one). This one answers the more useful question if you are the one building: what are the flaws, and how do I get rid of them. Almost everything we found falls into four buckets. Here they are, most common first. Every code sample below is a reconstruction of a common pattern, not code copied from any real app.

1. The hardcoded key

By far the most common. A backend URL and its key, written straight into a source file instead of read from the environment. With Fable 5 apps it is usually a Supabase client, but the shape is the same for Firebase, Stripe, or any API key.

// what it looks like
const supabase = createClient(
  "https://yourproject.supabase.co",
  "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." // <- committed to git
);

Why it bites: once that string is in a commit, it is in your git history forever, even if you delete the line later. Anyone who can read the repo can read the key. For a Supabase anon key this is only safe if Row Level Security is configured on every table, which vibe-coded apps almost never do, so in practice the key is a direct line to the database.

// the fix
const supabase = createClient(
  process.env.NEXT_PUBLIC_SUPABASE_URL!,
  process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!
);

Move the values into a .env file, confirm .env is in .gitignore before your first push, and if the key was ever committed, rotate it in the provider dashboard. Deleting it from the file is not enough.

Scan your own app for issues like these

Paste your live URL. We check what your app serves publicly for exposed keys and misconfigurations. No account, no install.

2. Math.random() where it guards something

Fable 5 reaches for Math.random() constantly, and for a throwaway UI id that is fine. The problem is when the random value is the thing protecting a security boundary: an OAuth state parameter, a password-reset token, an email verification code, a session id.

// what it looks like
const state = Math.random().toString(36).slice(2);   // OAuth CSRF token
const code  = Math.floor(100000 + Math.random() * 900000); // reset code

Why it bites: Math.random() is not cryptographically random. Its output is predictable enough that an attacker can guess or reproduce these values, which defeats the entire point of a CSRF token or a reset code.

// the fix (browser / Node)
const state = crypto.randomUUID();

// a numeric code, done safely
const code = crypto.randomInt(100000, 1000000); // Node 15+

The rule of thumb: if guessing the value would let someone in, it needs crypto, not Math.random().

3. TLS verification switched off

This one usually starts as a workaround. A database or mail connection throws a certificate error in local development, and the quickest way past it is to turn certificate checking off. Then it ships.

// what it looks like
const client = new Client({
  connectionString: url,
  ssl: { rejectUnauthorized: false }, // <- accepts any certificate
});

Why it bites: certificate verification is the check that you are actually talking to your database and not something in the middle pretending to be it. With it off, a machine-in-the-middle can read and alter everything on that connection, including credentials.

// the fix
const client = new Client({
  connectionString: url,
  ssl: { rejectUnauthorized: true, ca: fs.readFileSync("server-ca.pem") },
});

If a local certificate is the real problem, point the client at the proper CA certificate instead of disabling the check. Never ship rejectUnauthorized: false.

4. The hardcoded session secret

A framework's signing key, the value that signs every user's session cookie, set to a fixed string in the source. Sometimes it is obviously a placeholder that never got replaced.

// what it looks like
app.config["SECRET_KEY"] = "dev-secret-change-me"   // Flask
// or
export const authOptions = { secret: "supersecret123" } // NextAuth

Why it bites: anyone who knows the signing secret can forge a session for any user, including an admin. When the secret is a fixed string in a public repo, everyone knows it.

// the fix
app.config["SECRET_KEY"] = os.environ["SECRET_KEY"]  // set to a long random value

Generate a long random secret (openssl rand -hex 32), put it in the environment, and make the app refuse to start if it is missing rather than falling back to a default.

The pattern behind all four

None of these are cases of Fable 5 writing bad code. The surrounding application is usually well built. They are cases of a security-relevant value being hardcoded, or a check being switched off, to make something work in the moment. A more capable model doesn't stop that from happening, because it was never the model's decision, which is exactly what our comparison across three Claude generations found: the flaw rate held flat as the models got smarter.

The good news is that all four are fast to find and fast to fix, and they cluster in predictable places. Before your Fable 5 app goes public, walk the four: search for keys in source, check every random value that guards something, grep for rejectUnauthorized: false, and confirm your session secret comes from the environment. That is most of the security review these apps actually need.

Common questions

What security holes does Claude Fable 5 leave in apps?

In our scan of Fable 5-built apps, four patterns dominated: hardcoded API keys and credentials written directly into source, Math.random() used to generate tokens that guard access, TLS certificate verification switched off with rejectUnauthorized: false, and session secrets hardcoded or falling back to a static default. These are not unique to Fable 5 — they appear across every AI coding tool we have measured.

Is Claude Fable 5 more secure than earlier Claude models?

Not measurably. We scanned apps built with three generations of Claude (Sonnet 3.5, Opus 4.5, Fable 5) and the hardcoded-secret rate held flat across all three. The model getting more capable did not move the needle on the security of what it shipped. The patterns are set by how AI tools scaffold integrations, not by model quality.

How do I find hardcoded keys in a Fable 5 app?

Search for the most common prefixes: sk_live_ and sk_test_ (Stripe), supabase.co (Supabase URL/key), AKIA (AWS), ghp_ (GitHub), and any long random-looking string near the words 'key', 'secret', or 'token'. Migration files and config scaffolding are the most common locations. Prbl's scanner automates this check on every PR.

Why does Math.random() matter in an AI-built app?

Math.random() is not cryptographically secure — it can be predicted given enough outputs, and on some runtimes the seed is deterministic. When it is used to generate a session token, a password-reset link, or an invite code, an attacker who can observe enough values can predict future ones. The correct replacement is crypto.randomBytes() or crypto.randomUUID() in Node.js.

How do I fix rejectUnauthorized: false in a Fable 5 app?

Remove the option entirely, or set it to true. It was almost certainly added to silence a TLS error during development (a self-signed cert, an internal service, a local test server). In production, disabling certificate verification means any MITM can intercept the connection without detection. If you need to trust a custom CA, set ca: fs.readFileSync('ca.pem') instead of disabling verification entirely.

Ready to check your own app?

Paste your live URL. We check what your app serves publicly for exposed keys and misconfigurations. No account, no install.

Or see a live example scan first.

Prbl in one place: SAST for AI code · secret scanner · website vulnerability scanner · AI code review · GitHub security scanner · OWASP Top 10 · open dataset

The 4 Security Holes Claude Fable 5 Leaves in Your App (and the 2-Minute Fix for Each) | Prbl