version: 0.1.0
security-ownership-mapOverview
Static security audits catch what is wrong with the code. They don't tell you who would notice if it broke — and crucially, who would notice if it broke and the original author left. The bus-factor problem in security terms: a sensitive file with one contributor is a single-point-of-failure. When that contributor leaves, the file becomes "abandoned code" — no active review, no security vigilance, no migration path when frameworks upgrade.
This skill turns git history into a people↔file topology and surfaces the security-relevant patterns:
- Orphan sensitive files: high-risk files (auth, crypto, secrets, payment) with no contributor in 12+ months
- Hidden owners: a module whose commit history shows only one author; not officially documented as owner
- Bus-factor hotspots: critical-path files where N=1 contributor holds 100% of expertise
- Maintainer concentration: top-K authors' share of sensitive LOC; if one team owns 80% of sensitive code, the team's departure is a single point of failure
This is security governance — not security code review. Pairs with security-and-hardening (the per-line code audit) and security-devsecops (the supply-chain layer).
Source material adapted from openai's .curated/security-ownership-map; the YAML/Neo4j/Gephi outputs were dropped in favor of CSV/JSON + a Markdown summary, which covers the same questions without the dependency.
Path Convention (two-axis principle)
All output lands under docs/ownership-map/ , git tracked project-level audit log:
docs/ownership-map/<repo-hash>-<date>/ownership-map.yaml— §1 user hand-editable sensitive_paths input configurationdocs/ownership-map/<repo-hash>-<date>/ownership-map.md— §5 main reportdocs/ownership-map/<repo-hash>-<date>/ownership-map.csv— §6 machine-readabledocs/ownership-map/<repo-hash>-<date>/ownership-map.json— §6 machine-readable
<repo-hash>: at report generation time, the git rev-parse --short=8 HEAD(8-character short SHA;<date> = YYYY-MM-DD)。
Frontmatter required fields(ownership-map.{md,csv,json} must carry;ownership-map.yaml input configuration does not add frontmatter, because the user needs to hand-edit it):
---
generated_at: <ISO-8601 UTC>
repo_hash: <8-char SHA>
repo_name: <basename of CWD>
---
csv and json use the same fields (csv use YAML frontmatter on the first line of the file, then the CSV header; if the tool does not allow this, accept the # generated_at: ... comment form).
Collision semantics:<repo-hash>-<date> The probability of the same day and same SHA being triggered repeatedly is extremely low (git short SHAs are unlikely to collide on the same day); if a collision does occur, 3 options:覆盖 / 重命名为 <repo-hash>-<date>-HHMMSS/ / 跳过本次。
When to Use
Use when:
- Before a major refactor (e.g. extracting a module, splitting a monolith) that risks orphaning sensitive code
- After a security incident: map "if the author of the vulnerable code left tomorrow, who would catch the next one?"
- During hiring or team-formation: identify sensitive-code coverage gaps
- Designing on-call or rotation schemes that need real coverage understanding (not just org-chart claim)
- Quarterly security governance review: re-run to catch drift
- Compliance audit asking "who has expertise in the auth code?" (SOC2 / ISO27001-style)
- About to deprecate a feature: "does anyone else still own this?"
NOT for: (scenario description — let description match decide)
- Per-line code audit of existing code →
security-and-hardening - Code-contract risk review (state / timing / concurrency / boundary) →
contract-strengthening - Per-PR review of changes →
verification-before-completion+slice-reviewif multi-slice - Threat modeling (who could attack, what could they reach) →
security-threat-model - People / org-chart mapping without git-history grounding → use HR / team data; this skill only uses git
Process
1. Classify the repo into sensitive vs non-sensitive paths
Sensitive paths are where security bugs have outsized blast radius. Pick a heuristic that fits the repo; common categories:
| Category | Examples |
|---|---|
| Authn / authz | auth/, login/, session/, rbac/, permissions/, oauth/, sso/ |
| Secrets / crypto | crypto/, keys/, secrets/, vault/, kms/ |
| Payment / billing | billing/, payment/, invoice/, stripe/, ledger/ |
| PII / regulated | gdpr/, pii/, phi/, consent/, dsar/ |
| Privileged operations | admin/, iam/, sudo/, dangerous/, unsafe/ |
| External integrations | integrations/, webhooks/, third-party/, vendors/ |
Define these as glob patterns at the top of the run (so the user can adjust before generation):
# docs/ownership-map/<repo-hash>-<date>/ownership-map.yaml
sensitive_paths:
- "auth/**"
- "**/crypto/**"
- "billing/**"
- "secrets/**"
- "**/admin/**"
low_risk_paths:
- "**/*.md"
- "docs/**"
- "tests/**"
- "fixtures/**"
default_sensitive_window_months: 12 # "no contributor in N months" = orphan
2. Walk git history for the people↔file graph
Use git log to enumerate authors per file, weighted by recency:
# Per-file: list all contributors + their last-commit date + commit count
for f in $(find . -type f -name '*.ts' -o -name '*.go' -o -name '*.py' -o -name '*.rs' -o -name '*.java' | grep -v -E '\.(test|spec)\.' ); do
git log --follow --format='%ae|%aI' -- "$f" | sort -u
done
Map email → canonical author (git allows same person with multiple emails; reconcile via .mailmap if present, otherwise treat emails as distinct identities and warn).
Compute per file:
- Contributor count (unique authors)
- Top author (by commit count in window)
- Last contribution date (most recent commit)
- Days since last contribution (vs today)
- Sensitive-LOC share by top author
3. Surface the 4 default queries
Query A: Orphan sensitive files
Sensitive file with days_since_last_contribution > sensitive_window_months × 30 AND contributor_count <= 2.
These files had contributors in the past but no recent activity. Risk: stale code, no active review, framework upgrades may silently break them, security patches may not get applied.
## Query A — Orphan sensitive files
| File | Last touched | Last contributor | Bus factor | Risk |
|---|---|---|---|---|
| src/auth/legacy-saml.ts | 2024-03-15 | alice@oldcorp.com | 1 | HIGH |
Query B: Hidden owners
Sensitive file where one author holds ≥ 80% of commits AND no author listed in the project's CODEOWNERS or team rosters. Risk: institutional knowledge concentrated in one person who is not officially accountable.
## Query B — Hidden owners (single-author + no CODEOWNERS coverage)
| File | Single-author | Single-author's commit share | CODEOWNERS coverage |
|---|---|---|---|
| src/billing/tax-engine.go | bob | 100% (47/47) | none |
Query C: Bus-factor hotspots
Critical-path files where contributor_count == 1 regardless of recency. These are single points of failure for expertise. The bus-factor-1 owner leaving means no one knows the file.
## Query C — Bus-factor-1 hotspots (sensitive paths only)
| File | Owner | Last touched | Status |
|---|---|---|---|
| src/crypto/legacy-rsa.ts | carol | 2026-06-01 | active — but solo |
Query D: Maintainer concentration
Per author, total sensitive-LOC under their ownership (using "most-recent-touch-author" as the proxy). Top-K by share.
## Query D — Top maintainers by sensitive-LOC share
| Author | Sensitive files | Sensitive LOC | Share |
|---|---|---|---|
| alice@corp.com | 47 | 12,400 | 38% |
| bob@corp.com | 31 | 9,800 | 30% |
| carol@corp.com | 22 | 7,200 | 22% |
| others | — | 3,200 | 10% |
If top-1 > 40% OR top-3 > 85%, flag concentration risk.
4. Optional deeper queries
- Cross-team coupling: which teams' code touches the same file?
git log --pretty=format:'%ae' -- fileclustered by email-domain. - Tenure vs recency: contributors active > 1 year ago but NOT in last 3 months — the people who may have moved teams.
- Off-hours velocity: commits outside 9-17 by author — signal of bus-factor pressure.
- Sensitive-vs-public exposure gap: sensitive files without test coverage from non-author contributors — no peer review evidence.
5. Output to docs/ownership-map/<repo-hash>-<date>/ownership-map.md
# Security ownership map — <repo>
> Date: <today>
> Window: <sensitive_paths> definition, default window 12 months
> Source: git history (sha <HEAD>)
## Summary
- Total sensitive files: <N>
- Orphan sensitive: <N> (<X>%)
- Hidden owner: <N> files
- Bus-factor-1: <N> files
- Top-1 maintainer share: <X>%
## Query A — Orphan sensitive files
[table]
## Query B — Hidden owners
[table]
## Query C — Bus-factor hotspots
[table]
## Query D — Maintainer concentration
[table]
## Optional: deeper queries (if requested)
[…]
## Recommendations
- Bus-factor-1 files: pair-program a current contributor onto each; document the result in a runbook
- Hidden owners: cross-reference CODEOWNERS or team rosters; either add to CODEOWNERS or assign an official owner
- Orphan files: deprecate (if unused), or assign a current owner with a "first pass" review
- Maintainer concentration: spread sensitive-LOC knowledge through rotation / pair-programming
## Methodology
- All numbers derived from `git log --follow` per file (commands in process §2)
- Sensitive paths defined by `docs/ownership-map/<repo-hash>-<date>/ownership-map.yaml` (top of run); adjust per repo
- Email canonicalization via `.mailmap` if present
- Recency window: 12 months (configurable)
6. Optional CSV / JSON artifacts
For machine-readable downstream analysis, also write:
docs/ownership-map/<repo-hash>-<date>/ownership-map.csvwith one row per file: path, sensitive_yes, contributor_count, top_author, top_author_share, last_touched, days_since_touch, bus_factor, hidden_ownerdocs/ownership-map/<repo-hash>-<date>/ownership-map.jsonwith the same data nested:{repo, repo_hash, repo_name, generated_at, sensitive_paths, files: [...], queries: {A, B, C, D}, recommendations: [...]}
Do NOT include Neo4j / Gephi / Graphviz outputs unless explicitly requested — CSV/JSON is enough for downstream tooling.
omo Integration
| OMO capability | Used for |
|---|---|
oracle agent |
Calibration: "is this contributor count really a bus-factor hotspot, given tenure and the team's documented rotation scheme?" |
websearch MCP |
Lookup org-chart or team-rotation data when reasoning about hidden owners |
OMO general agent |
Parallel subagents to compute per-team summaries if the repo is large (>5k files) |
OMO review-work skill |
Stage-2 review of the ownership map before sharing with leadership |
Common Rationalizations
| Excuse | Why it's wrong |
|---|---|
| "Our CODEOWNERS covers everything" | CODEOWNERS lists required reviewers, not who knows the code. Hidden owners often pre-date CODEOWNERS or are missing entries. |
| "Bus-factor is an org problem, not security" | A bus-factor-1 sensitive file is exactly the security risk: if that person is coerced, compromised, or simply leaves without handover, no one reviews the next change. |
| "We have 10 years of git history; this is too big to compute" | The compute is git log --follow per file; even 100k commits takes <5 minutes. The output is one Markdown file. |
| "We don't know who's on which team anymore" | Git email domain (@corp.com / @subsidiary.com) is a reasonable proxy; pair with HR data if you have it. |
| "The output will be political" | That's a feature, not a bug. Bus-factor concentration IS a leadership question. Run the report quarterly; don't make it a one-shot. |
| "Let me run this once a year" | Bus-factor changes fast in fast-moving teams. Quarterly cadence catches drift; monthly is overkill for most teams. |
| "Sensitive paths depend on context; we can't auto-classify" | Heuristics (auth/crypto/billing/admin) cover ~80% of cases; tune the YAML per repo for the rest. Perfect classification isn't the goal — flag concentration risk. |
| "All our sensitive code is owned by senior staff anyway" | That's the concentration risk: if seniors leave or are unavailable, the file becomes orphan. |
Red Flags
The ownership map is going wrong if:
- §1 sensitive_paths is empty or only contains
*.md— the user hasn't classified the repo; stop and ask - §2 walks
git log --followfor binary files (locks, fixtures, generated code) — exclude these - §2 reconciles emails by ignoring
.mailmap— multiple emails for the same person will inflate "contributor count" - §3 Query A returns 0 results on a 5-year-old repo — the recency window is too short, or the heuristic is wrong
- §3 Query D top-1 share > 60% — concentration risk, must be flagged
- §5 Output has no Recommendations section — the map is a description; the value is the action items
- §5 Methodology is missing — second reviewer can't reproduce
- §6 CSV/JSON missing
sensitive_yescolumn — downstream can't filter without it - Recommendations are vague ("improve documentation") not specific ("assign alice + bob as co-owners of src/crypto/; pair on first change")
- Repo is AI-heavy (
Co-authored-by: Claude/Cursor> 50% of recent commits) and Query C/D outputs are treated as truth — in AI-era codebases, "Alice owns src/crypto/" via 100 AI-authored commits does NOT mean Alice has any actual knowledge of that file. Bus-factor-1 may mask a deeper problem: nobody human understands the code. Cross-reference the ownership map with human-review log or pair-programming rotation before acting on Query C/D. Query A (orphan by recency) and Query B (single-author concentration) remain valid; their meaning shifts from "one human" to "one human + their AI" which is arguably riskier.
Verification
Before claiming the ownership map is done, produce evidence:
- §1 sensitive_paths YAML at top of run (or in
docs/ownership-map/<repo-hash>-<date>/ownership-map.yaml); reviewed by user before generation - §2 walked git log per file; excluded binary / generated / fixture files
- §2 reconciled authors via
.mailmapif present - §3 Query A: orphan sensitive files listed with
last_touched+days_since - §3 Query B: hidden owners listed; cross-referenced against CODEOWNERS (or absence noted)
- §3 Query C: bus-factor-1 hotspots listed (sensitive paths only)
- §3 Query D: top-K maintainers share calculated; concentration risk flagged if thresholds breached
- §5 Markdown output saved at
docs/ownership-map/<repo-hash>-<date>/ownership-map.mdwith frontmatter (generated_at/repo_hash/repo_name) - §5 Recommendations are concrete actions (specific file + specific remediation)
- §5 Methodology section: reproducible commands listed
- §6 CSV/JSON artifacts at
docs/ownership-map/<repo-hash>-<date>/ownership-map.{csv,json}withsensitive_yescolumn for downstream filtering
Acceptance criterion: A second reviewer can rerun the same commands (listed in Methodology), see the same numbers, and reach the same conclusion about which files are at risk and why.