From 970cada4da1b788d26f98254b2f434fa42ecea21 Mon Sep 17 00:00:00 2001 From: kamalsrini <6233046+kamalsrini@users.noreply.github.com> Date: Tue, 16 Jun 2026 08:56:00 -0700 Subject: [PATCH] Add CODEOWNERS review gates --- .github/CODEOWNERS | 19 ++++++++-- .github/workflows/validate-codeowners.yml | 17 +++++++++ CONTRIBUTING.md | 7 ++++ README.md | 6 +++ scripts/validate_codeowners.rb | 46 +++++++++++++++++++++++ 5 files changed, 92 insertions(+), 3 deletions(-) create mode 100644 .github/workflows/validate-codeowners.yml create mode 100644 scripts/validate_codeowners.rb diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 89bf37fb..015dcfa5 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -4,15 +4,28 @@ # Default owners for all files * @kamalsrini -# AI security skills require review from a maintainer with AI security background -# per SECURITY.md Section 4: "Any skill in the ai-security/ directory requires -# review from a maintainer with AI security background" +# Domain skill ownership gates. Replace @kamalsrini or add teams when GitHub +# teams are available; keep these patterns so branch protection can require +# CODEOWNER review by domain. +skills/appsec/ @kamalsrini +skills/cloud/ @kamalsrini skills/ai-security/ @kamalsrini +skills/compliance/ @kamalsrini +skills/secops/ @kamalsrini + +# Adjacent domain surfaces. +skills/devsecops/ @kamalsrini +skills/identity/ @kamalsrini +skills/incident-response/ @kamalsrini +skills/network/ @kamalsrini +skills/vuln-management/ @kamalsrini +roles/ @kamalsrini # CI/CD workflows require maintainer review (supply chain risk) .github/workflows/ @kamalsrini # Index and contributing guidelines require maintainer review index.yaml @kamalsrini +data/frameworks.yaml @kamalsrini CONTRIBUTING.md @kamalsrini SECURITY.md @kamalsrini diff --git a/.github/workflows/validate-codeowners.yml b/.github/workflows/validate-codeowners.yml new file mode 100644 index 00000000..b828fe21 --- /dev/null +++ b/.github/workflows/validate-codeowners.yml @@ -0,0 +1,17 @@ +name: Validate CODEOWNERS + +on: + pull_request: + push: + branches: + - main + +jobs: + validate-codeowners: + runs-on: ubuntu-latest + steps: + - name: Check out repository + uses: actions/checkout@v4 + + - name: Validate required domain review gates + run: ruby scripts/validate_codeowners.rb diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 0c0e263b..82d9d2e5 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -180,6 +180,13 @@ If your contribution changes CI/CD examples, update ruby scripts/validate_ci_cd_examples.rb ``` +If your contribution changes skill ownership or review routing, update +[.github/CODEOWNERS](.github/CODEOWNERS) and run: + +```bash +ruby scripts/validate_codeowners.rb +``` + Release artifacts are checksummed by the GitHub release workflow. See [docs/release-integrity.md](docs/release-integrity.md) before changing release packaging. diff --git a/README.md b/README.md index e43b98e2..20bce500 100644 --- a/README.md +++ b/README.md @@ -126,6 +126,12 @@ ruby scripts/validate_framework_registry.rb ruby scripts/validate_framework_registry.rb --stale --max-age-days 365 ``` +Validate required domain CODEOWNERS review gates with: + +```bash +ruby scripts/validate_codeowners.rb +``` + Release archives include SHA-256 checksums generated by the release workflow. See [`docs/release-integrity.md`](docs/release-integrity.md) for verification steps. diff --git a/scripts/validate_codeowners.rb b/scripts/validate_codeowners.rb new file mode 100644 index 00000000..58c1e8ad --- /dev/null +++ b/scripts/validate_codeowners.rb @@ -0,0 +1,46 @@ +#!/usr/bin/env ruby +# frozen_string_literal: true + +ROOT = File.expand_path("..", __dir__) +CODEOWNERS_PATH = File.join(ROOT, ".github", "CODEOWNERS") +REQUIRED_PATTERNS = { + "AppSec" => "skills/appsec/", + "Cloud" => "skills/cloud/", + "AI Security" => "skills/ai-security/", + "Compliance" => "skills/compliance/", + "SecOps" => "skills/secops/" +}.freeze + +def rel(path) + path.delete_prefix("#{ROOT}#{File::SEPARATOR}") +end + +errors = [] + +unless File.file?(CODEOWNERS_PATH) + errors << "#{rel(CODEOWNERS_PATH)}: missing CODEOWNERS file" +else + entries = File.readlines(CODEOWNERS_PATH, chomp: true) + .map(&:strip) + .reject { |line| line.empty? || line.start_with?("#") } + + REQUIRED_PATTERNS.each do |domain, pattern| + entry = entries.find { |line| line.split(/\s+/).first == pattern } + if entry.nil? + errors << "#{rel(CODEOWNERS_PATH)}: missing #{domain} owner pattern #{pattern}" + next + end + + owners = entry.split(/\s+/)[1..] || [] + errors << "#{rel(CODEOWNERS_PATH)}: #{pattern} must list at least one owner" if owners.empty? + end +end + +if errors.empty? + puts "OK: CODEOWNERS includes required domain review gates." +else + puts "FAIL: CODEOWNERS validation failed." + errors.each { |error| puts " - #{error}" } +end + +exit(errors.empty? ? 0 : 1)