# Pentest

> Outside-in black-box security audit of a running web application. Use when the user asks to check a web app / Telegram mini app for vulnerabilities, find security issues, audit an app from the outside, do a pentest / security review of a deployed app. Keywords: pentest, проверь на уязвимости, security audit, найди дыры, vulnerability scan, mini app audit, Telegram mini app, vercel app security, Next.js security, Supabase RLS check. Produces a human-readable report, an evidence log, and per-finding fix files that a coding agent can consume. Four aggression levels L0-L3. NOT for load testing (use loadtest instead) and NOT for hardening the user's own server (use security-hardening instead).

- Skill: `oxi-717/pentest` (Agent Skill, multi-file: 10 files)
- Install (CLI): `npx skillmds@latest add oxi-717/pentest`
- Raw SKILL.md: https://api.skillmd.com/api/skills/oxi-717/pentest/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Web & Frontend
- Author: OXI-717 (https://skillmd.com/u/oxi-717)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/oxi-717/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 in `{cwd}/pentest-output/{target-slug}-{YYYY-MM-DD}/`.

**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

```bash
slug=$(echo "<target host>" | tr -cd 'a-z0-9-')
day=$(date +%Y-%m-%d)
workdir="${PWD}/pentest-output/${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.

### 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`
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

---

## 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, 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 |

