Skip to content

Check_control_mapping

FieldValue
TypeSkill Resource
Source~/.copilot/skills/security/scripts/check_control_mapping.py
DescriptionNot specified

Source Content

#!/usr/bin/env python3
"""
Companion checker for lint_control_mapping.sh — stdlib-only Markdown-table
parsing of an ATO control-mapping doc. Not a compliance scanner: this
validates that the table itself is complete, not that the controls are
actually satisfied.
Requires the table to have a control-ID column (NIST 800-53 IDs like AC-2,
AU-3, SC-7), an impact-level column, and an evidence column. Any row whose
verdict/status cell uses an accepted-state word (aligned, met, satisfied,
implemented, compliant) must have a non-empty, non-placeholder evidence cell.
Usage:
python3 check_control_mapping.py FILE
Exit 0 if every accepted-state row has evidence and all three columns exist,
1 otherwise.
"""
import re
import sys
CONTROL_ID = re.compile(r"^[A-Z]{2}-\d{1,2}(\(\d+\))?$")
ACCEPTED_STATE_WORDS = re.compile(
r"\b(aligned|met|satisfied|implemented|compliant)\b", re.IGNORECASE
)
PLACEHOLDER_CELLS = {"", "-", "n/a", "na", "tbd", "todo"}
TABLE_ROW = re.compile(r'^\s*\|(.+)\|\s*$')
TABLE_SEP = re.compile(r'^\s*\|[\s:\-|]+\|\s*$')
def parse_table(lines):
header = None
rows = []
for line in lines:
if TABLE_SEP.match(line):
continue
m = TABLE_ROW.match(line)
if not m:
if header is not None and rows:
# a non-table line after we've started a table — table block ended
break
continue
cells = [c.strip() for c in m.group(1).split('|')]
if header is None:
header = cells
else:
rows.append(cells)
return header, rows
def col_idx(header, name_re):
for i, h in enumerate(header):
if name_re.search(h):
return i
return None
def main():
if len(sys.argv) != 2:
print("Usage: check_control_mapping.py FILE", file=sys.stderr)
return 2
path = sys.argv[1]
try:
with open(path, 'r') as f:
lines = f.read().splitlines()
except OSError as e:
print(f"ERROR: cannot read {path}: {e}", file=sys.stderr)
return 2
header, rows = parse_table(lines)
errors = []
oks = []
if not header:
errors.append("No Markdown table found at all — expected a control-mapping table.")
for msg in errors:
print(f"ERROR::{msg}")
return 1
control_col = col_idx(header, re.compile(r"control", re.IGNORECASE))
impact_col = col_idx(header, re.compile(r"impact", re.IGNORECASE))
evidence_col = col_idx(header, re.compile(r"evidence", re.IGNORECASE))
verdict_col = col_idx(header, re.compile(r"verdict|status|state", re.IGNORECASE))
missing_cols = []
if control_col is None:
missing_cols.append("control ID column")
if impact_col is None:
missing_cols.append("impact-level column")
if evidence_col is None:
missing_cols.append("evidence column")
if missing_cols:
errors.append(f"Table is missing required column(s): {', '.join(missing_cols)}")
for msg in errors:
print(f"ERROR::{msg}")
return 1
def cell(row, i):
return row[i].strip() if i is not None and i < len(row) else ""
checked_any = False
for row in rows:
control_cell = cell(row, control_col)
m = CONTROL_ID.match(control_cell)
if not m:
continue
checked_any = True
control_id = control_cell
# verdict may live in its own column, or be folded into the impact
# column text — check whichever column(s) exist for an accepted word.
verdict_text = cell(row, verdict_col) if verdict_col is not None else ""
if not verdict_text:
verdict_text = " ".join(row)
is_accepted = bool(ACCEPTED_STATE_WORDS.search(verdict_text))
evidence_cell = cell(row, evidence_col)
has_evidence = evidence_cell.lower() not in PLACEHOLDER_CELLS
if is_accepted and not has_evidence:
errors.append(
f"control {control_id}: marked accepted-state "
f"({ACCEPTED_STATE_WORDS.search(verdict_text).group(0)!r}) "
f"with an empty/placeholder evidence cell"
)
elif is_accepted:
oks.append(f"control {control_id}: accepted-state row has non-empty evidence")
else:
oks.append(f"control {control_id}: not an accepted-state row, evidence not required")
if not checked_any:
errors.append(
"No row with a recognizable NIST 800-53 control ID (e.g. AC-2, SC-7) found in the table."
)
for msg in oks:
print(f"OK::{msg}")
for msg in errors:
print(f"ERROR::{msg}")
return 1 if errors else 0
if __name__ == '__main__':
sys.exit(main())