philipp.baumann/dse-security-reports
Watch
0
0
Fork
You've already forked dse-security-reports
0

feat(pipeline): close the three named triage gaps (nginx upstream, MDE presence, kernel inventory) #15

Merged
Philipp Baumann merged 2 commits from fm/dse-security-reports-pipeline-gaps into main 2026-10-27 07:28:02 +00:00

Summary

Closes the three named pipeline gaps from the captain instruction
(2026-10-27): (a) nginx upstream tracking, (b) MDE presence assertion,
(c) running-vs-stale kernel classification. All three are implemented
end to end in both derivation lanes (stdlib scripts/derive_findings.py
and polars src/dse_security_reports/derive.py) so just py-crosscheck
stays byte-identical; nothing real is committed (all caches stay under
gitignored .local/).

What changed

(a) nginx upstream tracking -- complete

  • New one-shot cache recipe just nginx-upstream
    (scripts/fetch_nginx_upstream.sh): fetches the nginx.org security
    advisories page once into .local/nginx-cache/ (never committed,
    snapshot-dated) and parses it into per-CVE numeric fixed floors and
    vulnerable ranges. The parse lives in the fetch script, so both
    derivation lanes read one canonical cache; an advisory that cannot be
    reduced to numeric ranges (e.g. "Vulnerable: all") is recorded
    unparsed: true and never backs a clear verdict (fail-fast: zero
    parsed advisories aborts the fetch).
  • Derivation: every Defender row and every trivy-only detection whose
    token resolves to an installed .ngx build carries
    upstreamOrigin / upstreamVersion / upstreamVerdict. The verdict
    compares the evidence-installed upstream version against the cached
    advisory: upstream-clear only when an upstream advisory names the
    version as not vulnerable; upstream-affected, upstream-uncovered,
    no-upstream-advisory (distro-stream advisory that does not apply to
    the .ngx build) and upstream-no-cache (honest gap, never assumed
    clear) name the other outcomes. A payload-level upstream aggregate
    (snapshot, advisory count, per-build verdict distribution, affected
    count) feeds the rendered P17 statement.
  • Render: the nginx ranking row (P17) carries the data-derived upstream
    statement; the trivy-only table gains a "davon Upstream (.ngx)" count
    column.

(b) MDE presence assertion -- complete

  • scripts/ssh_evidence.sh runs a bounded, read-only Defender-sensor
    probe (rpm -q mdatp + systemctl is-active mdatp) per unit and
    records it in the evidence cache. A cached evidence file that
    predates the probe gets a bounded mdatp-only refresh merged in; a
    failed probe leaves the file untouched (never an error read as
    clean).
  • Derivation: per-unit defender block and defenderCoverage:
    asserted (package installed and service active), missing (probe
    ran, sensor absent or inactive), unknown (probe could not run) --
    never assumed green.
  • Render: new MDE column in the Evidenzabdeckung table.

(c) old-kernel inventory / running-vs-stale classification -- complete

  • Kernel posture gains installedCount and staleInstalled (distinct
    installed kernel EVRs vs non-running retains, kernel and kernel-core
    of one build counting once); rendered as the new Kernel-Bestand
    column (running release tag + N installiert + M Altbestand).
  • Every kernel-token finding gains a data-driven kernelStance:
    running-affected (claimed version names the running kernel),
    stale-only (a retained non-running kernel -- the installonly_limit
    noise), claim-not-installed (matches no installed kernel), or null
    when no kernel evidence exists (render states the gap). No hand-set
    text anywhere.

Behaviour change

  • just nginx-upstream is a new manual cache recipe; renders stay
    offline and byte-identical and read the cache only. Without the
    cache, upstream verdicts are the honest upstream-no-cache gap.
  • just evidence now always probes mdatp; the fixed command sequence
    gained the MDE section (a fresh full collection requires it; existing
    caches are refreshed bounded).
  • Derived JSON gains finding fields kernelStance, upstreamOrigin,
    upstreamVersion, upstreamVerdict; unit fields defender,
    kernel installedCount/staleInstalled; detection fields
    upstreamOrigin/upstreamVersion/upstreamVerdict; payload field
    upstream. Both lanes emit identical bytes (just py-crosscheck,
    just py-repro green).

Proof (real caches, as prior tasks)

  • Real-cache run: 2026-10-26 Defender exports + evidence + trivy scans
    copied into .local/, nginx.org snapshot fetched 2026-10-27 (67
    advisories), live mdatp refresh over SSH. Results:
    • (a) nginx 1.31.8 on 7 units: 66 of 69 trivy comparisons
      upstream-clear per nginx.org, 3 without an upstream advisory, 0
      affected -- the distro fix versions trivy names never apply to the
      .ngx build.
    • (b) mdatp asserted on 6 units; unknown on the 2 workbenches
      (deallocated during the probe window, ~22:01-05:05; rerun just evidence in their running window to assert) -- honestly not green.
    • (c) every unit carries 3 installed kernels with 2 stale
      non-running retains; running kernel is the fixed newest one on all
      units, matching the ranking rows' data-derived statements.
  • Two consecutive just triage and just report renders from the
    same caches: byte-identical (cmp clean).
  • just test (27 passed), just lint, just fmt, just py-crosscheck, just py-repro, just repro all green.

Files changed

  • scripts/fetch_nginx_upstream.sh (new): one-shot nginx.org advisory
    cache fetch + parser.
  • scripts/ssh_evidence.sh: MDE probe in the fixed sequence; bounded
    mdatp refresh for caches predating the probe.
  • scripts/derive_findings.py: stance/coverage/upstream derivation,
    extended aggregates, new payload upstream.
  • src/dse_security_reports/derive.py: identical mirror of all of it.
  • reports/defender-triage.typ: MDE + Kernel-Bestand columns,
    P17 upstream statement, trivy-only upstream count (no prose
    rewritten).
  • tests/test_derive.py: outcome tests for all new logic + joined
    cache-dataset assertions.
  • Justfile: nginx-upstream recipe; evidence comment updated.
  • AGENTS.md: project-memory notes updated.
## Summary Closes the three named pipeline gaps from the captain instruction (2026-10-27): (a) nginx upstream tracking, (b) MDE presence assertion, (c) running-vs-stale kernel classification. All three are implemented end to end in both derivation lanes (stdlib scripts/derive_findings.py and polars src/dse_security_reports/derive.py) so `just py-crosscheck` stays byte-identical; nothing real is committed (all caches stay under gitignored `.local/`). ## What changed ### (a) nginx upstream tracking -- complete - New one-shot cache recipe `just nginx-upstream` (`scripts/fetch_nginx_upstream.sh`): fetches the nginx.org security advisories page once into `.local/nginx-cache/` (never committed, snapshot-dated) and parses it into per-CVE numeric fixed floors and vulnerable ranges. The parse lives in the fetch script, so both derivation lanes read one canonical cache; an advisory that cannot be reduced to numeric ranges (e.g. "Vulnerable: all") is recorded `unparsed: true` and never backs a clear verdict (fail-fast: zero parsed advisories aborts the fetch). - Derivation: every Defender row and every trivy-only detection whose token resolves to an installed `.ngx` build carries `upstreamOrigin` / `upstreamVersion` / `upstreamVerdict`. The verdict compares the evidence-installed upstream version against the cached advisory: `upstream-clear` only when an upstream advisory names the version as not vulnerable; `upstream-affected`, `upstream-uncovered`, `no-upstream-advisory` (distro-stream advisory that does not apply to the .ngx build) and `upstream-no-cache` (honest gap, never assumed clear) name the other outcomes. A payload-level `upstream` aggregate (snapshot, advisory count, per-build verdict distribution, affected count) feeds the rendered P17 statement. - Render: the nginx ranking row (P17) carries the data-derived upstream statement; the trivy-only table gains a "davon Upstream (.ngx)" count column. ### (b) MDE presence assertion -- complete - `scripts/ssh_evidence.sh` runs a bounded, read-only Defender-sensor probe (`rpm -q mdatp` + `systemctl is-active mdatp`) per unit and records it in the evidence cache. A cached evidence file that predates the probe gets a bounded mdatp-only refresh merged in; a failed probe leaves the file untouched (never an error read as clean). - Derivation: per-unit `defender` block and `defenderCoverage`: `asserted` (package installed and service active), `missing` (probe ran, sensor absent or inactive), `unknown` (probe could not run) -- never assumed green. - Render: new MDE column in the Evidenzabdeckung table. ### (c) old-kernel inventory / running-vs-stale classification -- complete - Kernel posture gains `installedCount` and `staleInstalled` (distinct installed kernel EVRs vs non-running retains, kernel and kernel-core of one build counting once); rendered as the new Kernel-Bestand column (running release tag + N installiert + M Altbestand). - Every kernel-token finding gains a data-driven `kernelStance`: `running-affected` (claimed version names the running kernel), `stale-only` (a retained non-running kernel -- the installonly_limit noise), `claim-not-installed` (matches no installed kernel), or null when no kernel evidence exists (render states the gap). No hand-set text anywhere. ## Behaviour change - `just nginx-upstream` is a new manual cache recipe; renders stay offline and byte-identical and read the cache only. Without the cache, upstream verdicts are the honest `upstream-no-cache` gap. - `just evidence` now always probes mdatp; the fixed command sequence gained the MDE section (a fresh full collection requires it; existing caches are refreshed bounded). - Derived JSON gains finding fields `kernelStance`, `upstreamOrigin`, `upstreamVersion`, `upstreamVerdict`; unit fields `defender`, kernel `installedCount`/`staleInstalled`; detection fields `upstreamOrigin`/`upstreamVersion`/`upstreamVerdict`; payload field `upstream`. Both lanes emit identical bytes (`just py-crosscheck`, `just py-repro` green). ## Proof (real caches, as prior tasks) - Real-cache run: 2026-10-26 Defender exports + evidence + trivy scans copied into `.local/`, nginx.org snapshot fetched 2026-10-27 (67 advisories), live mdatp refresh over SSH. Results: - (a) nginx 1.31.8 on 7 units: 66 of 69 trivy comparisons upstream-clear per nginx.org, 3 without an upstream advisory, 0 affected -- the distro fix versions trivy names never apply to the .ngx build. - (b) mdatp asserted on 6 units; unknown on the 2 workbenches (deallocated during the probe window, ~22:01-05:05; rerun `just evidence` in their running window to assert) -- honestly not green. - (c) every unit carries 3 installed kernels with 2 stale non-running retains; running kernel is the fixed newest one on all units, matching the ranking rows' data-derived statements. - Two consecutive `just triage` and `just report` renders from the same caches: byte-identical (`cmp` clean). - `just test` (27 passed), `just lint`, `just fmt`, `just py-crosscheck`, `just py-repro`, `just repro` all green. ## Files changed - `scripts/fetch_nginx_upstream.sh` (new): one-shot nginx.org advisory cache fetch + parser. - `scripts/ssh_evidence.sh`: MDE probe in the fixed sequence; bounded mdatp refresh for caches predating the probe. - `scripts/derive_findings.py`: stance/coverage/upstream derivation, extended aggregates, new payload `upstream`. - `src/dse_security_reports/derive.py`: identical mirror of all of it. - `reports/defender-triage.typ`: MDE + Kernel-Bestand columns, P17 upstream statement, trivy-only upstream count (no prose rewritten). - `tests/test_derive.py`: outcome tests for all new logic + joined cache-dataset assertions. - `Justfile`: `nginx-upstream` recipe; evidence comment updated. - `AGENTS.md`: project-memory notes updated.
- Track the nginx upstream delivery model end to end: a new one-shot
  cache recipe (`just nginx-upstream`, scripts/fetch_nginx_upstream.sh)
  fetches and parses the nginx.org security advisories into numeric
  fixed floors and vulnerable ranges under .local/nginx-cache/ (never
  committed, snapshot-dated, parse lives in the fetch so both
  derivation lanes read one canonical cache); the derive marks every
  Defender row and trivy-only detection whose token resolves to an
  installed .ngx build with upstreamOrigin/upstreamVersion/
  upstreamVerdict -- the distro advisory does not apply to the upstream
  build, and a version is only ever claimed clear when an upstream
  advisory names it as not vulnerable (upstream-clear); upstream-
  affected / upstream-uncovered / no-upstream-advisory / upstream-no-
  cache name the other outcomes, and a payload-level upstream aggregate
  feeds the rendered P17 statement.
- Assert MDE presence from a bounded evidence probe (rpm -q mdatp plus
  systemctl is-active mdatp) recorded per unit as defenderCoverage
  asserted/missing/unknown -- unknown whenever the probe could not run
  (cache predates it, machine unreachable, probe error), never assumed
  green; cached evidence files predating the probe get a bounded
  mdatp-only refresh merged in.
- Separate the stale old-kernel CVE noise from real findings: the
  kernel posture gains installedCount/staleInstalled (distinct installed
  EVRs vs non-running retains) and every kernel-token finding gains a
  data-driven kernelStance classification (running-affected / stale-
  only / claim-not-installed, None when the evidence is missing).
- Render the new fields: the Evidenzabdeckung table carries MDE and
  Kernel-Bestand columns, the trivy-only table a davon-Upstream count,
  and the nginx ranking row the upstream verdict statement.
- Mirror every addition in the polars lane (src/dse_security_reports/
  derive.py) so `just py-crosscheck` stays byte-identical; tests cover
  the stance, coverage, version-key and verdict outcomes plus all new
  joins on a synthetic cache dataset.

Proof on the real caches (2026-10-26 exports, evidence + trivy scans,
live nginx.org snapshot 2026-10-27, live mdatp refresh): nginx 1.31.8
on 7 units -- 66 of 69 trivy comparisons upstream-clear per nginx.org,
3 without an upstream advisory, 0 affected; mdatp asserted on 6 units,
unknown on the 2 workbenches (deallocated during the probe window);
every unit 3 installed kernels, 2 stale retains, running kernel fixed.
Two consecutive triage and findings renders are byte-identical;
`just test/lint/fmt/py-crosscheck/repro` green.
Resolve the AGENTS.md conflict by keeping PR 14's structure (triage-report
bullet, new captain's-record-artifacts bullet) and folding the PR 15
pipeline facts into it: the nginx-upstream cache recipe with the
upstreamOrigin/upstreamVerdict fields, the mdatp/defenderCoverage probe,
installedCount/staleInstalled/kernelStance, the MDE/Kernel-Bestand render
columns and the P17 upstream statement, plus the byte-identical both-lanes
note.
Philipp Baumann deleted branch fm/dse-security-reports-pipeline-gaps 2026-10-27 07:28:02 +00:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
philipp.baumann/dse-security-reports!15
No description provided.