All lessons › Function C — Application Security with an AI SDLC › The AI SDLC — an Agentic AppSec Pipeline, Before and After Deploy
C2.12

Black-box agentic pentest — inference, and refusing to report it as fact

C2
▶
Lightboard recording

Not recorded yet. The lesson below is complete and runnable today — the video is added later without touching this page.

Video placeholderpublishes automatically from recordings/C2.12.mp4
Risk

An agent narrates a confident architecture from status codes, and the report is fiction that reads like findings.

Control

Every claim is labelled observed or inferred, with the evidence that supports it, and inferences never carry a severity.

The lab

Score twelve claims from an external probe run and see which survive the observed/inferred split.

◉Use case relevance

From outside, an agent will tell you the target runs Django on PostgreSQL with asynchronous refunds, in fluent prose, from a cookie name and a stack trace. Some of that is entailed by the evidence and most of it is a guess, and the report does not say which until you make it.

At CyberTravels. pentest.Claim makes the distinction structural rather than editorial: an inference cannot be given a severity, and an observation with no evidence will not construct. report_claims() prints the inference ratio in the header, because a black-box report that is 80% inference is a hypothesis document and saying so is what makes the other 20% worth reading.

◴What this lesson is — Day 0, Day 1, Day 2

Keeping observation and inference apart structurally rather than editorially — an inference cannot carry a severity, an observation cannot exist without evidence — and publishing the ratio, which is what makes the observed part worth acting on.

Day 0 — why

An agent narrates a confident architecture from status codes, and the report is fiction that reads like findings.

Day 1 — how

Label every claim observed or inferred, keep the evidence beside it, and attach no severity to an inference.

Day 2 — measure

Findings versus open questions — five and seven on the sample run — and what each open question would need to resolve it.

Measure this

what to measuredenominator or target
findings against open questionstwo counts — five and seven on the sample run
what each open question would need to resolve itnamed, per question

▦The framework, and how it works

   evidence                 claim                         verdict

   handshake completed      serves TLS 1.3                OBSERVED  -> finding
   200 known / 404 random   /bookings/{id} exists         OBSERVED  -> finding
   404 on a random id       returns ANOTHER tenant's       INFERRED  -> question
   csrftoken cookie         runs Django                    INFERRED  -> question
   1.9s vs 120ms            refunds are synchronous        INFERRED  -> question

   a finding carries a severity. a question carries what would resolve it.
   an inference with a CVSS score is a finding to everyone downstream.

Black box is the mode with the least information and the most room to narrate. An agent handed status codes, headers, error strings and timings will produce a fluent architecture, and every sentence of it will be plausible. Some of it is entailed by the evidence. Most of it is not, and the report does not separate them unless you make it.

The single discipline is provenance per claim. For each thing the agent wants to assert, keep the actual observation beside it and ask one question: does this evidence entail the claim, or is it merely consistent with it?

  • A 404 on a random id is consistent with a missing object and with correct authorisation refusing you. It entails neither.
  • A csrftoken cookie is consistent with Django and with anything that copied its conventions.
  • 1.9-second latency on refunds is consistent with synchronous processing and with a dozen other causes.

Observed claims are findings. Inferred ones are open questions, and they carry no severity — because an inference with a CVSS number beside it reads as a finding to everyone downstream, and the person who has to retract it is never the person who wrote it.

The open questions are not filler. Each one says what mode would resolve it, and the ones that need a second credential are telling you the engagement should become grey box (C2.13) rather than a more elaborate black-box guess.

2 · Demo — twelve claims from an external probe, with their evidence

3 · Where it breaks — the fluent architecture the evidence does not support

4 · The control — observed vs inferred, and no severity on a guess

5 · The procedure, as a skill

The skill takes twelve claims from an outside probe of CyberTravels and sorts them into two piles:

  • Claims the evidence proves.
  • Claims the evidence merely suggests.

It reports the two piles separately — findings in one, open questions in the other. Neither pile gets a severity attached to a guess.

▶Real time execution as skill

The skill — skills/redteam/blackbox-claim-provenance/SKILL.md

name: blackbox-claim-provenance
description: >-
  Split an external probe's claims into what the evidence entails and what it
  merely suggests, and refuse to attach a severity to the second kind. Use for a
  black-box engagement, or when a report reads more confidently than its
  evidence supports.
allowed-tools: Read, Grep, Glob

An inference with a severity beside it is a finding to everyone downstream

Black box is the mode with the least information and the most room to narrate. A model handed status codes, headers and timings will produce a fluent architecture, and every sentence of it will be plausible. Some of it is entailed by the evidence. Most of it is not, and the report does not distinguish them unless you make it.

When to use this

An external engagement with no credential, a bug-bounty triage queue, and any report where the reader cannot tell which claims were tested.

Procedure

1 — List what the mode may look at. DNS and certificate transparency, the TLS handshake, HTTP status/headers/body, error strings, response timing, public artefacts, the scope document. The list is short, and that is the constraint.

2 — Write each claim with the evidence beside it. Not a summary of the evidence — the actual observation that produced the claim.

3 — Ask one question per claim: does this evidence entail the claim, or is it merely consistent with it? A 404 on a random id is consistent with correct authorisation and with a missing object. A csrftoken cookie is consistent with Django and with anything copying its conventions.

4 — Report observed claims as findings and inferred ones as open questions, with no severity on the second list. Then say what each open question would need. The ones needing a second credential are grey-box tests, not better black-box ones.

Example

report: 5 findings, 7 open questions
no severity is attached to anything in the second list.

The run continues past this. test_skills.py executes the script on every build, with no model configured — so what CI proves is that it runs and refuses legibly, not that it prints these lines. The output above is a recorded run against a real model, and nothing re-diffs it: a model's answer is not reproducible, and this repository does not claim otherwise anywhere it can be checked.

Output contract

{
  "data_sources": [{"source": "str", "establishes": "str"}],
  "claims": [{"claim": "str", "evidence": "str", "entailed": true}],
  "findings": ["str"],
  "open_questions": [{"claim": "str", "would_need": "str"}]
}

Failure modes

  • Scoring an inference. A CVSS number converts a guess into a finding for every reader after you.
  • Treating untested as disproved. "No per-account rate limit" from a single address is a gap in the test, not a property of the system.
  • Fingerprinting from headers. They are copied, proxied and templated.
  • Dropping the open questions. They are the engagement's most useful output: they say what mode to run next.

Three ways to do this lesson. Pick one. They run the same procedure against the same code — what changes is who drives it, and how much you see.

1In your AI agent — you are taught

The agent reads the lesson, explains the idea, fetches the code, runs the check and answers your questions as you go. This is the one to take if you are here to learn.

Install it, once. From the folder you cloned. --all does every agent it finds on your machine:

python3 scripts/install_skills.py --all --lessons C2.12

Then restart your agent and open it in that folder. On Windows, if python3 says Python was not found, use python instead.

Then pick c2-12-black-box-agentic-pentest:

agentinstall just for this agentstart it
Claude Codepython3 scripts/install_skills.py --tool claude --lessons C2.12type /c2-12-black-box-agentic-pentest
GitHub Copilot (VS Code)python3 scripts/install_skills.py --tool copilot --lessons C2.12type /c2-12-black-box-agentic-pentest in Chat
Cursorpython3 scripts/install_skills.py --tool cursor --lessons C2.12type / in Agent chat and search for it
Codexpython3 scripts/install_skills.py --tool agents --lessons C2.12type $c2-12-black-box-agentic-pentest, or run /skills

2As one Python command — you just run it

No agent, no conversation. The same lesson executes and prints the same readback. Take this one if you have no assistant, if you are reviewing rather than learning, or if you want the result without the teaching around it.

python3 scripts/lesson.py C2.12

3Read the code, and the difference — nothing runs

Neither of the above, and no model needed. The first command writes CyberTravels exactly as it stood at the end of this lesson; the second prints the diff — the lines this lesson added or changed, and nothing from any later one.

python3 scripts/checkpoint.py --at C2.12 --out work/cybertravels
python3 scripts/checkpoint.py --at C2.12 --diff

On Windows, type python wherever these say python3 — and use \ instead of / in the paths. Everything else is identical. A0.0 step 2 explains why.

Ways 1 and 2 need a model; way 3 does not. A signed-in Claude Code CLI needs no API key, and any OpenAI-compatible endpoint works too. With neither, the audit exits 2 and says so rather than inventing an answer. A0.0 sets this up.

Ways 1 and 2 both end with the same readback: What I did, What changed, The number and Read this next. A step that was skipped or refused is reported as exactly that, never as a pass.

If you did the last lesson’s challenge, expect this to stop. Most challenges ask you to edit the code, and ways 1 and 2 will not overwrite your edits: you get stopped, not a failure, with two options — --out for a fresh folder that keeps your work, or --force to discard it and start this lesson clean.

✓What you just proved

Five claims are entailed by their evidence and reported as findings; seven are only consistent with it and become open questions with no severity attached. The one that matters — whether the refund endpoint accepts a booking it does not own — is untestable with one account, so it is escalated to a grey-box test rather than guessed at.

✎Your turn

Take your last external report and mark every claim observed or inferred. The inferred ones that carry a severity are the ones a client can disprove, and disproving one is how they learn to discount the rest.

Tools used

Nuclei

Skill source: skills/redteam/blackbox-claim-provenance

← PreviousC2.11 · White-box agentic pentest — the source, and what it lets you prove