Scoring Security Findings: An Impact-by-Reachability Table
CVSS alone lacks context: a practical prioritization table crossing data class and tenant breach with ease of access.
Scoring Security Findings: An Impact-by-Reachability Table#
A scan output lists ten findings side by side. Each one carries a severity label. One is marked high, one medium, one low. The team picks where to start by reading those labels. That is where the trouble starts. The label describes the abstract weight of a weakness type, not what it means at runtime. Who can touch which data, under which preconditions, is missing from that label. Once a score is cut off from context, the ordering drifts away from reality.
Why a score without context misleads#
Severity and priority are not the same thing. Severity describes how heavy a weakness type is in the abstract. Priority describes what that weight means in your system. The same type of finding earns a different priority in different systems. Any ordering that skips this distinction sends the team to the wrong task.
An admin-only high against an anonymous medium#
Place two cases side by side. The first finding looks heavy in theory but triggers only with a system administrator role. An attacker must first obtain that role. The second finding looks lighter in theory but triggers for any unauthenticated visitor. Which one should close first cannot be answered by reading labels. Ease of access changes the equation.
Keep this distinction sharp with one question per finding: what must an attacker already hold to use this finding. The less the answer requires, the earlier the finding belongs in the queue. Paths that need an authenticated session, special knowledge, a timing race, or persuading a user belong later at the same impact level.
Three questions that set priority#
Three questions are enough for each finding. First, what does the impact touch. Second, who can reach that impact. Third, how much effort does reaching it take. The first question feeds the impact axis. The second and third feed the reachability axis. A decision made before the two axes cross is either overstated or understated.
What the table fixes and what it does not#
This table is not a magic formula. It is an ordering discipline. It gives developers and security engineers a shared language. It makes sure the word important means the same thing on both sides. It explains to an auditor why this finding closed first. It shows management why three medium findings came before one high finding.
The table does not find root causes. It does not compute exploit chains. It does not convert business impact into money. It does not guess attacker intent. Those are separate tasks. The table answers only one question: in which order should the findings at hand move forward.
Ordering rule (summary): 1. Classify impact: data class + tenant breach + write effect. 2. Classify reachability: identity, predictability, interaction, complexity. 3. Cross both axes in the table. 4. Break ties in the same cell by business effect.
Impact axis: what gets touched#
The impact axis describes what changes when a finding is used. It answers three questions: which data is read or changed, which tenant boundary the breach crosses, and whether the result is read-only or a state change. These three answers combine into one impact level: High, Medium, or Low.
Read data class and tenant breach together. Sensitive data inside one tenant is still heavy. Public data spread across many tenants carries a different weight. The two must be read as a pair. Write effect works as a multiplier. Read-only access and state change do not belong at the same level.
High impact: sensitive data, tenant breach, state change#
High impact produces results that are hard to reverse. Mark impact as high when one or more of the following hold.
For data class, high impact touches data tied directly to a person: identity, contact, financial, or health records. Authentication secrets belong here too. Session tokens, password reset secrets, and backup codes for multi-factor authentication fall in this group. Even a single record of this kind counts as heavy once exposed.
For tenant breach, high impact crosses a tenant boundary. Reading your own data and reading someone else's data do not belong on the same scale. A read spread across every tenant belongs at the top. In systems without tenants, read this question as crossing a user boundary. Access to another user's records counts here.
For write effect, high impact means lasting state change. Role change, payment status update, approval step takeover, record deletion, or overwrite belong here. Do not treat read access and state change as small variations. Reading leaks information, writing breaks the accuracy of the system.
High impact check:
- Does the data read or changed tie to a person?
- Does the breach reach another user or tenant?
- Does the result produce a lasting state change?
- One yes marks impact as High.
Keep the scope narrow when marking high impact. Marking every internal record as sensitive breaks the table. When no classification standard exists, use this split: data that alone enables identity theft or money movement is sensitive. Operational information is internal. Public information is public. Apply this split the same way through every report.
Medium impact: internal data, bounded breach, reversible change#
Medium impact produces results that matter without being destructive. Internal messages, operational records, and configuration details belong here. They do not enable identity theft alone, but they guide an attacker. Do not dismiss internal data leaks. They shorten the reconnaissance stage.
For tenant breach, medium impact means bounded crossing. Unauthorized reads inside the same tenant, or access to a resource outside your own role, sit at this level. No crossing into another tenant happens, yet an authorization boundary falls. Such findings prove that an access check is missing. They are not destructive alone, but they signal a control gap.
For write effect, medium impact means reversible or low-consequence change. Updating your own profile field through an unauthorized path, creating a draft under someone else's name, or changing a low-value status belong here. No lasting harm follows, or harm is easy to undo. Record accuracy still suffers, so the finding still needs a fix.
Medium impact check:
- Does the data stay in the internal class?
- Is the breach bounded by the same tenant or role set?
- Is the change reversible?
- Three yes answers mark impact as Medium.
Avoid exaggeration when reporting medium impact. Medium does not mean trivial. Findings at this level close in a planned cycle. They can wait, but they must not be forgotten. Keep the tracking record open.
Low impact: public data, no breach, read-only#
Low impact covers results that disturb normal operation in no real way. Public information, version strings, and generic error text belong here. The data is already public or gives an attacker no useful edge. Alone, it produces no harm.
Absence of breach is the mark of this level. A user sees only their own data. No tenant boundary falls. No record of another user opens. Only a presentation-layer flaw remains. That may be a functional defect, but no security boundary falls with it.
Read-only output and low-value leaks belong here too. Verbose error text, fragments of stack traces, and internal path details are low impact alone. They ease reconnaissance but produce no breach by themselves. They count only as part of a chain, never alone.
Low impact check:
- Is the data already public or of little value?
- Is there a move to another user or tenant? (no)
- Is there a lasting state change? (no)
- All three answers together mark impact as Low.
Do not throw low findings away. Two low findings can combine into a medium result. A verbose error message plus a predictable identifier changes the table. Note the combination chance while closing low findings.
Writing the impact call in one line#
An impact call should stand on one line in the report. Use this shape: which data, which boundary crossing, which write result. Pack all three into one sentence. Example shape: internal data read, no tenant crossing, no state change, hence low impact. That sentence carries both the call and its reason. Auditors ask for exactly that sentence.
| Question | High | Medium | Low |
|---|---|---|---|
| Data class | Sensitive or identity secrets | Internal and operational data | Public or low-value detail |
| Boundary crossing | Another tenant or user | Unauthorized area in same tenant | No crossing |
| Write result | Lasting state change | Reversible change | No change |
Reachability axis: how hard is it to reach#
The reachability axis describes what an attacker needs before touching the finding. It answers four questions: is authentication needed, is the target identifier predictable, is victim interaction needed, and is exploitation complex. These four answers combine into one reachability level: Easy, Limited, or Hard.
Judge this axis by the easiest path. When three paths lead to a finding, write down the easiest one. Writing down the hardest path paints the table too bright. Defense plans follow the easiest path. The other paths belong in the notes.
Easy reach: no identity, predictable target, single request#
Easy reach needs no special condition. No authentication is needed, or any ordinary user account is enough. No elevated role is needed. The target identifier runs in sequence or follows an easy-to-guess shape. No click or approval from a victim is needed. One request returns the result.
Run this check before marking easy. Log out and resend the request. When the result stays the same, no identity barrier exists. Raise and lower the identifier by one. When another record returns, predictability holds. Repeat from another tab or browser. When no timing or race is needed, complexity is low.
GET /api/invoices/1042 HTTP/1.1 Host: sample-app.local Cookie: session=ordinary-user-session
The request shape above shows the typical look of easy reach. Ordinary account, predictable identifier, no extra header, no extra interaction. Keep this raw request in the report. It is enough for reproduction.
Limited reach: ordinary account, conditional guess, one interaction#
Limited reach means some barriers exist without being out of reach. A valid user account is needed. No anonymous access exists. The target identifier looks random but can be learned through a leak or a listing. One click or one approval from a victim may be needed. Exploitation takes a short multi-step flow.
Write each condition down when judging this level. Which role is needed. How the identifier is learned. Which interaction triggers the flow. How many steps the flow takes. Each condition moves access one step harder. Yet no single condition alone makes the finding harmless.
Access control gaps are often caught at this level. Attempts to reach privileged functions with an ordinary account belong to this axis. Readers who want deeper test coverage for this area can use the role and function matrix in API access control test plan.
Limited reach check: - Is a valid account needed? (yes) - Is the target learned through listing or a leak? - Is one victim click enough? - Does the flow finish in 2-5 steps?
Hard reach: elevated role, hidden identifier, many conditions#
Hard reach means several barriers pile up. An elevated role is needed. The target identifier is random and cannot be learned without another leak. A victim must finish several steps. Exploitation needs a timing race, a header chain, or a narrow state window.
Neither dismiss nor inflate hard reach. Hard does not mean impossible. A targeted attacker climbs barriers one by one. An opportunistic attacker skips this path. So findings with hard reach belong in a planned cycle. High impact still pulls them forward when combined with hard reach.
Write each barrier apart when marking this level. Do not compress them into one sentence. Which role, which secrecy, which interaction chain, which timing condition. Once barriers are spelled out, it becomes clear which removed barrier would change the table. That detail strengthens the mitigation advice.
| Question | Easy | Limited | Hard |
|---|---|---|---|
| Authentication | None or ordinary account | Valid account needed | Elevated role needed |
| Identifier guess | Sequential or guessable | Learned through a leak | Random and hidden |
| User interaction | None needed | One click or approval | Multi-step chain |
| Complexity | Single request | Short flow | Race or narrow window |
The 3x3 decision table and how to read each cell#
Once both axes are ready, the decision follows. Put impact on rows and reachability on columns. Each cell returns one action: fix now, fix this cycle, or mitigate and watch. Read row and column together. Never decide from one axis alone.
| Impact / Reachability | Easy | Limited | Hard |
|---|---|---|---|
| High | Fix now | Fix now | Fix this cycle |
| Medium | Fix this cycle | Fix this cycle | Mitigate and watch |
| Low | Fix this cycle | Mitigate and watch | Mitigate and watch |
How to read fix-now cells#
Fix now does not mean all work stops. It means an urgent queue. The crossing of high impact with easy reach always lands here. High impact with limited reach lands here too. The reason is plain: the result is heavy and reaching it is fairly easy. Waiting only piles up risk.
Plan temporary mitigation plus lasting repair together for this cell. Temporary mitigation stops the bleed. Lasting repair removes the cause. Write both into the same record. Write when the mitigation comes off too. Mitigations left open are forgotten over time.
How to read fix-this-cycle cells#
Fix this cycle means planned work. High impact with hard reach sits here. Medium impact with easy and limited reach sits here. Low impact with easy reach sits here too. They share one trait: either the result is middling or reaching it takes effort. Both can wait, but neither may slip past the cycle.
Findings in this cell enter the sprint or release plan. Each gets an owner. Each gets an acceptance check. Each gets a retest date. A finding without owner and date is not closed. Track them under a separate label in your planning tool.
Teams that tie checks to automated tests should treat this cell as the right place to add rules. Walking access and identity checks with a matrix keeps the work orderly. For a sample flow that carries this discipline into automated scans, see BOLA test matrix automation.
How to read mitigate-and-watch cells#
Mitigate and watch does not mean ignore. It means apply a cheap control and keep the record open. Medium impact with hard reach plus low impact with limited and hard reach sit here. The result is light and reaching it is hard. A tightened setting or added logging is enough instead of direct repair.
Write the reason for this call. Why it was not repaired at once, which mitigation was applied, and under which condition it returns to review. Example conditions: the identifier shape changes, the data class changes, a new path is found. Once a condition fires, the finding is classified again.
Cell decision writing shape: Impact: Medium (internal data, no crossing, no write) Reachability: Hard (elevated role + hidden id + many conditions) Cell: Mitigate and watch Mitigation: verbose error text shortened Watch condition: reclassify when identifier shape changes
Method demos: three worked examples#
The three examples below are not real findings. They are constructed illustrative method demos. Their purpose is to show how the same table returns different cells from different inputs. None of them belongs to a real system, a real record, or a real engagement. Names, identifier values, and endpoints were picked to keep the explanation simple.
Example 1: reading another user's invoice through an invoice id (illustrative method demo)#
The story goes like this. An invoice view endpoint accepts the identifier from the path as given. No ownership check runs. With an ordinary user login, writing another invoice identifier returns that invoice. Identifiers run in sequence. No victim interaction is needed. One request is enough.
Impact reads as follows. Invoice content carries personal fields such as name, address, and amount. A record owned by another user is read. A user or tenant boundary falls. No state change follows, only read access. Data plus crossing together mark impact as high.
Reachability reads as follows. An ordinary account is enough. Identifier guessing is easy. No interaction is needed. One request is enough. So reachability marks as easy. High impact crossed with easy reach returns a fix-now cell.
Example 1 classification (method demo, not a real finding):
Impact: High (sensitive invoice fields + another user's record + read-only)
Reachability: Easy (ordinary account + sequential id + no interaction)
Cell: Fix now
Repair direction: ownership check + unguessable reference
Repair runs in two parts. Every request passes a server-side ownership check. The identifier shape moves to unguessable references. Records hidden on the client are checked again on the server. Listing endpoints return only owned records.
Example 2: reflected input echo (illustrative method demo)#
The story goes like this. A search page reflects user input in the result heading as given. Output encoding is missing. Input runs in the browser of whoever opens the link. A victim click is needed to trigger it. The sender needs no authentication. The link can be sent to anyone.
Impact reads as follows. No direct data leak follows. Session theft needs extra conditions. No read or write follows alone. As an interactive client-side result, it marks medium impact. No extra guess is added to inflate impact. Extra conditions, when present, belong in a separate finding.
Reachability reads as follows. No identity is needed. Target choice sits with the sender. One victim click is needed. The flow takes a few steps: prepare a link, send it, wait for the click. So reachability marks as limited. Medium impact crossed with limited reach returns a fix-this-cycle cell.
Example 2 classification (method demo, not a real finding):
Impact: Medium (no direct data read + interactive client result)
Reachability: Limited (no identity + one click + short flow)
Cell: Fix this cycle
Repair direction: context-aware output encoding + security headers
Repair centers on server output. Every reflected field is encoded for its context. Security headers are tightened. The link-sharing flow gets a separate review. A shared rule at the template layer replaces a one-spot patch.
Example 3: verbose error message (illustrative method demo)#
The story goes like this. Failed requests return internal paths and query fragments in error text. Anyone can trigger this text. No direct record read follows. No tenant crossing follows. No state change follows. The detail helps reconnaissance but produces no breach alone.
Impact reads as follows. Data is public or low value. No move to another user happens. No write result happens. So impact marks as low. The note records that detail guides an attacker, without inflating impact. Inflation needs proof of combination.
Reachability reads as follows. No identity is needed. No special interaction is needed. One request triggers it. So reachability marks as easy. Low impact crossed with easy reach returns a fix-this-cycle cell. The reason is cheap repair. A short setting change is enough.
Example 3 classification (method demo, not a real finding):
Impact: Low (public detail + no crossing + no write)
Reachability: Easy (no identity + no interaction + single request)
Cell: Fix this cycle
Repair direction: generic error text + detail moved to server log
Repair is simple. Users see a generic message. Detail goes to the server log. A reference code shown to the user maps to the log entry. Support finds the code in the log. No detail is lost, only its place changes.
Reading three examples side by side#
The three examples show how one table returns different results. The first joins high impact with easy reach and moves first. The second joins medium impact with limited reach and enters the plan. The third joins low impact with easy reach and enters the same plan as a cheap fix.
| Example (method demo) | Impact | Reachability | Cell |
|---|---|---|---|
| Invoice id read | High | Easy | Fix now |
| Reflected input echo | Medium | Limited | Fix this cycle |
| Verbose error text | Low | Easy | Fix this cycle |
This side-by-side read shows the payoff of dropping label habit. By label alone, the third example would sink to the bottom. Through the table, it moves up because repair is cheap and fast. The second stays behind the first because interaction is needed. The ordering can be defended with reasons.
Priority rule: which finding closes first#
The ordering rule stays simple. Fix-now cells close first. Fix-this-cycle cells enter the plan next. Mitigate-and-watch cells stay tied to a record. Findings left in the same cell split by business effect. Payment and identity flows move first inside one cell.
Splitting ties inside one cell#
When several findings pile in one cell, use this order. Outward-facing endpoints come before internal ones. Findings with direct money or identity results come before detail leaks. Cheap repairs close before costly ones and build pace. A finding tied to a tracked task comes before an orphaned one.
Add no invented numbers while splitting. No weight factor, no percent math. The order stands on a cell plus a business-effect sentence. The sentence follows this shape: two findings share a cell, the payment flow closed first because it touches direct money movement.
Closing order: 1. Fix-now cells (high + easy or limited). 2. Fix-this-cycle cells (written into planned work). 3. Mitigate-and-watch cells (mitigation applied, record open). 4. Same-cell ties split by business effect and repair cost.
Root-cause closure over symptom repair#
Patching endpoints one by one moves the problem around. The same control gap reopens at another endpoint. The root cause is a missing policy. Until the policy is repaired, the finding stays open. In the invoice case the root cause is missing central ownership checks. In the reflected input case the root cause is a missing encoding rule at the template layer. In the error text case the root cause is a scattered error handling policy.
Root-cause closure takes three steps. First, write the policy: which check runs, at which layer, in which order. Second, apply it at one place: an authorization helper, an encoding filter, an error handler. Third, scan backward: does the same flaw live at another endpoint. The record closes only after all three steps finish.
This view matches the test side too. Instead of staring at one endpoint, walk function and role crossings. Teams that test authorization on a routine catch root causes earlier. Over time the same matrix feeds both testing and repair, which keeps the backlog short.
Root-cause record:
Policy: server-side ownership check on every record access.
Enforcement point: central helper in the data access layer.
Backward scan: endpoints with the same shape listed and checked.
Retest: another user's identifier access denied.
Report record, limits of the table, and short result#
A report should be a short record, not a long story. Every finding uses the same headings. A reader should grasp a record in one minute. The table call belongs inside the record. Reason and cell are written together. Priority debate then rests on data.
Short finding format#
Use these five headings per finding. Component: which endpoint or page. Preconditions: which account, role, or state is needed. Observed: which request returned which response. Impact: the data, crossing, and write sentence. Limits: which conditions stayed untested and which combination chance was noted.
## Finding: record read without ownership check (method demo) - Component: /api/invoices/{id} - Preconditions: ordinary user session - Observed: another user's identifier returned another user's record - Impact: internal-sensitive data read, user boundary crossed, no write - Classification: impact High, reachability Easy, cell Fix now - Limits: write untested, chain effect not filed as separate finding
This shape pays back in reproduction ease. Another person follows the same steps and reaches the same result. Raw request and response join the record. Personal data is masked. Date and version are written. The record is retested once the version changes.
Limits of the table itself#
This table does not explain everything. Knowing its honest limits stops false trust. The table does not compute chained effects. Two findings that look low alone can return a high result together. Every record notes the combination chance. Chains belong in a separate study.
The table does not know attacker intent. A targeted attacker and an opportunistic attacker read the same table in different ways. A targeted attacker accepts hard reach. So high impact findings stay in the plan even when reach is hard. They never drop off the table.
The table does not convert business impact into money. Downtime, contract penalty, and trust loss need separate review. Findings sharing a cell split by those measures. Security records and business records link to each other. Neither waits in the same orphaned queue.
The table ages over time. Identifier shapes change, data classes change, new paths appear. Classification is not a one-time stamp. Open records return to review before each release. A changed condition changes the cell. This review stays short, but it is never skipped.
Short result#
Write impact and reachability apart, then decide at their crossing. Move high impact forward, move easy reach forward, never hold their crossing back. Split same-cell ties by business effect. Repair the policy, not the symptom. Keep records short and limits stated. That discipline turns fewer findings into more progress.
What do you think?
React to show your appreciation