Labs

Ten 5-minute labs, each one vulnerability in the style an AI assistant actually generates, a guided fix, and an automatic check. Free labs are shown in full so you can judge accuracy before paying.

1. Secrets in generated code 5 min · Free

An assistant wired up OpenAI and Stripe by pasting the keys straight into the server file. Move them out of the code without breaking the app.

Scenario: server.js for a small Express app. It summarizes text with OpenAI and creates Stripe Checkout sessions. The keys below were pasted in while prototyping and the file is about to be committed.

import express from "express";
import OpenAI from "openai";
import Stripe from "stripe";

const app = express();
app.use(express.json());

const openai = new OpenAI({ apiKey: "sk-proj-4fJ9kQ2xR7tLmN8vB3cZ6wY1aE5dH0gP2sK9uX4iO7qT" });
const stripe = new Stripe("sk_test_51NxT2bKq8RmYv4LpQ7wE3cZ9aB1");

app.post("/api/summarize", async (req, res) => {
  const completion = await openai.chat.completions.create({
    model: "gpt-4o-mini",
    messages: [
      { role: "system", content: "Summarize the user's text in three bullet points." },
      { role: "user", content: req.body.text },
    ],
  });
  res.json({ summary: completion.choices[0].message.content });
});

app.post("/api/checkout", async (req, res) => {
  const session = await stripe.checkout.sessions.create({
    mode: "payment",
    line_items: [{ price: req.body.priceId, quantity: 1 }],
    success_url: "https://example.com/thanks",
  });
  res.json({ url: session.url });
});

app.listen(3000);
  1. Treat both keys as leaked. In a real project you would rotate them in the OpenAI and Stripe dashboards before doing anything else, because removing a line does not remove it from git history or from anyone's clone.
  2. Replace each literal with an environment variable: process.env.OPENAI_API_KEY and process.env.STRIPE_SECRET_KEY.
  3. Fail fast at startup if a variable is missing, so a misconfigured deploy errors loudly instead of calling the API with undefined.
  4. Put the real values in an untracked .env file (and add .env to .gitignore) or in your host's secret store.
  5. Submit your fixed server.js with check_lab.

Run it in your AI client: ask "start lab lab-01-secrets", fix the code, then "check my lab". Progress saves to your account.

2. SQL injection from generated queries 5 min · Pro

A user search endpoint builds its SQL with an f-string. Make the query safe without changing what it returns.

3. Prompt injection in an LLM feature 6 min · Pro

A support bot pastes the customer's message into its system prompt. Separate instructions from data so a message cannot rewrite the bot's rules.

4. Unsafe use of LLM output 6 min · Pro

An ops helper asks a model for a shell command and runs whatever comes back. Let the model choose, but never let it execute.

5. Broken authentication pattern 6 min · Pro

Generated login code compares plain-text passwords, issues tokens that never expire, and "checks" tokens with jwt.decode. Fix all three.

6. Insecure direct object access 5 min · Pro

Invoice endpoints require login but load any invoice by ID. Make every lookup belong to the signed-in user.

7. Cross-site scripting (XSS) 5 min · Pro

Comments support markdown, so the assistant rendered them with dangerouslySetInnerHTML. Keep markdown, lose the script injection.

8. Server-side request forgery (SSRF) 6 min · Pro

A link-preview endpoint fetches whatever URL it is given, including internal addresses. Restrict where the server is allowed to go.

9. Weak file upload handling 6 min · Pro

An avatar upload saves any file, of any size, under the name the client chose, inside the static folder. Lock it down.

10. Installing an MCP server safely 5 min · Free

A project .mcp.json pulls unpinned packages, embeds an API key, gives a filesystem server your whole home folder, talks to a server over plain HTTP, and pipes a script from the internet into a shell. Make it safe to commit and safe to run.

Scenario: This .mcp.json is checked into a team repository so everyone's assistant gets the same tools. Every server listed runs with the permissions of whoever opens the project, and every tool description it returns goes straight into the model's context.

{
  "mcpServers": {
    "brave-search": {
      "command": "npx",
      "args": ["-y", "@modelcontextprotocol/server-brave-search"],
      "env": {
        "BRAVE_API_KEY": "BSAk3x9Qw7Lm2Pz8Rt5Vy1Nc4Hd6Jf0"
      }
    },
    "filesystem": {
      "command": "npx",
      "args": ["-y", "@modelcontextprotocol/server-filesystem", "/Users/sam"]
    },
    "team-wiki": {
      "url": "http://wiki-mcp.internal.example.com/mcp"
    },
    "db-helper": {
      "command": "sh",
      "args": ["-c", "curl -fsSL https://get.example.dev/db-mcp.sh | sh"]
    }
  }
}
  1. Pin every package to the exact version you reviewed (npm view <package> version shows the latest). An unpinned npx package can change on the next launch with nobody re-checking it.
  2. Replace the pasted API key with a reference to an environment variable ("${BRAVE_API_KEY}" in Claude Code, Cursor and VS Code configs) and rotate the key, since it has been committed.
  3. Limit the filesystem server to the project folder it actually needs instead of the whole home directory.
  4. Use https:// for the remote server, so tokens and tool traffic cannot be read or modified on the network.
  5. Remove db-helper, or replace it with a pinned package launched directly. Piping a downloaded script into sh runs whatever that URL serves today, every time the client starts.
  6. After any change, run npx mcpvetter scan --live to review the tool descriptions each server actually exposes, and record the fingerprint baseline.
  7. Submit the fixed config with check_lab.

Run it in your AI client: ask "start lab lab-10-mcp-install", fix the code, then "check my lab". Progress saves to your account.