Risk, Residual Risk and Honest Reporting
Security is risk management, so the output of security work is not "secure" but four lists: identified controls, known risks we accepted, residual risks that survive our controls, and unknowns we have not examined.
Frame the problem
Security starts with a concrete asset, attacker capability and trust crossing.
Severity is not risk
A vulnerability scanner reports a critical-severity issue in a library. Whether that matters to you depends on facts the scanner does not have: is the vulnerable function ever called, is the affected component reachable from untrusted input, what would an attacker gain, and what else would have to fail. Severity is a property of the vulnerability; risk is a property of your system.
Risk is roughly likelihood × impact, adjusted for exposure and existing compensating controls. A critical remote-code-execution bug in a library used only by an offline batch job with no untrusted input is a low risk and a scheduled upgrade. A medium-severity information disclosure on an unauthenticated endpoint returning customer email addresses is a high risk and a same-day fix. Teams that patch strictly by CVSS spend their capacity in the wrong places and lose credibility when they cry wolf.
The practical device is an exposure question asked before the severity question: *can an attacker reach this, from where, with what?* If the answer is "not without already having a shell on the host", the finding is real and its priority is low. Writing that down — rather than arguing about the score — is what makes a vulnerability queue tractable.
| Finding A | Finding B | |
|---|---|---|
| Description | Critical RCE in an image library | Medium: user emails returned by an unauthenticated endpoint |
| Severity score | 9.8 | 5.3 |
| Reachable by? | Only the offline batch job, from trusted files | Anyone on the internet |
| Attacker gains | Code execution in a sandboxed job with no credentials | The full customer email list |
| Compensating controls | No network egress, no secrets mounted | None |
| Actual risk | Low — schedule the upgrade | High — fix today |
The four lists
Replace the security sign-off with four explicit lists, each of which a non-security engineer can read and act on.
Identified controls — what actually exists, named specifically enough to test: "authorization enforced in the repository layer for all tenant-scoped reads", not "we use RBAC". Known risks — things you found, understood and consciously chose not to fix, each with the reason and the person who accepted it. An accepted risk with no name attached is an unowned risk that will be rediscovered as a surprise.
Residual risks — what remains after the controls work as designed. Every control has a shape, and the space outside the shape is not empty. Parameterized queries do not stop an authorized user reading their own data in bulk. Rate limiting does not stop a distributed low-and-slow attempt. Least privilege does not stop the identity from doing exactly what it is permitted to do. Naming residual risk is what stops a control from being mistaken for a solution.
Unknowns — what has not been examined. This is the list teams omit, and it is the most valuable one, because it converts "we do not know" from a silence into a work item. "We have never reviewed the mobile API's authorization model" is enormously more useful than a report that simply does not mention mobile.
- Identified controls: specific enough that a test could verify each one.
- Known risks: accepted deliberately, with the reason, the owner and a review date.
- Residual risks: what survives the controls working correctly — always non-empty.
- Unknowns: what has not been looked at, stated plainly, so absence of findings is not read as absence of problems.
Keeping the register from becoming a graveyard
Risk registers die in a predictable way: entries accumulate, nothing is closed, everything is "medium", and eventually nobody reads it. Three habits keep one alive.
First, every entry names an owner and a review date, and passing the review date is itself an event. Second, accepted risks expire: an acceptance is valid for a quarter, after which it must be re-argued or fixed. Circumstances change — the endpoint that had no sensitive data now has some — and an acceptance made under old facts is a decision nobody actually made. Third, entries are written as scenarios, not categories: "a support agent can read any customer's messages, and we have no alert on bulk reads" is actionable; "insufficient access control" is not.
Finally, resist scoring everything into a single number. A register whose entries are ranked by an aggregate score invites arguing about the score instead of the scenario. Rank by exposure and blast radius, keep the reasoning visible, and let the ordering be a judgment the team can inspect and disagree with.
Key points
- Severity describes the vulnerability; risk describes your system. Ask about reachability before arguing about a score.
- Report four lists — identified controls, known risks, residual risks, unknowns — and never the word "secure".
- Residual risk is always non-empty; naming it is what stops a control from being mistaken for a solution.
- The unknowns list is the most valuable and the most often omitted; absence of findings is not absence of problems.
- Accepted risks expire and have owners, otherwise the register becomes a graveyard nobody reads.
Boundary control exercise
This lesson uses the shared boundary-control exercise.
Follow the attack
Safe conceptual simulation: capability → missing control → crossed boundary → asset impact.
- 1Attacker → the unexamined: whatever is on the unknowns list, because nobody has looked there.
- 2Unexamined → assumption: a control everyone believed existed and nobody verified.
- 3Assumption → asset: the gap between the mental model and the running system is the vulnerability.
- Misallocated effort: capacity spent on low-risk high-severity findings while a high-risk medium sits open.
- False confidence: a "secure" declaration that stops further examination exactly where it was most needed.
- Credibility loss: a security function that escalates everything is eventually ignored on the thing that mattered.
Defend, detect, recover
One prevention is a single point of security failure. Layer it and make failure observable.
- • Triage by exposure first (who can reach it, from where, with what), severity second.
- • Require every finding to state the asset, the attacker and the blast radius before it gets a priority.
- • Maintain and publish the unknowns list; treat "we have not looked" as a first-class output.
- • Give accepted risks an owner and an expiry, and re-argue them on schedule.
- • Track time-to-fix by actual risk rank rather than by severity label; a divergence means the triage is not working.
- • Track how often findings arrive from outside the process (a customer, a researcher, an incident) — that rate estimates the size of the unknowns list.
- • Review accepted risks that have expired as a standing agenda item.
- • After an incident, add both the specific bug and the class of bug to the register, and ask which list it should have been on.
- • Ask why it was not found: was it in unknowns, was a control believed to exist, or was the risk accepted under different facts?
- • Update the acceptance criteria rather than only patching the instance.
- • Risk estimates are judgments made with incomplete information and will be wrong in both directions.
- • Registers reflect the moment they were written; every deploy changes the system and not the register.
- • The unknowns list is itself incomplete, because you cannot enumerate what you have not conceived of.