secskills
secskills / core / reporting-security-findings

reporting-security-findings

core verified 2026-07-28

Write security findings and assessment reports — severity scoring with CVSS and business impact, reproducible proof of concept, remediation guidance, executive summaries, and coordinated disclosure. Use when writing up a vulnerability, producing a pentest or audit report, triaging a bug bounty submission, or preparing a disclosure timeline.

$ /plugin install secskills-core

The report is the product. Findings that are not understood are not fixed, and findings that cannot be reproduced are disputed. Most of the value an assessment creates is destroyed or preserved in the write-up.

When to Use

When NOT to Use

register, evidence, dead ends) — use maintaining-engagement-state; this skill consumes that record

different audience

Anatomy of a Finding

Every finding answers five questions, in this order:

### F-03  Tenant isolation bypass in report export     [High]

**Summary**
An authenticated user of any tenant can export reports belonging to other
tenants by supplying an arbitrary report ID to `GET /api/v2/reports/{id}/export`.

**Impact**
Full read access to other customers' report data, including the financial
figures and contact records those reports contain. Any customer account —
including a self-service trial — is sufficient. This is a cross-tenant
confidentiality breach with likely contractual and regulatory consequences.

**Affected**
`api/handlers/reports.go:214` (`handleExport`), deployed in production as of
commit `a1b2c3d`. Reproduced on staging 2026-07-24 14:02 UTC.

**Reproduction**
1. Authenticate as `trial-user@tenant-a` and obtain a session token.
2. Note your own report ID from `GET /api/v2/reports` (e.g. `1041`).
3. Request a neighbouring ID:
   curl -H "Authorization: Bearer $TOKEN" \
        https://staging.example.com/api/v2/reports/1042/export -o out.csv
4. `out.csv` contains tenant B's data. Confirmed with IDs 1042, 1043, 1055.

**Root cause**
The handler looks the report up by primary key and checks only that the
session is valid. The tenant scope present on the list endpoint
(`WHERE tenant_id = ?`) is absent from the export query.

**Remediation**
Add the tenant predicate to the export lookup, and enforce it at the data
access layer rather than per handler so new endpoints inherit it:
    SELECT ... FROM reports WHERE id = ? AND tenant_id = ?
Then audit the remaining 14 handlers that call `findByID` without a scope —
listed in Appendix B.

**References**
CWE-639, OWASP API1:2023 Broken Object Level Authorization

Rules that make findings act-on-able:

guarantees partial fixes.

you reproduced it.

the setup, the request, and the observed result — not just the payload.

checked and what you did not.

confirm the vulnerable code is not already fixed on a branch you haven't pulled (git log --oneline <checkout>..origin/main -- <file>). One already-patched finding teaches the reader to distrust the rest.

Severity

CVSS is the common currency, but it scores a vulnerability in the abstract. Score with CVSS, then state business impact separately — engineering prioritizes on the second.

CVSS 4.0 base vector example:
CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:H/VI:N/VA:N/SC:N/SI:N/SA:N   → 6.9 Medium

Then adjust for reality and say why:

FactorRaises priorityLowers priority
ExposureInternet-facing, unauthenticatedInternal only, requires admin
DataRegulated, customer, credentialsSynthetic, public
Exploit maturityPublic exploit, active exploitationTheoretical, complex chain
Compensating controlsNoneWAF rule, network segmentation, monitoring
Blast radiusCross-tenant, whole fleetSingle account, single record

A Medium CVSS that breaks tenant isolation for a SaaS product is a P1 regardless of the number. Say that explicitly rather than letting the score argue for you. Conversely, do not inflate scores: a report where everything is Critical gets triaged by ignoring it.

Chained findings: report the components individually and report the chain as its own finding with the chain's severity. The chain is what an attacker does; the components are what engineering fixes.

Proof of Concept

Scale the PoC to what proves the point:

row proves the bug. Dumping the full database does not prove it harder.

write access; write a benign marker to a record you own, or demonstrate the authorization decision without the effect.

in the report and store the raw evidence separately with access controls.

artifacts, list them so they can be cleaned up.

For memory-safety and exploitation findings, a crash with a controlled instruction pointer plus an analysis of exploitability is usually the right depth. A weaponized exploit belongs in the report only when the engagement explicitly calls for it.

The Report

1. Executive summary        — 1 page, no jargon, answers "how bad and what now"
2. Scope and methodology    — what was tested, how, and with what access
3. Coverage and limitations — what was NOT tested, and why
4. Findings                 — ordered by severity, each self-contained
5. Strategic recommendations— themes across findings, not per-finding fixes
6. Appendices               — tooling, raw output, evidence index, retest results

The executive summary is written for someone who will read only it. Three things: the overall risk position in a sentence, the two or three findings that matter, and what decision is being asked for. No CVSS vectors, no tool names, no "we ran Nessus."

Coverage and limitations is the section that protects everyone. State the time box, the accounts and environments you had, the components you could not reach, and the testing you were asked not to do. A report silent on limitations implies coverage it did not have, and that silence is what turns a missed bug into a dispute.

Strategic recommendations are where an assessment earns repeat work: the themes. "Authorization is enforced per handler rather than at the data layer; 9 of 14 findings share this root cause." That sentence is worth more than the nine findings.

Writing for the Audience

ReaderWantsGive them
ExecutiveRisk and decisionOne page, plain language, business consequence
Engineering managerPrioritization and effortSeverity, root cause, scope of the fix
EngineerTo fix it todayExact location, reproduction, concrete change
Compliance/auditEvidence and mappingMethodology, coverage statement, framework refs

Write the finding for the engineer, and the summary for the executive. Do not average the two into prose that serves neither.

Tone: describe the defect, not the developer. "The export handler omits the tenant predicate" — not "the developer forgot." Reports circulate, and an accusatory report makes the next engagement harder.

Disclosure

For findings in software you do not own:

Day 0     Report privately: security.txt, /security, GitHub advisory, CERT
Day 0-7   Acknowledge receipt; agree a timeline
Day 45    Check in; offer help reproducing
Day 90    Standard public disclosure deadline (adjust for severity and
          exploitation in the wild — actively exploited issues warrant faster
          public warning; complex fixes may warrant an extension you agree to)

impact and mitigation instead.

than going straight to publication.

use a finding as leverage. Both convert a research contribution into a legal problem.

For bug bounty submissions, read the program's scope and rules first, report one issue per submission, and include the impact statement the triager needs to justify the payout internally.

Rationalizations to Reject

they will downgrade it.

report stops being read.

evidence you have and state the uncertainty. Silent omission is worse.

checkout. Revalidate against what ships before you file it — a finding fixed three commits ago discredits the whole report.

after a breach in an area the report implied was covered.

findings is a valid result. Say what you tested and how deeply.

References