feat(security): wire Dependabot alert posture into portfolio risk model#27
Conversation
Schema 0.4.0 -> 0.5.0: new SecurityFields (Dependabot / code-scanning / secret-scanning counts) on every PortfolioTruthProject. New risk factor active-high-severity-alerts — open high alerts add one factor toward the 3+ elevation threshold; an open critical alert force-elevates on its own, so a lone unpatched critical CVE cannot hide in a clean repo. Opt-in via --portfolio-truth-include-security, overlaying the latest output/ghas-alerts-<user>-*.json (mirrors the release_count overlay; the truth pipeline itself stays network-free / offline-testable). Weekly digest gains a ## Security Posture section distinguishing scanned-clean from unscanned repos. Fully inert unless fed: defaults keep the factor dormant and all existing risk tiers unchanged. 27 new tests across risk math, GHAS mapping, opt-in, force-elevate, deferred short-circuit, digest states, and the CLI loader. 2140 pass.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 4ec9f4fad6
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| "--portfolio-truth-include-security", | ||
| action="store_true", |
There was a problem hiding this comment.
Expose the security overlay flag on the report subcommand
This only registers --portfolio-truth-include-security on the legacy top-level parser, but the documented/normal portfolio truth flow is audit report <user> --portfolio-truth and _build_report_subparser() only adds --portfolio-truth there. In that subcommand context, audit report user --portfolio-truth --portfolio-truth-include-security is rejected by argparse before _run_portfolio_truth_mode() can load the GHAS overlay, so users following the current CLI shape cannot enable the new security risk factor.
Useful? React with 👍 / 👎.
The security overlay is keyed by GitHub repo name, but local dir display
names often differ ("Signal & Noise" vs "signal-noise"), so 40 repos with
open alerts were silently missed. Extract `_select_security_entry`: match
on the repo name from repo_full_name first, fall back to display_name.
Live impact: overlay match rate 113 -> 153 of 161 local projects; e.g.
Signal & Noise (9 high), Devil's Advocate (6), Interruption Resume Studio
(3) now correctly join. 4 join-precedence tests added.
…es (#28) * feat(security): surface Dependabot posture in portfolio render surfaces The radar's truth-layer security dimension (RiskFields.security_risk, SecurityFields Dependabot counts, the active-high-severity-alerts factor) was wired into the risk model and weekly digest in #27, but the two human-facing render surfaces — PORTFOLIO-AUDIT-REPORT.md and project-registry.md — did not surface it. This adds that, mirroring the digest's Security Posture treatment: - Portfolio report: a Coverage Summary line + a dedicated '## Security Posture' section (TOC entry included) with the same three states as the digest — per-repo open high/critical (critical-first, capped at 5), 'all N scanned clear', or 'overlay not run'. - Registry: a pipe-free per-repo security flag in the Notes column (fires only for scanned repos with open high/critical) plus four aggregate rows in the Portfolio Summary table. Shared _security_overview / _security_attention_items helpers mirror the digest's aggregation on the in-memory snapshot. The Notes flag is pipe-free and the summary rows are digit-valued, so the registry still round-trips through parse_registry unchanged; both markdown validators stay green. 5 new tests cover all three report states, the registry flag + round-trip, and the unscanned case. * test(security): guard Security Posture section + cover cap/sort and registry clean path Addresses code-review findings on the render surfaces: - validate_portfolio_report_markdown now requires the '## Security Posture' header, so the section can't silently vanish in a future refactor (every other section header is already guarded). - New unit test pins _security_attention_items' cap-at-5 and critical-desc / high-desc / name-asc sort — the one behavior unique to the attention list. - Extends the scanned-clear test to assert the registry's per-repo flag is absent for a medium-only repo while it still counts as scanned.
The radar — a security dimension for the portfolio OS
The 6-factor risk model was 100% context/path/docs — zero security signal. A portfolio-wide Dependabot sweep (61 PRs, ~290 alerts) ran with no instrument tracking it. This wires GHAS Dependabot posture into the truth layer.
What changed
0.4.0 → 0.5.0— new frozenSecurityFieldson everyPortfolioTruthProject(Dependabot critical/high/medium/low, code-scanning, secret-scanning,alerts_available).alerts_availabledistinguishes scanned-clean from never-looked.active-high-severity-alerts— open high alerts add one factor toward the existing 3+ elevation bar; an open critical alert force-elevates on its own (mirrors the existing weak-context+investigate short-circuit precedent). A lone unpatched critical CVE can't hide in a clean repo.--portfolio-truth-include-securityreads the latestoutput/ghas-alerts-<user>-*.json(already name-keyed). Mirrors therelease_count_by_namepattern: the truth pipeline itself stays network-free / offline-testable; the CLI precomputes and injects.## Security Posturesection, critical-first, distinguishing scanned-clean / unscanned / open-alert states.Safety
Tests
27 new tests: risk-tier math + force-elevate + active-gate + deferred short-circuit, GHAS→
SecurityFieldsmapping + opt-in overlay end-to-end, digest states, CLI loader. 2140 pass, ruff clean. Smoke-tested against the real 126-repo GHAS file.Reviewed via
python-reviewer— all four invariants (opt-in inertness, active-gate,alerts_availablesemantics, critical force-elevate) confirmed; no CRITICAL/HIGH findings.