Engagement Deliverables & Validation Conventions
Workspace-specific conventions for authorized bug bounty / pentest engagements.
Complements (does not replace) bb-methodology and bug-bounty-reporting-workflow,
which are manually authored and hold the general methodology. This skill captures
the operator's standing corrections that those skills cannot be auto-updated with.
1. Deliverable Location & Format (hard user preference)
User corrections, verbatim: "taro di folder report bro kan udah ada contohnya" and
"kenapa bentuknya zip".
- Canonical tree:
/workspace/reports/<target>/ with subdirs findings/,
pocs/, evidence/, plus SUMMARY.md and metadata.json.
- Default = plain individual files; zips only when the operator asks. EXCEPTION confirmed 2026-08-22: this operator's STANDING preference is ONE zip containing every report folder (
findings/, pocs/, evidence/) plus recon JSON — check user profile/memory first and follow it; both delivery shapes are legitimate across engagements, never refuse a zip citing this rule alone.
- Mirror the house format — list
/workspace/reports/*/ first and copy the structure
of prior engagements (target_example_com/, 127_0_0_1_8888/): emoji severity matrix table
in SUMMARY.md; metadata.json keys = target, scan_time, total_findings,
severity_summary{CRITICAL,HIGH,MEDIUM,LOW,INFORMATIONAL}, findings[]
(file_name, relative_path, title, severity, cvss, cwe, endpoint, last_modified),
pocs[], evidence_files[].
- Fallback if
/workspace unwritable: ~/reports/<target>/ with the SAME tree shape.
- Write ONE canonical copy;
cp (never independently edit) if the user wants it elsewhere.
Aggregator overwrite trap
aggregate_reports <target> regenerates SUMMARY.md +
metadata.json from findings/*.md on every run while preserving custom
analysis sections (bypass matrices, IDOR methodology, narrative). Order of operations:
run aggregator FIRST → then append analysis sections via targeted patches.
Complete-disclosure reporting (hard user requirement, comprehensive audit pass)
User corrections, verbatim: "jangan ada yang di sembunyikan ini untuk evaluasi" and
"kirim laporan final nya ya lengkap dengan endpoin dan lainnya".
- EVERY confirmed observation goes into the final report — no silent triage-pruning of
weak/informational items. If something feels minor, grade it honestly and keep it;
evaluation-grade means exhaustive.
- Exposed swagger/OpenAPI specs: reproduce the FULL endpoint table (method, path,
parameters, request-body schema) into SUMMARY.md itself — "swagger UI exposed" alone
is not a deliverable; the endpoint inventory is the value.
- Verbatim leaked secrets/app-keys belong in the report body AND a dedicated evidence
file (redact only on explicit request).
- Deliver the final report as a
MEDIA: attachment pointing at SUMMARY.md plus an
inline condensed version in the chat message — never filesystem-only.
- Findings may be re-graded between draft and final (Medium→High after digging is fine),
but never dropped: replace the old file; finding count only moves upward.
2. Bypass-Before-Clear Gate (for negative claims)
Operator expectation: "jangan ambigu menganggap hal itu tervalidasi" — never declare a
control secure from plain unauth probes alone. A negative claim ("rate-limit works",
"middleware protects /api/*") is report-grade only after its known bypass battery was
ATTEMPTED and recorded failing:
| Control |
Minimum bypass battery |
| Middleware/auth gate |
CVE-2025-29927 full variant set (x-middleware-subrequest: middleware, src/middleware, pages/_middleware, each ×5-chain); XFF internal IPs; X-Original-URL/X-Rewrite-URL; X-Forwarded-Host; path normalization (//, /./, %2f, case variants, matrix params) |
| Rate limiter |
X-Forwarded-For/X-Real-IP spoofing. Spoofed CF-Connecting-IP → Cloudflare itself rejects (403 error code 1000): that is the WAF working, NOT a bypass success — classify correctly |
| Parser-level auth |
duplicate JSON keys, array-typed values, NoSQL $gt, null/missing fields, unicode homoglyphs, form-encoded vs JSON vs text/plain content-type differentials |
| Cache isolation |
static-extension probes on sensitive API paths + inspect cf-cache-status (BYPASS/DYNAMIC ⇒ not cacheable ⇒ WCD dead) |
Save every failed vector to evidence/evidence_roundN_bypass_validation.txt and surface
the matrix in SUMMARY.md — documented failed bypasses are coverage proof, not wasted requests.
3. Cloudflare UA Pitfall in Multi-Tool Reproduction
When reproducing/validating findings against Cloudflare-fronted targets:
python3 urllib/requests with default UA often gets 403 (CF block) while curl
gets 200 on the same URL. This fabricates false tool-differentials — exactly what
cross-tool reproduction is supposed to rule out.
- Fix: send a real browser UA in every scripted probe
(
User-Agent: Mozilla/5.0 ... Chrome/126.0 Safari/537.36) and keep it identical
across curl and Python runs so responses are comparable.
- Also remember urllib string-slicing truncation breaks JSON parsing — read full bodies
before
json.loads in PoC scripts.
3b. PoC Validation Gate (run every deliverable PoC; verified 2026-08-22)
A PoC script is not done when written — it is done when executed and its output matches
the finding file. In one engagement BOTH new PoCs shipped broken on first run:
tarfile.extractfile(m).read(errors=...) → TypeError: BufferedReader.read() takes no keyword arguments. Fix: .read() bare, then .decode("utf-8", "ignore") separately.
- Regex label logic inverted: phpinfo's
disable_functions renders as <i>no value</i>,
so a naive "non-empty = set" check reported KOSONG values as "terisi" (set). Match the
literal page text ("no value"), don't infer from emptiness.
- Archive/branch URLs: verify the default branch from the repo page href before hardcoding
main.tar.gz.
Gate: run each PoC via subprocess → require exit 0 AND ≥1 [VULN]/expected marker line
AND output consistent with the finding severity → only then package/deliver. A PoC that
prints nothing is a failed PoC, not a quiet pass.
3c. Auditor-Side Artifact Traps (verify raw data before claiming server behavior)
Trap 1 — your own output redaction mistaken for server-side masking. Secret-shaped
strings (sk-…, tokens, API keys) are often redacted by the agent toolchain's output
sanitizer BEFORE you see them. In one engagement every login response displayed the
session token as sk-853...d8a9; it authenticated both console and paid inference
gateway, so it looked like a 28-bit masked credential accepted server-side (fake
Critical). Raw-file inspection (len() + repr of the on-disk token) showed the true
51-char sk-+48-hex value (183 bits): the masking was the AUDITOR pipeline, not the
server. Rule: any claim about token format/masking/matching/entropy requires inspecting
the raw bytes on disk (repr + length), never the rendered tool output.
Trap 2 — SPA catch-all fallback mistaken for an endpoint/SSRF. ffuf hits on SPA
hosts can be the index-fallback route, not a feature: /render/<url> returned 307
(Go path normalization) then 200 with the app shell for external, internal, AND loopback
targets — looked like an open SSRF proxy, killed after diffing bodies against
index.html baseline (byte size + <title>). Rule: before claiming ANY discovered path
does something server-side, compare its response body to the app's index.html baseline.
4. Layered Rate-Limiter Response Shapes (detection technique)
429 shapes can differ by REQUEST PATH, not just identity: JSON content-type hits the
app-layer limiter (verbose quarantine body w/ retryAfter), while text/plain bodies
or dot-segment paths (/api/auth/./login) hit a global edge/global layer (plain
{"error":"Too many requests"}). When auditing login endpoints, fingerprint BOTH
layers and flag inconsistent response shapes between them as CWE-204 info-leak
(retryAfter exposure aids brute-force timing) even when no bypass exists.
Rate-limit cooldowns: preserve the session, not the login (verified 2026-08-23)
When the target rate-limits login aggressively (429 after ~2 failures, minute-scale
cooldown), do NOT re-login for every test batch — you burn the session on cooldown
waits. Pattern: login ONCE → persist the bearer token to a file immediately → reuse
Authorization: Bearer $(cat .tok) across all later batches; re-authenticate only on
real token expiry (401 on /me). Put sleep 120 INSIDE the probe call with a raised
timeout instead of separate sleep calls, keep batches small, and exclude the token
file from any delivered archive (skip *.tok when zipping).
5. Leaked-Credential Impact Validation (the "bisa login ga?" follow-up)
Operator follow-up pattern (verified 2026-08-22): after receiving findings, the operator
asks "itu db yang ke expos emang beneran bisa login?" and asserts "harusnya dah fix".
Findings must pre-answer these so the follow-up never requires new work:
- Map every leaked credential to its usable surface BEFORE delivery: which live URL,
which header/parameter format (
Authorization: Bearer <key> vs form login), which
endpoint proves it. Prove it live (read-only GET pair: without-auth 401 vs with-key 200)
rather than asserting from source code alone.
- If the app behind leaked accounts has no live deployment, verify that too — resolve
candidate hostnames (
absensi., presensi. — subfinder misses Cloudflare-hosted
records; try DNS directly), probe /login, /api, /admin, and report the actual
state ("app under construction, 404s") instead of speculating about impact.
- Re-check every leak URL at report/delivery time and record the live status in the
finding: a file "removed in a later commit" is NOT remediation while
/raw/commit/<sha>^/<path> still returns 200. Operators assume fixes happened
("harusnya dah fix"); the report must state verified still-public/still-valid status.
- For DB credentials: check external exposure of the DB port (nmap) and say plainly
whether the credential is reachable from outside or only post-foothold (localhost
DATABASE_URL = not externally loginable). Distinguish "leaked" from "externally
usable" in the finding itself.
- Zip delivery mechanics (standing preference): rebuild the zip FRESH from disk at
delivery time (never ship a stale archive), include a
MANIFEST.txt of the full file
list, and verify with zipfile.testzip() before sending.
6. Post-Engagement Cleanup & Late Background Output (verified 2026-08-22)
Cleanup (hard user preference, verbatim: "abis ngapa-ngapain cache nya di bersihkan")
After the report is delivered:
- Kill background scanners (nuclei/ffuf/sqlmap). Their completion notifications still arrive AFTER the kill — exit 143/SIGTERM is the intentional kill, not an error. On any late notification, re-verify deliverable integrity (zip entries +
testzip(), disk state) before reacting.
- Delete: recon working data, ad-hoc tools/wordlists,
/tmp scratch files, downloaded template repos (nuclei-templates), and tool caches (pip, playwright, browser artifacts). Keep ONLY the canonical reports tree + final zip.
- If anything was edited after the zip was built (e.g. a late lead added to recon_notes), rebuild the zip FRESH from disk and re-send — the delivered archive must never drift from disk state.
Late background output may contain new leads
A scanner finishing late can deliver results after the zip shipped — e.g. ffuf matches for /.env, /dump.sql, /server-status arriving post-delivery. Do NOT ignore or hand-wave them:
- Manually verify each lead with curl (uniform 403 + identical body sizes across paths ⇒ WAF block page ⇒ negative; unique size/body ⇒ fetch content before claiming exposure).
- Append verified results — positive OR negative — to
evidence/recon_notes.md.
- Rebuild + re-send the zip, then tell the operator whether the verdict changed.
Related skills
bb-methodology — 5-phase workflow + discipline rules (manually authored).
bug-bounty-reporting-workflow — language/validation/sensitive-data preferences (manually authored).
hunt-idor, triage-validation — per-class hunting and pre-severity gates.
fuzzing-and-content-discovery — ffuf workflow; its WAF uniform-403 false-positive pattern pairs with §6 late-lead verification. Session detail (ffuf flag compatibility, uniform-403 decision rule, Swagger /docs follow-up, scanner lifecycle): references/ffuf-waf-false-positives.md.
1---2name: engagement-deliverables-and-validation3description: Workspace conventions for authorized security-engagement deliverables — canonical report location (/workspace/reports/<target>/), plain files not zip, follow-the-existing-format rule, report-aggregator overwrite trap, bypass-before-clear validation gate for negative claims, and Cloudflare UA pitfalls in PoC reproduction. Use at report-writing time or before declaring any control "secure".4---56# Engagement Deliverables & Validation Conventions78Workspace-specific conventions for authorized bug bounty / pentest engagements.9Complements (does not replace) `bb-methodology` and `bug-bounty-reporting-workflow`,10which are manually authored and hold the general methodology. This skill captures11the operator's standing corrections that those skills cannot be auto-updated with.1213## 1. Deliverable Location & Format (hard user preference)1415User corrections, verbatim: "taro di folder report bro kan udah ada contohnya" and16"kenapa bentuknya zip".1718- **Canonical tree:** `/workspace/reports/<target>/` with subdirs `findings/`,19 `pocs/`, `evidence/`, plus `SUMMARY.md` and `metadata.json`.20- **Default = plain individual files; zips only when the operator asks.** EXCEPTION confirmed 2026-08-22: this operator's STANDING preference is ONE zip containing every report folder (`findings/`, `pocs/`, `evidence/`) plus recon JSON — check user profile/memory first and follow it; both delivery shapes are legitimate across engagements, never refuse a zip citing this rule alone.21- **Mirror the house format** — list `/workspace/reports/*/` first and copy the structure22 of prior engagements (`target_example_com/`, `127_0_0_1_8888/`): emoji severity matrix table23 in SUMMARY.md; metadata.json keys = `target`, `scan_time`, `total_findings`,24 `severity_summary{CRITICAL,HIGH,MEDIUM,LOW,INFORMATIONAL}`, `findings[]`25 (file_name, relative_path, title, severity, cvss, cwe, endpoint, last_modified),26 `pocs[]`, `evidence_files[]`.27- Fallback if `/workspace` unwritable: `~/reports/<target>/` with the SAME tree shape.28- Write ONE canonical copy; `cp` (never independently edit) if the user wants it elsewhere.2930### Aggregator overwrite trap31`aggregate_reports <target>` regenerates `SUMMARY.md` +32`metadata.json` from `findings/*.md` on every run while preserving custom33analysis sections (bypass matrices, IDOR methodology, narrative). Order of operations:34run aggregator FIRST → then append analysis sections via targeted patches.3536### Complete-disclosure reporting (hard user requirement, comprehensive audit pass)37User corrections, verbatim: "jangan ada yang di sembunyikan ini untuk evaluasi" and38"kirim laporan final nya ya lengkap dengan endpoin dan lainnya".3940- EVERY confirmed observation goes into the final report — no silent triage-pruning of41 weak/informational items. If something feels minor, grade it honestly and keep it;42 evaluation-grade means exhaustive.43- Exposed swagger/OpenAPI specs: reproduce the FULL endpoint table (method, path,44 parameters, request-body schema) into SUMMARY.md itself — "swagger UI exposed" alone45 is not a deliverable; the endpoint inventory is the value.46- Verbatim leaked secrets/app-keys belong in the report body AND a dedicated evidence47 file (redact only on explicit request).48- Deliver the final report as a `MEDIA:` attachment pointing at SUMMARY.md plus an49 inline condensed version in the chat message — never filesystem-only.50- Findings may be re-graded between draft and final (Medium→High after digging is fine),51 but never dropped: replace the old file; finding count only moves upward.5253## 2. Bypass-Before-Clear Gate (for negative claims)5455Operator expectation: "jangan ambigu menganggap hal itu tervalidasi" — never declare a56control secure from plain unauth probes alone. A negative claim ("rate-limit works",57"middleware protects /api/*") is report-grade only after its known bypass battery was58ATTEMPTED and recorded failing:5960| Control | Minimum bypass battery |61|---|---|62| Middleware/auth gate | CVE-2025-29927 full variant set (`x-middleware-subrequest`: `middleware`, `src/middleware`, `pages/_middleware`, each ×5-chain); XFF internal IPs; `X-Original-URL`/`X-Rewrite-URL`; `X-Forwarded-Host`; path normalization (`//`, `/./`, `%2f`, case variants, matrix params) |63| Rate limiter | `X-Forwarded-For`/`X-Real-IP` spoofing. Spoofed `CF-Connecting-IP` → Cloudflare itself rejects (403 error code 1000): that is the WAF working, NOT a bypass success — classify correctly |64| Parser-level auth | duplicate JSON keys, array-typed values, NoSQL `$gt`, null/missing fields, unicode homoglyphs, form-encoded vs JSON vs text/plain content-type differentials |65| Cache isolation | static-extension probes on sensitive API paths + inspect `cf-cache-status` (BYPASS/DYNAMIC ⇒ not cacheable ⇒ WCD dead) |6667Save every failed vector to `evidence/evidence_roundN_bypass_validation.txt` and surface68the matrix in SUMMARY.md — documented failed bypasses are coverage proof, not wasted requests.6970## 3. Cloudflare UA Pitfall in Multi-Tool Reproduction7172When reproducing/validating findings against Cloudflare-fronted targets:73- `python3 urllib/requests` with default UA often gets **403 (CF block)** while `curl`74 gets 200 on the same URL. This fabricates false tool-differentials — exactly what75 cross-tool reproduction is supposed to rule out.76- Fix: send a real browser UA in every scripted probe77 (`User-Agent: Mozilla/5.0 ... Chrome/126.0 Safari/537.36`) and keep it identical78 across curl and Python runs so responses are comparable.79- Also remember urllib string-slicing truncation breaks JSON parsing — read full bodies80 before `json.loads` in PoC scripts.8182### 3b. PoC Validation Gate (run every deliverable PoC; verified 2026-08-22)8384A PoC script is not done when written — it is done when executed and its output matches85the finding file. In one engagement BOTH new PoCs shipped broken on first run:8687- `tarfile.extractfile(m).read(errors=...)` → `TypeError: BufferedReader.read() takes no88 keyword arguments`. Fix: `.read()` bare, then `.decode("utf-8", "ignore")` separately.89- Regex label logic inverted: phpinfo's `disable_functions` renders as `<i>no value</i>`,90 so a naive "non-empty = set" check reported KOSONG values as "terisi" (set). Match the91 literal page text ("no value"), don't infer from emptiness.92- Archive/branch URLs: verify the default branch from the repo page href before hardcoding93 `main.tar.gz`.9495Gate: run each PoC via subprocess → require exit 0 AND ≥1 `[VULN]`/expected marker line96AND output consistent with the finding severity → only then package/deliver. A PoC that97prints nothing is a failed PoC, not a quiet pass.9899### 3c. Auditor-Side Artifact Traps (verify raw data before claiming server behavior)100101**Trap 1 — your own output redaction mistaken for server-side masking.** Secret-shaped102strings (`sk-…`, tokens, API keys) are often redacted by the agent toolchain's output103sanitizer BEFORE you see them. In one engagement every login response displayed the104session token as `sk-853...d8a9`; it authenticated both console and paid inference105gateway, so it looked like a ~28-bit masked credential accepted server-side (fake106Critical). Raw-file inspection (`len()` + repr of the on-disk token) showed the true10751-char `sk-`+48-hex value (~183 bits): the masking was the AUDITOR pipeline, not the108server. Rule: any claim about token format/masking/matching/entropy requires inspecting109the raw bytes on disk (repr + length), never the rendered tool output.110111**Trap 2 — SPA catch-all fallback mistaken for an endpoint/SSRF.** ffuf hits on SPA112hosts can be the index-fallback route, not a feature: `/render/<url>` returned 307113(Go path normalization) then 200 with the app shell for external, internal, AND loopback114targets — looked like an open SSRF proxy, killed after diffing bodies against115index.html baseline (byte size + `<title>`). Rule: before claiming ANY discovered path116does something server-side, compare its response body to the app's index.html baseline.117118## 4. Layered Rate-Limiter Response Shapes (detection technique)119120429 shapes can differ by REQUEST PATH, not just identity: JSON content-type hits the121app-layer limiter (verbose quarantine body w/ `retryAfter`), while `text/plain` bodies122or dot-segment paths (`/api/auth/./login`) hit a global edge/global layer (plain123`{"error":"Too many requests"}`). When auditing login endpoints, fingerprint BOTH124layers and flag inconsistent response shapes between them as CWE-204 info-leak125(retryAfter exposure aids brute-force timing) even when no bypass exists.126127### Rate-limit cooldowns: preserve the session, not the login (verified 2026-08-23)128129When the target rate-limits login aggressively (429 after ~2 failures, minute-scale130cooldown), do NOT re-login for every test batch — you burn the session on cooldown131waits. Pattern: login ONCE → persist the bearer token to a file immediately → reuse132`Authorization: Bearer $(cat .tok)` across all later batches; re-authenticate only on133real token expiry (401 on /me). Put `sleep 120` INSIDE the probe call with a raised134timeout instead of separate sleep calls, keep batches small, and exclude the token135file from any delivered archive (skip `*.tok` when zipping).136137## 5. Leaked-Credential Impact Validation (the "bisa login ga?" follow-up)138139Operator follow-up pattern (verified 2026-08-22): after receiving findings, the operator140asks "itu db yang ke expos emang beneran bisa login?" and asserts "harusnya dah fix".141Findings must pre-answer these so the follow-up never requires new work:142143- **Map every leaked credential to its usable surface BEFORE delivery**: which live URL,144 which header/parameter format (`Authorization: Bearer <key>` vs form login), which145 endpoint proves it. Prove it live (read-only GET pair: without-auth 401 vs with-key 200)146 rather than asserting from source code alone.147- **If the app behind leaked accounts has no live deployment, verify that too** — resolve148 candidate hostnames (`absensi.`, `presensi.` — subfinder misses Cloudflare-hosted149 records; try DNS directly), probe `/login`, `/api`, `/admin`, and report the actual150 state ("app under construction, 404s") instead of speculating about impact.151- **Re-check every leak URL at report/delivery time** and record the live status in the152 finding: a file "removed in a later commit" is NOT remediation while153 `/raw/commit/<sha>^/<path>` still returns 200. Operators assume fixes happened154 ("harusnya dah fix"); the report must state verified still-public/still-valid status.155- For DB credentials: check external exposure of the DB port (nmap) and say plainly156 whether the credential is reachable from outside or only post-foothold (localhost157 `DATABASE_URL` = not externally loginable). Distinguish "leaked" from "externally158 usable" in the finding itself.159- Zip delivery mechanics (standing preference): rebuild the zip FRESH from disk at160 delivery time (never ship a stale archive), include a `MANIFEST.txt` of the full file161 list, and verify with `zipfile.testzip()` before sending.162163## 6. Post-Engagement Cleanup & Late Background Output (verified 2026-08-22)164165### Cleanup (hard user preference, verbatim: "abis ngapa-ngapain cache nya di bersihkan")166After the report is delivered:1671. Kill background scanners (nuclei/ffuf/sqlmap). Their completion notifications still arrive AFTER the kill — exit 143/SIGTERM is the intentional kill, not an error. On any late notification, re-verify deliverable integrity (zip entries + `testzip()`, disk state) before reacting.1682. Delete: recon working data, ad-hoc tools/wordlists, `/tmp` scratch files, downloaded template repos (nuclei-templates), and tool caches (pip, playwright, browser artifacts). Keep ONLY the canonical reports tree + final zip.1693. If anything was edited after the zip was built (e.g. a late lead added to recon_notes), rebuild the zip FRESH from disk and re-send — the delivered archive must never drift from disk state.170171### Late background output may contain new leads172A scanner finishing late can deliver results after the zip shipped — e.g. ffuf matches for `/.env`, `/dump.sql`, `/server-status` arriving post-delivery. Do NOT ignore or hand-wave them:1731. Manually verify each lead with curl (uniform 403 + identical body sizes across paths ⇒ WAF block page ⇒ negative; unique size/body ⇒ fetch content before claiming exposure).1742. Append verified results — positive OR negative — to `evidence/recon_notes.md`.1753. Rebuild + re-send the zip, then tell the operator whether the verdict changed.176177## Related skills178- `bb-methodology` — 5-phase workflow + discipline rules (manually authored).179- `bug-bounty-reporting-workflow` — language/validation/sensitive-data preferences (manually authored).180- `hunt-idor`, `triage-validation` — per-class hunting and pre-severity gates.181- `fuzzing-and-content-discovery` — ffuf workflow; its WAF uniform-403 false-positive pattern pairs with §6 late-lead verification. Session detail (ffuf flag compatibility, uniform-403 decision rule, Swagger `/docs` follow-up, scanner lifecycle): `references/ffuf-waf-false-positives.md`.