# Webapp Pentest Checklist

> Authorized self-pentest checklist for web applications you own. Walks the OWASP Web/API Top 10 against a locally-running target, discovering the base URL via env or entrypoint files (never hardcoded). Use when the user asks to "pentest", "attack", "probe", or "test the security of" their own running web app and the framework is unknown or mixed. For framework-specific deeper checks, use express/django/spring-boot/nextjs/nestjs/fastapi attack-probe skills.

- Skill: `dolphinllc/webapp-pentest-checklist` (Agent Skill)
- Install (CLI): `npx skillmds@latest add dolphinllc/webapp-pentest-checklist`
- Raw SKILL.md: https://api.skillmd.com/api/skills/dolphinllc/webapp-pentest-checklist/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Security
- Author: Dolphinllc (https://skillmd.com/u/dolphinllc)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/dolphinllc/webapp-pentest-checklist

---


# Web App Self-Pentest Checklist

Authorized, rate-limited probe of a web app running on the user's own machine. Follow the [shared probing conventions](../../../PROBING.md) for authorization, target discovery, and output schema.

## Procedure

1. **Authorization preflight.** Default to `localhost`/`127.0.0.1`. If a non-loopback host is named, require explicit user confirmation in chat.
2. **Discover base URL** per `PROBING.md` (env → entrypoint → ask). Record `target.base_url` and `target.discovered_via`.
3. **Confirm liveness.** `GET /` and `GET /healthz`/`/health` (if any). If unreachable, return `PREFLIGHT-BLOCKED`.
4. **Enumerate routes** from the app's source if you can read it; otherwise crawl from `/` to depth 2 (max 50 URLs) and stop.
5. **Run the rule pack below.** Stop at the first request budget hit (default 200 requests).

## Rules

| ID | Severity (if confirmed) | Probe | Confirmed when |
|----|-------------------------|-------|----------------|
| WEB-INFO-001 | low | Fetch `/.env`, `/.env.local`, `/.git/config`, `/.git/HEAD`, `/package.json`, `/composer.json` | Body is a non-404 file containing expected markers (`DB_`, `API_KEY`, `[core]`) |
| WEB-INFO-002 | medium | Fetch `/server-status`, `/.well-known/security.txt`, `/robots.txt`, `/sitemap.xml`, `/api-docs`, `/swagger`, `/openapi.json`, `/graphql` | Returns a populated body that exposes routes/internals |
| WEB-AUTH-001 | high | For each mutating route discovered, send the request with no auth header / cookie | 2xx response identical to authenticated response |
| WEB-AUTH-002 | high | Replay an authenticated request from user A's session against `/users/{B}/...` | 2xx instead of 403 = IDOR |
| WEB-AUTH-003 | medium | Send `Authorization: Bearer eyJhbGciOiJub25lIn0.<base64-mod-payload>.` (alg=none JWT) | 2xx accepted = JWT alg confusion |
| WEB-XSS-001 | high | Submit `"><svg/onload=__probe('xss')>` to each text input; render-back endpoints reflect it un-encoded | `<svg/onload` appears verbatim in HTML response (not entity-encoded) |
| WEB-SQLI-001 | high | Append `'` and `' OR '1'='1` to each numeric/string param; compare against baseline | Different status code OR DB error string in response |
| WEB-SSRF-001 | high | If app fetches URLs (preview, image proxy, webhook), submit `http://127.0.0.1:1`, `http://169.254.169.254/latest/meta-data/` | Connection succeeds / metadata returns |
| WEB-OR-001 | medium | Submit redirect param values like `//evil.test`, `https://evil.test` to login/return URLs | 30x with `Location:` outside same-origin |
| WEB-CORS-001 | high | Send `Origin: https://evil.test` with credentials to authenticated endpoint | `Access-Control-Allow-Origin` reflects origin AND `Allow-Credentials: true` |
| WEB-CSRF-001 | high | State-changing endpoint reachable via cross-origin form POST without CSRF token | 2xx on a request lacking the CSRF header/token |
| WEB-UPLOAD-001 | high | Upload file with double-extension `shell.php.jpg` / wrong MIME / SVG with embedded script | Stored and served with executable type / `Content-Type: image/svg+xml` rendered |
| WEB-MASS-001 | medium | Submit extra fields to update endpoints (`role: "admin"`, `is_staff: true`) | Field accepted and persisted = mass-assignment |
| WEB-ERR-001 | low | Trigger errors via `/?id[]=1`, malformed JSON, type confusion | Stack trace in 500 response = info disclosure |
| WEB-RATE-001 | medium | Send 10 rapid `POST /login` with random passwords | All accepted without 429 / lockout = no rate limiting |

## Workflow notes

- Reserve mutation probes (`WEB-MASS-001`, `WEB-UPLOAD-001`, `WEB-CSRF-001`) for endpoints clearly safe to write to (or skip if uncertain).
- For `WEB-AUTH-002` (IDOR), require the user to provide two test accounts; otherwise mark as `info: not-tested`.
- Render IDs from the running app's data, never inject random UUIDs that match nothing.

## Wrong-target safety

If the resolved `base_url` host is not in `{localhost, 127.0.0.1, ::1, *.localhost}` AND was not explicitly confirmed by the user this session — return a single `PREFLIGHT-BLOCKED` finding and stop.

## References

- OWASP Web Security Testing Guide: https://owasp.org/www-project-web-security-testing-guide/
- OWASP API Security Top 10: https://owasp.org/API-Security/
- PortSwigger Web Security Academy: https://portswigger.net/web-security

