Files
matt 4d60fdd139 Initial commit: Imperva WAF demo lab
Flight-booking app scaffolded from the source running on the GCP host,
plus the four-screen frontend and three OWASP attack endpoints (SQLi,
reflected XSS, path traversal) built and locally verified against a
live uvicorn/vite dev pair. Includes deploy.sh (local build -> ship
over ssh -> swap on host) and docs/ carried over from the handoff.
2026-07-29 11:08:56 -07:00

8.1 KiB

CLAUDE.md

Context and working guide for the Imperva WAF demo lab. Read this first, then the files under docs/ for depth. This file is meant to orient any AI agent (or human) picking up the project so they don't re-derive decisions that were already made and deliberately reversed once.


What this project is

A deliberately-vulnerable flight-booking web app sitting behind the Imperva Cloud WAF, used to demonstrate that the WAF detects and blocks common OWASP attacks and surfaces them in the Imperva portal. The app itself is not the point — it exists to give the WAF something realistic to protect and to produce clean, distinct blocked events on screen during a live demo.

The stack runs as rootless Podman containers on a single small GCP instance. All public traffic is forced through Imperva; the origin only accepts connections from Imperva's IP ranges.

Current state (as of this handoff)

  • Backend (backend-api), frontend (frontend-vue), and Caddy (caddy-proxy) are deployed and working on the GCP host.
  • Edge/origin TLS is done and verified. Imperva terminates the visitor's TLS at the edge and re-encrypts to the origin over HTTPS using a self-signed cert that Caddy serves on :443. Confirmed working end to end (https://flytoouter.space/ loads with Imperva's trusted edge cert; origin serves self-signed to Imperva only).
  • A local-build → ship-over-SSH → swap-on-host deploy pipeline is defined (deploy.sh). See docs/DEPLOY.md.
  • Repo scaffolded locally by pulling the exact source that's live on the host (~/imperva-backend, ~/imperva-frontend, ~/imperva-proxy) via scp, so the repo matches what's actually running, not just what was intended.
  • The frontend on the GCP host is still a near-empty stub (just whitespace and a page title) — the real four-screen Vue SPA now exists in this repo's frontend/ but has not been shipped yet. See "What's next".

What's next

The four-screen frontend and three attack endpoints described below are built and locally verified, but not yet deployed to the GCP host — the host still runs the old stub frontend and two-endpoint backend. Next step is running ./deploy.sh from a real host terminal (see the Podman/sandbox gotcha below), then walking the docs/DEMO_RUNBOOK.md pre-demo checklist against https://flytoouter.space/.

Three OWASP attacks, each blocked by an Imperva default signature and shown as a distinct event type in the portal:

  1. SQL Injection — /api/v1/users?q= (Passenger / Admin Lookup screen)
  2. Reflected XSS — /api/v1/search?term= (Flight Search screen)
  3. Path Traversal — /api/v1/download?file= (Boarding Pass Download screen)

Plus a Login screen (/api/v1/login) kept as the credential-stuffing bonus (not in the core demo — needs a custom rate policy, see DECISIONS.md D7).

See docs/DEMO_RUNBOOK.md for payloads and expected portal classifications.

Resolved: confirmed via podman inspect and the host's .bash_history that backend-api and frontend-vue have no bind mounts — demo.db is created inside the image by init_db() on container start, not mounted from the host. The real run commands were:

podman run -d --name backend-api  --restart always -p 8000:8000 backend-api
podman run -d --name frontend-vue --restart always -p 8080:8080 frontend-vue

deploy.sh's swap() reproduces exactly this — --name and -p only, nothing else needed.


Key facts an agent needs (these override the original handoff doc)

  • Host: GCP e2-micro, Ubuntu, static IP 35.203.147.44, domain flytoouter.space.
  • Host user: matt. Rootless Podman socket is under UID 1001, not 1000 — the original architecture doc says 1000; that was wrong for this host. Prefer the connection alias so the UID is irrelevant.
  • Podman connection alias (local → host): gcp-lab, already configured and set as default: ssh://matt@35.203.147.44:22/run/user/1001/podman/podman.sock key: ~/.ssh/google_compute_engine.
  • SSH ingress is locked to the operator's static residential IP.
  • HTTP/HTTPS ingress (allow-imperva-waf) is restricted to Imperva's published IPv4 CIDRs only; all 0.0.0.0/0 rules were purged. The vulnerable app is therefore never directly reachable — attacks must traverse the WAF, which is what makes them appear in the portal.
  • e2-micro cannot build the Vue multi-stage image (npm build is memory-hungry even with the swapfile). Always build locally and ship the finished image. Never build on the host.

Repo layout (intended)

.
├── CLAUDE.md              # this file
├── deploy.sh             # local build → ship over ssh → swap on host
├── backend/              # FastAPI app + Containerfile
├── frontend/             # Vue SPA + multi-stage Containerfile
├── imperva-proxy/
│   ├── Caddyfile         # origin reverse proxy (mirrors host ~/imperva-proxy/)
│   └── certs/            # origin.crt / origin.key (self-signed; do NOT commit key)
└── docs/
    ├── ARCHITECTURE.md   # topology, TLS legs, request flow, network
    ├── DECISIONS.md      # every design choice + why + rejected alternatives
    ├── DEPLOY.md         # the ship-over-ssh workflow in detail
    └── DEMO_RUNBOOK.md   # attacks, payloads, expected portal events

Note: On the host, the Caddyfile and certs live at ~/imperva-proxy/. Keep the repo copy in sync with what's mounted into caddy-proxy.

Conventions / gotchas (learned the hard way — see DECISIONS.md)

  • One catch-all :80/:443 site block in the Caddyfile. With auto_https off, a hostname block (e.g. flytoouter.space { }) collapses onto the same port as a :443/:80 block and Caddy fails with ambiguous site definition. Host-based routing, if ever needed, goes inside the single block as a matcher — never as a second site block.
  • Use handle, not handle_path, for /api/* so the /api prefix is preserved for the backend.
  • Mount paths in podman run must be absolute (/home/matt/...), not ~. A tilde that doesn't expand creates an empty anonymous mount and Caddy dies with open /etc/caddy/certs/origin.crt: no such file or directory.
  • Origin cert is self-signed on purpose. Only Imperva sees it, so no browser warnings. Never put the self-signed cert on the edge (visitor-facing) side — that stays Imperva's managed cert.
  • https://35.203.147.44/ resetting is expected when the origin serves only the intended port and/or your source IP isn't an Imperva CIDR. Test the origin from the host with curl -vk https://localhost/, and test the real path via https://flytoouter.space/.
  • Do not commit origin.key. Add imperva-proxy/certs/*.key to .gitignore.
  • podman builds must run on the real host terminal, not inside a sandboxed coding-agent shell. If you're an AI agent editing this repo from within a sandbox, check whether podman/docker resolves before assuming you can run deploy.sh yourself. On this project's dev machine, the agent sandbox is a nested container (different userland/libc than the real host) sitting on the same physical disk as ~/workspace — so files edited in the sandbox are real and immediately usable, but a podman binary installed inside the sandbox will collide with the host's existing rootless container storage (~/.local/share/containers, shared because it's the same disk) and fail with a database configuration mismatch error. Don't try to work around this by resetting or pointing storage elsewhere — it risks corrupting the host's real container state. Edit code from the sandbox; run ./deploy.sh from a normal terminal on the actual workstation.

How to deploy (short version)

From the repo root, with the gcp-lab connection present:

./deploy.sh                 # build both images, ship over ssh, swap both containers
VERSION=v2 ./deploy.sh      # same, but tag images v2 for a named rollback point
./deploy.sh --restart-caddy # after editing the Caddyfile (new routes)

Full detail, including per-step flags and the layer-shipping trade-off, in docs/DEPLOY.md.