compliance governance

Conducting a Cyber Risk Assessment with NIST SP 800-30

Conduct a defensible cybersecurity risk assessment using the NIST SP 800-30 Rev 1 methodology: prepare scope and a risk model, identify threat sources and threat events, identify vulnerabilities and predisposing conditions, determine likelihood and impact, compute risk, and communicate results as a prioritized risk register. Use when an organization needs an actual risk *assessment* (not a maturity score), when a control framework (CSF, ISO 27001, RMF, SOC 2, PCI) requires a documented risk analysis as input, when leadership asks "what are our top risks and how bad are they", when assessing risk for a new system or major change, or when building a risk register from scratch. This is the methodology that feeds framework selection, ATO packages, and treatment decisions. Keywords: risk assessment, NIST 800-30, threat modeling, likelihood and impact, risk register, risk analysis, threat sources, vulnerabilities, risk determination, qualitative risk, risk matrix, residual risk, risk treatment.

compliancegovernancenist-800-30nist-800-39risk-assessmentrisk-managementrisk-registerthreat-modeling
Install this skill
npx skills add mukul975/Anthropic-Cybersecurity-Skills
Framework mappings

When to Use

  • When the organization needs a real risk assessment — an analysis of specific threats, likelihoods, and impacts — rather than a maturity score against a framework. (Maturity tells you how mature your practices are; a risk assessment tells you what could hurt you and how badly.)
  • When another framework requires a documented risk analysis as a mandatory input: NIST CSF (ID.RA), ISO 27001 (Clause 6.1.2), NIST RMF / 800-37 (the Prepare and Select steps), SOC 2 (CC3), PCI DSS, or HIPAA (§164.308(a)(1)(ii)(A)).
  • When standing up or significantly changing a system and you must understand its risk before authorization or go-live.
  • When leadership asks for the organization's top risks, ranked, with a rationale they can defend to a board or regulator.
  • When building or refreshing an enterprise risk register.

Prerequisites

  • An inventory of in-scope assets, systems, and the information types they handle (system boundary defined).
  • Access to threat intelligence (internal incident history, sector ISAC feeds, MITRE ATT&CK) to ground threat-event likelihood in observed behavior.
  • Vulnerability data (scan results, pen-test findings, configuration/architecture review) for the in-scope systems.
  • Business context: which missions/processes the systems support, and what impact to confidentiality, integrity, or availability would mean in business terms.
  • Agreement on the risk model and scales before scoring, so results are comparable and repeatable (see references/standards.md).
  • Familiarity with the three-tier risk-management context from NIST SP 800-39 (organization, mission/business process, information system).

Workflow

NIST SP 800-30 Rev 1 defines four steps. Steps 1 and 4 bookend the assessment; Step 2 is the analytic core.

1. Prepare for the assessment

Define and document:

  • Purpose (e.g., support an authorization decision, inform control selection, satisfy ISO 6.1.2).
  • Scope — organizational tier (Tier 1/2/3), systems, and time horizon.
  • Assumptions and constraints (e.g., assume an external adversarial threat with moderate capability).
  • Information sources — threat, vulnerability, and impact inputs.
  • Risk model and analytic approach — the factors (threat source, threat event, vulnerability, likelihood, impact) and the scales (qualitative Very Low–Very High, or semi-quantitative 0–10). Lock these now.

2. Conduct the assessment

Work through the analytic tasks in order. The 800-30 appendices provide the reference taxonomies (D–I).

2a. Identify threat sources (Appendix D). Classify by type: Adversarial (individuals, groups, nation-states — characterize capability, intent, targeting), Accidental (user error), Structural (equipment/software failure), Environmental (natural disasters, infrastructure outages).

2b. Identify threat events (Appendix E). The specific actions a source could take (e.g., "adversary exfiltrates credentials via phishing then moves laterally"). Map adversarial events to MITRE ATT&CK techniques for traceability.

2c. Identify vulnerabilities and predisposing conditions (Appendix F). Weaknesses (missing MFA, unpatched service) and conditions that make exploitation more or less likely (internet exposure, flat network, lack of segmentation).

2d. Determine likelihood (Appendix G). Assess the likelihood that a threat event is initiated (adversarial) or occurs (non-adversarial), and the likelihood it results in adverse impact given the vulnerabilities. Combine into an overall likelihood on the agreed scale.

2e. Determine impact (Appendix H). Magnitude of harm if the event succeeds — to operations, assets, individuals, other organizations, or the nation. Express against the agreed scale and in business terms.

2f. Determine risk (Appendix I). Risk is a function of likelihood and impact. Plot each threat event on the risk matrix (e.g., likelihood × impact → Very Low … Very High). Record the risk level, the contributing factors, and uncertainty/assumptions.

3. Communicate and share results

Produce the risk register and an executive briefing. For each risk: the threat event, affected assets, likelihood, impact, risk level, key contributing vulnerabilities, and a recommended treatment. Rank by risk level so decision-makers see the top risks first.

4. Maintain the assessment

Risk is not static. Define a refresh cadence and the triggers that force re-assessment (new system, major architecture change, significant incident, new threat intel). Track risk-acceptance decisions and treatment progress over time.

5. Drive treatment decisions

Hand the ranked register to risk owners. For each risk choose a treatment — mitigate (add/strengthen controls), transfer (insurance, contractual), avoid (stop the activity), or accept (document residual risk with an authorizing signature). Re-score residual risk after planned controls to show the post-treatment position.

Key Concepts

Concept Definition
Threat source The cause of a threat event: adversarial, accidental, structural, or environmental.
Threat event A specific action or occurrence that could cause harm (mapped to ATT&CK for adversarial cases).
Vulnerability A weakness that a threat event can exploit.
Predisposing condition A condition that increases or decreases the likelihood of adverse impact (e.g., internet exposure).
Likelihood The chance a threat event initiates/occurs and results in adverse impact.
Impact The magnitude of harm if the event succeeds.
Risk A function of likelihood and impact; the expected harm to the organization.
Inherent vs residual risk Risk before vs after planned/implemented controls.
Risk tolerance / appetite The level of risk leadership is willing to accept.
Risk register The prioritized record of risks, scores, owners, and treatments.

Tools & Systems

  • NIST SP 800-30 Rev 1 — the methodology and Appendices D–I taxonomies (threat sources, events, vulnerabilities, likelihood, impact, risk).
  • NIST SP 800-39 — enterprise risk-management context (three tiers).
  • MITRE ATT&CK — to enumerate and ground adversarial threat events in observed TTPs.
  • Vulnerability scanners / pen-test reports — empirical vulnerability input.
  • Threat intel sources — sector ISACs, vendor feeds, internal incident history for likelihood grounding.
  • GRC / risk-register tooling — spreadsheet, or platforms such as ServiceNow GRC, Archer, OneTrust, to store and track the register.
  • FAIR (optional) — a quantitative model if leadership wants risk expressed in dollar ranges rather than qualitative bands.

Common Scenarios

  • CSF/ISO/SOC 2 needs a risk analysis. Run 800-30 to produce the documented assessment those frameworks require as input, then map results to their control sets.
  • New system before go-live. Assess threat events against the system's architecture and feed the result into the authorization (RMF Select/Authorize) decision.
  • Board wants the top risks. Deliver a ranked register with impacts in business terms and clear treatment recommendations.
  • Post-incident. Re-assess the affected systems; the incident updates likelihood evidence and may surface new threat events.
  • Annual refresh. Re-score against current threat intel and control changes; show movement in residual risk year over year.

Output Format

Produce a Risk Assessment Report using assets/template.md, containing:

  1. Purpose, scope, and tier — what was assessed and why.
  2. Assumptions, constraints, and risk model — the factors and scales used (so results are reproducible).
  3. Threat sources — by type, with adversarial characterization.
  4. Threat events — each with affected assets and ATT&CK mapping where adversarial.
  5. Vulnerabilities and predisposing conditions — tied to threat events.
  6. Risk register — table: ID, threat event, asset, likelihood, impact, risk level, contributing vulnerabilities, recommended treatment, owner, residual risk.
  7. Top risks summary — ranked, in business terms, for leadership.
  8. Maintenance plan — refresh cadence and re-assessment triggers.

Use scripts/process.py to score the register from a risk-input JSON (likelihood × impact → risk level on a configurable matrix), rank risks, and emit the register table.

Source materials

References and resources

Everything below is rendered for inspection. Script files are read-only and never run.

References 1

standards.md3.7 KB

NIST SP 800-30 — Standards & Reference

Primary standard

NIST SP 800-30 Revision 1 — Guide for Conducting Risk Assessments

Companion standards

Document Role
NIST SP 800-39 Managing Information Security Risk — three-tier context (Tier 1 organization, Tier 2 mission/business process, Tier 3 information system).
NIST SP 800-37 Rev 2 Risk Management Framework — risk assessment feeds the Prepare, Select, and Authorize steps.
NIST SP 800-53 Rev 5 Control catalog — source of mitigating controls chosen in risk treatment.
FIPS 199 / FIPS 200 Security categorization (L/M/H per C/I/A) and minimum requirements.
MITRE ATT&CK Adversarial threat-event enumeration and traceability.
FAIR (Open Group) Optional quantitative risk model (dollar-range loss exposure).

The four-step process (800-30 Rev 1)

  1. Prepare — purpose, scope, assumptions/constraints, sources, risk model and scales.
  2. Conduct — tasks 2a–2f below.
  3. Communicate — risk register + briefing.
  4. Maintain — monitoring and refresh.

Conduct tasks and their reference appendices

Task Appendix Output
Identify threat sources D Adversarial / Accidental / Structural / Environmental sources
Identify threat events E Specific events (map adversarial to ATT&CK)
Identify vulnerabilities & predisposing conditions F Weaknesses + conditions affecting impact likelihood
Determine likelihood G Likelihood of initiation/occurrence and of adverse impact
Determine impact H Magnitude of harm
Determine risk I Risk level = f(likelihood, impact)

Threat source types (Appendix D)

  • Adversarial — individuals, groups, organizations, nation-states. Characterize by capability, intent, and targeting.
  • Accidental — erroneous actions by authorized users.
  • Structural — failures of equipment, software, or environmental controls.
  • Environmental — natural or man-made disasters, infrastructure outages.

Assessment scales (qualitative / semi-quantitative)

800-30 uses five-level scales. A common qualitative mapping:

Level Semi-quantitative (0–10)
Very Low 0–4
Low 5–20
Moderate 21–79
High 80–95
Very High 96–100

Reference 5×5 risk matrix (likelihood × impact → risk)

Likelihood ↓ / Impact → Very Low Low Moderate High Very High
Very High Very Low Low Moderate High Very High
High Very Low Low Moderate High Very High
Moderate Very Low Low Moderate Moderate High
Low Very Low Low Low Low Moderate
Very Low Very Low Very Low Very Low Low Low

Document whichever matrix the organization adopts during Prepare; the engine in scripts/process.py defaults to the table above and is configurable.

NIST CSF 2.0 alignment

CSF 2.0 ID Relevance
GV.RM-01 Risk management objectives established
ID.RA-01 Vulnerabilities in assets identified
ID.RA-03 Internal and external threats identified
ID.RA-04 Potential impacts and likelihoods identified
ID.RA-05 Threats, vulnerabilities, likelihoods, and impacts used to understand inherent risk and prioritize response

Risk treatment options

  • Mitigate — implement/strengthen controls (re-score residual risk).
  • Transfer — insurance or contractual shift.
  • Avoid — discontinue the risk-generating activity.
  • Accept — document residual risk with an authorizing signature within risk tolerance.

Scripts 1

process.py7.8 KB
Display-only source. This catalog never executes bundled scripts.
#!/usr/bin/env python3
"""
NIST SP 800-30 Rev 1 risk-register scoring engine.

Reads a risk-input JSON file describing threat events with their assessed
likelihood and impact, computes a risk level for each using the 800-30 5x5
reference matrix (configurable), ranks the register highest-risk-first, and
emits a Markdown risk register plus a top-risks summary.

Input JSON shape:
{
  "assessment": {
    "name": "Q2 FY26 Enterprise Risk Assessment",
    "scope": "Internet-facing application tier",
    "tier": 3
  },
  "matrix": {                      # OPTIONAL - overrides the default 800-30 matrix
    "High": {"Very High": "Very High", ...}
  },
  "risks": [
    {
      "id": "R-01",
      "threat_event": "Adversary phishes credentials and moves laterally",
      "threat_source": "Adversarial",
      "asset": "Customer portal / identity provider",
      "attack_techniques": ["T1566", "T1021"],
      "likelihood": "High",
      "impact": "Very High",
      "vulnerabilities": ["No phishing-resistant MFA", "Flat internal network"],
      "treatment": "Mitigate",
      "owner": "IAM Lead",
      "residual_risk": "Moderate"
    }
  ]
}

Likelihood / impact values must be one of:
  Very Low | Low | Moderate | High | Very High

Usage:
  python process.py --input risks.json [--output register.md] [--fail-on High]
  python process.py --print-matrix
"""

import argparse
import json
import sys

LEVELS = ["Very Low", "Low", "Moderate", "High", "Very High"]
RANK = {lvl: i for i, lvl in enumerate(LEVELS)}

# 800-30 Rev 1 reference 5x5 matrix: MATRIX[likelihood][impact] -> risk level
MATRIX = {
    "Very High": {"Very Low": "Very Low", "Low": "Low", "Moderate": "Moderate", "High": "High", "Very High": "Very High"},
    "High":      {"Very Low": "Very Low", "Low": "Low", "Moderate": "Moderate", "High": "High", "Very High": "Very High"},
    "Moderate":  {"Very Low": "Very Low", "Low": "Low", "Moderate": "Moderate", "High": "Moderate", "Very High": "High"},
    "Low":       {"Very Low": "Very Low", "Low": "Low", "Moderate": "Low", "High": "Low", "Very High": "Moderate"},
    "Very Low":  {"Very Low": "Very Low", "Low": "Very Low", "Moderate": "Very Low", "High": "Low", "Very High": "Low"},
}


def validate_level(value, field, rid):
    if value not in LEVELS:
        raise ValueError(
            f"risk {rid}: {field} '{value}' is not a valid 800-30 level "
            f"(expected one of {LEVELS})"
        )
    return value


def score_risk(risk, matrix):
    rid = risk.get("id", "?")
    likelihood = validate_level(risk.get("likelihood"), "likelihood", rid)
    impact = validate_level(risk.get("impact"), "impact", rid)
    level = matrix[likelihood][impact]
    return level


def render_markdown(data, matrix):
    meta = data.get("assessment", {})
    risks = data.get("risks", [])

    scored = []
    for r in risks:
        level = score_risk(r, matrix)
        scored.append((r, level))

    # rank highest risk first; tie-break by impact then likelihood
    scored.sort(
        key=lambda rl: (
            RANK[rl[1]],
            RANK[rl[0].get("impact", "Very Low")],
            RANK[rl[0].get("likelihood", "Very Low")],
        ),
        reverse=True,
    )

    lines = []
    title = meta.get("name", "Risk Assessment")
    lines.append(f"# Risk Register - {title}")
    lines.append("")
    if meta.get("scope"):
        lines.append(f"- **Scope:** {meta['scope']}")
    if meta.get("tier"):
        lines.append(f"- **Risk-management tier (SP 800-39):** Tier {meta['tier']}")
    lines.append(f"- **Risks assessed:** {len(scored)}")
    lines.append("")
    lines.append("Risk level computed from likelihood x impact on the NIST SP 800-30 Rev 1 5x5 matrix.")
    lines.append("")

    # main register
    header = (
        "| ID | Threat event | Source | Asset | ATT&CK | Likelihood | Impact "
        "| Risk | Key vulnerabilities | Treatment | Residual | Owner |"
    )
    sep = "|" + "---|" * 12
    lines.append(header)
    lines.append(sep)
    for r, level in scored:
        attck = ", ".join(r.get("attack_techniques", [])) or "-"
        vulns = "; ".join(r.get("vulnerabilities", [])) or "-"
        lines.append(
            "| {id} | {event} | {src} | {asset} | {attck} | {like} | {imp} "
            "| **{risk}** | {vulns} | {treat} | {res} | {owner} |".format(
                id=r.get("id", "-"),
                event=r.get("threat_event", "-"),
                src=r.get("threat_source", "-"),
                asset=r.get("asset", "-"),
                attck=attck,
                like=r.get("likelihood", "-"),
                imp=r.get("impact", "-"),
                risk=level,
                vulns=vulns,
                treat=r.get("treatment", "-"),
                res=r.get("residual_risk", "-"),
                owner=r.get("owner", "-"),
            )
        )

    # top risks summary
    top = [rl for rl in scored if RANK[rl[1]] >= RANK["High"]]
    lines.append("")
    lines.append("## Top risks (High and above)")
    lines.append("")
    if not top:
        lines.append("_No risks scored High or above._")
    else:
        for r, level in top:
            lines.append(f"- **{level}** - {r.get('id','-')}: {r.get('threat_event','-')} "
                         f"(impact {r.get('impact','-')}, likelihood {r.get('likelihood','-')})")

    # distribution
    dist = {lvl: 0 for lvl in LEVELS}
    for _, level in scored:
        dist[level] += 1
    lines.append("")
    lines.append("## Risk distribution")
    lines.append("")
    lines.append("| Risk level | Count |")
    lines.append("|---|---|")
    for lvl in reversed(LEVELS):
        lines.append(f"| {lvl} | {dist[lvl]} |")

    return "\n".join(lines), scored, dist


def main():
    ap = argparse.ArgumentParser(description="NIST SP 800-30 risk-register scoring engine")
    ap.add_argument("--input", "-i", help="Path to risk-input JSON")
    ap.add_argument("--output", "-o", help="Write Markdown register to this path")
    ap.add_argument("--fail-on", choices=LEVELS,
                    help="Exit non-zero if any risk scores at or above this level (for pipelines)")
    ap.add_argument("--print-matrix", action="store_true", help="Print the active risk matrix and exit")
    args = ap.parse_args()

    if args.print_matrix:
        print("NIST SP 800-30 Rev 1 reference 5x5 matrix (likelihood x impact -> risk):\n")
        hdr = "Likelihood \\ Impact | " + " | ".join(LEVELS)
        print(hdr)
        print("-" * len(hdr))
        for like in reversed(LEVELS):
            row = " | ".join(MATRIX[like][imp] for imp in LEVELS)
            print(f"{like} | {row}")
        return 0

    if not args.input:
        ap.error("--input is required unless --print-matrix is used")

    try:
        with open(args.input) as f:
            data = json.load(f)
    except (OSError, json.JSONDecodeError) as e:
        print(f"ERROR: could not read input JSON: {e}", file=sys.stderr)
        return 2

    matrix = data.get("matrix", MATRIX)

    try:
        md, scored, dist = render_markdown(data, matrix)
    except ValueError as e:
        print(f"ERROR: {e}", file=sys.stderr)
        return 2

    if args.output:
        with open(args.output, "w") as f:
            f.write(md + "\n")
        print(f"Risk register written to {args.output}", file=sys.stderr)
    else:
        print(md)

    # summary to stderr
    summary = ", ".join(f"{lvl}:{dist[lvl]}" for lvl in reversed(LEVELS) if dist[lvl])
    print(f"Scored {len(scored)} risks ({summary}).", file=sys.stderr)

    if args.fail_on:
        threshold = RANK[args.fail_on]
        breaches = [(r.get("id", "?"), lvl) for r, lvl in scored if RANK[lvl] >= threshold]
        if breaches:
            ids = ", ".join(f"{rid}={lvl}" for rid, lvl in breaches)
            print(f"FAIL: {len(breaches)} risk(s) at or above {args.fail_on}: {ids}", file=sys.stderr)
            return 1

    return 0


if __name__ == "__main__":
    sys.exit(main())

Assets 1

template.mdtext/markdown · 4.9 KB
Keep exploring