Installs into .claude/skills of the current project.
Are you the author of Pentest?
Add the live security badge to your README. It updates with every re-scan.
[](https://www.skillsdirectory.com/skills/oxi-717-pentest)
---
name: pentest
description: Use for black-box audits of deployed web or Telegram mini apps; not load testing. Triggers — "проверь на уязвимости", "pentest".
---
# Pentest
Black-box security audit of a running web application, from the outside, via HTTP.
**Default language:** match the user's request language (Russian or English). Pass `--lang ru|en` to override. Reports live **outside any git repo** in `${PENTEST_OUTPUT_DIR:-$HOME/cc/_pentest}/{target-slug}-{YYYY-MM-DD}/` (override the root with `PENTEST_OUTPUT_DIR`, or pass `--into-cwd` for `{cwd}/pentest-output/…`).
**Assumption:** the user has authorization from the target's owner. Do not ask for legalese — trust the user. But never run this skill against a target the user hasn't explicitly named.
---
## Workflow
### Phase 0 — Scope
Before touching the target, confirm:
- **Target URL** — full `https://…` URL of the deployed app (the mini app entry URL, not `t.me/...`).
- **Aggression level** — default `L1`. Let the user override:
- `L0` passive: public metadata only, zero probes of the app itself
- `L1` safe (default): single-request probes, read-only, no mutations
- `L2` authorized: mutations on the auditor's own test account (profile updates, intros, etc.)
- `L3` destructive: real injection payloads, cross-tenant access attempts, rate-limit probing *without* load testing
- **Target type hint** — `telegram-miniapp` | `nextjs-vercel` | `supabase` | `generic`. Auto-detect from headers if not provided.
- **Language** — `ru` or `en` (default: match user's language).
- **Credentials needed?** — if the target needs auth to explore (most apps do), ask the user to paste the auth artifact once:
- Telegram mini apps: the launch URL fragment (`https://…/#tgWebAppData=…`) captured from DevTools when the app opens
- Cookie-based apps: a session cookie or token from the user's logged-in session
- Store it in `secrets.txt` under the workspace, add `.gitignore` to exclude it
- **Never** include the raw auth artifact in any report file.
### Phase 1 — Workspace
Pick the output root. **Default is outside any git repo** — pentest artifacts (vulnerability
details, auth secrets) must never risk landing in a commit, and `{cwd}` is often a repo (or a
locked read-only checkout, where the write just fails). Only `--into-cwd` writes next to the
current dir, and only when `{cwd}` is a normal writable work dir.
```bash
# default: $HOME/cc/_pentest (a non-repo data dir); override root with PENTEST_OUTPUT_DIR;
# --into-cwd => "${PWD}/pentest-output"
out_root="${PENTEST_OUTPUT_DIR:-$HOME/cc/_pentest}"
slug=$(echo "<target host>" | tr -cd 'a-z0-9-')
day=$(date +%Y-%m-%d)
workdir="${out_root}/${slug}-${day}"
mkdir -p "${workdir}"/{recon,bundle,scripts,findings,fixes}
echo 'secrets.txt
*.local.*
cookies.txt' > "${workdir}/.gitignore"
```
Write `${workdir}/README.md` with: target URL, scope, level, language, start time, operator.
**If `--into-cwd` is used**, `{cwd}/pentest-output/` lives inside a repo: before writing, confirm
`pentest-output/` is git-ignored in that repo (add it to the repo `.gitignore` if not), so a
vuln report is never accidentally committed.
### Phase 2 — Recon (L0 — always run)
Read `${CLAUDE_PLUGIN_ROOT:-.}/skills/pentest/references/checklist-generic.md` for the full command list. Minimum:
1. DNS (`A`, `AAAA`, `CNAME`, `NS`) and provider fingerprint
2. `curl -D headers_root.txt -o body_root.html <target>` — save headers + body
3. Probe well-known paths: `robots.txt`, `sitemap.xml`, `/.well-known/security.txt`, `favicon.ico`, `manifest.json`
3b. Run the exposed-artifacts sweep (checklist §3b): `.git/*`, `.env*`, logs, backups/dumps, deploy descriptors, AI-era metadata (`llms.txt`, `CLAUDE.md`, `AGENTS.md`, `.cursorrules`), framework actuators. **Mandatory:** build the SPA-fallback baseline first (hash of root body) so catch-all 200s don't produce false positives.
4. Grep the root HTML for asset references → full JS/CSS chunk inventory
5. Extract `og:*` meta, `<script src>`, `<link href>`, any inline data
6. Check security headers on both root and sample API endpoint: HSTS, CSP, X-Frame-Options, X-Content-Type-Options, Referrer-Policy, CORS
Fingerprint the stack from response headers:
- `server: Vercel` + `x-matched-path` + `x-nextjs-*` → Next.js on Vercel → load `references/nextjs-vercel.md`
- URLs like `…supabase.co` → Supabase backend → load `references/supabase.md`
- `https://telegram.org/js/telegram-web-app.js` or `tgWebAppData` fragment → Telegram mini app → load `references/telegram-miniapp.md`
Apply stack-specific reference only when fingerprint matches. Don't read all references upfront.
### Phase 3 — Bundle analysis
Download all JS chunks referenced from the root HTML to `bundle/`. For each:
```bash
for f in bundle/*.js; do
curl -sS -o "$f.map" "https://<host>/path/to/$(basename $f).map?<dpl>"
[ -s "$f.map" ] && echo "source map exposed: $f"
done
```
Grep the bundle for:
- API routes: `/api/[^"]+`
- `fetch(` / `axios(` call sites
- Absolute URLs of third-party services
- Secrets patterns: `eyJ[a-zA-Z0-9_-]{40,}`, `sk_[a-z]+_[a-zA-Z0-9]{20,}`, `NEXT_PUBLIC_[A-Z_]+`, `SUPABASE_[A-Z_]*`, `api_key`, `service_role`
- Auth header name (`Authorization`, `x-telegram-init-data`, `X-Api-Key`, etc.) and how it's injected
- `/directory/${...}`, `/profile/${...}` — templated app-level routes
Save the full inventory to `recon/api-inventory.md` — this becomes the map for Phase 4.
**Client wiring audit.** Once the auth header is identified (say `x-telegram-init-data`), grep for `fetch(` calls that hit `/api/` endpoints but do NOT go through the app's auth wrapper (typically `apiFetch` or similar). Example: a `fetch("/api/referrals/attribute", {method:"POST", ...})` with no `x-telegram-init-data` header, while the server demands it, means the feature is functionally broken on the client. That's a reportable bug (info severity, «broken feature») even if it's not a security vulnerability. Flag these:
```bash
# find raw fetch() calls to /api
grep -hoE 'fetch\([^)]{0,300}/api/[^)]{0,300}\)' bundle/*.js | head -20
# compare: which ones go through apiFetch (auth wrapper)?
grep -hoE 'apiFetch\([^)]{0,300}\)' bundle/*.js | head -20
```
If a route appears in plain `fetch(` but is also listed as requiring auth in your Phase 4 map, note «client-wiring mismatch: server requires auth, client sends none — feature broken».
### Phase 4 — Endpoint probing (L1 default)
For every `/api/*` endpoint found:
1. **Unauthenticated GET** — record status code, size, body preview
2. **Authenticated GET** (with user's auth artifact) — record the same
3. Compare: endpoints that return 200 unauth AND contain PII are privacy leaks
Then for any auth/login endpoint (`/api/auth/*`, `/api/session`, etc.):
- Empty body → expected 400, flag 500 as an input-validation bug
- Missing credentials → expected 401
- Bad credentials → expected 401
- Credentials with tampered fields but valid signature → expected 401 (this tests the signature actually covers the tampered field)
If stack = `telegram-miniapp`, run the full auth test matrix from `references/telegram-miniapp.md` (empty initData, no hash, fake hash, tampered user_id, stale auth_date replay).
### Phase 5 — Authorization / IDOR (L1 default)
Collect any IDs visible in Phase 4 responses (userId, memberId, eventId, intro_id, etc.) and try:
- `GET /api/members/{otherUserId}` with your own auth — do you see their data? Expected: 200 (if public) or 404/403 (if protected)
- `GET /api/{thing}?userId={otherUserId}` — does the filter actually filter? Or is it silently ignored?
- `GET /api/{thing}?limit=999999`, `?limit=-1`, `?offset=abc` — input validation smoke. Note 500s as bugs.
- `?role=admin`, `?membershipStatus=banned` — does passing a privilege filter return different data?
- Path confusion: `/api/{thing}/..%2F..%2Fetc%2Fpasswd`, `/api/{thing}/%00.json`
**Do not** fetch another real user's sensitive data past proof-of-access. Stop at the first 200 that proves the vulnerability and record just the structure of the response, not the content. Redact names/emails/ids of real users in the report.
### Phase 6 — L2 (mutations on own account, only if user asked for L2)
On the auditor's own test account:
- `POST` endpoints: profile update, intro send, referral attribute, upload
- Test: server-side validation vs client-side. Can you send fields not shown in the client form?
- Test: are reserved/internal fields accepted? (`is_admin`, `role`, `membership_status`, `credits`, `tier`)
- Test: can you change another user's resource by sending their ID in the body? (IDOR on mutations)
- Test: file upload — MIME sniffing, path traversal in filename, SVG-with-script upload, oversize file
Always label each mutation request in `EVIDENCE.md` with what it did and which object it touched, so it's easy to clean up afterward.
### Phase 7 — L3 (destructive, only if user asked for L3)
- SQL/NoSQL injection payloads (`' OR 1=1--`, `'; DROP --`, `{"$ne": null}`) on fields that hit the DB
- SSRF via URL parameters (`url=http://169.254.169.254/latest/meta-data/`)
- XSS in stored fields (`<script>alert(1)</script>`, `javascript:alert(1)` in avatarUrl) — check where it reflects
- Prototype pollution via JSON body (`{"__proto__":{"isAdmin":true}}`)
- Cross-tenant: attempt to read/modify a non-test user's data. **Stop at proof-of-access, don't exfiltrate.**
Single requests only. **No load / brute-force / enumeration loops.** If a finding needs automation to be proven, write it up as a theoretical finding with the single-request PoC and note the limitation.
### Phase 8 — Generate deliverables
Three artifacts, two locations:
- `${workdir}/findings/REPORT.md` — human-readable, for the target's owner. Use `references/report-template.md` as the skeleton. Order findings by severity descending. Each finding has: description, why it matters, PoC steps, recommended fix (with code snippet). End with "What works well ✅" and "What I didn't test" sections.
- `${workdir}/findings/EVIDENCE.md` — raw request/response log, one section per finding. Use `references/evidence-template.md`. Redact real user PII. Keep auth artifacts out (they're in `secrets.txt`, .gitignored).
- `${workdir}/fixes/` (sibling of `findings/`, NOT nested) — machine-actionable per-finding fix plan, designed to be consumed by another coding agent:
- `${workdir}/fixes/INDEX.md` — markdown table of all fixes sorted by priority with a one-line summary each
- `${workdir}/fixes/NN-slug.md` — one file per finding, two-digit priority prefix (e.g. `01-auth-date-ttl.md`, `02-public-member-list.md`)
- Template: `references/fix-template.md`
After writing, print the three absolute paths in chat: `${workdir}/findings/REPORT.md`, `${workdir}/findings/EVIDENCE.md`, `${workdir}/fixes/INDEX.md`. Don't dump report contents into chat.
---
## Consuming `fixes/` with another agent
The per-finding fix files are self-contained. To apply fixes in a different session, user can say:
> "Read `fixes/INDEX.md` and apply the fixes one by one, running tests between each."
Each fix file has:
- `severity`, `title`, `location` (URL/file), `summary`
- `patch` — concrete code change (or pseudo-patch if the repo is not available)
- `verification` — commands or HTTP probes that confirm the fix landed
---
## Watch mode (scheduled deterministic sweeps)
The interactive phases above are the deep audit. Alongside them, the plugin ships a
non-interactive scanner for regular runs against a fixed registry of surfaces:
- `scanner/scan.py` — per surface: root page, canary-based catch-all baseline,
exposed-artifacts sweep, registered unauth endpoint probes, JS bundle download,
API route discovery from bundles, and a secret scan over every fetched byte.
Output: `run.json` + `FINDINGS.md`; exit code 2/1/0 = critical/high/clean.
`--baseline <prev-run>` diffs probe maps (static hashed assets excluded).
- `scanner/secret_patterns.py` — detector library. **Incident → pattern loop:** when a
real leak happens, add the detector here FIRST (with a comment naming the incident),
so every later run catches the class. Born-detectors: Telegram bot token /
`api.telegram.org/file/bot<token>` URLs (real incident 2026-09-16).
- `scanner/surfaces.example.yaml` — registry format: host, tier (1 nightly / 2 weekly /
3 rare), `endpoints` with `expect: public|auth`, `access: public|contour`
(contour-only hosts get findings downgraded one level — they are unreachable from
the public internet but visible to the whole tailnet).
- `scanner/test_secret_patterns.py` — detector regression tests; extend with every new
pattern.
Watch runs obey the same limits as L1: single GET per path, `--delay` between
requests, no mutations, no load. Findings with secrets are stored redacted + hashed,
never raw.
---
## Rules
1. **Never** run load / stress tests here — route the user to `loadtest` instead. A burst of 100 requests in a tight loop is load testing, not probing.
2. **Redact every human name, email, phone, and telegram handle by default** before writing to `REPORT.md` or `EVIDENCE.md`. Even if a name appears in the target's own public listing (e.g., the owner's name in their own member directory), redact. The only exception is a name explicitly named by the user as «OK to keep»: «you can keep Aleksey's name» — otherwise mask to `<redacted-name>`, `<redacted-email>`, `<redacted-uuid>`. Default wins; opt-out is per-name.
3. **Never** write the raw auth artifact to any file outside `secrets.txt`. Never paste it in chat.
4. **Never** exploit a finding past proof-of-access. If you prove `GET /api/admin/*` returns 200, stop — don't iterate through the list.
5. **Every** mutation (L2+) must be labeled in EVIDENCE.md with (a) endpoint, (b) body sent, (c) object affected, (d) reversibility note.
6. **Never escalate levels without a fresh user confirmation in the current turn.** If the user invoked L1 and mid-run you find something that looks «L2-interesting», record it as an L1 finding with the single-request PoC and a note «L2 probe needed to confirm». Do **not** escalate on your own initiative even when the next probe would obviously land.
7. **Stop and ask** the user before running L2 on any endpoint marked «mutation» if it looks like it touches billing/payment/external service integration.
8. **Never** use the Claude-only `AskUserQuestion`, `TeamCreate`, `TaskCreate` APIs — this skill runs in both Claude Code and Codex.
---
## Stack-specific references
Load only when the fingerprint from Phase 2 matches:
| Stack | Reference | When |
|-------|-----------|------|
| Telegram mini app | `${CLAUDE_PLUGIN_ROOT:-.}/skills/pentest/references/telegram-miniapp.md` | initData / HMAC / Ed25519 / replay / TTL / self-referral / referral fraud |
| Next.js on Vercel | `${CLAUDE_PLUGIN_ROOT:-.}/skills/pentest/references/nextjs-vercel.md` | chunks, source maps, env exposure, `/api/*` conventions, edge vs serverless, x-vercel-* headers |
| Supabase backend | `${CLAUDE_PLUGIN_ROOT:-.}/skills/pentest/references/supabase.md` | anon key, RLS, storage buckets, PostgREST |
| Any web app | `${CLAUDE_PLUGIN_ROOT:-.}/skills/pentest/references/checklist-generic.md` | headers, well-known paths, exposed-artifacts sweep (§3b), CORS, method confusion |
Templates:
| File | Purpose |
|------|---------|
| `references/levels.md` | Definitions of L0-L3 with examples of what's in/out of scope |
| `references/report-template.md` | REPORT.md skeleton with severity table + section order |
| `references/evidence-template.md` | EVIDENCE.md skeleton with per-finding sections |
| `references/fix-template.md` | Single fix file template for coding agents |