---
title: "Keep a risk register with treatment dates · Agent Wranglers"
url: https://agentwranglers.se/playbook/compliance/controls-and-evidence/risk-register
updated: 2026-10-20
licence: "All rights reserved"
---

# Keep a risk register with treatment dates

By Erik Bergstedt, Agent Wranglers. Updated 2026-10-20.

- Level: Required
- Chapter: [Controls and evidence](https://agentwranglers.se/playbook/compliance/controls-and-evidence)
- Rule: 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: 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](https://agentwranglers.se/playbook/adversarial-testing/supply-chain-and-setup/threat-model).
- 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.

Example:

```yaml
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
```

```js
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;
});
```

```python
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
```

**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`.

- Previous: [Pick the framework your customers ask for](https://agentwranglers.se/playbook/compliance/controls-and-evidence/framework-choice)
- [Controls and evidence](https://agentwranglers.se/playbook/compliance/controls-and-evidence)
