Skills DirectorySkills Directory
SkillsLearnSecurityCategoriesDocsBlogPro
Sign InSubmit Skill
Skills Directory

Security-tested agent skills for Claude, coding agents, and AI workflows.

Directory

  • Browse Skills
  • All Skills A–Z
  • Claude Skills
  • Claude Code Skills
  • Agent Skills
  • Categories
  • Authors
  • Submit a Skill

Learn

  • Learn Hub
  • Install Claude Skills
  • Write SKILL.md
  • Skills vs MCP
  • Directories Compared

Security

  • Security
  • Methodology
  • Secure Claude Skills
  • Security Badges
  • Chrome Extension
  • Skill Manager

Company

  • About
  • Community
  • Blog
  • API Docs
  • Advertise

2026 Skills Directory. All rights reserved.

ProTermsPrivacyRefunds
Back to skills

Codeowners Drift Audit

BSecurity

Audit CODEOWNERS for drift: finds unowned files, dead rules (patterns matching zero files), and invalid owner references. Works with GitHub and GitLab remotes, and degrades to advisory-only output when neither CLI is authenticated. Run when CODEOWNERS is out of date, files lack coverage, or ownership is unclear.

8 stars
0 votes
0 copies
0 views
Added 10/6/2026
ai-agentsrustgoshellbashgitapi

Works with

cliapi

Security Analysis

B75/100
criticalPipes output to a shell interpreter

Pro scans all 2 files and shows the line behind each finding

Scanned 10/6/2026

$npx -y skills add bordenet/superpowers-plus --skill codeowners-drift-audit --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Codeowners Drift Audit?

Add the live security badge to your README — it updates automatically with every re-scan.

Security grade badge for Codeowners Drift Audit
[![Security: B — Skills Directory](https://www.skillsdirectory.com/api/skills/bordenet-codeowners-drift-audit/badge)](https://www.skillsdirectory.com/skills/bordenet-codeowners-drift-audit)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
Files
skill.md
---
name: codeowners-drift-audit
disable-model-invocation: true
source: superpowers-plus
augment_menu: true
triggers: ["/sp-codeowners-drift-audit", "/codeowners-drift-audit", "CODEOWNERS out of date", "who owns this file", "codeowners audit", "unowned files", "CODEOWNERS drift"]
anti_triggers: ["PR review assignment", "auto-assign reviewers"]
description: "Audit CODEOWNERS for drift: finds unowned files, dead rules (patterns matching zero files), and invalid owner references. Works with GitHub and GitLab remotes, and degrades to advisory-only output when neither CLI is authenticated. Run when CODEOWNERS is out of date, files lack coverage, or ownership is unclear."
summary: "Use when: CODEOWNERS is out of date, files lack owners, or ownership assignments seem stale. Audits unowned files, dead rules, and invalid owners."
coordination:
  group: engineering
  order: 5
  requires: []
  enables: []
  escalates_to: []
  internal: false
composition:
  consumes: [repo-state]
  produces: [codeowners-audit-report]
  capabilities: [audits-codeowners, detects-drift]
  priority: 10
---

# CODEOWNERS Drift Audit

> **Wrong skill?** PR reviewer auto-assignment → not this skill (read-only audit only). Blast radius of a code change → `blast-radius-check`.
>
> **Source:** `superpowers-plus`
> **Part of:** Engineering Rigor skill family

## When to Use

- CODEOWNERS hasn't been reviewed in a while and ownership may have drifted
- Files or directories seem to lack a clear owner
- A rule in CODEOWNERS may no longer match anything (renamed/deleted paths)
- An owner reference (`@user`, `@org/team`) may be stale or misspelled

## How to Run

Run the single script below via `bash` (enforced at runtime, see Failure Modes). Locates CODEOWNERS (`.github/`, then root, then `docs/` -- GitHub's
own order), finds unowned files, dead rules, and invalid owners. Stateless.

> **Exit code:** exit 0 with all three `===` headers printed means complete.
> Anything else is an incomplete audit -- never read that as "no issues found."
>
> **Large-repo advisory:** cap `FILES_ARR` post-build for a quick sample (e.g.
> `FILES_ARR=("${FILES_ARR[@]:0:100}")`) -- but this also caps Step 2 (Dead
> Rules); see the Failure Modes row before deleting a rule it flags.

```bash
#!/usr/bin/env bash
set -euo pipefail

# -- Step 0: Preconditions --
# Under zsh, matches_pattern()'s case-glob silently mismatches everything.
if [ -z "${BASH_VERSION:-}" ]; then
  echo "ERROR: this script requires bash -- re-run as 'bash <this-script>'" >&2; exit 1
fi
if ! git rev-parse --is-inside-work-tree >/dev/null 2>&1; then
  echo "ERROR: not inside a git working tree" >&2; exit 1
fi
# CODEOWNERS candidates and git ls-files are both cwd-relative -- root first.
cd "$(git rev-parse --show-toplevel)" || { echo "ERROR: cannot cd to repo root" >&2; exit 1; }

CODEOWNERS_PATH=""
for candidate in .github/CODEOWNERS CODEOWNERS docs/CODEOWNERS; do
  [ -f "$candidate" ] && { CODEOWNERS_PATH="$candidate"; break; }
done
if [ -z "$CODEOWNERS_PATH" ]; then
  echo "ERROR: no CODEOWNERS file found" >&2; exit 1
fi
if [ ! -r "$CODEOWNERS_PATH" ]; then
  echo "ERROR: CODEOWNERS found but not readable: $CODEOWNERS_PATH" >&2; exit 1
fi
echo "CODEOWNERS: $CODEOWNERS_PATH"

# Match gitignore-style; anchoring MUST be decided before "/" is stripped
# (see Failure Modes for the anchoring/globstar regressions this fixes).
matches_pattern() {
  local f="$1" pattern="$2"
  local anchored=0
  case "$pattern" in
    '**/'*) pattern="${pattern#\*\*/}" ;;
    *)
      case "${pattern%/}" in
        */*) anchored=1 ;;
      esac
      ;;
  esac
  pattern="${pattern#/}"
  pattern="${pattern%/}"
  # gitignore's "/**/ " also matches zero intervening dirs.
  local pattern2="${pattern//\/\*\*\//\/}"
  if [ "$anchored" -eq 1 ]; then
    # shellcheck disable=SC2254
    case "$f" in
      $pattern|$pattern/*|$pattern2|$pattern2/*) return 0 ;;
    esac
  else
    # shellcheck disable=SC2254
    case "$f" in
      $pattern|$pattern/*|*/$pattern|*/$pattern/*|$pattern2|$pattern2/*|*/$pattern2|*/$pattern2/*) return 0 ;;
    esac
  fi
  return 1
}

# Rules (GitLab [Section] headers excluded, trailing "# comment" text and any
# CRLF stripped so neither pollutes Step 3's owner-token field split below).
RULES=$(grep -Ev '^\s*(#|$|\[)' "$CODEOWNERS_PATH" 2>/dev/null | tr -d '\r' | sed -E 's/[[:space:]]*#.*$//' || true)
echo "Rules: $(echo "$RULES" | grep -c . || true)"

# NUL-delimited into an array: git ls-files -z is never C-quoted, unlike
# newline-delimited output (handles non-ASCII/backslash filenames correctly).
FILES_ARR=()
while IFS= read -r -d '' f; do
  FILES_ARR+=("$f")
done < <(git ls-files -z)
echo "Tracked files: ${#FILES_ARR[@]}"

# -- Step 1: Unowned files --
echo ""
echo "=== Unowned Files ==="
for f in "${FILES_ARR[@]+"${FILES_ARR[@]}"}"; do
  owned=0
  while IFS= read -r rule; do
    [ -z "$rule" ] && continue
    read -r pattern _ <<< "$rule"
    if matches_pattern "$f" "$pattern"; then owned=1; break; fi
  done <<RULES_EOF
$RULES
RULES_EOF
  if [ "$owned" -eq 0 ]; then echo "  UNOWNED: $f"; fi
done

# -- Step 2: Dead rules --
echo ""
echo "=== Dead Rules ==="
echo "$RULES" | while IFS= read -r rule; do
  [ -z "$rule" ] && continue
  read -r pattern _ <<< "$rule"
  matched=0
  for f in "${FILES_ARR[@]+"${FILES_ARR[@]}"}"; do
    if matches_pattern "$f" "$pattern"; then matched=1; break; fi
  done
  if [ "$matched" -eq 0 ]; then echo "  DEAD RULE: $rule"; fi
done

# -- Step 3: Owner validity (best-effort) --
echo ""
echo "=== Owner Validity ==="
# Check every remote, parsing the actual hostname (see Failure Modes).
IS_GITHUB=0
for remote_name in $(git remote 2>/dev/null); do
  remote_url=$(git remote get-url "$remote_name" 2>/dev/null) || continue
  remote_host=$(printf '%s\n' "$remote_url" | sed -E 's#^[a-zA-Z][a-zA-Z0-9+.-]*://##; s#^[^@/]*@##; s#[:/].*##')
  if [ "$remote_host" = "github.com" ]; then IS_GITHUB=1; break; fi
done

# gh only for github.com, glab only otherwise (prevents cross-API misvalidation).
HAS_GH=0
if [ "$IS_GITHUB" -eq 1 ] && command -v gh >/dev/null 2>&1; then
  if gh auth status >/dev/null 2>&1; then
    HAS_GH=1
  else
    echo "  ADVISORY: gh not authenticated -- owner check skipped"
  fi
fi

HAS_GLAB=0
if [ "$IS_GITHUB" -eq 0 ] && command -v glab >/dev/null 2>&1; then
  if glab auth status >/dev/null 2>&1; then
    HAS_GLAB=1
  else
    echo "  ADVISORY: glab not authenticated -- owner check skipped"
  fi
fi

if [ "$HAS_GH" -eq 0 ] && [ "$HAS_GLAB" -eq 0 ]; then
  echo "  ADVISORY: owner verification skipped -- no authenticated CLI matching this remote's type on PATH"
  echo "  (unowned-file and dead-rule results above are unaffected -- CLI is only needed for owner validity)"
else
  echo "$RULES" \
    | awk '{for(i=2;i<=NF;i++) print $i}' \
    | sort -u \
    | while IFS= read -r owner; do
        [ -z "$owner" ] && continue
        # Email-format: non-@ char before @ + dot after @ = email, not username
        case "$owner" in
          [!@]*@*.*) echo "  UNVERIFIABLE: $owner (email-format)"; continue ;;
        esac
        # No "@" and not email-shaped: not a valid CODEOWNERS owner reference
        case "$owner" in
          @*) : ;;
          *) echo "  UNVERIFIABLE: $owner (missing @ prefix)"; continue ;;
        esac
        slug="${owner#@}"
        if [ "$HAS_GH" -eq 1 ]; then
          # Distinguish team refs (@org/team) from user refs (@user)
          if echo "$slug" | grep -q '/'; then
            if gh api "orgs/${slug%%/*}/teams/${slug##*/}" --silent 2>/dev/null; then
              echo "  VALID: $owner"
            else
              echo "  NOT_FOUND: $owner"
            fi
          else
            if gh api "users/${slug}" --silent 2>/dev/null; then
              echo "  VALID: $owner"
            else
              echo "  NOT_FOUND: $owner"
            fi
          fi
        elif [ "$HAS_GLAB" -eq 1 ]; then
          # glab users?username= exits 0 even for empty results -- inspect body
          ubody=$(glab api "users?username=${slug}" 2>/dev/null || echo "[]")
          # URL-encode slash for namespaced group paths (e.g. myorg/myteam -> myorg%2Fmyteam)
          encoded_slug=$(printf '%s' "$slug" | sed 's|/|%2F|g')
          gbody=$(glab api "groups/${encoded_slug}" 2>/dev/null || echo "{}")
          if echo "$ubody" | grep -q '"username"' || echo "$gbody" | grep -q '"full_path"'; then
            echo "  VALID: $owner"
          else
            echo "  NOT_FOUND: $owner"
          fi
        else
          echo "  UNVERIFIABLE: $owner (no matching CLI for remote type)"
        fi
      done
fi
echo ""
echo "=== Done. Review UNOWNED/DEAD RULE/NOT_FOUND lines above. ==="
```

## Companion Skills

- `blast-radius-check` — Before modifying code that a drifted CODEOWNERS rule might miss
- `pre-commit-gate` — Pre-commit checks independent of ownership review

## Failure Modes

Top rows only -- **see `reference.md`** for the full table.

| Failure | Prevention |
|---|---|
| zsh silently mismatches every pattern; sh/dash fail differently but still non-zero | Step 0 checks `$BASH_VERSION`, refuses to run without it |
| Leading-slash pattern (`/build/`) loses root-anchoring if `/` is stripped before the anchor decision | `anchored` is decided from `${pattern%/}` before the leading `/` is stripped |
| Leading `**/`, or mid-string `/**/`, doesn't match zero intervening dirs (`case` has no recursive-glob) | Leading `**/` special-cased to any-depth; mid-string gets a collapsed-to-`/` candidate |
| `.github/CODEOWNERS` vs root, or a subdirectory invocation, silently scopes the search/file-list wrong | Search order `.github/`→root→`docs/`; Step 0 `cd`s to repo root first |
| Non-GitHub/non-GitLab remote + authenticated `glab` gets a confident but meaningless result | Disclosed, unfixed (reference.md) -- don't trust Owner Validity here |
| Large repo: capping `FILES_ARR` also corrupts Step 2 | See "Large-repo advisory" -- never delete a flagged `DEAD RULE` from a capped run |
| Non-zero exit, or stopping before "=== Done." | Never read as "no issues found" -- rerun |

Attribution

bordenetbordenet
View sourceSee grades on GitHubMore from bordenet →
SSkills DirectorySkills Directory

Ship a skill? Prove it's safe.

Free 120-pattern security scan, letter grade, and an embeddable README badge.

Submit a skill

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments (0)

No comments yet. Be the first to comment!

SSkills DirectorySkills Directory

Ship a skill? Prove it's safe.

Free 120-pattern security scan, letter grade, and an embeddable README badge.

Submit a skill

Related Skills

Caveman

Terse caveman voice: answer first, fluff gone, every technical fact kept. Use for /caveman, "caveman mode", "talk like caveman", "be brief", "less tokens". Stays on until "stop caveman" or "normal mode".

1100021 votes

Hyperplan

Adversarial multi-agent planning skill. Self-orchestrates 5 hostile category members (unspecified-low, unspecified-high, deep, ultrabrain, artistry) via team-mode for ruthless cross-critique debate, distills only the defensible insights, then MANDATORILY hands the distilled insight bundle to the `plan` agent for executable plan formalization. Use when planning needs maximum rigor and surfacing of weak assumptions, blind spots, and over-engineering. Triggers: 'hyperplan', 'hpp', '/hyperplan', ...

698621 votes

Writing Skills

Create and manage Claude Code skills in HASH repository following Anthropic best practices. Use when creating new skills, modifying skill-rules.json, understanding trigger patterns, working with hooks, debugging skill activation, or implementing progressive disclosure. Covers skill structure, YAML frontmatter, trigger types (keywords, intent patterns), UserPromptSubmit hook, and the 500-line rule. Includes validation and debugging with SKILL_DEBUG. Examples include rust-error-stack, cargo-dep...

3931 votes

Mcp Code Execution

Routes multi-tool workflows through MCP servers for large datasets and pipelines. Use when Bash tool overhead is limiting throughput on data-heavy tasks.

3421 votes

catchup

Recovers the conversation and failed tool calls of a previous Codex, Amp, Claude Code, Antigravity, Cline, Copilot CLI, Cursor, DeepSeek Harness, Grok Build, Kimi, OpenCode, Pi Agent, or ZCode session. Use when the user says "catch up", "what did the last session do", "get me up to speed", "I switched agents", asks to recover/summarize a previous session before continuing, or asks to diagnose or report a catchup failure. Do NOT use for the current conversation, git history, or any non-agent log.

741 votes
View all in ai-agents →