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.
Wondering if your code has any of this? Scan a public repo free, no account needed.
Scan a repo →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" } // NextAuthWhy 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.