Documentation
How Penjester works
Written so you can decide whether to believe a result, which is the only thing that matters about a security tool.
The model
Penjester keeps four things: identities (an account and the credential set that proves it), requests (what the browser did, attributed to an identity), endpoints (a method, a host and a path template, with every request that matched it), and objects (identifier values, who owns them, and where they have been reachable).
Requests are capped and evicted oldest-first. Endpoints are never evicted, which is why anything that has to survive a long run or a restart is keyed to an endpoint rather than to a request.
Session attribution
Every recorded request is matched to the identity whose credentials it carried. This is harder than comparing a cookie, because the same product routinely gives two accounts cookies with different names, and because a session can quietly die mid-run.
Penjester scores a request against each saved identity across cookies, authorization headers and body tokens, and records the winner. Traffic that matches nobody is grouped as "unknown A", "unknown B", a grouping of what is in front of you, not an invented account.
The cookie, the X-…CSRF header, and the body parameter that header names.
Swapping one of three produces a 400 that looks exactly like a working access
control, which is the most expensive kind of false negative there is.
Verdicts
A replay gets one of these, and only the first is worth an evening.
| Verdict | Means |
|---|---|
| Got through | The other account received the owner's data, the endpoint does resolve its identifier, it answers consistently, it requires credentials, and the account was not already entitled to the object. |
| Open to anyone | The same data comes back with every credential stripped. Not a cross-account problem, a worse one. |
| Already entitled | This account had reached the same object on its own session before any replay ran. Reaching it again proves nothing. |
| Ignores the identifier | An identifier belonging to nobody returns the same reply, so the endpoint never resolved the one that was changed. |
| Inconsistent | The same request twice gave different answers. Neither is evidence yet. |
| Blocked / own data | The control held. Recorded, because coverage is the output. |
| Not sent | Refused by a guardrail. Untested, never clean. The reason is on the row. |
Evidence
Everything that decides a verdict is measured, and every measurement is carried to the write-up with the bar it was weighed against and where that bar came from.
- Owner markers returned, out of how many existed. The line a finding lives or dies on. Zero of twenty is evidence the control held; zero of zero means there was nothing to find and the test said nothing.
- Body similarity, a reading, never decisive. Two renders of one page are alike by construction.
- Size gap, against a learned floor. The floor is measured from the endpoint's own repeated reads, so a volatile endpoint is not judged by the same bar as a byte-stable one. Until it has enough samples the row says the threshold is a default.
- The actor's own baseline. If the reply looks more like this account's own view than the owner's, it was served its own data, the strongest benign explanation, and it is rendered rather than discarded.
A red row rests on the owner's data being in the reply, never on two pages looking alike. Only a count of returned owner values, or a value you declared yourself, can settle a finding. This is enforced in one function and asserted in the test suite rather than left as a convention.
Triage
Everything any engine notices passes through a gate before it reaches the feed. The
method is from the public bug-bounty methodology in
mdpsec/bug-bounty-hunting-prompts, built into the engine rather than
bolted on as prompts, so it applies whether or not a model is driving.
| Bucket | Means |
|---|---|
| Dig | Impact is demonstrated against this target. Not "a 200 came back". |
| Candidate | Plausible, and the evidence does not settle it. The work is to prove or disprove it. |
| Not applicable | Archived with its reason, never deleted. |
The gate filters by issue type, never by severity, and that distinction is the
whole reason it is trustworthy. A severity sweep would archive
Session token in URL alongside the cookie-flag advisories, and a token in
a URL leaks through the Referer header, proxy logs and browser history, so it is a real
finding. A filter that throws away one real finding is a filter nobody turns on twice.
Hardening misses are archived by name with the precondition that would make each one real: HSTS not enforced, cacheable HTTPS response, cookie without HttpOnly. The last of which only matters once a reachable XSS exists, and says so on the row.
Coverage
Coverage is counted per endpoint and per vulnerability class. A cross-account replay credits access control and nothing else; a fired payload credits the class it tests; the DOM and secret scans credit every endpoint they examined, not every hit they found, because counting only hits makes "we looked and it was clean" indistinguishable from "we never looked".
A category no part of the build can credit yet reads not measured, with the reason, rather than 0 %, which would look like work you have not got round to instead of work the tool cannot see.
The tabs
| Tab | What it is for |
|---|---|
| Dashboard | Counters, per-host coverage, what is left to do. |
| Proxy | Did it even go past? Intercept, identity swap, send-as. |
| History | Every recorded request with its result. Where auto-test is turned on. |
| Live audit | Every engine in one gated feed. |
| Sessions | Identity slots, capture, binding. |
| Map | The endpoint tree. Selecting a branch filters History. |
| Knowledge | What the application is, and what has not been checked. Survives restarts. |
| Analyser | The account × feature access-control matrix. |
| Findings | What three probes established. |
| AI findings | A model's opinion, beside Findings and never inside it. |
| DOM XSS | Source-to-sink results. Passive; sends nothing. |
| Vault | Cookies and tokens, plus the secret scan. |
| Notes · Claude · Local model · Tasks · AI pentest · Agent | Working notes, the model integrations, and the staged-payload queue you fire by hand. |
Proxy
A real intercept queue, owned by the extension rather than handed to Burp's own. Hold a request, edit it, forward it or drop it. Right-click a held request to swap an identifier for another account's real value, or rewrite the whole session to another account, both land in the editor, unsent, so forwarding stays the only thing that releases a request.
A request you modify in intercept is annotated, so it is findable in Burp's own HTTP history and in Penjester's, rather than being indistinguishable from the forty rows beside it.
in scope only is on by default and the allowlist fails closed, so with
no scope set nothing is ever held. The row says so, in those words, rather than
sitting there looking broken.
Live audit
Five sources merged into one gated feed: the cross-account sweep, Burp's own passive issues, reflections, the secret scan and the passive rule engine, each row carrying its bucket, its severity, the evidence it rests on, the benign explanations and what is against them, and what an attacker would need first. Archived rows are hidden behind a counted toggle, never deleted.
It is off until you turn it on, and the empty state says why it is empty rather than showing a confident zero.
Passive rules, and the Top Ten
Twenty-one rules read traffic you have already generated. None of them sends anything, so none of them can disturb a live system, and none pretends to settle a question that only a payload could. Every row carries its OWASP 2021 category, and the Top Ten… summary reports per category rather than as one score.
| Category | What the rules read |
|---|---|
| A01 | Write with no anti-forgery token · untested object endpoint · open redirect · permissive CORS. The cross-account sweep is the real answer here; these point at where to aim it. |
| A02 | Session identifier in a URL · cacheable personal data · a JWT with alg: none or a shared secret, read from the token's own header. No signature is checked and nothing is forged. |
| A03 | Input transformed or decoded on the way back. Partial, and it stays partial. Proving injection means sending a payload, which Burp Scanner does. |
| A04 | Not reachable from traffic. A control that was never designed is invisible in a reply. |
| A05 | Verbose error · missing hardening headers · host header reflected · edge-side includes · directory listing · a debug or admin surface answering 2xx, because a path match is not a finding. |
| A06 | Component version disclosed. It reports the version and explicitly refuses to claim it is vulnerable. That lookup is not done here. |
| A07 | Session cookie set without SameSite, Secure or HttpOnly. Only cookies that look like a session, and SameSite is tied to the CSRF candidates on the same host. |
| A08 | Third-party script with no integrity attribute. The page's security becomes that host's security. |
| A09 | Not reachable from traffic. Whether anybody noticed you is a question the target's logs answer, not its replies. |
| A10 | A parameter carrying a URL the server may fetch. Name and value must both point that way. A candidate; Collaborator settles it. |
Grep rules
Seven starters: debug or trace mode, internal hostname, private address, cloud metadata address, storage bucket URL, source map reference, admin or impersonation flag, and an editor for your own. A pattern that will not compile is reported as broken rather than silently matching nothing, which is what makes a grep list worse than useless.
Sensitive data
Email addresses, payment cards, national insurance and tax numbers, IBANs, keys and tokens, found in responses you already hold. Knowledge stores the rule name and the count, never the value. Persisting a full address would turn a Burp project file into a customer PII database for no benefit.
DOM XSS and JavaScript
Sources and sinks in the scripts the application actually loaded, a script inventory, and the flows between them. Reading, not probing: the DOM engines send nothing and cannot, by construction, move the coverage number for a class they never tested.
Knowledge
The application as a tree: host, then product area, then endpoint. An RPC endpoint that multiplexes thirty operations behind one path is shown as one branch with its operations listed, because inventing thirty branches would be the tool asserting a structure the application does not have.
Each endpoint carries its nature, the identifiers it holds, the vectors on it: which parameter, header or cookie decides what comes back, and what each is a candidate for, what classes of work have been done against it, and whether a reply has ever returned personal data. Your own marks and notes live here and are persisted.
Penjester records that an endpoint returned, say, four distinct email addresses. It does not store the addresses. A project file is not the place for somebody's customer list, and the question worth answering in the morning, which endpoints return personal data, is answered without them.
Working by hand
The sweep is the automated half. The other half is you, and these are the parts built for that, all of them staged rather than fired, because a payload crafted from an understanding of a feature is still a payload, and this tool does not press send.
- Send to Repeater, from everywhere. History, the proxy queue, Findings, Knowledge. A crafted payload arrives with the detail of what was crafted and why.
- Bypass attempts. Header overrides, method changes and path tricks, built from what the endpoint actually does rather than from a wordlist.
- Multi-step sequences. Where a control is spread across several requests and only the order of them matters.
- Write proof. A write that returns 200 has proved nothing. The change is confirmed by reading the object back as its owner.
- Staged payloads. A queue with the exact bytes visible. Nothing in it leaves until you fire it.
- Burp's own Scanner. Penjester does not duplicate payload scanning; it can point Burp Professional's active Scanner at one request and report progress. That is hundreds of requests per audit, so it sits behind an explicit tick.
Scope and accounts
Scope belongs to the engagement, so it lives in the Burp project rather than in your preferences; all four places that save it agree about that now. One engagement can hold several scopes at once, and every tab that shows scoped data filters by the one you are looking at.
Hosts are added from a list of everything Burp has seen, by right-click or by typing a URL that is parsed down to its host. Paste a full URL with a path and a query and you get the host. Membership is never inferred from “this host has rows”: traffic is traffic, scope is scope, and conflating them is how a tool quietly tests something it was never pointed at.
Accounts
An account is a credential set: cookies, authorization headers and body tokens, each one checked against what the browser is actually sending, with drift reported and a re-learn from traffic escape hatch. Capture them by right-clicking a request, or arm a capture and log in.
Sessions export to and import from a JSON file. An afternoon of logging in lives in that table, and this project asks you to untick the extension after every build. Nobody should have to weigh “is my work safe” against “load the new jar”. The file holds live credentials; treat it as one.
Pace, and stopping
The request rate is yours to set. One call stops everything in flight, and it is reachable over MCP as well as from the Dashboard, so an agent can be told to stand down by something that is not a person at a keyboard.
Guardrails
- Scope fails closed. Nothing outside the allowlist is recorded, replayed or held.
- Each registrable domain is confirmed once, by you, before anything is sent to it.
- Risk classification from the method and the path. Anything that looks like deletion, payment, role change or credential modification is classified high-risk.
- Automation and you are different callers. Destructive paths are refused to the sweep and permitted to your own deliberate click. That asymmetry is intentional and is not a bug to be tightened away.
- Every refusal is recorded with its reason and appears in the report.
Language models
Penjester can use a model, a local one or Claude, and the boundary is absolute:
- A model chooses which feature to look at and which result to write up.
- A model never composes a request, never picks a payload, never gets a shell. One function sends everything, and no model is on that path.
- No credential ever enters a prompt. The tool API returns cookie names. It cannot return a value.
- Nothing a model proposes is sent until you fire it. Staged payloads sit in a queue with the exact bytes visible.
This is why Penjester is not an "autonomous pentester". A model that can send requests to a live estate is a liability dressed as a feature, and the interesting part of this problem was never the sending.
Local models
Point Penjester at any OpenAI-compatible /v1/chat/completions endpoint:
Ollama, LM Studio, vLLM, llama.cpp, or a model on your own network. Paste the base URL
with or without the /v1; both are handled. A self-signed certificate on the
model host can be trusted explicitly, because the machine running your model is usually
not the machine with a public certificate on it.
On an engagement against somebody else's production system, nothing left the network is usually the only answer that passes a review. That is the reason this exists, rather than a preference.
- A bearer token for the endpoint lives in Burp's extension storage, never the project file, never a prompt, never the activity log.
- The tab shows the last exchange verbatim so you can check that for yourself. The
Authorizationheader is not in it. - Every tool the model may call is listed, including the few behind a tick and the two it never gets.
- You set how much of a tool's answer it is shown, how long a reply to ask for, how much conversation it carries, and which hosts it may work on.
Claude, and the context readout
The Claude tab drives the CLI already on your machine, so there is no second API key to manage. It shows context used, context remaining, and a compact that carries the prior conversation forward rather than discarding it.
It works with no model at all
Every verdict in this document is reached by measurement. A model is a reader and a writer here. Turn it off and you lose the prose, not the findings.
The MCP server
Penjester exposes fifty-three tools over MCP on a loopback port, so a local model or an agent can read what it knows: the attack surface of a request, the object graph, candidate identifier swaps, coverage, the host profile, and ask for work to be staged. Everything a tool returns is redacted the same way the UI redacts it. Reading is free; sending is still yours.
| Group | Tools |
|---|---|
| State | hunter_status get_scope list_identities list_endpoints object_graph object_pairs app_profile attack_surface list_objects pentest_brief engagement activity_log |
| Testing | run_auth_tests replay_as stage_payload fire_staged replace_and_send active_audit audit_progress check_payload cross_account_candidates swap_candidates |
| Findings | file_findings file_direct_access list_findings list_checks requests_for_action send_to_repeater send_to_organizer repeater_registry |
| Passive | search_responses scan_reflections scan_secrets js_sinks fetch_scripts list_js_flows list_dom_findings list_scanner_issues find_requests get_request |
| Setup | add_scope set_pace set_canary set_session_cookies refresh_anti_forgery ensure_account arm_session_capture |
| Control | task_plan task_step task_done stop_all |
list_identities returns cookie names, never values. There is no tool
that returns a credential, no tool that opens a shell, and no tool that composes a request
from free text. If the driver ever needs a cookie jar, that is a loopback-only endpoint
behind a one-shot token, not an MCP tool.
What persists
Into the Burp project file: your sessions, scope, the domains you confirmed, your IDOR rules, what the application taught the tool about itself, the learned noise floors, and the Knowledge store: your marks, your notes, personal-data sightings and recorded work. Autosaved every fifteen seconds, because a Burp that is killed never gets to run a shutdown hook.
The model endpoint and API key go to preferences rather than the project, so a project file you hand to somebody else does not carry your key.
Not built yet
Penjester refuses to let a skipped test look like a clean one, so a page describing it that quietly included intentions would be the same failure in a different place. These are real, and they are not finished.
Five entries left this table on 17 October 2026, having been built. The request
journal is read by requests_for_action, which tallies each click's
requests and flags when the account that sent them is not the account the driver
intended. The browser driver logs in, surveys, explores and reports under a
1,290-check suite of its own. The method-override, URL-override and multi-step
bypasses are implemented rather than carried as gaps. A session that dies mid-run is
detected before the next cross-account test and the account is logged back in, with
anything tested in between reported as untested rather than clean. And owner markers
are declared per field: an account's email, organisation and display name, so a
leak is reported as whose and which field rather than as a
distinctive-looking string; the driver declares them at login, because it typed the
login and therefore knows them outright.
| What | State |
|---|---|
| The report | Partial. Findings are grouped and written up; they are not yet clustered by root cause, so forty routes sharing one flaw still read as forty entries rather than one flaw reachable forty ways. |
FAQ
Does it work with Burp Community?
Yes. The only thing missing is Burp's active scanner, which Penjester drives for injection work it deliberately does not do itself.
Will it find SQL injection?
No, and it will not pretend to. Burp Scanner is good at that. Penjester stays on access control, which nothing else covers.
How many accounts do I need?
Two to be useful. Three, two in one organisation and one in another, to tell a tenant-isolation failure from a within-org IDOR.
Will it break the application I am testing?
Automation will not send to paths classified destructive. You can, deliberately, from a right-click. Nothing runs on a schedule and nothing starts on load.
Why does it report things that are not findings?
It reports what the control held on, and what it never reached, because a findings list that only shows failures cannot tell "we tested this and it held" from "we never tested this". Those rows are archived, not hidden.
Is my traffic sent anywhere?
No. Everything is in your Burp. The only outbound traffic is to the target you scoped, and to a model endpoint if you configure one, which can be a local one.