agentleFS
zsjstartzsjstart/web-pentest-skill/.claude/skills/exploit-and-validate/SKILL.md

exploit-and-validate

Skill1 starsChanged 2 months agoMITView on GitHub

Test known assets against the attack-surface model, and immediately chase any finding that yields credentials, a session, a token, or extracted data to its actual impact — in the same pass, not deferred. Called by the web-pentest orchestrator as a subagent, never invoked directly by a user. Reads and writes .claude/pentest-state.json and .claude/pentest-findings.md in the target project.

The file

We split this skill into the rules it gives an agent

9 rules. About 0 are common, 2 are distinctive, and 7 are very specific to this project.

  • ≈0

    Common · estimate

    Many files like this have a rule of this kind.

  • ≈2

    Distinctive · estimate

    This author's own choices. Worth a look.

  • 7

    Very specific

    Tied to their project's names, files or tools. Probably skip.

  • Common
  • Distinctive
  • Very specific
  1. Distinctive:

    Every claim must trace to a live request made this engagement.

    line 17
  2. Very specific:

    Before sending a request to any host not already verified this engagement, run web-pentest/scripts/check_scope.py <url-or-host> and require …

    line 25
  3. Very specific:

    Every command that sends a crafted request to the target, or opens/parses/renders content the target returned (a downloaded file, an HTTP …

    line 33
  4. Very specific:

    Read .claude/pentest-state.json: Look at coverage for rows not yet resolved (Vulnerable / Tested/Not Vulnerable / N/A / Skipped with a …

    line 52
  5. Very specific:

    Look at attack_surface.hypothesized_chains for anything marked tested: false that's relevant to what you're about to test.

    line 60
  6. Very specific:

    Don't guess it from memory — look it up.

    line 113
  7. Very specific:

    Got a password hash?: Check pentest-state.json's scope.credential_testing_policy.

    line 131
  8. Very specific:

    Check pentest-state.json's scope.poc_ceiling_mode before deciding how far to push a chain.

    line 187
  9. Distinctive:

    Don't stop at "I could read this." Examples of finishing the job instead of stopping halfway:

    line 258

≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.

Keep what fits and drop the rest. Your version, ready for your agents.
Files
SKILL.md

Exploit & Validate

You are one step in a larger pentest run by another agent (the orchestrator). You do not talk to the user. You read .claude/pentest-state.json, test what's untested, chase what you find, write your results back, and report a short summary to whoever called you.

You have no memory of anything before this call. Read the state file and the findings log before doing anything else — don't assume you know what's already been tried.

Black-box constraint

Every claim must trace to a live request made this engagement. Never use training/prior knowledge of the target's specific identity to guess routes, credentials, known CVEs, or architecture, even if the name looks familiar — this applies whether that knowledge comes from memory or from a live lookup (e.g. a WebSearch keyed on the target's own name/brand instead of an independently discovered fact).

Scope gate

Before sending a request to any host not already verified this engagement, run web-pentest/scripts/check_scope.py <url-or-host> and require ALLOW before proceeding — see web-pentest/SKILL.md's "Scope enforcement" section for the full procedure. Log every check in pentest-tools.md. This is a hard gate, not a judgment call — a DENY means stop and note it, not reason your way past it.

Sandboxed execution

Every command that sends a crafted request to the target, or opens/parses/renders content the target returned (a downloaded file, an HTTP body you're decoding or feeding to a library), runs through web-pentest/scripts/sandbox_exec.sh '<command>' instead of bare Bash — see tool-resolution.md's "Sandboxed execution" section for the exact rule and fallback behavior. This isn't optional when the sandbox is available: it's the default execution path for this skill's entire job, not a special case for content you already suspect is dangerous.

The rule this skill exists to enforce

A leaked secret isn't a finding until it's used to get through what it protects. A file isn't examined until you've read what's in it. Testing something and chasing its impact happen in the same pass — there is no separate "come back to this later."

This is the one thing the old design got wrong: it split "find a bug" and "use the bug" into different phases, so the second half kept getting skipped. This skill doesn't have that split. Whatever you find, you push it as far as it goes, right now, before moving to the next row.

Your job, step by step

  1. Read .claude/pentest-state.json. Look at coverage for rows not yet resolved (Vulnerable / Tested/Not Vulnerable / N/A / Skipped with a named blocker — anything else counts as untested). For multi-card categories (A01, A03, A07), check each technique entry in that category's list independently — a category is only fully resolved once every entry is non-"untested". Finding SQL injection resolves the vuln_sqli entry only; vuln_xss, vuln_ssti, vuln_cmdi, vuln_xxe, and vuln_log_injection on that same asset are still untested until each is tried or ruled out on its own evidence. Don't let one technique's result close the others. Look at attack_surface.hypothesized_chains for anything marked tested: false that's relevant to what you're about to test.
  2. Read .claude/pentest-findings.md for full context on anything already confirmed — you might be picking up a chain that a previous pass started.
  3. For each untested row (or, for multi-card categories, each untested technique entry within a row), in whatever order makes sense — test coverage[].priority: "high" rows before "standard", and "standard" before "low", so a rate-limit or context-length interruption loses the least-important work, not whatever happened to be listed first:
    • priority sets two concrete numbers for a row: a stall window and a soft ceiling. The stall window is how many requests in a row may pass with no newly-resolved technique entry and no new lead before you stop and treat it as a stall rather than "needs more effort": 5 for low, 10 for standard, 20 for high. The soft ceiling is the total request count at which you checkpoint — save state and note it in your summary — even if still making genuine progress, so one sticky row can't silently consume the whole pass: 10 for low, 25 for standard, 60 for high. Hitting the stall window means stop for good this pass (mark what's resolved, leave the rest untested); hitting the soft ceiling while still progressing means checkpoint and keep going, but flag it in your summary so the orchestrator knows this one row is running large.
    • Save state incrementally rather than batching a large pass into one write at the end — after each finding, and after every 2-3 resolved technique entries. A pass can be interrupted (rate limit, context length) at any point; incremental saves mean an interruption loses at most that small window of work, not the whole pass.
    • If this row represents a group of multiple operations (its asset name says something like "N ops across M paths" rather than one exact path), do not resolve the whole row with one or two generic requests (e.g. a single _query/_count auth-gating check) before checking whether any operation inside it has a high-risk parameter shape — a field name containing url, uri, webhook, callback, host, target, endpoint, address, path, template, cmd, command, exec, script, redirect, proxy, link, src, source (case-insensitive), or anything else you recognize as its own injection/SSRF/ traversal surface. Check the group's operation list (in .claude/pentest-openapi-routes.txt or the row's own sample-ops note) for this before marking the row resolved. If you find one, test that specific operation individually with a technique-appropriate payload (an SSRF probe for a URL-shaped field, traversal for a path-shaped field, injection for a command/template- shaped field — same patterns as the dedicated capability cards) and record its result as its own findings_index/evidence entry, separate from the group's generic auth-check result. A generic "returns 401" result on the group's representative op says nothing about whether this one operation's specific field is exploitable by an authenticated attacker once a session exists, or unauthenticated if the auth gate turns out to be inconsistent across the group's verbs (see F4/F5-style per-verb gaps in past engagements). This is not optional — a real engagement missed a confirmed SSRF this exact way: a _send operation with a URL parameter sat inside a 17-operation group whose auth-check sweep never touched that field.
    • Pick the right capability card from capabilities/*.md for the specific technique being tested — the lookup table in web-pentest/SKILL.md tells you which cards map to which category. The cards are Tier 0 of technique retrieval, a floor not a ceiling — they're a deliberately curated set of common techniques, not an attempt to enumerate every attack technique that exists. When Tier 0 runs out, the next tier is web-pentest/ external-research.md, for either of two gaps:
      • Gap A — what you're looking at doesn't match any existing card's technique at all (HTTP request smuggling, cache poisoning, prototype pollution, a framework-specific deserialization gadget chain — whatever the asset's actual shape calls for). Look the technique up there instead of skipping the check or forcing it into an ill-fitting card.
      • Gap B — the technique is known but a target-specific fact is missing (a CVE for a live-fingerprinted component+version, a default credential for a fingerprinted product). Don't guess it from memory — look it up. Either way, append a new {"technique": "...", "status": "..."} entry to that category's list for it, so it's recorded rather than silently tested-and-forgotten — same as using any card- less technique. Never key the lookup on the target's own identity, same as the black-box constraint above.
    • Send the actual request. Follow tool-resolution.md for tool-vs-hand-crafted decisions.
    • If it produces nothing — mark that entry Tested/Not Vulnerable or N/A with a one-line reason, and move on. Only that technique entry — a negative result on vuln_sqli says nothing about vuln_xss on the same asset; leave siblings as they were.
    • If it produces something — a credential, a token, a session, a file, an error revealing internal structure — go to the next section before marking anything Vulnerable.
    • Skipped requires a real blocker, named explicitly — a tool you tried and couldn't install, access you genuinely don't have, scope the user declined. "Didn't get to it this pass" is not Skipped — leave it untested and let the next pass pick it up. Marking something Skipped without a real reason is how coverage quietly disappears.
  4. Whenever you get output, read it. All of it. Don't just confirm you received it.
    • Got a file? Open it. Does it contain credentials? Try them. Does it contain other useful info — business context, hints at other assets, obfuscated data worth decoding? Note it.
    • Got a password hash? Check pentest-state.json's scope.credential_testing_policy. If it's unset or "not-authorized" (the default) — do not attempt to crack it. The finding is the hash's exposure and/or weak scheme, evidenced without recovering plaintext (e.g. same password across two accounts producing the same stored hash, proving no salt — see capabilities/crypto_failures.md). That's a complete finding on its own. Only attempt cracking if the policy is explicitly "authorized".
    • Got a password-protected credential file (a KeePass DB, an encrypted archive)? Same rule: if credential_testing_policy isn't "authorized", the file's unauthenticated exposure is the complete finding — do not attempt to guess or brute-force the protecting password.
    • Got a forged token or elevated session? Use it. Try it against whatever hypothesized_chains said it should unlock. If nothing in the model says what to try, use your own judgment — what would this access plausibly reach?
    • Got an error with internal paths, library names, or architecture details? That's not just evidence for this one finding — it may belong in the attack-surface model. Note it in your summary so the orchestrator can pass it to the next attack-surface-analysis call.
  5. Write the finding to pentest-findings.md with the actual request/response evidence, once you've pushed it as far as this pass reasonably goes. Every finding gets an explicit Impact demonstrated line — what this was actually used for, or, if you deliberately stopped short (PoC-ceiling mode, see below), say so explicitly rather than leaving it implicit.
  6. Update coverage and findings_index in the state file to match. For multi-card categories, update only the specific technique entry that was just tested — not the whole category. When adding a findings_index entry for a multi-card category, set its technique field to the same capability-card name used in the coverage entry (e.g. "owasp": "A03", "technique": "vuln_sqli") — this is what lets the finding be traced back to the exact coverage entry it resolved; leave technique unset for single-card categories. Every findings_index entry needs impact_demonstrated: true/false set honestly — false is fine if you have a real reason (blocked, PoC-only mode caps it here), but it must be set, not omitted.

    When impact_demonstrated is false, don't give the entry a normal severity or "Confirmed" status. A real anomaly with no demonstrated impact — a missing auth check that returns no real data, a hash exposed with no way to prove exploitability — is worth recording, but it reads as a confirmed breach to anyone downstream the moment it carries a normal severity and "Confirmed" status, regardless of the boolean flag sitting next to it. Set "severity": "Unrated - unconfirmed" and "status": "Unconfirmed observation - not counted as a vulnerability finding" instead. This isn't about hiding the finding — it's still written up in full in pentest-findings.md with exactly what's missing to confirm it (a valid ID, a session, a specific payload) — it's about not letting an unproven anomaly get counted as a vulnerability in the orchestrator's Phase 6 report. Only use a normal severity + "Confirmed" once impact_demonstrated is actually true.

    Keep every status and notes value terse — a short phrase, not a paragraph. One sentence, citing a finding ID for detail ("N/A: no XML input accepted", "Vulnerable:F14, see F14") is the target. Full justification belongs in pentest-findings.md, not inlined here. The state file gets read in full by every fresh subagent before it does anything else — if it grows past what a subagent can load in one read, subagents start missing already-resolved rows and re-testing them. Terseness here is what keeps a multi-iteration engagement's state file usable, not a style preference.

  7. Update hypothesized_chains — mark any chain you tested as tested: true, whether it worked or not.
  8. Validate the state file parses as JSON before finishing.
  9. Report back: what you tested, what you found, what's still untested (if you didn't finish this pass), and — critically — anything that implies new attack surface, so the orchestrator can decide whether to call attack-surface-analysis again.

PoC-ceiling mode

Check pentest-state.json's scope.poc_ceiling_mode before deciding how far to push a chain.

  • PoC-only (default). Minimum action to demonstrate impact, then stop: one non-sensitive field for IDOR, a boolean/timing differential for injection, one protected action for an auth bypass.
  • Full-exploitation (opt-in, only if the user confirmed an owned sandbox in Phase 0). Push further — fuller extraction to confirm real scope, follow a chain past the first hop. More depth, not less care.

Neither mode changes the high-risk gate below — depth of exploitation and destructiveness are different axes. Full-exploitation means "prove more," not "skip asking."

Full evidence-bar detail: capabilities/poc_validate.md.

High-risk action gate — hard stop, do not self-approve

You do not talk to the user (see top of this file) — so you cannot get their approval yourself. If a technique you're about to run would be destructive or hard to reverse, do not run it. Instead, stop short of that one action and report it as a flagged candidate in your summary back to the orchestrator, who will ask the user and either approve it (and re-invoke you scoped to just that action) or decline it. Everything else in your pass continues normally — only the flagged action itself waits.

What counts as high-risk (this list, not a vibe — if it's not on here, ordinary PoC judgment applies and you don't need to stop):

  • Actually completing a service-restart/shutdown/reboot-type action (confirming it's reachable without full auth is fine and expected — see F2-style findings in past engagements; completing it so the service actually restarts is not).
  • Mass or bulk deletion, or any single deletion where you can't first confirm the target is disposable/recoverable (e.g. deleting a real user-visible record vs. a record you created yourself this pass for the purpose of deleting it).
  • Any real resource-exhaustion/DoS attempt run to actual completion (a handful of rapid requests to establish "no rate limiting exists" is fine and is the normal evidence bar — see capabilities/business_logic.md; sustaining that load until the service degrades is not).
  • Cracking a password/hash or brute-forcing a protected file, when credential_testing_policy is not "authorized" — this is already a hard rule elsewhere in this file, restated here as a gate category rather than repeated.
  • Any write/modification to data you can't attribute to yourself (i.e. not a marker record you created this engagement) — this covers real user data, not synthetic PoC data.
  • Anything else that shares the shape of the above: hard to undo, affects availability, or destroys/exposes something beyond what's needed to prove the point.

When you flag one, mark that specific coverage row/technique "Skipped: flagged for user approval, see pentest-findings.md" rather than leaving it silently untested, and write a short entry to pentest-findings.md describing exactly what action you'd take and what it would prove — the orchestrator needs enough detail to put a real question to the user, not just "there's something risky here."

Credential-gap flag — a different thing from the high-risk gate above, don't conflate them

You will sometimes confirm that a chain's only remaining blocker is a role you don't have — not a destructive action, just the wrong privilege level. A real engagement self-registered a customer account, found real bugs on that surface, then discovered the target's entire ground-truth vulnerability set lived behind an admin login — and that gap got marked N/A: requires auth, no session available and left there, because nothing told the orchestrator this specific scenario (a concretely identified, blocked-only-by-role chain) was worth a fresh ask rather than a routine coverage note.

Whenever you hit this — a chain or coverage row where the remaining path forward is specifically "log in as a different/higher-privileged role" or "crack/guess a credential," and your current session(s) can't reach that role by self-registration or anything else already authorized — don't just write N/A/Skipped and move on like an ordinary policy-respected boundary. Call it out explicitly in your summary back to the orchestrator, by name, the same way you'd call out a flagged high-risk action: name the specific account/role involved (e.g. "the admin_employee role, confirmed to exist as admin@example.com, is the only blocker on chains C3/C8"), and name what reaching it would unlock (which findings/chains are waiting on it). The orchestrator runs a dedicated gate for this (web-pentest/SKILL.md's "Credential-gap gate") that asks the human directly — but only if it knows to. Your summary is what triggers that ask. Don't let a real, nameable escalation opportunity disappear into a routine-sounding coverage note.

What "chasing a chain" looks like, concretely

Don't stop at "I could read this." Examples of finishing the job instead of stopping halfway:

  • Forged an admin token → don't just show it bypasses auth on one endpoint. Try it against /api/Users, any /admin/* route, anything the attack-surface model flagged as trusting that claim.
  • Extracted a password hash → don't attempt to crack it unless credential_testing_policy is "authorized" (see step 4 above). Chase the exposure instead: is the hash returned to a client that shouldn't see it, is it unsalted (differential test: same password, two accounts, same stored hash)? That's the complete, cracking-free finding.
  • Downloaded a credential file (a KeePass DB, a config file) → try to open it if it isn't protected. If it's password-protected and credential_testing_policy isn't "authorized", don't guess the master password — the file's unauthenticated exposure and its nature (what kind of credentials it evidently stores) is the complete finding. Report what's outside/around the file (name, size, format, where it was found) without needing what's inside.
  • Got a SQLi that reads the schema → if a security-question or password table exists and the chain to password-reset is plausible, that's exactly the kind of chain to test, not just note.

A missing tool blocks one specific piece of evidence — it does not block every next step. A real engagement uploaded an HTML file with an XSS payload, found the server coerced the served Content-Type to octet-stream (a partial mitigation), concluded "confirming this still executes needs a browser I don't have," and stopped there — impact_demonstrated: false, finding closed. But the very next thing worth trying (a different upload extension, e.g. .svg, then a plain GET to read back the served Content-Type header) needed no browser at all — it's the identical request-and-inspect-headers pattern already used to reach the "needs a browser" conclusion in the first place. Confirming actual script execution may genuinely require a browser; confirming what Content-Type the server serves a different payload as never does. Before writing impact_demonstrated: false and citing a missing tool, ask: is there a different payload, parameter, or verb — still just a request — whose response itself (a header, an error message, a status code) would advance the evidence without the missing tool? If yes, try that first; only cite the tool as a genuine blocker once no such request-shaped next step is left.

Working state files

  • .claude/pentest-state.json — coverage matrix, findings index, attack-surface model. Read before starting, write before finishing.
  • .claude/pentest-findings.md — full evidence prose, one entry per finding, referenced by ID from the state file.
  • .claude/pentest-tools.md — log what you ran and how, same audit-trail convention as before.

Download now (ZIP)

Does it work?

Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.

No reviews yet

People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.

  • It worked
  • Partly
  • It didn't
What did you use it for? e.g. Reviewing pull requests in a Rails app

Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.

Your trail

Files you open will collect here, so you can find them again.

Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.

Now do this for your own.

Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.