Governance

Risk register

The risk register is the catalog of known risks the workspace is tracking, who owns each, what the mitigation plan is, and whether each is open,…

Last updated

Overview

The risk register is the catalog of known risks the workspace is tracking, who owns each, what the mitigation plan is, and whether each is open, mitigated, accepted, or closed. Unlike the incident system (which is reactive to things that broke), the risk register is proactive — risks live in the register before they materialize, and a good register is half of any mature security program. The model is Risk; per-row actions let an owner claim, mitigate, or close a risk; an automation layer auto-creates risk entries when certain triggers fire (cert issuer anomalies via WA.15, renewal failures, vendor risk escalations from the vendor article).

The pages live at /ws/{slug}/governance/risks. The default view is the register list, sortable by severity, status, owner, last update. Tabs filter by status; chips filter by source (manual, cert anomaly, vendor escalation, renewal failure).

Why it exists

Without a register, risks live as anxiety in individual engineers' heads. The risk register externalizes them: "we know about this, here's who owns it, here's what we're doing about it, here's the deadline". When something does materialize into an incident, the postmortem can refer back: "this was risk R-42, raised six months ago, mitigation slipped". When something doesn't materialize, the register still has value: "we tracked it, we made an informed decision, we accepted it".

Auditors love the register because it shows risk management isn't ad hoc. Engineers learn to use it because it shifts the question from "should we worry about X?" to "what's the next action on risk R-42?".

Risk model

Fields:

  • workspace — scope.
  • title — short description.
  • description — markdown free-text.
  • severity — enum: low, medium, high, critical. Drives sort order and alerting.
  • status — enum: identified, assessing, mitigating, mitigated, accepted, closed.
  • ownerUserProfile responsible.
  • mitigation_plan — markdown free-text describing the planned remediation.
  • treatment — enum: mitigate, accept, transfer, avoid. Standard ISO 31000 treatment options.
  • target_resolution_date — DateField; risks past this with status not mitigated/closed get a red badge.
  • ct_alert_link — optional FK to a CertificateTransparencyAlert (the WA.15 source). Populated automatically when the risk was auto-created from a cert anomaly.
  • source — enum: manual, cert_anomaly, cert_renewal_failure, vendor_escalation, incident_postmortem, compliance_gap. Determines the auto-create path that produced this risk.
  • mapped_controls — many-to-many to Control. Lets a control's evidence stream reference the risk.
  • audit_log — implicit via the workspace's AuditLog; every state change writes an entry.

Auto-creation triggers

Cert issuer anomaly (WA.15)

The cert anomaly detector compares consecutive CertificateSnapshot rows for the same host. When the issuer changes between snapshots in a way that doesn't match the host's allowlist of expected issuers, a CertificateTransparencyAlert row is created AND a Risk row is opened with:

  • source = cert_anomaly
  • severity = high
  • ct_alert_link populated
  • owner = the host's owner (or the workspace security lead if no host owner)
  • title = "Unexpected issuer for {hostname}: {old_issuer} → {new_issuer}"

The risk lands in the register with status identified and a default treatment = mitigate. The owner reviews; if the issuer change was legitimate (e.g. internal CA migration), they close with treatment accept.

Cert renewal failure

The certificate renewal subsystem fires vigilo.renewal.failed events. The handler creates a Risk with:

  • source = cert_renewal_failure
  • severity = critical if cert expires in <7d, high otherwise
  • title = "Cert renewal failed for {hostname}, expires in {n} days"
  • owner = the host's owner

If the renewal succeeds on retry within 24 hours, the auto-created risk is auto-closed with status mitigated, source-of-truth audit entry preserved.

Vendor risk escalation

When a VendorAssessment writes a risk score ≥80 (configurable per workspace), the workflow opens a Risk:

  • source = vendor_escalation
  • severity = critical for tier-1 vendors, high for tier-2, medium otherwise
  • title = "Vendor {name} scored {score}/100 on {template_name}"
  • owner = the vendor's owner

This puts the vendor risk in the same queue as everything else, so it doesn't drift in a separate vendor-only view.

Per-row actions

Each row in the register has a row-action menu:

  • Claim — set the current user as owner. Visible when owner is unassigned or when current user has risk.reassign permission. Writes an audit entry.
  • Mitigate — opens a dialog to update mitigation plan, mark progress, and optionally flip status to mitigating or mitigated. Required: a comment describing what was done.
  • Close — flip status to closed. Required: a treatment (mitigate/accept/transfer/avoid) and a closing comment. A closed risk can be reopened by an admin.
  • Add evidence — attach a file or URL proving the mitigation. Most useful when the risk maps to a compliance control.
  • Link to control — pick a Control to add to mapped_controls. Once linked, the risk appears in the control's risk panel.

Linking to compliance

A risk with mapped_controls shows in two places: the risk register, and the linked control's detail page under a Risks tab. Auditors find this useful — they look at a control, see "two open risks reference this control", click through, and follow the mitigation work.

ComplianceEvidenceQuery can also pull risks as evidence (query_kind=risk is on the roadmap; today, file the closing summary as a file evidence kind).

Common workflows

1. Triage an auto-created cert anomaly risk

  1. Governance → Risks → Source filter: cert_anomaly.
  2. Open the risk; the ct_alert_link button opens the side-by-side snapshot comparison from the certificate transparency feature.
  3. If the change was legitimate: Close with treatment accept, comment explaining why (e.g. "internal CA rotation").
  4. If illegitimate: Mitigate with action plan (e.g. "investigating unauthorized cert issuance — incident I-128 opened").

2. Open a new manual risk

  1. Governance → Risks → New risk.
  2. Title, description, severity, treatment, target date.
  3. Optionally pick mapped_controls.
  4. Assign owner. Save.
  5. Owner gets in-app + email notification.

3. Quarterly risk review

  1. Risks → Status: open OR mitigating, sort by target_resolution_date ascending.
  2. For each risk past target date: comment with reason for slip, update target date.
  3. Export CSV for the review meeting.

4. Track a control's risk surface

  1. Open the control on the framework page.
  2. Risks tab shows every risk in mapped_controls.
  3. Count open vs closed → controls with many open risks are weak areas.

Permissions

Action Role
View register Viewer or higher
Create manual risk Engineer or higher
Claim a risk Engineer or higher
Mitigate / close own risks Owner (of the risk) or admin
Reopen closed risk Admin / Owner
Edit auto-created risks Owner (of the risk) or admin

The risk.reassign permission lets an admin reassign any risk; otherwise reassignment is only possible by the current owner.

Troubleshooting

Auto-created risk has no owner. The trigger couldn't resolve a default owner (e.g. cert anomaly on a host with no host-owner field, and no workspace security lead). Set the workspace security lead in Settings → Security → Default risk owner.

Risk shows past target date even though I mitigated it. Status wasn't updated to mitigated or closed. Re-open and update.

Risk auto-closed when I wasn't done. Cert renewal succeeded on retry within 24h. Re-open if there's still residual risk (e.g. you want to investigate why the first attempt failed).

Linked control doesn't show the risk. The risk's mapped_controls is empty. Open the risk → Link to control → pick.

Vendor escalation risk severity feels wrong. The default severity mapping is in vendor_escalation handler code. To adjust, override VIGILO_VENDOR_ESCALATION_SEVERITY_MAP env var — a JSON dict mapping tier to severity.

Closing requires a treatment but I'm not sure which. Use ISO 31000 defaults: mitigate (we did work to reduce it), accept (we chose to live with it), transfer (we got insurance or pushed to a vendor), avoid (we changed our approach so the risk no longer applies).

Related articles