Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:

Expand Down
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
198 changes: 198 additions & 0 deletions data/frameworks.yaml
Original file line number Diff line number Diff line change
@@ -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]
31 changes: 31 additions & 0 deletions docs/framework-reference-registry.md
Original file line number Diff line number Diff line change
@@ -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.
114 changes: 114 additions & 0 deletions scripts/validate_framework_registry.rb
Original file line number Diff line number Diff line change
@@ -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)
Loading