Skip to content
Back to skills

Whitebox Pentest

ASecurity

Whitebox pentest of your own code before it ships: run every scanner installed, rank findings by reachability, prove each with a working exploit, fix, re-run. Fires on pentest, exploit, hardening.

  • 5 stars
  • 0 votes
  • 3 copies
  • 5 views
  • Added September 20, 2026
securitypythonrustgorubyphpshellbashsqlgitsecurity

Works with

  • cli

Security analysis

A100/100

Scanned September 20, 2026

npx -y skills add itsvedantkumar/vstack --skill whitebox-pentest --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Whitebox Pentest?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Whitebox Pentest
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/itsvedantkumar-whitebox-pentest/badge)](https://www.skillsdirectory.com/skills/itsvedantkumar-whitebox-pentest)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: whitebox-pentest
description: "Whitebox pentest of your own code before it ships: run every scanner installed, rank findings by reachability, prove each with a working exploit, fix, re-run. Fires on pentest, exploit, hardening."
---

# Whitebox pentest

You have the source, the config, the dependencies and the deploy scripts. That is a whitebox
engagement, and it is a much better position than the attacker has. Use it.

The job is not to produce a list. Scanners produce lists, and most of a scanner's list is noise
that will never be reachable in production. The job is to end with **fewer exploitable paths into
this system than when you started**, and to be able to show which ones closed and how you know.

Announce at the start: "Running a whitebox pentest of <target>."

## The rule that separates this from a scan

**A finding is not real until you have made it happen.** Not "this could allow SQL injection" but
a request, a test, a script that demonstrates the effect. Not "this dependency has CVE-2024-x" but
the call path from your code into the vulnerable function, or an honest statement that you could
not find one.

Everything below serves that rule.

## Phase 0: know what you are protecting

Before any tool runs, write down four things. Ten minutes, and it decides which of the next 300
findings matter:

1. **The crown jewels.** What data or capability would actually hurt if it leaked or was abused?
   Money movement, PII, credentials, the ability to run code, the ability to delete.
2. **The trust boundaries.** Where does untrusted input enter? HTTP handlers, webhooks, file
   uploads, queue consumers, CLI arguments, environment, anything an LLM or an agent writes.
3. **The identities.** Who can call what. Anonymous, authenticated user, admin, service-to-service,
   CI. Note every place authorisation is decided, because that is where it will be missing.
4. **What is already known.** Read `SECURITY.md`, past incidents, `git log --grep=security`, open
   Dependabot alerts. Do not rediscover what someone already wrote down.

Write this into the report as the threat model. A finding that touches a crown jewel through a
trust boundary outranks any scanner severity rating.

## Phase 1: run everything the machine has

```bash
.claude/whitebox-audit.sh --deep            # every applicable scanner, machine-readable output
.claude/whitebox-audit.sh --list            # what is installed and what is missing
```

The engine ships with the overlay (`claude/whitebox-audit.sh` in vstack). It detects the stack and
runs what applies: gitleaks and trufflehog and detect-secrets for secrets, semgrep and CodeQL for
cross-language taint, bandit/ruff for Python, npm audit and retire and eslint for JS, gosec and
govulncheck for Go, cargo-audit and cargo-deny and clippy for Rust, brakeman for Ruby, phpstan and
psalm for PHP, shellcheck for shell, hadolint and trivy and grype for containers, checkov and
terrascan and kics for IaC, zizmor and actionlint for GitHub Actions, osv-scanner for
dependencies, syft for an SBOM.

Read its three exit codes as they are meant:

- `0` at least one scanner ran and found nothing. **A statement about those tools, not about the
  code.**
- `1` something was found. Triage it below.
- `2` **nothing produced a result.** Either no scanner was installed, or every one that ran
  errored. This is not a clean report. Install scanners (`--list` prints the install command for
  each), fix the errors it names, and run it again. A security report produced by zero scanners is
  worse than no report, because it has a timestamp on it.

An `ERROR` line is not a finding and it is not a pass: it is a hole in coverage, and the report in
phase 5 has to name it. A tool that could not start has told you nothing about the code.

Missing tools are named individually with their install command. Install the ones that apply to
this stack before you believe the result — an audit that skipped the only scanner for the language
the service is written in has not audited the service.

**The scanners are the floor, not the ceiling.** They find the known shapes. Phase 3 is where you
find what they cannot.

## Phase 2: triage by reachability, not by severity

Scanner severity is assigned by whoever wrote the rule, with no knowledge of your system. Re-rank
every finding yourself, on two axes:

- **Reachable?** Is there a path from a trust boundary to this code, with attacker-controlled
  input? Trace it. If you cannot find a path, say so and drop it to the bottom — with the trace
  you attempted, so someone can disagree.
- **What does it get them?** Read access to one log line is not read access to the users table.

Then three buckets:

| bucket | meaning | action |
|---|---|---|
| **exploit** | reachable and it reaches a crown jewel | prove it in phase 3, fix it in phase 4 |
| **harden** | real weakness, not currently reachable | fix if cheap, record the reasoning if not |
| **noise** | rule fired on a pattern that is fine here | suppress it *in the tool's config, with a comment naming why*, never by deleting the finding |

A suppression with no reason is how the next audit inherits your blind spot.

## Phase 3: think like the attacker, where no scanner goes

Scanners match patterns. These are the classes they systematically miss, and they are where the
real findings live. Work them by hand, against the trust boundaries from phase 0:

- **Authorisation, per object.** Every handler that takes an id: can user A pass user B's id?
  IDOR is the single most common real-world web vulnerability and no SAST tool finds it, because
  the code looks perfectly correct.
- **Authentication edges.** Token expiry actually checked? Signature verified with the right
  algorithm, and is `alg: none` rejected? Password reset tokens single-use, time-limited, tied to
  the user? Session fixation after privilege change?
- **Multi-step and state.** Race conditions on balance or quota (fetch-then-write with no lock),
  workflows that can be entered at step 3, idempotency keys that are not.
- **Server-side request forgery.** Anything that fetches a URL the user supplied. Check redirects
  and DNS rebinding, not just the first hostname.
- **Injection past the ORM.** Raw query escape hatches, template rendering with user data,
  `eval`/`exec`/deserialisation, shell calls built by string concatenation, argument injection
  into a CLI you shell out to.
- **Secrets and their lifetime.** Not just "is a key in the repo" but: is it in git history
  (`gitleaks git --log-opts=--all`), in a build arg, in a log line, in an error page, in a client
  bundle? Rotate anything found in history; deleting the file changes nothing.
- **The supply chain.** Unpinned actions, install scripts, typosquat-adjacent names, a dependency
  that was updated by a maintainer nobody knows. `zizmor` covers workflows; the rest is judgement.
- **Denial of service by design.** Unbounded pagination, regex on user input, file upload with no
  size cap, an endpoint that does N queries where N is user-supplied.
- **Agent and LLM boundaries.** Prompt injection into any content the model reads, tool calls with
  unvalidated arguments, an agent with credentials wider than the task needs.

For each candidate, get to a demonstration. A failing test in the repo is the best form: it stays
after you leave.

## Phase 4: fix, and prove the fix

Per finding, in this order:

1. **Write the exploit as a test first.** It must fail on today's code, for the security reason —
   not because the route 404s.
2. **Fix the root cause.** Not the symptom, and not one call site of five. If input validation is
   missing, put it at the boundary (see the boundary-discipline principle), not in every consumer.
3. **Watch the test pass.** Then run the full suite: security fixes break behaviour more often
   than most changes, and a fix that breaks the product will be reverted by someone in a hurry.
4. **Re-run the engine** and confirm the finding is gone from `findings.jsonl` rather than trusting
   that it should be.
5. **Check the sibling instances.** Almost every real finding has copies. `grep` for the pattern
   across the repo before closing it.

Fix in small verified commits, one class of defect per commit. A single "security fixes" commit of
forty files cannot be reviewed and cannot be reverted selectively.

## Phase 5: the report

Ship a report that a person can act on and disagree with:

- The threat model from phase 0.
- **What was measured**: every tool that ran, and every tool that did not, by name. Coverage is
  the first thing an honest report states.
- Per finding: where it is, how it is reached, what it gets an attacker, the demonstration, the
  fix, and the commit.
- What you looked for and did not find — the phase 3 classes you worked. This is what stops the
  next person repeating it.
- What is still open, with a reason and an owner.

Never write "no vulnerabilities found". Write "these N tools and these M manual checks found
nothing in these areas; these areas were not covered."

## Rules of engagement

- **Never point an active-attack tool at something you do not own.** The engine keeps nuclei,
  nikto and ZAP behind `--dast URL --i-own-this-target` for exactly this reason. Staging, with the
  owner's say-so, or nothing.
- **Do not exfiltrate.** Prove read access with a row count, not by dumping the table. Prove RCE
  with `id`, not with a shell you keep.
- **Do not test destructive paths in production.** Ever. A proof that deletes real data is not a
  proof, it is an incident with your name on it.
- **Rotate what you find.** A key committed and then deleted is still valid. Finding it is half
  the job.
- If the target is not yours and you have no written authorisation, stop and say so.

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.