AI Risk Register
The risk register is where you write down the AI risks you have identified. For each risk, it records:
- which agent or program it concerns;
- how likely and how severe it is, before and after mitigation;
- who owns it;
- what is relied on to mitigate it;
- if the organization decides to live with it, why.
Every entry is keyed on the shared risk catalog: bias, fairness, robustness, privacy, security, transparency, accountability, human oversight and safety. The framework libraries and the governance assessment use the same vocabulary. That is what lets an assessment answer seed the register, and a control cite a risk.
Scores
Likelihood and impact are each rated from 1 (lowest) to 5 (highest), twice: inherent (before mitigation) and residual (after mitigation). The score is likelihood × impact. MITRITY computes it when a row is saved, and it cannot be typed in, so the register, the API and every report agree about every number.
The residual heat map on the page counts the entries listed below it (the current filter and page) by where their residual axes land. By default, the register is sorted by residual score, highest first. You can sort it by the widest gap instead; see How the computed score works.
A new entry starts from the catalog's defaults for its risk class, with residual equal to inherent, because nothing has been mitigated yet. Every value can be edited afterwards.
How the computed score works
Beside the residual you declare, every entry shows a computed residual: what the live signals say the risk looks like today. MITRITY derives it when the register is read, from signals at most a minute old, and again in a daily pass. It is never written into the entry, and it never changes the values you set.
The declared score is a decision with a name on it. The computed score is an observation. A gap between them is what deserves a look.
The rule is simple enough to read off the page. Likelihood starts at the entry's inherent likelihood, and moves by the steps each signal adds, shown in the table below. The signals are read over different windows:
- Drift and DLP matches are read over the last 90 days.
- Coverage, lifecycle and open findings are read as they stand now. The exceptions: lifecycle notes whether the agent acted in the window, and model findings count through the models the agent called in it.
| Signal | Effect on likelihood |
|---|---|
| Execution coverage | governed −1 · partial 0 · degraded, unattested or unknown +1 |
| Lifecycle | proposed, under review or sent back +1 · approved 0 · blocked or retired with no activity in the window −1 · blocked or retired but still acting in the window +1 |
| Drift | +1 when the agent's average drift score reaches 0.5, or at least a tenth of its actions scored above 0.5 — the anomaly line |
| Open findings | a critical finding +2, otherwise a high one +1 — on security, robustness, safety and privacy entries; listed for context on the others. Counted: open execution-coverage, host-observer and red-team findings, SaaS posture warnings (as medium), and open findings on the models the agent called in the window |
| DLP matches | +1 when matches went through with only an alert — on privacy and security entries; 0 when every match was blocked or redacted |
The result stays on the 1–5 scale. Impact is the residual impact you set, unchanged. What a failure would cost is a judgment no runtime signal can make.
The computed residual is likelihood × impact, on the same 1–25 scale. The Signals toggle under each score lists every step, with the value behind it and a link to the record.
A program-level entry reads every agent together:
- coverage: −1 when at least 80% of the agents with a coverage state are governed, +1 when 20% or more are degraded, unattested or unknown;
- lifecycle: +1 when any agent is proposed, under review or sent back, or blocked or retired but still acting;
- drift: over all agents' actions at once;
- every agent's open findings and DLP matches.
When there is nothing to compute from, there is no computed score, only a note saying so. That is the case with no coverage state, no actions in the window, no open findings and no DLP matches. An empty window is not evidence of anything. The same applies to an entry whose agent is no longer registered.
Sort the register by widest gap first to see where the declared residual sits furthest below what the signals show.
When an open or mitigating entry's computed residual sits six or more points above its declared residual, MITRITY raises a Risk register drift notification. Six points is one band on the heat map. The notification comes:
- the first time the gap appears;
- again whenever the gap has widened;
- otherwise, as a weekly reminder while the gap holds.
The condition is standing: an entry that is wrong stays wrong until you update it or accept the risk. Accepting the risk is what silences it. Accepted and closed entries never notify, and neither does a computed score below the declared one.
Status and acceptance
| Status | Meaning |
|---|---|
| Open | Identified, not yet being treated. |
| Mitigating | Treatment is under way. |
| Accepted | The organization has decided to live with the residual risk. Requires a justification. |
| Closed | No longer a risk. |
Accepting a risk is its own action, for a manager or owner. It needs a justification of at least ten characters. The note, who accepted and when are recorded with the row, and written to the audit trail as compliance.risk.accepted. A closed risk cannot be accepted.
Where entries come from
- Manual: added on the page or through the API by any member.
- Assessment: when a governance review is approved or attested, one entry is written per risk class that the answered questions touched. Its likelihood starts at the catalog default, and moves one step up when the answers for that class scored high. Its impact is the catalog default, and the catalog's mitigating capabilities are attached. Entries are pinned to the review, so resolving it again does not double the register. Every seeded value is a starting point for a person, not a score MITRITY asserts.
- Finding: opened from a finding.
A user whose access scope is scoped sees the program-level risks and the risks of the agents granted to them. They add agent risks only for those agents. A risk about any other agent answers 404 Not Found.
Mitigating capabilities
An entry can name the MITRITY capabilities relied on to mitigate it: the audit trail, human approval holds, DLP, injection scanning, the credential broker and the others in the catalog.
Naming them is what lets the register say which mitigations are actually running, rather than only written down. Each capability is backed by an evidence probe, the same one the frameworks page uses.
From the API
The catalog of risk classes and capabilities:
curl https://api.mitrity.com/api/v1/compliance/risk-catalog \
-H "Authorization: Bearer mk_your-api-key"
List the register, highest residual score first, or widest gap first with sort=gap. The filters are subject_type (agent or tenant), subject_id, status, risk_slug, sort (residual or gap), limit and offset.
Every entry carries computed. When the live signals cannot be read, the entries are served without it, and the response says signals_unavailable. A gap sort over a very large register says truncated:
curl "https://api.mitrity.com/api/v1/compliance/risks?status=open&sort=gap&limit=50" \
-H "Authorization: Bearer mk_your-api-key"
{
"risks": [
{
"id": "6b0b7c0e-2a4d-4c8f-9a3e-1f2e3d4c5b6a",
"subject_type": "agent",
"subject_id": "0f1e2d3c-4b5a-4968-8776-5a4b3c2d1e0f",
"subject_name": "triage-bot",
"risk_slug": "privacy",
"title": "Privacy",
"inherent_likelihood": 4,
"inherent_impact": 4,
"inherent_score": 16,
"residual_likelihood": 2,
"residual_impact": 4,
"residual_score": 8,
"mitigating_capabilities": ["dlp", "credential_broker", "audit_trail"],
"owner_name": "Ana Platform",
"status": "mitigating",
"source": "assessment",
"review_id": "…",
"created_at": "2026-09-21T14:02:11Z",
"computed": {
"residual_declared": 8,
"residual_computed": 16,
"computed_likelihood": 4,
"computed_impact": 4,
"gap": 8,
"computed_at": "2026-09-24T09:00:00Z",
"window_days": 90,
"signals": [
{ "signal": "coverage_state", "value": "governed", "effect": -1, "summary": "Execution coverage is governed: every execution tool the runtime reported is hooked, so an action outside the mission scope is stopped rather than only seen.", "deep_link": "/app/agents/0f1e2d3c-4b5a-4968-8776-5a4b3c2d1e0f" },
{ "signal": "lifecycle_status", "value": "approved", "effect": 0, "summary": "The agent is approved.", "deep_link": "/app/agents/0f1e2d3c-4b5a-4968-8776-5a4b3c2d1e0f" },
{ "signal": "drift", "value": "0.14 average, 3% past the anomaly line", "effect": 0, "summary": "Drift is within the normal band over the last 90 days: 0.14 average, 0.61 peak, 27 of 900 actions past the anomaly line.", "deep_link": "/app/audit?agent_id=0f1e2d3c-4b5a-4968-8776-5a4b3c2d1e0f" },
{ "signal": "dlp_events", "value": "12 matched, 9 stopped", "effect": 1, "summary": "DLP matched 12 transfers in the last 90 days; 3 went through with only an alert.", "deep_link": "/app/agents/0f1e2d3c-4b5a-4968-8776-5a4b3c2d1e0f" }
]
}
}
],
"total": 1,
"limit": 50,
"offset": 0,
"sort": "gap"
}
Add an entry (member and above). For an agent risk, subject_id names the agent. A program-level risk uses subject_type: "tenant" with no id. Omitted axes default from the catalog:
curl -X POST https://api.mitrity.com/api/v1/compliance/risks \
-H "Authorization: Bearer mk_your-api-key" \
-H "Content-Type: application/json" \
-d '{
"subject_type": "agent",
"subject_id": "0f1e2d3c-4b5a-4968-8776-5a4b3c2d1e0f",
"risk_slug": "privacy",
"title": "Personal data in support tickets",
"inherent_likelihood": 4,
"inherent_impact": 4,
"residual_likelihood": 2,
"residual_impact": 4,
"mitigations": "PII patterns bound in enforce mode; credentials brokered.",
"mitigating_capabilities": ["dlp", "credential_broker"],
"owner_email": "ana@example.com"
}'
Update an entry (member and above). Every field is optional, and status moves between open, mitigating and closed:
curl -X PUT https://api.mitrity.com/api/v1/compliance/risks/6b0b7c0e-2a4d-4c8f-9a3e-1f2e3d4c5b6a \
-H "Authorization: Bearer mk_your-api-key" \
-H "Content-Type: application/json" \
-d '{"residual_likelihood": 1, "status": "mitigating"}'
Accept an entry (manager or owner):
curl -X POST https://api.mitrity.com/api/v1/compliance/risks/6b0b7c0e-2a4d-4c8f-9a3e-1f2e3d4c5b6a/accept \
-H "Authorization: Bearer mk_your-api-key" \
-H "Content-Type: application/json" \
-d '{"note": "Residual exposure is inside the agreed appetite for this review cycle."}'
Remove an entry (manager or owner):
curl -X DELETE https://api.mitrity.com/api/v1/compliance/risks/6b0b7c0e-2a4d-4c8f-9a3e-1f2e3d4c5b6a \
-H "Authorization: Bearer mk_your-api-key"
Best practices
- Give every entry an owner. For the frameworks that ask for a risk register, it counts as full evidence only when every entry has an owner.
- Let assessments seed it. Approving an agent's assessment produces the entries its answers imply. Edit those entries rather than entering them again.
- Read the gap, not just the score. A declared residual well below the computed one means the mitigations you rely on are not showing up in the record. Update the mitigations, or the residual, so the two agree.
- Write the acceptance for a reader who was not in the room. The note is reproduced in reports.