SuperAPI AI Directory

risk-assessment-summarization

Data & analytics

View repository

Updated 10-15 22:12

About this skill

Risk Assessment Summarization

Overview

This skill produces comprehensive summaries of risk assessments in ServiceNow GRC, including:

  • Aggregating risk scores across profiles, categories, and business units
  • Comparing inherent risk versus residual risk to measure control effectiveness
  • Generating heat map data (likelihood x impact matrices) for visual reporting
  • Identifying risk trends over time (increasing, stable, decreasing)
  • Recommending mitigation strategies based on treatment plans and residual exposure
  • Linking risk assessments to underlying risk events and control effectiveness

When to use:

  • Preparing risk committee or board-level risk reports
  • After completing a risk assessment cycle and needing consolidated results
  • When comparing risk posture across business units or time periods
  • During strategic planning to understand enterprise risk exposure
  • When evaluating effectiveness of risk mitigation investments

Prerequisites

  • Roles: sn_risk.manager, sn_grc.manager, or admin
  • Plugins: com.sn_risk, com.sn_grc
  • Access: Read access to sn_grc_risk, sn_risk_definition, sn_risk_event, sn_grc_profile tables
  • Knowledge: Understanding of risk scoring methodology (likelihood x impact) and organizational risk appetite

Key Risk Tables

Table Purpose Key Fields
sn_grc_risk Risk register records number, short_description, risk_score, inherent_risk, residual_risk, state, treatment, likelihood, impact, profile, category, owner
sn_risk_definition Risk framework definitions number, short_description, risk_criteria, scoring_method, appetite
sn_risk_event Actual risk events/incidents number, short_description, event_date, impact_amount, risk, category
sn_grc_profile Entity profiles for risk scoping number, short_description, profile_type, applies_to
sn_grc_item GRC content items number, short_description, item_type, profile

Procedure

Step 1: Retrieve Active Risk Records

Query all active risks with scoring data.

Using MCP (Claude Code/Desktop):

Tool: SN-Query-Table
Parameters:
  table_name: sn_grc_risk
  query: active=true^ORDERBYDESCrisk_score
  fields: sys_id,number,short_description,risk_score,inherent_risk,residual_risk,state,treatment,likelihood,impact,profile,category,owner,sys_created_on,sys_updated_on
  limit: 100

Using REST API:

GET /api/now/table/sn_grc_risk?sysparm_query=active=true^ORDERBYDESCrisk_score&sysparm_fields=sys_id,number,short_description,risk_score,inherent_risk,residual_risk,state,treatment,likelihood,impact,profile,category,owner&sysparm_limit=100

Step 2: Retrieve Risk Framework Definitions

Understand the scoring methodology and risk appetite thresholds.

Using MCP:

Tool: SN-Query-Table
Parameters:
  table_name: sn_risk_definition
  query: active=true
  fields: sys_id,number,short_description,risk_criteria,scoring_method,appetite,description
  limit: 20

Using REST API:

GET /api/now/table/sn_risk_definition?sysparm_query=active=true&sysparm_fields=sys_id,number,short_description,risk_criteria,scoring_method,appetite,description&sysparm_limit=20

Step 3: Generate Risk Assessment Summary with Inherent vs Residual Analysis

Produce aggregated risk metrics and control effectiveness:

Tool: SN-Execute-Background-Script
Parameters:
  script: |
    var summary = {
      generated_date: new GlideDateTime().toString(),
      total_risks: 0,
      by_treatment: {},
      by_category: {},
      inherent_vs_residual: {
        avg_inherent: 0,
        avg_residual: 0,
        control_effectiveness_pct: 0,
        risks_above_appetite: 0
      },
      severity_distribution: { critical: 0, high: 0, medium: 0, low: 0 },
      top_risks: []
    };

    var inherentSum = 0, residualSum = 0, count = 0;
    var riskAppetite = 50; // Default; adjust per organization

    var gr = new GlideRecord('sn_grc_risk');
    gr.addQuery('active', true);
    gr.orderByDesc('risk_score');
    gr.query();

    while (gr.next()) {
      summary.total_risks++;
      count++;

      var inherent = parseInt(gr.inherent_risk.toString()) || 0;
      var residual = parseInt(gr.residual_risk.toString()) || 0;
      var score = parseInt(gr.risk_score.toString()) || 0;

      inherentSum += inherent;
      residualSum += residual;

      if (residual > riskAppetite) summary.inherent_vs_residual.risks_above_appetite++;

      // Severity distribution
      if (score >= 80) summary.severity_distribution.critical++;
      else if (score >= 60) summary.severity_distribution.high++;
      else if (score >= 30) summary.severity_distribution.medium++;
      else summary.severity_distribution.low++;

      // Treatment breakdown
      var treat = gr.treatment.getDisplayValue() || 'Not Assigned';
      summary.by_treatment[treat] = (summary.by_treatment[treat] || 0) + 1;

      // Category breakdown
      var cat = gr.category.getDisplayValue() || 'Uncategorized';
      summary.by_category[cat] = (summary.by_category[cat] || 0) + 1;

      // Top 10 risks
      if (summary.top_risks.length < 10) {
        summary.top_risks.push({
          number: gr.number.toString(),
          title: gr.short_description.toString(),
          risk_score: score,
          inherent: inherent,
          residual: residual,
          treatment: gr.treatment.getDisplayValue(),
          owner: gr.owner.getDisplayValue()
        });
      }
    }

    if (count > 0) {
      summary.inherent_vs_residual.avg_inherent = Math.round(inherentSum / count);
      summary.inherent_vs_residual.avg_residual = Math.round(residualSum / count);
      if (inherentSum > 0) {
        summary.inherent_vs_residual.control_effectiveness_pct =
          Math.round(((inherentSum - residualSum) / inherentSum) * 100);
      }
    }

    gs.info('RISK ASSESSMENT SUMMARY:\n' + JSON.stringify(summary, null, 2));
  description: "GRC: Generate risk assessment summary with inherent vs residual analysis"

Step 4: Generate Heat Map Data (Likelihood x Impact Matrix)

Build a likelihood-impact matrix for heat map visualization:

Tool: SN-Execute-Background-Script
Parameters:
  script: |
    // Heat map: 5x5 matrix (likelihood x impact)
    var heatMap = {};
    for (var l = 1; l <= 5; l++) {
      for (var i = 1; i <= 5; i++) {
        heatMap[l + 'x' + i] = { likelihood: l, impact: i, count: 0, risks: [] };
      }
    }

    var gr = new GlideRecord('sn_grc_risk');
    gr.addQuery('active', true);
    gr.query();

    while (gr.next()) {
      var likelihood = parseInt(gr.likelihood.toString()) || 0;
      var impact = parseInt(gr.impact.toString()) || 0;

      // Normalize to 1-5 scale if needed
      if (likelihood > 5) likelihood = Math.ceil(likelihood / 20);
      if (impact > 5) impact = Math.ceil(impact / 20);
      if (likelihood < 1) likelihood = 1;
      if (impact < 1) impact = 1;

      var key = likelihood + 'x' + impact;
      if (heatMap[key]) {
        heatMap[key].count++;
        if (heatMap[key].risks.length < 3) {
          heatMap[key].risks.push(gr.number.toString() + ': ' + gr.short_description.toString());
        }
      }
    }

    // Format for output
    var matrix = [];
    for (var cell in heatMap) {
      if (heatMap[cell].count > 0) {
        matrix.push(heatMap[cell]);
      }
    }
    matrix.sort(function(a, b) { return (b.likelihood * b.impact) - (a.likelihood * a.impact); });

    gs.info('RISK HEAT MAP DATA:\n' + JSON.stringify(matrix, null, 2));
    gs.info('\nHEAT MAP LEGEND:');
    gs.info('  Critical (Red): Likelihood >= 4 AND Impact >= 4');
    gs.info('  High (Orange): Score >= 12');
    gs.info('  Medium (Yellow): Score >= 6');
    gs.info('  Low (Green): Score < 6');
  description: "GRC: Generate risk heat map data (likelihood x impact matrix)"

Step 5: Analyze Risk Trends Over Time

Compare current risk posture to historical baseline:

Tool: SN-Execute-Background-Script
Parameters:
  script: |
    var periods = [
      { label: 'Current', days: 0 },
      { label: '30 Days Ago', days: 30 },
      { label: '60 Days Ago', days: 60 },
      { label: '90 Days Ago', days: 90 }
    ];

    var trends = [];
    for (var p = 0; p < periods.length; p++) {
      var period = periods[p];
      var totalScore = 0, count = 0, critical = 0;

      if (period.days === 0) {
        var gr = new GlideRecord('sn_grc_risk');
        gr.addQuery('active', true);
        gr.query();
        while (gr.next()) {
          var s = parseInt(gr.risk_score.toString()) || 0;
          totalScore += s;
          count++;
          if (s >= 80) critical++;
        }
      } else {
        // Use risk events as proxy for historical risk levels
        var ev = new GlideAggregate('sn_risk_event');
        ev.addQuery('sys_created_on', '>=', gs.daysAgo(period.days));
        ev.addQuery('sys_created_on', '<', gs.daysAgo(period.days - 30));
        ev.addAggregate('COUNT');
        ev.query();
        if (ev.next()) count = parseInt(ev.getAggregate('COUNT'));
      }

      trends.push({
        period: period.label,
        total_risks: count,
        avg_score: count > 0 ? Math.round(totalScore / count) : 0,
        critical_risks: critical
      });
    }

    gs.info('RISK TREND ANALYSIS:\n' + JSON.stringify(trends, null, 2));
  description: "GRC: Analyze risk score trends over time"

Step 6: Generate Mitigation Recommendations

Produce actionable recommendations based on risk treatment and residual exposure:

Tool: SN-Execute-Background-Script
Parameters:
  script: |
    var recommendations = [];

    var gr = new GlideRecord('sn_grc_risk');
    gr.addQuery('active', true);
    gr.addQuery('residual_risk', '>', 50);
    gr.orderByDesc('residual_risk');
    gr.setLimit(20);
    gr.query();

    while (gr.next()) {
      var inherent = parseInt(gr.inherent_risk.toString()) || 0;
      var residual = parseInt(gr.residual_risk.toString()) || 0;
      var reduction = inherent > 0 ? Math.round(((inherent - residual) / inherent) * 100) : 0;
      var treatment = gr.treatment.getDisplayValue() || 'Not Assigned';

      var rec = {
        risk_number: gr.number.toString(),
        title: gr.short_description.toString(),
        inherent_risk: inherent,
        residual_risk: residual,
        risk_reduction_pct: reduction,
        current_treatment: treatment,
        recommendations: []
      };

      // Generate recommendations based on treatment and residual level
      if (treatment === 'Accept' && residual >= 70) {
        rec.recommendations.push('Re-evaluate acceptance decision; residual risk exceeds organizational appetite');
        rec.recommendations.push('Conduct cost-benefit analysis for additional controls');
      }
      if (treatment === 'Mitigate' && reduction < 30) {
        rec.recommendations.push('Current controls are insufficient; risk reduction is only ' + reduction + '%');
        rec.recommendations.push('Evaluate additional compensating controls or alternative mitigation strategies');
      }
      if (treatment === 'Transfer' && residual >= 60) {
        rec.recommendations.push('Verify insurance coverage adequacy for residual exposure');
        rec.recommendations.push('Review third-party SLAs and liability clauses');
      }
      if (treatment === 'Not Assigned') {
        rec.recommendations.push('URGENT: Assign risk treatment strategy immediately');
        rec.recommendations.push('Schedule risk owner meeting to determine treatment approach');
      }
      if (residual >= 80) {
        rec.recommendations.push('CRITICAL: Escalate to executive leadership for immediate action');
      }

      // Check for related control failures
      var ctrl = new GlideRecord('sn_compliance_control');
      ctrl.addQuery('profile', gr.profile.toString());
      ctrl.addQuery('state', 'NOT IN', 'compliant,passed');
      ctrl.query();
      if (ctrl.getRowCount() > 0) {
        rec.recommendations.push('Address ' + ctrl.getRowCount() + ' failing controls linked to this risk profile');
      }

      recommendations.push(rec);
    }

    gs.info('MITIGATION RECOMMENDATIONS:\n' + JSON.stringify(recommendations, null, 2));
  description: "GRC: Generate mitigation recommendations for high-residual risks"

Step 7: Query Risk Events for Loss Data Context

Retrieve actual risk events to ground the assessment in real incidents:

Using MCP:

Tool: SN-Query-Table
Parameters:
  table_name: sn_risk_event
  query: sys_created_on>=javascript:gs.daysAgo(365)^ORDERBYDESCimpact_amount
  fields: sys_id,number,short_description,event_date,impact_amount,risk,category,state
  limit: 50

Using REST API:

GET /api/now/table/sn_risk_event?sysparm_query=sys_created_on>=javascript:gs.daysAgo(365)^ORDERBYDESCimpact_amount&sysparm_fields=sys_id,number,short_description,event_date,impact_amount,risk,category,state&sysparm_limit=50

Tool Usage

Operation MCP Tool REST Endpoint
Query Risks SN-Query-Table GET /api/now/table/sn_grc_risk
Query Risk Definitions SN-Query-Table GET /api/now/table/sn_risk_definition
Query Risk Events SN-Query-Table GET /api/now/table/sn_risk_event
Search Risks SN-Natural-Language-Search N/A
Complex Analysis SN-Execute-Background-Script POST /api/now/table/sys_trigger
Schema Discovery SN-Discover-Table-Schema GET /api/now/table/sys_dictionary

Best Practices

  • Inherent vs Residual: Always present both inherent and residual risk to demonstrate control value
  • Risk Appetite Context: Frame all scores against the organization's defined risk appetite thresholds
  • Heat Map Clarity: Use standardized 5x5 matrices with consistent color coding across reports
  • Trend Baselines: Establish quarterly baselines for meaningful trend comparison
  • Treatment Validation: Verify that risk treatment strategies are actively implemented, not just documented
  • Loss Data Integration: Incorporate actual risk event data to validate assessment accuracy
  • Owner Accountability: Include risk owner names in all summaries for clear accountability

Troubleshooting

Risk Scores Show as Zero

Symptom: All risk_score, inherent_risk, or residual_risk fields return 0 or empty Cause: Risk scoring may use calculated fields populated by assessment workflows, or custom scoring fields Solution:

Tool: SN-Discover-Table-Schema
Parameters:
  table_name: sn_grc_risk

Check for alternative scoring fields like calculated_risk, risk_score_calculated, or assessment_score. Also verify that risk assessments have been completed.

Likelihood and Impact Not Populated

Symptom: Heat map generation fails due to empty likelihood/impact values Cause: Risk records may use qualitative ratings (High/Medium/Low) instead of numeric scores Solution: Map qualitative values to numeric scale: Critical=5, High=4, Medium=3, Low=2, Very Low=1. Check the sys_choice table for field value definitions.

Risk Events Table Empty

Symptom: No records returned from sn_risk_event Cause: The Risk Events module may not be configured, or events are tracked in a custom table Solution: Check if com.sn_risk_advanced plugin is activated. Some organizations track loss events in custom tables or external systems.

Examples

Example 1: Quarterly Enterprise Risk Summary

Scenario: CRO needs a consolidated risk posture report for the board

Tool: SN-Query-Table
Parameters:
  table_name: sn_grc_risk
  query: active=true^risk_score>=70^ORDERBYcategory
  limit: 50

Generated Summary:

  • Total Active Risks: 156
  • Risk Distribution: Critical: 12 | High: 34 | Medium: 72 | Low: 38
  • Average Inherent Risk: 62/100 | Average Residual Risk:** 38/100
  • Control Effectiveness: 39% average risk reduction through controls
  • Above Appetite: 18 risks exceed organizational risk appetite (threshold: 50)
  • Top Category: Information Security (42 risks, 27%)
  • Trend: Risk count increased 8% from last quarter; average residual decreased 4%
  • Key Recommendation: 5 risks with "Accept" treatment exceed appetite -- re-evaluate acceptance decisions

Example 2: Heat Map Report for Risk Committee

Scenario: Risk committee requests a visual risk landscape

Heat Map Output (top-right = highest concern):

Impact →    1     2     3     4     5
Likelihood
    5      [ 0]  [ 2]  [ 5]  [ 8]  [ 3]  ← Highest concern row
    4      [ 1]  [ 4]  [12]  [15]  [ 6]
    3      [ 3]  [ 8]  [18]  [10]  [ 4]
    2      [ 5]  [12]  [ 9]  [ 6]  [ 2]
    1      [ 8]  [ 7]  [ 5]  [ 2]  [ 1]
  • Red Zone (L>=4, I>=4): 32 risks requiring immediate executive attention
  • Amber Zone (Score>=12): 47 risks under active mitigation
  • Green Zone (Score<6): 21 risks within acceptable tolerance

Related Skills

  • grc/issue-action-plan - Create action plans for risks that have materialized as issues
  • grc/issue-summarization - Summarize issues arising from risk assessments
  • grc/control-objective-management - Manage controls that mitigate assessed risks
  • grc/regulatory-alert-analysis - Assess regulatory changes impacting risk landscape
  • security/audit-compliance - Audit controls supporting risk mitigation

References