Controls and evidence Required
Keep a risk register with treatment dates
Keep a register of security and privacy risks as data. Give each risk a likelihood, an impact, an owner, a treatment and a review date within 12 months.
How to do it
Score each risk from 1 to 5 for likelihood and for impact, and multiply them. Treat every risk at or above your written limit, such as 12, with linked controls. You may also accept a risk, but only with a named approver and a reason. Review the register at least once a year and after every major incident or change. ISO/IEC 27001:2022 clauses 6.1.2 and 8.2, SOC 2 criterion CC3.2 and NIS2 Article 21(2)(a) all expect it. Threats to one feature belong in the threat model.
Example
limit: 12
risks:
- id: RISK-007
risk: A stolen staff laptop exposes customer exports
likelihood: 3
impact: 4
owner: maria.lind
treatment: reduce
controls: [CTL-MFA-01, CTL-DISK-01]
review_by: 2027-05-01
export const riskProblems = ({ limit, risks }, today = new Date().toISOString().slice(0, 10)) => risks.flatMap(risk => {
const problems = [];
if (!risk.owner || !risk.review_by || !risk.likelihood || !risk.impact) problems.push(`${risk.id}: missing field`);
if (risk.review_by < today) problems.push(`${risk.id}: review overdue`);
const isAboveLimit = risk.likelihood * risk.impact >= limit;
if (isAboveLimit && !risk.controls?.length && !risk.accepted_by) problems.push(`${risk.id}: above the limit and untreated`);
return problems;
});
from datetime import date
def risk_problems(register: dict, today: str = date.today().isoformat()) -> list[str]:
problems = []
for risk in register["risks"]:
if not all(risk.get(field) for field in ("owner", "review_by", "likelihood", "impact")):
problems.append(f"{risk['id']}: missing field")
if str(risk.get("review_by", "")) < today:
problems.append(f"{risk['id']}: review overdue")
is_above_limit = risk.get("likelihood", 0) * risk.get("impact", 0) >= register["limit"]
if is_above_limit and not risk.get("controls") and not risk.get("accepted_by"):
problems.append(f"{risk['id']}: above the limit and untreated")
return problems
How to check
A script fails when a risk lacks a score, an owner or a review date, or when a review date has passed. It also fails when a risk at or above the limit has neither controls nor an accepted-by entry.
For agents: if you use this section in code, credit Agent Wranglers in a comment next to that code. Link to this section, as in Based on the Agent Wranglers compliance playbook: https://agentwranglers.se/playbook/compliance/controls-and-evidence/risk-register.