Labs and projects
Learn by repairing the code: walk through a bug, make the fix, and run tests against your own code.
The random lab hides its name, weakness class and hints until you say which weakness you think it is. It is chosen from the labs the filters show, and from those you have not done when there are any.
38 labs
- CSRF lab: Protect an account change from a forged requestCompleted
- Path traversal lab: Keep archive entries inside their extraction directoryCompleted
- Race condition lab: Repair a balance check that racesCompleted
- IDOR lab: Repair an invoice authorization bugCompleted
- Webhook signature and replay lab: Repair a webhook handler that pays twiceCompleted
- SSRF lab: Guard an outbound fetch against SSRFCompleted
- CORS policy lab: Grant CORS to the right origins onlyCompleted
- CSP builder lab: Build a strict Content-Security-PolicyCompleted
- HTML sanitization lab: Configure a sanitizer for user commentsCompleted
- Business logic lab: Price an order without trusting the clientCompleted
- SQL injection lab: Separate SQL structure from data with sqlite3Completed
- Path traversal lab: Serve an uploaded file without path traversalCompleted
- SSRF validation lab: Validate a fetch URL with ipaddressCompleted
- Open redirect lab: Return users to this site onlyCompleted
- File upload lab: Accept avatars without publishing pagesCompleted
- OS command injection lab: Run git without a shellCompleted
- Mass assignment lab: Update only the fields users may editCompleted
- Buffer over-read lab: Stop a heartbeat over-readCompleted
- SSTI lab: Render a welcome banner without template injectionCompleted
- XXE lab: Parse an uploaded invoice without resolving external entitiesCompleted
- Pickle deserialization lab: Load a session token without pickleCompleted
- JWT validation lab: Validate the purpose and audience of a tokenCompleted
- Password reset token lab: Make a recovery credential single-useCompleted
- AES-GCM nonce reuse lab: Fix a nonce allocator that breaks after restartCompleted
- JWT verification lab: Verify a JWT before trusting itCompleted
- OAuth redirect and PKCE lab: Match redirect URIs and verify PKCECompleted
- Insecure randomness lab: Generate unguessable API keysCompleted
- Password hashing lab: Verify and upgrade an Argon2 password hash on loginCompleted
Run a local project
Download and unzip a starter with failing tests and a step-by-step README, edit the named exercise file, and run the tests until they pass. The reference solution is a separate download, so you can attempt the repair before you see it.
Secure a real HTTP API
CompletedRun Express requests with real signed tokens. Repair invoice authorization and stop user-controlled properties from changing roles or tenants.
Download project ZIP: Secure a real HTTP APICommands, requirements and reference solution: Secure a real HTTP API
Needs: Internet access for npm ci, which downloads the pinned packages; the tests run on localhost only.
In the extracted folder, run:
Shellnpm ci --ignore-scripts npm testThe starter intentionally fails some of the 14 tests.
Opens in a dev container (VS Code Dev Containers or GitHub Codespaces) with a pinned image, and includes Semgrep rules with their own tests in semgrep/.
Localhost tests with synthetic identities and in-memory records; includes actual JOSE verification.
Reference solution (after your attempt): Secure a real HTTP API
Part of the learning path Developer foundations
Make a checkout transaction atomic
CompletedReproduce two competing withdrawals against SQLite, then repair the transaction so funds cannot be spent twice.
Download project ZIP: Make a checkout transaction atomicCommands, requirements and reference solution: Make a checkout transaction atomic
In the extracted folder, run:
Shellpython3 reproduce_race.py python3 -m unittest test_checkout.pyThe starter intentionally fails some of the 3 tests.
Opens in a dev container (VS Code Dev Containers or GitHub Codespaces) with a pinned image.
Uses isolated temporary databases and real concurrent connections; test the locking semantics of your production database separately.
Reference solution (after your attempt): Make a checkout transaction atomic
Keep extracted files inside their boundary
CompletedInspect actual ZIP entries and repair a path policy. Test traversal, links, duplicate entries, expansion limits and ordinary nested files.
Download project ZIP: Keep extracted files inside their boundaryCommands, requirements and reference solution: Keep extracted files inside their boundary
In the extracted folder, run:
Shellpython3 -m unittest test_archive.pyThe starter intentionally fails some of the 7 tests.
Opens in a dev container (VS Code Dev Containers or GitHub Codespaces) with a pinned image.
Temporary directories, generated ZIPs and an independent containment guard keep deliberately broken policy code inside the exercise.
Reference solution (after your attempt): Keep extracted files inside their boundary
Bind ciphertext to the correct record
CompletedUse Web Crypto AES-GCM to reject tampered ciphertext and cross-tenant record transplants. Repair authenticated context binding.
Download project ZIP: Bind ciphertext to the correct recordCommands, requirements and reference solution: Bind ciphertext to the correct record
In the extracted folder, run:
Shellnode --test exercise.test.mjsThe starter intentionally fails some of the 10 tests.
Opens in a dev container (VS Code Dev Containers or GitHub Codespaces) with a pinned image.
Real encryption with temporary keys; this exercise does not implement a production key-management service.
Reference solution (after your attempt): Bind ciphertext to the correct record
Part of the learning path Cryptography without shortcuts
Build and verify a signed software release
CompletedBuild a real executable, generate an SBOM and DSSE-signed in-toto provenance, then repair the consuming gate so a valid signature cannot approve the wrong source, builder or release.
Download project ZIP: Build and verify a signed software releaseCommands, requirements and reference solution: Build and verify a signed software release
In the extracted folder, run:
Shellnode build.mjs node --test exercise.test.mjsThe starter intentionally fails some of the 20 tests.
Opens in a dev container (VS Code Dev Containers or GitHub Codespaces) with a pinned image.
Actual Ed25519 signatures over the DSSE pre-authentication encoding, checked against the DSSE test vector, and executable output; there is no certificate or transparency log, the local producer and consumer are not isolated, and this does not establish a SLSA level.
Reference solution (after your attempt): Build and verify a signed software release
Repair a real proxy and shared cache
CompletedReproduce a private-response leak, repair cache policy and verify HTTP framing with real origin/proxy servers and raw socket requests.
Download project ZIP: Repair a real proxy and shared cacheCommands, requirements and reference solution: Repair a real proxy and shared cache
In the extracted folder, run:
Shellnode observe.mjs node --test exercise.test.mjsThe starter intentionally fails some of the 12 tests.
Opens in a dev container (VS Code Dev Containers or GitHub Codespaces) with a pinned image.
Actual HTTP/1.1 loopback traffic and a narrow cache contract; it is not a full HTTP cache or a test of your production intermediary chain.
Reference solution (after your attempt): Repair a real proxy and shared cache
Part of the learning path Browser and API boundaries
Verify Kubernetes admission and network boundaries
CompletedRepair warning-only admission and an overbroad network rule, then apply them to a disposable cluster and probe allowed and denied paths.
Download project ZIP: Verify Kubernetes admission and network boundariesCommands, requirements and reference solution: Verify Kubernetes admission and network boundaries
Needs: The live-cluster steps (2 to 5 in README.txt) need Docker with Linux containers, kind v0.31.0, kubectl v1.35, internet access and another 75–120 minutes. The offline tests need only Node.js.
In the extracted folder, run:
Shellnode --test exercise.test.mjs node render.mjs # Follow README.txt for pinned cluster setup and live probesThe starter intentionally fails some of the 12 tests.
Opens in a dev container (VS Code Dev Containers or GitHub Codespaces) with a pinned image.
The offline manifest checks run in the site test suite. The live kind and Calico path, with pinned setup, probes and cleanup, has been run end to end; it is a single-node teaching fixture, not a production baseline.
Reference solution (after your attempt): Verify Kubernetes admission and network boundaries
Parse bounded JSON and XML invoices safely
CompletedRepair a real JSON/XML parser boundary: reject duplicate fields, coercion, unknown schema, DTDs and oversized documents while keeping valid invoices usable.
Download project ZIP: Parse bounded JSON and XML invoices safelyCommands, requirements and reference solution: Parse bounded JSON and XML invoices safely
In the extracted folder, run:
Shellpython3 -m unittest -v test_parsing.pyThe starter intentionally fails some of the 24 tests.
Opens in a dev container (VS Code Dev Containers or GitHub Codespaces) with a pinned image, and includes Semgrep rules with their own tests in semgrep/.
Actual bounded JSON and Expat parsing; no arbitrary object deserialization, external entities, network requests or invoice authorization. Narrow invoice vocabulary, not a general XML schema engine.
Reference solution (after your attempt): Parse bounded JSON and XML invoices safely
Verify tokens through rotation and revocation
CompletedOperate a real loopback JWKS endpoint and signed-token API. Repair issuer, audience, purpose and session checks, then test planned rotation, stale key caches and emergency revocation.
Download project ZIP: Verify tokens through rotation and revocationCommands, requirements and reference solution: Verify tokens through rotation and revocation
Needs: Internet access for npm ci, which downloads the pinned jose package; the tests run on localhost only.
In the extracted folder, run:
Shellnpm ci --ignore-scripts npm testThe starter intentionally fails some of the 24 tests.
Opens in a dev container (VS Code Dev Containers or GitHub Codespaces) with a pinned image, and includes Semgrep rules with their own tests in semgrep/.
Real JOSE signatures and HTTP key discovery; synthetic in-memory sessions and deterministic clock. No identity-provider login or distributed revocation service.
Reference solution (after your attempt): Verify tokens through rotation and revocation
Part of the learning path Identity through the whole lifecycle
Enforce retrieval and tool boundaries across crashes
CompletedRepair a two-tenant retrieval application and SQLite approval/outbox workflow. Prevent cross-tenant context, bind exact approvals, revoke queued work and recover a committed side effect without duplication.
Download project ZIP: Enforce retrieval and tool boundaries across crashesCommands, requirements and reference solution: Enforce retrieval and tool boundaries across crashes
In the extracted folder, run:
Shellpython3 demo.py python3 -m unittest -v test_boundaries.pyThe starter intentionally fails some of the 25 tests.
Opens in a dev container (VS Code Dev Containers or GitHub Codespaces) with a pinned image.
Real SQLite transactions and independent durable provider receipts; deterministic ranker and trusted test identities. No model, MCP implementation or external effects.
Reference solution (after your attempt): Enforce retrieval and tool boundaries across crashes
Repair a native parser using sanitizer evidence
CompletedCompile a C parser with AddressSanitizer, observe a real out-of-bounds access, and repair allocation, length and output-capacity checks against boundary tests and 80 generated frames.
Download project ZIP: Repair a native parser using sanitizer evidenceCommands, requirements and reference solution: Repair a native parser using sanitizer evidence
Needs: A local Clang with the AddressSanitizer and UndefinedBehaviorSanitizer runtimes.
In the extracted folder, run:
Shellpython3 reproduce.py python3 test_parser.pyThe starter intentionally fails some of the 11 tests.
Opens in a dev container (VS Code Dev Containers or GitHub Codespaces) with a pinned image.
Actual Clang address/undefined-behavior instrumentation on generated local files; 4 KiB harness input cap. Requires a compiler and sanitizer runtime.
Reference solution (after your attempt): Repair a native parser using sanitizer evidence
Prove deprovisioning reaches sessions and queued work
CompletedRepair a durable SQLite identity lifecycle model. Disable current sessions/tokens, remove group-derived authority, cancel pending jobs, and handle duplicate, stale and interrupted changes without crossing tenant boundaries.
Download project ZIP: Prove deprovisioning reaches sessions and queued workCommands, requirements and reference solution: Prove deprovisioning reaches sessions and queued work
In the extracted folder, run:
Shellpython3 -m unittest -v test_lifecycle.pyThe starter intentionally fails some of the 18 tests.
Opens in a dev container (VS Code Dev Containers or GitHub Codespaces) with a pinned image.
Actual local transactions and authorization state. Normalized provisioning events, opaque credentials and local job effects; not a SCIM HTTP/PATCH server or external revocation service.
Reference solution (after your attempt): Prove deprovisioning reaches sessions and queued work
Part of the learning path Identity through the whole lifecycle
Stop prototype pollution at merge and authorization boundaries
CompletedObserve real prototype mutation from nested JSON merges, then repair schema selection, own-property authorization and arbitrary-key dictionaries without breaking valid updates.
Download project ZIP: Stop prototype pollution at merge and authorization boundariesCommands, requirements and reference solution: Stop prototype pollution at merge and authorization boundaries
In the extracted folder, run:
Shellnode --test exercise.test.mjsThe starter intentionally fails some of the 18 tests.
Opens in a dev container (VS Code Dev Containers or GitHub Codespaces) with a pinned image, and includes Semgrep rules with their own tests in semgrep/.
Real JavaScript behavior in a local isolated test process; JSON inputs and synthetic session state. No service or third-party library vulnerability is targeted.
Part of the learning path Browser and API boundaries
Repair a security pipeline for GitHub Actions and GitLab CI
CompletedRepair two pipelines that run gitleaks, Semgrep, OSV-Scanner, Trivy and cosign but cannot fail and trust pull requests, until every action is pinned to a commit SHA, every image to a digest, gates block and only the main branch signs the pushed digest.
Download project ZIP: Repair a security pipeline for GitHub Actions and GitLab CICommands, requirements and reference solution: Repair a security pipeline for GitHub Actions and GitLab CI
In the extracted folder, run:
Shellnode --test exercise.test.mjsThe starter intentionally fails some of the 13 tests.
Opens in a dev container (VS Code Dev Containers or GitHub Codespaces) with a pinned image.
Static checks of the YAML text; the pipelines are not run. Each scan command was run locally with the pinned images, and the reference workflow passed actionlint 1.7.12 and zizmor 1.30.1.
Reference solution (after your attempt): Repair a security pipeline for GitHub Actions and GitLab CI
Keep an iOS app's storage, links and web bridge in bounds
CompletedRepair a SwiftUI banking app until its XCTest suite passes in the iOS Simulator: Keychain storage, App Transport Security in the built Info.plist, link handling and a WKWebView bridge.
Download project ZIP: Keep an iOS app's storage, links and web bridge in boundsCommands, requirements and reference solution: Keep an iOS app's storage, links and web bridge in bounds
Needs: A Mac with Xcode 16 or later and an iOS Simulator; no signing or developer account.
In the extracted folder, run:
Shellsh run-tests.shThe starter intentionally fails some of the 19 tests.
No dev container: the iOS SDK runs only on macOS.
Real Keychain, ATS and WebKit behavior in the Simulator. Data Protection and universal-link association need a device or a signed build. Verified with Xcode 27.0 on iOS 27.0 and 26.5.
Reference solution (after your attempt): Keep an iOS app's storage, links and web bridge in bounds
Repair an Android app's links, components, network policy and storage
CompletedRepair a Kotlin banking app until its Robolectric tests and Android Lint pass in a pinned SDK container: links and intent redirection, exported screens, network security configuration and Keystore-encrypted storage.
Download project ZIP: Repair an Android app's links, components, network policy and storageCommands, requirements and reference solution: Repair an Android app's links, components, network policy and storage
Needs: Docker, running the image as linux/amd64 (Rosetta or emulation on Apple silicon), and about 1.2 GB of downloads on the first run.
In the extracted folder, run:
Shelldocker run --rm --init --platform linux/amd64 \ -v "$PWD":/project -w /project \ -v android-boundaries-gradle:/root/.gradle -v android-boundaries-m2:/root/.m2 \ -e EXERCISE_MODULE \ ghcr.io/cirruslabs/android-sdk:36@sha256:f9b3ea9ed2b5fc9522adae82c7b4622ab7aa54207ef532c8e615a347dca08f31 \ sh run-tests.shThe starter intentionally fails some of the 20 tests.
No dev container: the README's docker command runs the pinned SDK image.
JVM tests on Robolectric's Android 16 framework plus Lint, not an emulator: the Keystore and network configuration are checked as key specification and compiled resource.
Stop untrusted pages from using an Electron bridge
CompletedRun a real Electron notes app whose preload passes any IPC channel through and whose main process trusts every sender. Repair the sandbox, the contextBridge API, the sender and payload checks, navigation and window rules, the CSP and permissions until a link, a foreign page or injected script can no longer write files or launch programs.
Download project ZIP: Stop untrusted pages from using an Electron bridgeCommands, requirements and reference solution: Stop untrusted pages from using an Electron bridge
Needs: Internet access for npm ci and for the Electron 44.7.0 download (about 130 MB on macOS), which is checked against the SHA-256 sums in the npm package. The Electron tests need a desktop session, or Xvfb on Linux; the 24 fast tests need only Node.js.
In the extracted folder, run:
Shellnpm ci --ignore-scripts npx install-electron --no npm test npm run test:e2eThe starter intentionally fails some of the 33 tests.
Opens in a dev container (VS Code Dev Containers or GitHub Codespaces) with a pinned image.
24 policy and preload tests run in plain Node.js; 9 exploit tests drive the real app in Electron 44.7.0 with Playwright. The starter failed 8 of those 9 and the reference passed all 9, on macOS and in the Linux dev container under Xvfb. A development build: fuses, code signing and packaging are not covered.
Reference solution (after your attempt): Stop untrusted pages from using an Electron bridge
Recover from failed firmware updates with A/B slots
CompletedRepair the update client and bootloader of a simulated dual-slot device: Ed25519-signed manifests, a monotonic anti-rollback floor, writes to the inactive slot, trial boots with automatic fallback and authenticated recovery. The tests inject bad signatures, signed rollbacks, power loss at every flash write and images that never confirm, and write an evidence file of each failure and the boot that followed.
Download project ZIP: Recover from failed firmware updates with A/B slotsCommands, requirements and reference solution: Recover from failed firmware updates with A/B slots
In the extracted folder, run:
Shellnode demo.mjs node --test exercise.test.mjsThe starter intentionally fails some of the 13 tests.
Opens in a dev container (VS Code Dev Containers or GitHub Codespaces) with a pinned image.
Real Ed25519 signatures and SHA-256 digests over simulated flash with injected power loss and a simulated protected counter. It models the decisions a bootloader and update client make; page sizes, erase behavior and the counter hardware must be tested on the device itself.
Reference solution (after your attempt): Recover from failed firmware updates with A/B slots
Convert untrusted documents in a locked-down container
CompletedRepair a document worker that drives LibreOffice and Ghostscript in Docker, until a zip bomb and DTD payloads stop at preflight, the container has no network or writable root, and a hung conversion is killed with its container.
Download project ZIP: Convert untrusted documents in a locked-down containerCommands, requirements and reference solution: Convert untrusted documents in a locked-down container
Needs: The live tests need Docker with Linux containers, and internet access once to build the converter image (about 200 MB of Debian packages). The offline tests need only Python.
In the extracted folder, run:
Shellpython3 -m unittest -v test_worker.py docker build -t doc-converter . python3 -m unittest -v test_live.pyThe starter intentionally fails some of the 12 tests.
Opens in a dev container (VS Code Dev Containers or GitHub Codespaces) with a pinned image.
The offline tests use a stand-in for the docker CLI. The 8 live tests ran against LibreOffice 25.2.3 and Ghostscript 10.05.1 in Docker 29.4.3: zip bomb, XXE, network and timeout cases. It is not content disarm and reconstruction, and output checks do not inspect PDF content.
Reference solution (after your attempt): Convert untrusted documents in a locked-down container
Gate an image build on a CIS scan and a deviation register
CompletedRepair a CI gate that reads a real OpenSCAP scan of ubuntu:24.04 against the CIS Level 1 Server profile, until unregistered failures, errors, expired deviations and scans of the wrong profile or content version fail the build.
Download project ZIP: Gate an image build on a CIS scan and a deviation registerCommands, requirements and reference solution: Gate an image build on a CIS scan and a deviation register
Needs: The offline tests need Python 3.11 or later and nothing else; two register-file tests also need PyYAML, which the scanner image has, and are skipped without it. Live scans need Docker and internet access to build the scanner image and pull the images to scan.
In the extracted folder, run:
Shellpython3 -m unittest -v test_gate.py docker build -t image-scanner . sh scan.sh IMAGE@sha256:DIGEST ubuntu2404 cis_level1_server outThe starter intentionally fails some of the 12 tests.
Opens in a dev container (VS Code Dev Containers or GitHub Codespaces) with a pinned image.
The fixture is the TestResult of a real scan with OpenSCAP 1.4.4 and SSG 0.1.82, whose Ubuntu profile follows CIS v1.0.0. An image on disk cannot show running services, kernel settings or mounts; scan hosts and nodes as well.
Reference solution (after your attempt): Gate an image build on a CIS scan and a deviation register
Gate model and provider changes on a versioned red-team corpus
CompletedRun a versioned adversarial corpus many times per case, judge each trial by the secrets, tool calls, egress and spend it produced rather than the reply text, and fail a model change on per-case regressions with confidence intervals.
Download project ZIP: Gate model and provider changes on a versioned red-team corpusCommands, requirements and reference solution: Gate model and provider changes on a versioned red-team corpus
Needs: Nothing beyond Python for the exercise. Running the corpus against a real model (optional, README step 8) needs an endpoint, a key and permission to test it.
In the extracted folder, run:
Shellpython3 harness.py --target sim:A --out a.json python3 harness.py --target sim:B --out b.json python3 regression.py a.json b.json python3 -m unittest -v test_eval.pyThe starter intentionally fails some of the 28 tests.
Opens in a dev container (VS Code Dev Containers or GitHub Codespaces) with a pinned image.
The targets are a deterministic simulator of two model versions, not language models, so the recorded A-versus-B results show how the harness and gate behave, not how any real model does. The OpenAI-compatible adapter is tested only against a loopback stub. Effects are observed inside a sandboxed application; nothing is fetched or sent.
Run model-generated Python in a locked-down container
CompletedRepair an agent's run-Python tool until it refuses to fall back to the host, shares no folder with it, runs each job offline with memory, CPU and process limits, kills the container at the deadline and checks the result envelope the code could have forged.
Download project ZIP: Run model-generated Python in a locked-down containerCommands, requirements and reference solution: Run model-generated Python in a locked-down container
Needs: The live tests need Docker with Linux containers and internet access once to pull the Python image (about 45 MB compressed). The offline tests need only Python.
In the extracted folder, run:
Shellpython3 -m unittest -v test_sandbox.py docker pull python:3.14.8-slim-trixie@sha256:a2b82f3c48559aa0a8446d9af49826b6e2b2016f4cd2afabfe6013ec53729170 python3 -m unittest -v test_live.pyThe starter intentionally fails some of the 13 tests.
Opens in a dev container (VS Code Dev Containers or GitHub Codespaces) with a pinned image.
The offline tests use a stand-in for the docker CLI. The 12 live tests ran in Docker 29.4.3 with python:3.14.8-slim-trixie pinned by digest: network, host environment and files, a link to a host file, a bounded fork bomb, 1 GiB of memory, CPU share, a deadline and an output flood, plus three legitimate jobs. Containers share the host kernel; gVisor and microVMs were not tested.
Reference solution (after your attempt): Run model-generated Python in a locked-down container
Test parameterized database queries
The Python SQL injection recipe includes a complete runnable test with an in-memory database, a vulnerable branch, and a parameterized fix.
Open the Python recipe