mcpField
I
issue-validator

issue-validator

github.com/happy-technologies-llcセキュリティ4141 回インストール2 日前に更新
リポジトリ

issue-validator について

ServiceNow GRC の課題を検証するスキルで、監査や状態変更の前にデータ品質を確認するコンプライアンス担当者向けです。項目、リスク、統制マッピング、是正計画を評価し、監査準備スコアを作成します。

主な機能

  1. 01必須項目の入力状況を確認
  2. 02リスク記録と評価を照合
  3. 03課題と統制の関連を検証
  4. 04是正タスクと計画を確認
  5. 05監査準備スコアのレポートを作成

活用シーン

  1. 01監査委員会への報告前に課題を一括確認
  2. 02定期的な GRC データ品質レビュー
  3. 03課題のクローズ前に是正証拠を確認

MCP 対応

MCP ツールで ServiceNow GRC の課題、リスク、統制を照会し、バックグラウンドスクリプトで検証とスコア算出を行います。

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/happy-technologies-llc/happy-platform-skills --skill issue-validator

インストール後、Claude Code で/issue-validatorを入力して読み込まれたか確認します。

詳細

GRC イシューバリデーター

概要

このスキルは、ServiceNow の GRC イシューについて、完全性、正確性、コンプライアンスへの対応状況を検証します。複数の観点から体系的にチェックし、監査レビューの前に、イシューが組織および規制上の基準を満たしていることを確認します。

主な検証機能:

  • フィールドの完全性: 必須フィールド(短い説明、カテゴリ、優先度、リスク評価、プロファイル、担当者)がすべて入力されていることを確認します
  • リスク評価の正確性: リスク評価と、関連付けられたリスクスコアおよび統制テストの結果を照合します
  • 統制マッピングの整合性: イシューが関連する統制および統制目標に適切にマッピングされていることを確認します
  • 修正計画の妥当性: 修正タスクが存在し、担当者、期限、測定可能な成果が設定されていることを検証します
  • データの整合性: イシューの重大度と、関連付けられたリスク/統制の状態との不一致を検出します
  • 監査対応状況: 社内または外部監査での提示に向けた対応状況を、スケールに基づいて評価します

使用する場面:

  • 監査委員会への報告前に、データ品質を確保するとき
  • 定期的な GRC の健全性レビューを行うとき
  • 自動スキャンや評価から新しいイシューを取り込むとき
  • 修正の証跡がすべて記録されていることを確認してからイシューをクローズするとき
  • イシュー管理ワークフローの品質ゲートの一環として

前提条件

  • ロール: sn_grc.manager、sn_compliance.manager、または admin
  • プラグイン: com.sn_grc、com.sn_compliance、com.sn_risk
  • アクセス権: sn_grc_issue、sn_grc_risk、sn_compliance_control、sn_grc_profile、sn_grc_remediation_task テーブルへの読み取りアクセス
  • 知識: 組織のイシュー管理ポリシーと必須フィールドについての理解

主な GRC テーブル

テーブル目的主なフィールド
sn_grc_issueGRC イシューと検出事項number, short_description, state, priority, risk_rating, profile, item, category, source, assigned_to, due_date
sn_grc_risk関連するリスクレコードnumber, risk_score, state, treatment, residual_risk, inherent_risk
sn_compliance_controlマッピングされた統制number, state, control_objective, effectiveness, test_result
sn_grc_profileエンティティプロファイルnumber, profile_type, applies_to
sn_grc_remediation_taskイシューの修正タスクnumber, state, assigned_to, due_date, short_description, issue

手順

ステップ 1:検証対象のイシューを取得する

単一のイシュー、または一括検証する複数のイシューを取得します。

MCP を使用(Claude Code/Desktop):

Tool: SN-Query-Table
Parameters:
  table_name: sn_grc_issue
  query: active=true^ORDERBYDESCsys_updated_on
  fields: sys_id,number,short_description,description,state,priority,risk_rating,profile,item,assigned_to,due_date,category,source,remediation_plan,sys_created_on,sys_updated_on,closed_at,close_notes
  limit: 50

REST API を使用:

GET /api/now/table/sn_grc_issue?sysparm_query=active=true^ORDERBYDESCsys_updated_on&sysparm_fields=sys_id,number,short_description,description,state,priority,risk_rating,profile,item,assigned_to,due_date,category,source,remediation_plan&sysparm_limit=50&sysparm_display_value=all
ステップ 2:フィールドの完全性を検証する

検証対象のすべてのイシューについて、包括的なフィールド完全性チェックを実行します。

MCP を使用:

Tool: SN-Execute-Background-Script
Parameters:
  script: |
    var requiredFields = [
      'short_description', 'description', 'priority', 'risk_rating',
      'profile', 'assigned_to', 'due_date', 'category', 'source'
    ];
    var results = { total_checked: 0, passed: 0, failed: 0, issues: [] };

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

    while (gr.next()) {
      results.total_checked++;
      var missing = [];

      for (var i = 0; i < requiredFields.length; i++) {
        var field = requiredFields[i];
        if (gr.getValue(field) === null || gr.getValue(field) === '') {
          missing.push(field);
        }
      }

      if (missing.length > 0) {
        results.failed++;
        results.issues.push({
          number: gr.number.toString(),
          title: gr.short_description.toString(),
          missing_fields: missing,
          completeness_pct: Math.round(((requiredFields.length - missing.length) / requiredFields.length) * 100)
        });
      } else {
        results.passed++;
      }
    }

    results.pass_rate = results.total_checked > 0
      ? Math.round((results.passed / results.total_checked) * 100) + '%'
      : 'N/A';

    gs.info('FIELD COMPLETENESS VALIDATION:\n' + JSON.stringify(results, null, 2));
  description: "GRC: Validate field completeness for all active issues"

REST API を使用:

GET /api/now/table/sn_grc_issue?sysparm_query=active=true^short_descriptionISEMPTY^ORassigned_toISEMPTY^ORdue_dateISEMPTY^ORcategoryISEMPTY^ORprofileISEMPTY&sysparm_fields=sys_id,number,short_description,assigned_to,due_date,category,profile&sysparm_limit=100
ステップ 3:リスク評価の整合性を検証する

イシューのリスク評価を、関連付けられたリスクレコードのスコアと照合し、不一致を検出します。

MCP を使用:

Tool: SN-Execute-Background-Script
Parameters:
  script: |
    var mismatches = [];
    var gr = new GlideRecord('sn_grc_issue');
    gr.addQuery('active', true);
    gr.addNotNullQuery('risk_rating');
    gr.addNotNullQuery('profile');
    gr.query();

    while (gr.next()) {
      var issueRating = parseInt(gr.risk_rating.toString()) || 0;
      var profileId = gr.profile.toString();

      var risk = new GlideAggregate('sn_grc_risk');
      risk.addQuery('profile', profileId);
      risk.addQuery('active', true);
      risk.addAggregate('MAX', 'residual_risk');
      risk.addAggregate('AVG', 'risk_score');
      risk.query();

      if (risk.next()) {
        var maxResidual = parseInt(risk.getAggregate('MAX', 'residual_risk')) || 0;
        var avgScore = parseInt(risk.getAggregate('AVG', 'risk_score')) || 0;

        // Flag if issue rating is significantly lower than risk data suggests
        if (issueRating <= 2 && maxResidual >= 70) {
          mismatches.push({
            issue: gr.number.toString(),
            issue_risk_rating: issueRating,
            max_residual_risk: maxResidual,
            avg_risk_score: avgScore,
            flag: 'Issue rated LOW but linked risks are HIGH'
          });
        } else if (issueRating >= 4 && maxResidual <= 30) {
          mismatches.push({
            issue: gr.number.toString(),
            issue_risk_rating: issueRating,
            max_residual_risk: maxResidual,
            avg_risk_score: avgScore,
            flag: 'Issue rated HIGH but linked risks are LOW'
          });
        }
      }
    }

    gs.info('RISK RATING CONSISTENCY:\nMismatches found: ' + mismatches.length + '\n' + JSON.stringify(mismatches, null, 2));
  description: "GRC: Cross-validate risk ratings against linked risk records"
ステップ 4:統制マッピングを検証する

各イシューが少なくとも 1 つの統制にマッピングされており、マッピングされた統制が有効な状態であることを確認します。

MCP を使用:

Tool: SN-Execute-Background-Script
Parameters:
  script: |
    var controlIssues = { unmapped: [], stale_controls: [], orphaned: [] };

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

    while (gr.next()) {
      var profileId = gr.profile.toString();
      if (!profileId) {
        controlIssues.unmapped.push({
          issue: gr.number.toString(),
          problem: 'No profile linked - cannot verify control mapping'
        });
        continue;
      }

      var ctrl = new GlideAggregate('sn_compliance_control');
      ctrl.addQuery('profile', profileId);
      ctrl.addAggregate('COUNT');
      ctrl.query();

      var controlCount = 0;
      if (ctrl.next()) {
        controlCount = parseInt(ctrl.getAggregate('COUNT')) || 0;
      }

      if (controlCount === 0) {
        controlIssues.unmapped.push({
          issue: gr.number.toString(),
          profile: gr.profile.getDisplayValue(),
          problem: 'Profile has no linked controls'
        });
      }

      // Check for stale control test results (over 12 months old)
      var stale = new GlideRecord('sn_compliance_control');
      stale.addQuery('profile', profileId);
      stale.addQuery('test_date', '<', gs.daysAgo(365));
      stale.query();
      while (stale.next()) {
        controlIssues.stale_controls.push({
          issue: gr.number.toString(),
          control: stale.number.toString(),
          last_tested: stale.test_date.getDisplayValue()
        });
      }
    }

    gs.info('CONTROL MAPPING VALIDATION:\n' + JSON.stringify(controlIssues, null, 2));
  description: "GRC: Validate control mappings for active issues"
ステップ 5:修正計画の妥当性を検証する

各イシューに、担当者と期限が設定された修正タスクが少なくとも 1 つあることを確認します。

MCP を使用:

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

    var gr = new GlideRecord('sn_grc_issue');
    gr.addQuery('active', true);
    gr.addQuery('state', 'NOT IN', 'new,draft');
    gr.query();

    while (gr.next()) {
      var tasks = new GlideRecord('sn_grc_remediation_task');
      tasks.addQuery('issue', gr.sys_id.toString());
      tasks.query();

      var taskCount = 0;
      var tasksWithoutOwner = 0;
      var tasksWithoutDueDate = 0;
      var overdueTasks = 0;
      var now = new GlideDateTime();

      while (tasks.next()) {
        taskCount++;
        if (!tasks.assigned_to.toString()) tasksWithoutOwner++;
        if (!tasks.due_date.toString()) tasksWithoutDueDate++;
        if (tasks.due_date.toString() && new GlideDateTime(tasks.due_date.toString()).compareTo(now) < 0 && tasks.state.toString() != 'closed') {
          overdueTasks++;
        }
      }

      var gaps = [];
      if (taskCount === 0) gaps.push('No remediation tasks defined');
      if (tasksWithoutOwner > 0) gaps.push(tasksWithoutOwner + ' task(s) without owner');
      if (tasksWithoutDueDate > 0) gaps.push(tasksWithoutDueDate + ' task(s) without due date');
      if (overdueTasks > 0) gaps.push(overdueTasks + ' overdue task(s)');

      if (gaps.length > 0) {
        remediationGaps.push({
          issue: gr.number.toString(),
          priority: gr.priority.getDisplayValue(),
          task_count: taskCount,
          gaps: gaps
        });
      }
    }

    gs.info('REMEDIATION PLAN VALIDATION:\nIssues with gaps: ' + remediationGaps.length + '\n' + JSON.stringify(remediationGaps, null, 2));
  description: "GRC: Validate remediation plans for active issues"
ステップ 6:包括的な検証レポートを生成する

すべての検証結果を 1 つのスコア付きレポートにまとめます。

MCP を使用:

Tool: SN-Execute-Background-Script
Parameters:
  script: |
    var report = {
      generated: new GlideDateTime().toString(),
      summary: { total_issues: 0, fully_valid: 0, needs_attention: 0 },
      scores: [],
      recommendations: []
    };

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

    while (gr.next()) {
      report.summary.total_issues++;
      var score = 100;
      var deductions = [];

      // Field completeness (-10 per missing required field)
      var reqFields = ['short_description','description','priority','risk_rating','profile','assigned_to','due_date','category'];
      for (var i = 0; i < reqFields.length; i++) {
        if (!gr.getValue(reqFields[i])) {
          score -= 10;
          deductions.push('Missing: ' + reqFields[i]);
        }
      }

      // Due date validation (-15 if overdue)
      if (gr.due_date.toString() && new GlideDateTime(gr.due_date.toString()).compareTo(new GlideDateTime()) < 0) {
        score -= 15;
        deductions.push('Issue is overdue');
      }

      // Remediation check (-20 if no tasks)
      var taskGr = new GlideAggregate('sn_grc_remediation_task');
      taskGr.addQuery('issue', gr.sys_id.toString());
      taskGr.addAggregate('COUNT');
      taskGr.query();
      var taskCount = 0;
      if (taskGr.next()) taskCount = parseInt(taskGr.getAggregate('COUNT'));
      if (taskCount === 0) {
        score -= 20;
        deductions.push('No remediation tasks');
      }

      if (score < 0) score = 0;

      if (score >= 80) report.summary.fully_valid++;
      else report.summary.needs_attention++;

      if (score < 80) {
        report.scores.push({
          issue: gr.number.toString(),
          title: gr.short_description.toString(),
          validation_score: score,
          deductions: deductions
        });
      }
    }

    report.scores.sort(function(a, b) { return a.validation_score - b.validation_score; });

    gs.info('GRC ISSUE VALIDATION REPORT:\n' + JSON.stringify(report, null, 2));
  description: "GRC: Generate comprehensive validation report with scores"

ツールの使用方法

操作MCP ツールREST エンドポイント
イシューのクエリSN-Query-TableGET /api/now/table/sn_grc_issue
単一イシューの読み取りSN-Get-RecordGET /api/now/table/sn_grc_issue/{sys_id}
検証スクリプトの実行SN-Execute-Background-ScriptPOST /api/now/table/sys_trigger
スキーマの検出SN-Discover-Table-SchemaGET /api/now/table/sys_dictionary
統制のクエリSN-Query-TableGET /api/now/table/sn_compliance_control

ベストプラクティス

  • 定期的に検証を実施する: 監査の直前だけでなく、毎週または隔週で検証を実施するようスケジュールします
  • 検証ルールを一元的に定義する: 必須フィールドとしきい値を共有のコンプライアンス標準に文書化し、検証で一貫した基準を使用できるようにします
  • スコアを付けて優先順位を決める: 検証スコアを使用してデータ品質の問題への対応に優先順位を付け、重大な不足から修正します
  • 可能な限り自動化する: 繰り返し行う検証チェックをビジネスルールまたはスケジュール済みジョブに変換し、継続的に監視します
  • 傾向を追跡する: データ品質の改善を測定するため、検証の合格率を時系列で監視します
  • 状態遷移の前に検証する: 問題が「In Remediation」または「Closed」状態に移行する前に、検証チェックを必ず実施します
  • レポートにコンテキストを含める: 検証の失敗には、修正方法に関する実行可能なガイダンスを必ず添えます

トラブルシューティング

修復タスクテーブルが見つからない

症状: sn_grc_remediation_task へのクエリで結果が返されない、またはテーブルが見つからない 原因: ServiceNow のバージョンやカスタム構成によって、修復タスクテーブルの名前が異なる場合があります 解決策:

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

タスクテーブルを参照する参照フィールドを確認してください。代替のテーブル名には、sn_grc_task、task、またはカスタムの拡張タスクテーブルがあります。

リスク評価フィールドで異なる尺度が使用されている

症状: リスク評価の一貫性チェックで誤検知が発生する 原因: 1~5 の尺度を使用する組織もあれば、1~100 や「Low/Medium/High」のラベルを使用する組織もあります 解決策: sys_choice テーブルで sn_grc_issue.risk_rating をクエリし、有効な値を特定したうえで、それに応じて検証のしきい値を調整します。

プロファイルベースのクエリでリンクされたレコードが返されない

症状: プロファイルの sys_id でクエリしても、コントロールまたはリスクが見つからない 原因: 関係で profile ではなく item が使われているか、多対多の関係テーブルが使われている可能性があります 解決策: sn_grc_m2m_item_profile を確認するか、profile ではなく、問題の item フィールドでクエリしてください。

例

例 1:監査前の一括検証

シナリオ: コンプライアンスマネージャーが、外部監査の前にすべての未解決の問題を検証する必要がある

  1. 有効な GRC の問題をすべて、計 85 件クエリする
  2. フィールドの完全性チェックを実行する:72 件が合格(85%)、13 件が不合格
  3. リスク評価の一貫性チェックを実行する:4 件の不一致を検出
  4. コントロールのマッピングチェックを実行する:6 件の問題にリンクされたコントロールがない
  5. 修復計画のチェックを実行する:11 件の問題に修復タスクがない
  6. スコア付きレポートを生成する:62 件の問題が 80 点以上、23 件に要対応
  7. 主な所見:重大な優先度の問題 5 件に担当者が割り当てられていない
例 2:クローズ前の単一問題の検証

シナリオ: 問題の担当者が、ISSUE0004521 をクローズできる状態かどうか確認したい

  1. すべてのフィールドを含む問題レコードを読み取る
  2. フィールドの完全性:100% — 必須フィールドがすべて入力済み
  3. リスク評価:「High」(4)と評価され、リンクされたリスクの残余スコアは 72 — 一貫性あり
  4. コントロールのマッピング:3 件のコントロールがリンクされ、すべて「Compliant」状態
  5. 修復:タスクは 2 件あり、両方とも完了証跡を添えてクローズ済み
  6. 検証スコア:95/100(わずかな減点:クローズ時のメモをさらに詳しくできる)
  7. 結果:問題はクローズが承認されました

関連スキル

  • grc/issue-summarization - 検証済みの問題をレポート用に要約する
  • grc/issue-action-plan - 検証に失敗した問題のアクションプランを生成する
  • grc/suggest-remediation-tasks - 修復タスクがない問題にタスクを作成する
  • grc/control-objective-management - 問題にリンクされたコントロールを管理する
  • grc/risk-assessment-summarization - 評価の検証に向けて、より詳細なリスク分析を行う

コメント