From 43bde4e3e19f683ea4e5ad13c878151bd4c30774 Mon Sep 17 00:00:00 2001 From: kamalsrini <6233046+kamalsrini@users.noreply.github.com> Date: Mon, 15 Jun 2026 23:03:18 -0700 Subject: [PATCH] Add framework reference registry --- CONTRIBUTING.md | 7 + README.md | 6 + data/frameworks.yaml | 198 +++++++++++++++++++++++++ docs/framework-reference-registry.md | 31 ++++ scripts/validate_framework_registry.rb | 114 ++++++++++++++ 5 files changed, 356 insertions(+) create mode 100644 data/frameworks.yaml create mode 100644 docs/framework-reference-registry.md create mode 100644 scripts/validate_framework_registry.rb diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index a90ced29..9a778622 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -165,6 +165,13 @@ fixture expectations and run: ruby scripts/test_remediation_fixtures.rb ``` +If your contribution adds or changes framework references, update +[data/frameworks.yaml](data/frameworks.yaml) and run: + +```bash +ruby scripts/validate_framework_registry.rb +``` + If your contribution changes CI/CD examples, update [docs/ci-cd-examples.md](docs/ci-cd-examples.md) and run: diff --git a/README.md b/README.md index 9e688499..62202d74 100644 --- a/README.md +++ b/README.md @@ -119,6 +119,12 @@ ruby scripts/generate_quality_scorecard.rb ruby scripts/generate_quality_scorecard.rb --check ``` +Validate framework provenance, versions, owners, and review dates with: + +```bash +ruby scripts/validate_framework_registry.rb +``` + CI/CD examples for GitHub Actions, GitLab CI, Azure DevOps, Jenkins, pre-commit, and local agent usage are available in [`docs/ci-cd-examples.md`](docs/ci-cd-examples.md). Validate those examples diff --git a/data/frameworks.yaml b/data/frameworks.yaml new file mode 100644 index 00000000..c3de8d23 --- /dev/null +++ b/data/frameworks.yaml @@ -0,0 +1,198 @@ +schema_version: "1.0.0" +last_reviewed: "2026-06-16" +required_families: + - OWASP + - NIST + - MITRE + - CIS + - CVSS + - SSVC + - EPSS + - SLSA + - CycloneDX + - SPDX +references: + - id: OWASP-Top-10-2021 + family: OWASP + name: OWASP Top 10 + version: "2021" + url: https://owasp.org/Top10/ + date_reviewed: "2026-06-16" + owner: appsec + aliases: [OWASP-Top-10, OWASP-Top-10-2021] + - id: OWASP-API-Security-2023 + family: OWASP + name: OWASP API Security Top 10 + version: "2023" + url: https://owasp.org/API-Security/editions/2023/en/0x00-header/ + date_reviewed: "2026-06-16" + owner: appsec + aliases: [OWASP-API-Security-2023] + - id: OWASP-ASVS-4.0.3 + family: OWASP + name: OWASP Application Security Verification Standard + version: "4.0.3" + url: https://owasp.org/www-project-application-security-verification-standard/ + date_reviewed: "2026-06-16" + owner: appsec + aliases: [OWASP-ASVS, OWASP-ASVS-4.0.3] + - id: OWASP-LLM-Top-10-2025 + family: OWASP + name: OWASP Top 10 for Large Language Model Applications + version: "2025" + url: https://genai.owasp.org/llm-top-10/ + date_reviewed: "2026-06-16" + owner: ai-security + aliases: [OWASP-LLM-Top-10-2025, OWASP-LLM01-2025, OWASP-LLM02-2025, OWASP-LLM03-2025] + - id: OWASP-Agentic-AI + family: OWASP + name: OWASP Agentic AI Security + version: "current" + url: https://genai.owasp.org/ + date_reviewed: "2026-06-16" + owner: ai-security + aliases: [OWASP-Agentic-AI] + - id: OWASP-Testing-Guide-v4.2 + family: OWASP + name: OWASP Web Security Testing Guide + version: "4.2" + url: https://owasp.org/www-project-web-security-testing-guide/ + date_reviewed: "2026-06-16" + owner: appsec + aliases: [OWASP-Testing-Guide-v4.2] + - id: OWASP-CICD-Top-10 + family: OWASP + name: OWASP Top 10 CI/CD Security Risks + version: "current" + url: https://owasp.org/www-project-top-10-ci-cd-security-risks/ + date_reviewed: "2026-06-16" + owner: devsecops + aliases: [OWASP-CICD-Top-10] + - id: NIST-CSF-2.0 + family: NIST + name: NIST Cybersecurity Framework + version: "2.0" + url: https://www.nist.gov/cyberframework + date_reviewed: "2026-06-16" + owner: compliance + aliases: [NIST-CSF-2.0] + - id: NIST-SP-800-53-Rev5 + family: NIST + name: NIST SP 800-53 Security and Privacy Controls + version: "Revision 5, Update 1" + url: https://csrc.nist.gov/pubs/sp/800/53/r5/upd1/final + date_reviewed: "2026-06-16" + owner: compliance + aliases: [NIST-SP-800-53, NIST-SP-800-53-AC, NIST-SP-800-53-AC-6] + - id: NIST-SP-800-63B + family: NIST + name: NIST SP 800-63B Digital Identity Guidelines + version: "Revision 4" + url: https://csrc.nist.gov/pubs/sp/800/63/b/4/final + date_reviewed: "2026-06-16" + owner: identity + aliases: [NIST-SP-800-63B] + - id: NIST-SP-800-207 + family: NIST + name: NIST SP 800-207 Zero Trust Architecture + version: "Final" + url: https://csrc.nist.gov/pubs/sp/800/207/final + date_reviewed: "2026-06-16" + owner: identity + aliases: [NIST-SP-800-207] + - id: NIST-AI-RMF-1.0 + family: NIST + name: NIST AI Risk Management Framework + version: "1.0" + url: https://www.nist.gov/itl/ai-risk-management-framework + date_reviewed: "2026-06-16" + owner: ai-security + aliases: [NIST-AI-RMF, NIST-AI-RMF-1.0] + - id: MITRE-ATTACK + family: MITRE + name: MITRE ATT&CK + version: "v19.1" + url: https://attack.mitre.org/resources/versions/ + date_reviewed: "2026-06-16" + owner: secops + aliases: [MITRE-ATT&CK, MITRE-ATT&CK-v16, MITRE-ATT&CK-v19.1] + - id: MITRE-ATLAS + family: MITRE + name: MITRE ATLAS + version: "current" + url: https://atlas.mitre.org/ + date_reviewed: "2026-06-16" + owner: ai-security + aliases: [MITRE-ATLAS] + - id: CWE + family: MITRE + name: Common Weakness Enumeration + version: "current" + url: https://cwe.mitre.org/ + date_reviewed: "2026-06-16" + owner: appsec + aliases: [CWE, CWE-Top-25] + - id: CIS-Controls-v8 + family: CIS + name: CIS Critical Security Controls + version: "v8" + url: https://www.cisecurity.org/controls/v8 + date_reviewed: "2026-06-16" + owner: compliance + aliases: [CIS-Controls-v8] + - id: CIS-Benchmarks + family: CIS + name: CIS Benchmarks + version: "current" + url: https://www.cisecurity.org/cis-benchmarks + date_reviewed: "2026-06-16" + owner: cloud + aliases: [CIS-Benchmarks, CIS-AWS-v3.0.0, CIS-Azure-v2.1.0, CIS-GCP-v2.0.0, CIS-Docker-v1.6.0, CIS-Kubernetes-v1.9.0] + - id: CVSS-4.0 + family: CVSS + name: Common Vulnerability Scoring System + version: "4.0" + url: https://www.first.org/cvss/v4.0/ + date_reviewed: "2026-06-16" + owner: vuln-management + aliases: [CVSS-4.0] + - id: SSVC-2.1 + family: SSVC + name: Stakeholder-Specific Vulnerability Categorization + version: "2.1" + url: https://certcc.github.io/SSVC/ + date_reviewed: "2026-06-16" + owner: vuln-management + aliases: [SSVC, SSVC-2.1] + - id: EPSS-v4 + family: EPSS + name: Exploit Prediction Scoring System + version: "v4" + url: https://www.first.org/epss/ + date_reviewed: "2026-06-16" + owner: vuln-management + aliases: [EPSS, EPSS-v3, EPSS-v4] + - id: SLSA-v1.2 + family: SLSA + name: Supply-chain Levels for Software Artifacts + version: "1.2" + url: https://slsa.dev/spec/ + date_reviewed: "2026-06-16" + owner: devsecops + aliases: [SLSA, SLSA-v1.0, SLSA-v1.2] + - id: CycloneDX-1.7 + family: CycloneDX + name: CycloneDX Software Bill of Materials Standard + version: "1.7" + url: https://cyclonedx.org/specification/overview/ + date_reviewed: "2026-06-16" + owner: devsecops + aliases: [CycloneDX, CycloneDX-1.5, CycloneDX-1.7] + - id: SPDX-3.0.1 + family: SPDX + name: System Package Data Exchange + version: "3.0.1" + url: https://spdx.github.io/spdx-spec/v3.0.1/ + date_reviewed: "2026-06-16" + owner: devsecops + aliases: [SPDX, SPDX-2.3, SPDX-3.0.1] diff --git a/docs/framework-reference-registry.md b/docs/framework-reference-registry.md new file mode 100644 index 00000000..399c5737 --- /dev/null +++ b/docs/framework-reference-registry.md @@ -0,0 +1,31 @@ +# Framework Reference Registry + +The framework registry in [`data/frameworks.yaml`](../data/frameworks.yaml) +records the authoritative references SecuritySkills uses when skills cite +external security frameworks, standards, scoring systems, and control catalogs. + +Every registry entry includes: + +- `id`: canonical registry identifier. +- `family`: framework family such as `OWASP`, `NIST`, `MITRE`, `CIS`, `CVSS`, + `SSVC`, `EPSS`, `SLSA`, `CycloneDX`, or `SPDX`. +- `name`: human-readable framework name. +- `version`: reviewed version or stable source state. +- `url`: authoritative HTTPS source. +- `date_reviewed`: date the registry entry was checked. +- `owner`: repo domain responsible for keeping the entry current. +- `aliases`: framework identifiers used in skill frontmatter or `index.yaml`. + +The registry is intentionally separate from individual skill frontmatter. Skills +should continue to cite the most specific framework identifiers they use, while +the registry provides provenance, ownership, and review metadata for those +identifiers. + +Validate the registry locally with: + +```bash +ruby scripts/validate_framework_registry.rb +``` + +Future stale-framework checks should use `date_reviewed` and `owner` from this +registry instead of scraping individual skill files. diff --git a/scripts/validate_framework_registry.rb b/scripts/validate_framework_registry.rb new file mode 100644 index 00000000..73a7d863 --- /dev/null +++ b/scripts/validate_framework_registry.rb @@ -0,0 +1,114 @@ +#!/usr/bin/env ruby +# frozen_string_literal: true + +require "yaml" + +ROOT = File.expand_path("..", __dir__) +REGISTRY_PATH = File.join(ROOT, "data", "frameworks.yaml") +REQUIRED_TOP_LEVEL = %w[schema_version last_reviewed required_families references].freeze +REQUIRED_ENTRY_FIELDS = %w[id family name version url date_reviewed owner aliases].freeze +REQUIRED_FAMILIES = %w[OWASP NIST MITRE CIS CVSS SSVC EPSS SLSA CycloneDX SPDX].freeze +DATE_PATTERN = /\A\d{4}-\d{2}-\d{2}\z/ + +def rel(path) + path.delete_prefix("#{ROOT}#{File::SEPARATOR}") +end + +def load_registry(errors) + YAML.safe_load(File.read(REGISTRY_PATH), permitted_classes: [], aliases: false) || {} +rescue Errno::ENOENT + errors << "#{rel(REGISTRY_PATH)}: missing registry" + {} +rescue Psych::SyntaxError => e + errors << "#{rel(REGISTRY_PATH)}: invalid YAML: #{e.message}" + {} +end + +def validate_string(value, label, errors) + errors << "#{label} must be a non-empty string" unless value.is_a?(String) && !value.empty? +end + +errors = [] +registry = load_registry(errors) + +REQUIRED_TOP_LEVEL.each do |field| + errors << "#{rel(REGISTRY_PATH)}: missing top-level field #{field}" unless registry.key?(field) +end + +families = registry["required_families"] +unless families.is_a?(Array) + errors << "#{rel(REGISTRY_PATH)}: required_families must be an array" +else + missing = REQUIRED_FAMILIES - families + errors << "#{rel(REGISTRY_PATH)}: missing required families: #{missing.join(', ')}" unless missing.empty? +end + +references = registry["references"] +unless references.is_a?(Array) && !references.empty? + errors << "#{rel(REGISTRY_PATH)}: references must be a non-empty array" +else + seen_ids = {} + seen_families = Hash.new(0) + alias_index = {} + + references.each_with_index do |entry, index| + prefix = "#{rel(REGISTRY_PATH)}: references[#{index}]" + unless entry.is_a?(Hash) + errors << "#{prefix} must be an object" + next + end + + REQUIRED_ENTRY_FIELDS.each do |field| + errors << "#{prefix}: missing required field #{field}" unless entry.key?(field) + end + + %w[id family name version url date_reviewed owner].each do |field| + validate_string(entry[field], "#{prefix}.#{field}", errors) if entry.key?(field) + end + + if entry["id"].is_a?(String) + errors << "#{prefix}: duplicate id #{entry['id']}" if seen_ids[entry["id"]] + seen_ids[entry["id"]] = true + end + + seen_families[entry["family"]] += 1 if entry["family"].is_a?(String) + + unless entry["url"].to_s.match?(%r{\Ahttps://}) + errors << "#{prefix}.url must use https://" + end + + if entry.key?("date_reviewed") && !entry["date_reviewed"].to_s.match?(DATE_PATTERN) + errors << "#{prefix}.date_reviewed must use YYYY-MM-DD" + end + + aliases = entry["aliases"] + unless aliases.is_a?(Array) && !aliases.empty? + errors << "#{prefix}.aliases must be a non-empty array" + next + end + + aliases.each_with_index do |framework_alias, alias_index_in_entry| + alias_label = "#{prefix}.aliases[#{alias_index_in_entry}]" + validate_string(framework_alias, alias_label, errors) + next unless framework_alias.is_a?(String) + + if alias_index[framework_alias] + errors << "#{alias_label}: duplicate alias also used by #{alias_index[framework_alias]}" + else + alias_index[framework_alias] = entry["id"] + end + end + end + + missing_families = REQUIRED_FAMILIES.reject { |family| seen_families[family].positive? } + errors << "#{rel(REGISTRY_PATH)}: no reference entries for required families: #{missing_families.join(', ')}" unless missing_families.empty? +end + +if errors.empty? + puts "OK: validated framework registry with #{references.size} reference(s)." +else + puts "FAIL: framework registry validation failed." + errors.each { |error| puts " - #{error}" } +end + +exit(errors.empty? ? 0 : 1)