On this page
Open one finding from a Violet report and you see the same eight pieces every time: a header, a description, a proof of concept, evidence, an impact statement, a likelihood statement, a recommendation, and a verification block. This guide walks through all eight using a real finding from a real scan.
The header card
The header card is the first thing you see. Skim the chips left to right and you have the bottom line in five seconds.
The severity badge is the top-line risk classification: Critical, High, Medium, Low, or Informational. The CVSS score is the numeric expression of that classification — 9.8 here, which reflects a network-reachable, unauthenticated, no-interaction-required exploit with full confidentiality, integrity, and availability impact. The Impact and Likelihood chips are Violet's qualitative labels — useful for teams that don't want to decode CVSS on the fly. The Status chip tracks where the finding is in the remediation lifecycle: Open, Mitigated, Fixed, or Accepted. The vector string at the end is the full CVSS breakdown — everything you need to re-derive the score from scratch.
Description
The description is the elevator pitch for the bug. A good one names the endpoint, the bug class, the root-cause mechanism, and the impact — in that order, in four sentences or fewer.
If the description doesn't tell you the endpoint, the bug class, the mechanism, and the impact in three or four sentences, push back on whoever wrote it.
Proof of concept
The proof-of-concept is the recipe. If you can't follow it and reproduce the bug, the finding is not really a finding.
# Confirm the boolean oracle — TRUE condition curl -s -X POST "http://app.example.com/login" \ -d "username=admin' AND password='admin'--&password=anything" # Expected: HTTP 302 redirect (login succeeded → condition was TRUE)
Steps should be exact. Real curl commands, real payloads, real expected responses. If the steps say “manipulate the request”, they are not steps.
Evidence
Evidence is the captured proof that the PoC actually worked. Good evidence is the actual response body, the actual HTTP status, or the actual extracted data — not a description of what happened.
HTTP/1.1 302 Found Location: /dashboard Set-Cookie: session=eyJ1c2VyX2lkIjogMX0...; HttpOnly # The server redirected to /dashboard — the login condition evaluated TRUE. # The attacker is now authenticated as user_id=1 (the admin account).
Evidence is what you forward to the engineering team. Without it, you're asking them to take your word for it.
Impact
Impact translates the bug into business consequences. It should be specific to your application.
Generic: “An attacker could read data from the database.” Calibrated: an unauthenticated attacker can enumerate every row in the users table, including plaintext passwords and the admin account credentials, without triggering any login failure log entries. They can bypass authentication entirely by injecting a tautology condition, gaining full administrative access to the application in a single request.
Likelihood
Likelihood describes the prerequisites: what does an attacker need before they can run the PoC?
- No prerequisites? Assume someone is already trying.
- Needs a logged-in user? Assume an attacker has one. Anyone who can register qualifies.
- Needs admin? Still treat seriously, but lower probability of opportunistic exploitation.
Likelihood is the input to your urgency calculation. A High likelihood finding with a zero-prerequisite exploit — like this SQLi — is not theoretical. It is happening right now to applications that haven't patched it.
Recommendation
The recommendation has three parts: root cause, the fix, and additional hardening. A good recommendation gives you the fix, not just the diagnosis.
The root cause here is direct string interpolation into SQL. The fix is parameterized queries. The additional hardening is bcrypt for password comparison, rate limiting on the login endpoint, and disabling debug mode in production.
# VULNERABLE: f-string lets username/password inject SQL
query = f"SELECT * FROM users WHERE username = '{username}' AND password = '{password}'"
user = db.execute(query).fetchone()import bcrypt
# SECURE: parameterized query prevents injection
user = db.execute(
"SELECT * FROM users WHERE username = ?",
(username,)
).fetchone()
# SECURE: timing-safe hash comparison (never compare plaintext passwords)
if user and bcrypt.checkpw(password.encode('utf-8'), user['password']):
session['user'] = dict(user)
return redirect(url_for('index'))Two changes, not one. Parameterized queries fix the injection. bcrypt fixes the plaintext password storage that the injection exposed. Shipping only the first fix leaves the second problem in production.
Verification
After you ship the fix, you re-run the verification steps from the report. If they pass in the secure direction, the finding is closed honestly.
# Step 1: Verify single-quote no longer causes SQL error curl -s -X POST "http://app.example.com/login" -d "username=test'&password=test" # Expected (secure): HTTP 200 with "Invalid credentials" # Unexpected (still vulnerable): HTTP 500 with sqlite3.OperationalError
Verification is what makes “closed” mean something. Running the step and seeing the secure response is evidence. Everything else is optimism.
References
Every finding ends with references to the canonical write-ups for that bug class — OWASP cheat sheets, the relevant CWE, sometimes the framework's own security docs.
Read them once per bug class, not once per finding. After you've read the OWASP SQL Injection Prevention Cheat Sheet, you don't need to read it again the next time SQLi shows up. The CWE number — CWE-89 for this finding — is your cross-reference into the national vulnerability database if you want to see how widespread the pattern is.
A finding is a hypothesis with proof attached. Read the proof first.