Security Ownership Map
Overview
Build a bipartite graph of people and files from git history, then compute ownership risk and export graph artifacts for Neo4j/Gephi. Also build a file co-change graph (Jaccard similarity on shared commits) to cluster files by how they move together while ignoring large, noisy commits.
Requirements
- Python 3
networkx(required; community detection is enabled by default)
Install with:
pip install networkx
Philosophy
- Prefer root-cause understanding over quick symptom patches.
- Keep guidance evidence-based, explicit, and reproducible.
- Optimize for decisions that reduce rework and operational risk.
Workflow
- Scope the repo and time window (optional
--since/--until). - Decide sensitivity rules (use defaults or provide a CSV config).
- Build the ownership map with
scripts/run_ownership_map.py(co-change graph is on by default; use--cochange-max-filesto ignore supernode commits). - Communities are computed by default; graphml output is optional (
--graphml). - Query the outputs with
scripts/query_ownership.pyfor bounded JSON slices. - Persist and visualize (see
references/neo4j-import.md).
Before running, confirm:
- repo path and whether subpaths are in or out of scope;
- whether CODEOWNERS should be compared against observed ownership;
- the time window that best matches the user's operational question;
- whether author or committer attribution is the better model for this repo.
By default, the co-change graph ignores common "glue" files (lockfiles, .github/*, editor config) so clusters reflect actual code movement instead of shared infra edits. Override with --cochange-exclude or --no-default-cochange-excludes. Dependabot commits are excluded by default; override with --no-default-author-excludes or add patterns via --author-exclude-regex.
If you want to exclude Linux build glue like Kbuild from co-change clustering, pass:
python product/security/security-ownership-map/scripts/run_ownership_map.py \
--repo /path/to/linux \
--out ownership-map-out \
--cochange-exclude "**/Kbuild"
Quick start
Run from the repo root:
python product/security/security-ownership-map/scripts/run_ownership_map.py \
--repo . \
--out ownership-map-out \
--since "12 months ago" \
--emit-commits
Defaults: author identity, author date, and merge commits excluded. Use --identity committer, --date-field committer, or --include-merges if needed.
Example (override co-change excludes):
python product/security/security-ownership-map/scripts/run_ownership_map.py \
--repo . \
--out ownership-map-out \
--cochange-exclude "**/Cargo.lock" \
--cochange-exclude "**/.github/**" \
--no-default-cochange-excludes
Communities are computed by default. To disable:
python product/security/security-ownership-map/scripts/run_ownership_map.py \
--repo . \
--out ownership-map-out \
--no-communities
Sensitivity rules
By default, the script flags common auth/crypto/secret paths. Override by providing a CSV file:
# pattern,tag,weight
**/auth/**,auth,1.0
**/crypto/**,crypto,1.0
**/*.pem,secrets,1.0
Use it with --sensitive-config path/to/sensitive.csv.
Output artifacts
ownership-map-out/ contains:
people.csv(nodes: people)files.csv(nodes: files)edges.csv(edges: touches)cochange_edges.csv(file-to-file co-change edges with Jaccard weight; omitted with--no-cochange)summary.json(security ownership findings)commits.jsonl(optional, if--emit-commits)communities.json(computed by default from co-change edges when available; includesmaintainersper community; disable with--no-communities)cochange.graph.json(NetworkX node-link JSON withcommunity_id+community_maintainers; falls back toownership.graph.jsonif no co-change edges)ownership.graphml/cochange.graphml(optional, if--graphml)
people.csv includes timezone detection based on author commit offsets: primary_tz_offset, primary_tz_minutes, and timezone_offsets.
LLM query helper
Use scripts/query_ownership.py to return small, JSON-bounded slices without loading the full graph into context.
Examples:
python product/security/security-ownership-map/scripts/query_ownership.py --data-dir ownership-map-out people --limit 10
python product/security/security-ownership-map/scripts/query_ownership.py --data-dir ownership-map-out files --tag auth --bus-factor-max 1
python product/security/security-ownership-map/scripts/query_ownership.py --data-dir ownership-map-out person --person alice@corp --limit 10
python product/security/security-ownership-map/scripts/query_ownership.py --data-dir ownership-map-out file --file crypto/tls
python product/security/security-ownership-map/scripts/query_ownership.py --data-dir ownership-map-out cochange --file crypto/tls --limit 10
python product/security/security-ownership-map/scripts/query_ownership.py --data-dir ownership-map-out summary --section orphaned_sensitive_code
python product/security/security-ownership-map/scripts/query_ownership.py --data-dir ownership-map-out community --id 3
Use --community-top-owners 5 (default) to control how many maintainers are stored per community.
Basic security queries
Run these to answer common security ownership questions with bounded output:
# Orphaned sensitive code (stale + low bus factor)
python product/security/security-ownership-map/scripts/query_ownership.py --data-dir ownership-map-out summary --section orphaned_sensitive_code
# Hidden owners for sensitive tags
python product/security/security-ownership-map/scripts/query_ownership.py --data-dir ownership-map-out summary --section hidden_owners
# Sensitive hotspots with low bus factor
python product/security/security-ownership-map/scripts/query_ownership.py --data-dir ownership-map-out summary --section bus_factor_hotspots
# Auth/crypto files with bus factor <= 1
python product/security/security-ownership-map/scripts/query_ownership.py --data-dir ownership-map-out files --tag auth --bus-factor-max 1
python product/security/security-ownership-map/scripts/query_ownership.py --data-dir ownership-map-out files --tag crypto --bus-factor-max 1
# Who is touching sensitive code the most
python product/security/security-ownership-map/scripts/query_ownership.py --data-dir ownership-map-out people --sort sensitive_touches --limit 10
# Co-change neighbors (cluster hints for ownership drift)
python product/security/security-ownership-map/scripts/query_ownership.py --data-dir ownership-map-out cochange --file path/to/file --min-jaccard 0.05 --limit 20
# Community maintainers (for a cluster)
python product/security/security-ownership-map/scripts/query_ownership.py --data-dir ownership-map-out community --id 3
# Monthly maintainers for the community containing a file
python product/security/security-ownership-map/scripts/community_maintainers.py \
--data-dir ownership-map-out \
--file network/card.c \
--since 2025-01-01 \
--top 5
# Quarterly buckets instead of monthly
python product/security/security-ownership-map/scripts/community_maintainers.py \
--data-dir ownership-map-out \
--file network/card.c \
--since 2025-01-01 \
--bucket quarter \
--top 5
Notes:
- Touches default to one authored commit (not per-file). Use
--touch-mode fileto count per-file touches. - Use
--window-days 90or--weight recency --half-life-days 180to smooth churn. - Filter bots with
--ignore-author-regex '(bot|dependabot)'. - Use
--min-share 0.1to show stable maintainers only. - Use
--bucket quarterfor calendar quarter groupings. - Use
--identity committeror--date-field committerto switch from author attribution. - Use
--include-mergesto include merge commits (excluded by default).
Summary format (default)
Use this structure, add fields if needed:
{
"orphaned_sensitive_code": [
{
"path": "crypto/tls/handshake.rs",
"last_security_touch": "2023-03-12T18:10:04+00:00",
"bus_factor": 1
}
],
"hidden_owners": [
{
"person": "alice@corp",
"controls": "63% of auth code"
}
]
}
Graph persistence
Use references/neo4j-import.md when you need to load the CSVs into Neo4j. It includes constraints, import Cypher, and visualization tips.
Notes
bus_factor_hotspotsinsummary.jsonlists sensitive files with low bus factor;orphaned_sensitive_codeis the stale subset.- If
git logis too large, narrow with--sinceor--until. - Compare
summary.jsonagainst CODEOWNERS to highlight ownership drift. - Prefer bounded JSON query outputs over loading the raw graph files into context.
- For org-facing reports, separate:
- current observed maintainers
- CODEOWNERS policy owners
- gaps where the two diverge materially.
Anti-patterns
- Skipping investigation and jumping directly to fixes.
- Making claims without evidence, logs, or reproducible steps.
- Mixing unrelated workstreams in a single execution path.
Constraints / Safety
- Redact secrets, tokens, credentials, and PII by default; never echo raw environment values.
- Prefer safe defaults and avoid irreversible changes without explicit confirmation.
Deliverables
- A concrete next-step response with explicit, reproducible actions.
- A short verification checklist and caveats for the user.
Validation
- Fail fast: stop at the first failed check and do not continue.
- Re-run the required checks before proceeding to the next step.
- Report any failed check and requested follow-up actions clearly.
When to use
- Use this skill when the request matches the skill's intent and scope.
- Do not use it when a different domain or higher-privilege workflow is required.
Philosophy and tradeoffs
- Use this skill when consistent decision-making matters more than one-off execution because project context should drive the approach.
- Principle and mindset: prioritize tradeoffs and constraints over rigid checklists; understand why each step exists.
- Ask this to keep outcomes robust: Why is this the right default, and what could change this outcome?
- How do we adapt if constraints shift?
- What evidence is needed before choosing one path over another?
Anti-patterns and caveats
- Avoid applying this playbook generically without checking repository-specific context.
- NEVER skip required validation gates when behavior changes.
- DO NOT use this skill as a rigid replacement for engineering judgment.
- DON'T ignore warnings or assume one pattern fits all repos.
- Common pitfall: treating anti-patterns as optional.
- Incorrect assumptions here can lead to fragile guidance.
- Warning: wrong sequencing can create avoidable regressions.
Variation and adaptation
- Vary the workflow by team size, risk, and deployment target.
- Use different strategies for small, medium, and large changes.
- Adapt recommendations to the specific environment and avoid repetitive templates.
- Avoid generic or cookie-cutter responses; craft context-specific alternatives.
- Keep outputs diverse and not repetitive.
- Converge on a custom path only after evidence review.
- Different constraints should produce different, non-generic recommendations.
Empowering execution style
- Be capable of exploring multiple options and enabling the team to make safe decisions.
- Unlock confidence by explaining options and tradeoffs clearly.
- Feel free to be creative while staying rigorous and precise.
- Push boundaries with practical alternatives when simple recipes fail.
- Enable outcomes-oriented problem solving.
See Also
| Skill | When to use together |
|---|---|
| [[security-threat-model]] | Use ownership map to assign threat model mitigations |
| [[security-best-practices]] | Route best-practice remediation to the right owners |
| [[recon-workbench]] | Correlate recon findings with ownership data |
| [[gh-workflow]] | Create GitHub issues for identified owners |
Topic map: [[security-ops]]
Decision feedback protocol (required):
- If post-run feedback capture is enabled for this runtime, emit a non-blocking
post_run_feedbackevent viarequest_user_inputafter result delivery. - Capture:
decision(accepted|partial|rejected|deferred),outcome(good|neutral|bad|unknown), andconfidence(high|medium|low). - Persist with:
python3 utilities/skill-builder/scripts/record_skill_feedback.py --skill-path <path/to/SKILL.md> --decision <...> --outcome <...> --confidence <...> --notes "...". - The recorder tags
subject(for exampleui,code_review,backend,security) for cross-domain quality analytics.
Gotchas
- None yet. Capture recurring failures here as symptom -> cause -> do instead -> check.
Required inputs
- Repository path or
owner/repotarget plus the branch/ref to analyze. - A defined scope for the run: full repo, selected paths, or sensitive-code slice.
- Whether to compare against
CODEOWNERS, emit graph artifacts, or export CSV/JSON only. - Any known sensitive directories, ownership expectations, or reporting audience constraints.
Failure mode
- If git history, target scope, or ownership signals cannot be read reliably, stop, report the exact blocker, and fall back to a smaller scoped ownership snapshot rather than asserting maintainers from incomplete evidence.
Converted and distributed by TomeVault — claim your Tome and manage your conversions.