From dff6fa84e31d04244e684d953e836c2392792ac9 Mon Sep 17 00:00:00 2001 From: Solomon S Joseph <36000051+solomonsjoseph@users.noreply.github.com> Date: Sat, 2 May 2026 15:39:33 -0400 Subject: [PATCH 1/2] Add requirement traceability validation --- .../workflows/security-methodology-check.yml | 3 ++ README.md | 8 ++++ scripts/check-requirement-traceability.sh | 41 +++++++++++++++++++ scripts/check-security-methodology.sh | 1 + scripts/verify-methodology.sh | 3 ++ 5 files changed, 56 insertions(+) create mode 100755 scripts/check-requirement-traceability.sh diff --git a/.github/workflows/security-methodology-check.yml b/.github/workflows/security-methodology-check.yml index d245d83..1171c11 100644 --- a/.github/workflows/security-methodology-check.yml +++ b/.github/workflows/security-methodology-check.yml @@ -19,3 +19,6 @@ jobs: - name: Run methodology structure check run: bash scripts/check-security-methodology.sh + + - name: Run requirement traceability check + run: bash scripts/check-requirement-traceability.sh diff --git a/README.md b/README.md index 5779f89..6c293df 100644 --- a/README.md +++ b/README.md @@ -116,6 +116,14 @@ Secure-AI-Flow separates the AI delivery process into four roles. verify-methodology.sh ``` +## Automated Methodology Checks + +The repository includes automated checks for: + +- Structure validation with `scripts/check-security-methodology.sh`. +- Methodology verification with `scripts/verify-methodology.sh`. +- Requirement traceability validation with `scripts/check-requirement-traceability.sh`. + ## How to Use This Methodology ### Step 1: Fill Governance First diff --git a/scripts/check-requirement-traceability.sh b/scripts/check-requirement-traceability.sh new file mode 100755 index 0000000..8e85337 --- /dev/null +++ b/scripts/check-requirement-traceability.sh @@ -0,0 +1,41 @@ +#!/usr/bin/env bash +set -euo pipefail + +python3 - <<'PY' +from pathlib import Path +import re +import sys + +requirements_path = Path("context/06-security-requirements.md") +specs_dir = Path("specs") +id_pattern = re.compile(r"\b[A-Z][A-Z0-9]+-\d{3}\b") + +valid_ids = set(id_pattern.findall(requirements_path.read_text(encoding="utf-8"))) + +invalid_refs = [] +for spec_path in sorted(specs_dir.glob("*.md")): + lines = spec_path.read_text(encoding="utf-8").splitlines() + in_security_requirements = False + + for line in lines: + if line.startswith("## "): + in_security_requirements = line.strip() == "## Security Requirements" + continue + + if not in_security_requirements: + continue + + for requirement_id in id_pattern.findall(line): + if requirement_id not in valid_ids: + invalid_refs.append((spec_path, requirement_id)) + +if invalid_refs: + for spec_path, requirement_id in invalid_refs: + print("Invalid security requirement ID reference:", file=sys.stderr) + print(f" file: {spec_path}", file=sys.stderr) + print(f" invalid ID: {requirement_id}", file=sys.stderr) + print(f" valid IDs source: {requirements_path}", file=sys.stderr) + sys.exit(1) + +print("Requirement traceability check passed.") +PY diff --git a/scripts/check-security-methodology.sh b/scripts/check-security-methodology.sh index 5f973e1..9b0a96f 100755 --- a/scripts/check-security-methodology.sh +++ b/scripts/check-security-methodology.sh @@ -28,6 +28,7 @@ required_files=( "specs/001-secure-file-upload.md" "ci/security-gates.md" ".github/pull_request_template.md" + "scripts/check-requirement-traceability.sh" ) missing=0 diff --git a/scripts/verify-methodology.sh b/scripts/verify-methodology.sh index 747ef30..5d82d6b 100755 --- a/scripts/verify-methodology.sh +++ b/scripts/verify-methodology.sh @@ -26,6 +26,7 @@ required=( "context/15-security-stress-test-matrix.md" "ci/security-gates.md" "references/official-source-map.md" + "scripts/check-requirement-traceability.sh" ) missing=0 @@ -45,4 +46,6 @@ grep -q "Data" context/02-data-classification-and-privacy.md grep -q "Threat" context/04-threat-model.md grep -q "Reviewer" context/13-reviewer-playbook.md +bash scripts/check-requirement-traceability.sh + echo "Secure-AI-Flow methodology structure verified." From 49db7c902719f45d37113718f263321c9e0e2ab3 Mon Sep 17 00:00:00 2001 From: Solomon S Joseph <36000051+solomonsjoseph@users.noreply.github.com> Date: Sun, 3 May 2026 22:33:18 -0400 Subject: [PATCH 2/2] Harden requirement traceability validation --- scripts/check-requirement-traceability.sh | 37 +++++++++++++++++------ 1 file changed, 27 insertions(+), 10 deletions(-) diff --git a/scripts/check-requirement-traceability.sh b/scripts/check-requirement-traceability.sh index 8e85337..f71bf36 100755 --- a/scripts/check-requirement-traceability.sh +++ b/scripts/check-requirement-traceability.sh @@ -8,16 +8,17 @@ import sys requirements_path = Path("context/06-security-requirements.md") specs_dir = Path("specs") -id_pattern = re.compile(r"\b[A-Z][A-Z0-9]+-\d{3}\b") +valid_id_pattern = re.compile(r"^[A-Z][A-Z0-9]+-[0-9]{3}$") +source_id_pattern = re.compile(r"\b[A-Z][A-Z0-9]+-\d{3}\b") -valid_ids = set(id_pattern.findall(requirements_path.read_text(encoding="utf-8"))) +valid_ids = set(source_id_pattern.findall(requirements_path.read_text(encoding="utf-8"))) -invalid_refs = [] +errors = [] for spec_path in sorted(specs_dir.glob("*.md")): lines = spec_path.read_text(encoding="utf-8").splitlines() in_security_requirements = False - for line in lines: + for line_number, line in enumerate(lines, start=1): if line.startswith("## "): in_security_requirements = line.strip() == "## Security Requirements" continue @@ -25,15 +26,31 @@ for spec_path in sorted(specs_dir.glob("*.md")): if not in_security_requirements: continue - for requirement_id in id_pattern.findall(line): - if requirement_id not in valid_ids: - invalid_refs.append((spec_path, requirement_id)) + stripped = line.strip() + if not stripped or not stripped.startswith("-"): + continue + + bullet = stripped[1:].strip() + if bullet == "": + continue + + first_token = bullet.split()[0] + if not valid_id_pattern.fullmatch(first_token): + issue = "malformed ID" if ("-" in first_token or any(c.isdigit() for c in first_token)) else "missing ID" + value = first_token if issue == "malformed ID" else bullet + errors.append((spec_path, line_number, value, issue)) + continue + + if first_token not in valid_ids: + errors.append((spec_path, line_number, first_token, "unknown ID")) -if invalid_refs: - for spec_path, requirement_id in invalid_refs: +if errors: + for spec_path, line_number, value, issue in errors: print("Invalid security requirement ID reference:", file=sys.stderr) print(f" file: {spec_path}", file=sys.stderr) - print(f" invalid ID: {requirement_id}", file=sys.stderr) + print(f" line: {line_number}", file=sys.stderr) + print(f" issue: {issue}", file=sys.stderr) + print(f" value: {value}", file=sys.stderr) print(f" valid IDs source: {requirements_path}", file=sys.stderr) sys.exit(1)