Software Securityhome
Fix a findingSearch
Learn

Labs and projects

Learn by repairing the code: walk through a bug, make the fix, and run tests against your own code.

Repair code, then prove the fix. Browser labs supply broken JavaScript or Python, explained cases and runnable tests. Local projects below use real HTTP, database, archive and encryption libraries.

38 labs

Application security

21 labsAppSec guides

Software supply chain

3 labsSupply chain guides

Runtime, cloud & APIs

3 labsRuntime & cloud guides

Identity & cryptography

7 labsIdentity & crypto guides

Governance & frameworks

1 labGovernance guides

AI security

3 labsAI security guides

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

    Node.js 22+ · npm · 45–60 min · 14 regression tests

    Run Express requests with real signed tokens. Repair invoice authorization and stop user-controlled properties from changing roles or tenants.

    Commands, 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:

    Shell
    npm ci --ignore-scripts
    npm test

    The 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

    Download project ZIP: Secure a real HTTP API
  • Make a checkout transaction atomic

    Python 3.11+ · standard library · 30–45 min · 3 regression tests

    Reproduce two competing withdrawals against SQLite, then repair the transaction so funds cannot be spent twice.

    Commands, requirements and reference solution: Make a checkout transaction atomic

    In the extracted folder, run:

    Shell
    python3 reproduce_race.py
    python3 -m unittest test_checkout.py

    The 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

    Download project ZIP: Make a checkout transaction atomic
  • Keep extracted files inside their boundary

    Python 3.11+ · standard library · 30–45 min · 7 regression tests

    Inspect actual ZIP entries and repair a path policy. Test traversal, links, duplicate entries, expansion limits and ordinary nested files.

    Commands, requirements and reference solution: Keep extracted files inside their boundary

    In the extracted folder, run:

    Shell
    python3 -m unittest test_archive.py

    The 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

    Download project ZIP: Keep extracted files inside their boundary
  • Bind ciphertext to the correct record

    Node.js 22+ · no dependencies · 30–45 min · 10 regression tests

    Use Web Crypto AES-GCM to reject tampered ciphertext and cross-tenant record transplants. Repair authenticated context binding.

    Commands, requirements and reference solution: Bind ciphertext to the correct record

    In the extracted folder, run:

    Shell
    node --test exercise.test.mjs

    The 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

    Download project ZIP: Bind ciphertext to the correct record
  • Build and verify a signed software release

    Node.js 22+ · no dependencies · 60–90 min · 20 regression tests

    Build 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.

    Commands, requirements and reference solution: Build and verify a signed software release

    In the extracted folder, run:

    Shell
    node build.mjs
    node --test exercise.test.mjs

    The 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

    Download project ZIP: Build and verify a signed software release
  • Repair a real proxy and shared cache

    Node.js 22+ · loopback servers · no dependencies · 45–60 min · 12 regression tests

    Reproduce a private-response leak, repair cache policy and verify HTTP framing with real origin/proxy servers and raw socket requests.

    Commands, requirements and reference solution: Repair a real proxy and shared cache

    In the extracted folder, run:

    Shell
    node observe.mjs
    node --test exercise.test.mjs

    The 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

    Download project ZIP: Repair a real proxy and shared cache
  • Verify Kubernetes admission and network boundaries

    Node.js 22+ offline · kind/Docker/kubectl for live cluster · 45–60 min · 12 regression tests

    Repair warning-only admission and an overbroad network rule, then apply them to a disposable cluster and probe allowed and denied paths.

    Commands, 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:

    Shell
    node --test exercise.test.mjs
    node render.mjs
    # Follow README.txt for pinned cluster setup and live probes

    The 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

    Download project ZIP: Verify Kubernetes admission and network boundaries
  • Parse bounded JSON and XML invoices safely

    Python 3.13+ · Expat 2.6.0+ · standard library · 60–90 min · 24 regression tests

    Repair a real JSON/XML parser boundary: reject duplicate fields, coercion, unknown schema, DTDs and oversized documents while keeping valid invoices usable.

    Commands, requirements and reference solution: Parse bounded JSON and XML invoices safely

    In the extracted folder, run:

    Shell
    python3 -m unittest -v test_parsing.py

    The 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

    Download project ZIP: Parse bounded JSON and XML invoices safely
  • Verify tokens through rotation and revocation

    Node.js 22+ · npm · 60–90 min · 24 regression tests

    Operate 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.

    Commands, 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:

    Shell
    npm ci --ignore-scripts
    npm test

    The 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

    Download project ZIP: Verify tokens through rotation and revocation
  • Enforce retrieval and tool boundaries across crashes

    Python 3.11+ · standard library · 60–90 min · 25 regression tests

    Repair 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.

    Commands, requirements and reference solution: Enforce retrieval and tool boundaries across crashes

    In the extracted folder, run:

    Shell
    python3 demo.py
    python3 -m unittest -v test_boundaries.py

    The 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

    Download project ZIP: Enforce retrieval and tool boundaries across crashes
  • Repair a native parser using sanitizer evidence

    Clang + Python 3.11+ · 45–75 min · 11 regression tests

    Compile 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.

    Commands, 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:

    Shell
    python3 reproduce.py
    python3 test_parser.py

    The 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

    Download project ZIP: Repair a native parser using sanitizer evidence
  • Prove deprovisioning reaches sessions and queued work

    Python 3.11+ · standard library · 60–90 min · 18 regression tests

    Repair 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.

    Commands, requirements and reference solution: Prove deprovisioning reaches sessions and queued work

    In the extracted folder, run:

    Shell
    python3 -m unittest -v test_lifecycle.py

    The 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

    Download project ZIP: Prove deprovisioning reaches sessions and queued work
  • Stop prototype pollution at merge and authorization boundaries

    Node.js 22+ · no dependencies · 45–60 min · 18 regression tests

    Observe real prototype mutation from nested JSON merges, then repair schema selection, own-property authorization and arbitrary-key dictionaries without breaking valid updates.

    Commands, requirements and reference solution: Stop prototype pollution at merge and authorization boundaries

    In the extracted folder, run:

    Shell
    node --test exercise.test.mjs

    The 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.

    Reference solution (after your attempt): Stop prototype pollution at merge and authorization boundaries

    Part of the learning path Browser and API boundaries

    Download project ZIP: Stop prototype pollution at merge and authorization boundaries
  • Repair a security pipeline for GitHub Actions and GitLab CI

    Node.js 22+ · no dependencies · GitHub Actions and GitLab CI YAML · 45–75 min · 13 regression tests

    Repair 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.

    Commands, requirements and reference solution: Repair a security pipeline for GitHub Actions and GitLab CI

    In the extracted folder, run:

    Shell
    node --test exercise.test.mjs

    The 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

    Download project ZIP: Repair a security pipeline for GitHub Actions and GitLab CI
  • Keep an iOS app's storage, links and web bridge in bounds

    Swift 6 · Xcode 16+ · iOS Simulator · 45–75 min · 19 regression tests

    Repair 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.

    Commands, 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:

    Shell
    sh run-tests.sh

    The 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

    Download project ZIP: Keep an iOS app's storage, links and web bridge in bounds
  • Repair an Android app's links, components, network policy and storage

    Kotlin · Android Gradle Plugin 9.4 · Robolectric 4.17 · Docker · 45–75 min · 20 regression tests

    Repair 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.

    Commands, 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:

    Shell
    docker 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.sh

    The 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.

    Reference solution (after your attempt): Repair an Android app's links, components, network policy and storage

    Download project ZIP: Repair an Android app's links, components, network policy and storage
  • Stop untrusted pages from using an Electron bridge

    Node.js 22.12+ · npm · Electron 44 · 60–90 min · 33 regression tests

    Run 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.

    Commands, 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:

    Shell
    npm ci --ignore-scripts
    npx install-electron --no
    npm test
    npm run test:e2e

    The 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

    Download project ZIP: Stop untrusted pages from using an Electron bridge
  • Recover from failed firmware updates with A/B slots

    Node.js 22+ · no dependencies · 60–90 min · 13 regression tests

    Repair 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.

    Commands, requirements and reference solution: Recover from failed firmware updates with A/B slots

    In the extracted folder, run:

    Shell
    node demo.mjs
    node --test exercise.test.mjs

    The 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

    Download project ZIP: Recover from failed firmware updates with A/B slots
  • Convert untrusted documents in a locked-down container

    Python 3.11+ offline · Docker for the live tests · 45–75 min · 12 regression tests

    Repair 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.

    Commands, 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:

    Shell
    python3 -m unittest -v test_worker.py
    docker build -t doc-converter .
    python3 -m unittest -v test_live.py

    The 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

    Download project ZIP: Convert untrusted documents in a locked-down container
  • Gate an image build on a CIS scan and a deviation register

    OpenSCAP 1.4.4 and SSG 0.1.82 in Docker · 45–60 min · 12 regression tests

    Repair 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.

    Commands, 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:

    Shell
    python3 -m unittest -v test_gate.py
    docker build -t image-scanner .
    sh scan.sh IMAGE@sha256:DIGEST ubuntu2404 cis_level1_server out

    The 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

    Download project ZIP: Gate an image build on a CIS scan and a deviation register
  • Gate model and provider changes on a versioned red-team corpus

    Python 3.11+ · standard library · 60–90 min · 28 regression tests

    Run 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.

    Commands, 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:

    Shell
    python3 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.py

    The 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.

    Reference solution (after your attempt): Gate model and provider changes on a versioned red-team corpus

    Download project ZIP: Gate model and provider changes on a versioned red-team corpus
  • Run model-generated Python in a locked-down container

    Python 3.11+ offline · Docker for the live tests · 45–75 min · 13 regression tests

    Repair 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.

    Commands, 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:

    Shell
    python3 -m unittest -v test_sandbox.py
    docker pull python:3.14.8-slim-trixie@sha256:a2b82f3c48559aa0a8446d9af49826b6e2b2016f4cd2afabfe6013ec53729170
    python3 -m unittest -v test_live.py

    The 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

    Download project ZIP: 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

Find your next answer

Type to search. Search runs in your browser.

Searches run in your browser, but the host can infer them from the index files they load. The address shows your search so you can bookmark or share it.