Security Basics

9 min

What a Severity Rated Vulnerability Report Tells You

What a Severity Rated Vulnerability Report Tells You

Severity is a business decision, not a scariness score. What each rating should actually mean, from an exposed service-role key at critical down to a missing header at low, and how to act on the report Monday morning.

Severity is a business decision, not a scariness score. What each rating should actually mean, from an exposed service-role key at critical down to a missing header at low, and how to act on the report Monday morning.

A customer should never be the person who discovers that one changed URL reveals another user’s invoice, or that a leaked service-role key can read an entire database. A severity rated vulnerability report is designed to stop that scenario. It tells you what was found, how an attacker could use it, what the realistic business impact is, and what needs fixing first.

For a founder moving quickly with AI-assisted code, the distinction matters. An audit that simply produces a long list of warnings creates more work and little confidence. A useful report makes a decision easier: pause this release, fix this before taking payments, schedule this hardening work, or accept this limited risk with eyes open.

Severity is a business decision, not a scariness score

Security tools can generate hundreds of findings. Some deserve immediate action. Others are hygiene improvements, false positives, or issues that only matter in a highly specific setup. Severity gives the report a practical order of operations.

The rating should consider more than the technical name of a weakness. A missing header and an unauthenticated endpoint may both appear in a scan, but they do not carry the same risk. The question is whether a real attacker can reach the issue, how difficult exploitation is, what they gain, and how much damage follows.

A strong finding connects technical evidence to an outcome. Rather than saying “IDOR detected”, it should explain that a logged-in customer may be able to change an order ID in a request and view another customer’s delivery address, purchase history, or account data. Rather than saying “Supabase configuration issue”, it should identify the affected table, the policy gap, and whether row-level security is disabled or incorrectly scoped.

That context prevents two common mistakes: spending a day polishing low-value findings while a critical access-control flaw remains live, or treating a serious report as noise because it is full of unfamiliar terminology.

What each severity level should mean

Severity labels vary between consultancies, but their practical meaning should be consistent. The rating needs to reflect the live application and repository, not just a generic scanner rule.

Critical: fix before further exposure

A critical issue can usually lead directly to major data exposure, account takeover, fraud, or administrative compromise. It is often exploitable remotely with little effort and may not require a valid user account.

Examples include an exposed Supabase service-role key in a public JavaScript bundle, a production database reachable without effective row-level security, or authentication logic that accepts unsigned JWTs. If an attacker can use the issue to access all users, alter payment records, or take control of an administrator account, it is not a backlog item.

The report should state immediate containment steps as well as the permanent fix. That may mean rotating credentials, revoking active sessions, restricting an endpoint, or disabling a risky deployment while the code is corrected.

High: urgent, credible impact

A high-severity issue has meaningful impact but may need a logged-in account, a particular role, or a few exploitation steps. Broken authorisation checks are a frequent example.

Consider a marketplace where any seller can call an API endpoint with another seller’s listing ID and edit the listing. Or an application where a user can request a password reset token for another account and complete the flow because token validation is incomplete. These weaknesses can lead to fraud, privacy incidents, and expensive support work even if they do not expose the whole database at once.

High findings should be fixed on a short, defined timescale. If the team cannot remediate immediately, the report should identify a temporary control, such as restricting a route, adding server-side ownership checks, or limiting a role’s permissions.

Medium: real weakness, limited conditions

Medium findings are exploitable under narrower conditions or have constrained impact. An endpoint may reveal internal implementation details to authenticated users, or an upload flow may accept files without enough validation but place them in a non-executable storage location.

These issues still matter because they can combine with other weaknesses. A verbose error response might reveal table names, environment details, or API structure that makes a separate authorisation flaw easier to exploit. Medium severity is not a reason to ignore the finding. It is a reason to plan remediation without displacing urgent work.

Low and informational: improve the baseline

Low-severity findings commonly cover defensive gaps with limited standalone impact, such as an incomplete Content Security Policy, missing HSTS on a correctly HTTPS-only service, or dependency updates with no reachable vulnerable path. Informational findings may document observations rather than vulnerabilities, including publicly exposed version headers or a development route that is correctly protected.

These items are useful when they come with proportionate advice. A report should not imply that every absent header will cause a breach. It should explain what the control reduces, where it applies, and whether the current application architecture changes the priority.

What makes a severity rating credible

A rating is only as useful as the evidence behind it. For every meaningful finding, expect a clear affected asset: a URL, API route, repository file path, configuration value, database table, policy, or dependency. You should also see a concise proof of how the weakness was verified.

That proof does not need to read like a penetration-testing textbook. It does need to be specific enough for an engineer to reproduce safely. For an IDOR, that may be the request method, affected parameter, expected ownership check, and observed response. For exposed credentials, it may be the bundle path, key type, scope, and confirmation of whether the key is active.

Credible reports also distinguish confirmed vulnerabilities from potential issues. Automated tools are valuable for finding secrets, insecure dependencies, common misconfigurations, and suspicious patterns. They cannot reliably determine whether an AI-generated authorisation check protects the intended resource, whether a Supabase policy matches the product’s tenant model, or whether a payment webhook can be forged. Those questions require manual review.

The severity should change when the facts change. A secret committed to Git history but revoked months ago is not equal to a current production key exposed in a browser bundle. A dependency with a published CVE may be low priority if the vulnerable function is not used, but urgent if it processes attacker-controlled files. Good reporting makes those distinctions visible.

A report should tell the team exactly what to do next

The most frustrating security finding is technically correct but operationally vague. “Improve access controls” does not tell a small team where to start. A practical report identifies the route or file, the broken assumption, the remediation approach, and the validation needed after the fix.

For example, an authorisation finding may direct the team to move ownership validation from the client into the API handler, query the resource by both its ID and the authenticated user or organisation ID, and add tests proving cross-account requests fail. An RLS finding may specify which tables require row-level security, which policies permit overly broad reads, and how to test with ordinary user JWTs rather than privileged service credentials.

It should also identify dependencies between fixes. Rotating an exposed key before removing it from a public bundle merely creates another leaked key on the next deployment. Adding a restrictive CSP without checking third-party payment scripts can disrupt checkout. Security work should be decisive, but it should account for how the product actually operates.

Use severity to manage release risk

You do not need to become a security specialist to act on a report. You need a simple operating rule. Critical findings receive immediate containment and remediation. High findings have an owner and short deadline. Medium issues enter the next planned engineering cycle. Low and informational items improve the baseline when they fit the team’s capacity.

This approach is particularly valuable before a launch, investor demo, major marketing push, or payment rollout. The goal is not a perfect score or a report with no findings. The goal is to know whether the application can expose customers, money, or operational control, then remove the most consequential paths before they become an incident.

HollowByte reviews the live application alongside the repository because both matter: the source may contain the flaw, while the deployed configuration determines whether it is exploitable. The result should leave you with a prioritised set of verified issues and fixes your team can implement without guessing.

A good report earns its place when it changes Monday morning’s engineering plan. If it cannot tell you what to fix first, why it matters, and how to verify the fix, it is documentation, not risk reduction.

Back to blog

(function() { function applyMainRole() { var hero = document.getElementById('hero'); if (!hero) return false; var node = hero; while (node.parentElement && node.parentElement !== document.body) { node = node.parentElement; } if (node && node.parentElement === document.body) { node.setAttribute('role', 'main'); return true; } return false; } if (applyMainRole()) return; var attempts = 0; var interval = setInterval(function() { attempts++; if (applyMainRole() || attempts > 20) { clearInterval(interval); } }, 250); })();