Skip to content

M-26-14 Maturity

M-26-14 Maturity makes Logrite the tool that gets a federal agency to OMB M-26-14 logging maturity and proves it. Three roles:

  • Instrument — the analyzer generates the baseline a–e event logs (identity, network, object-access, privilege, infra) into the agency’s apps.
  • Operate — Logrite is the agency’s CEM/THIRF stack over its ingested logs (centralization, anomaly detection, alerting, AI — present today; plus retention tiering, Merkle integrity, and access governance — the Phase 2 backend is now shipped, see below).
  • Prove — an Appendix-C maturity dashboard + self-assessment export scoring the agency’s posture (lowest-watermark, % of the agency’s systems at each level).
PhaseScopeStatus
Phase 1Prove (scoring + dashboard) + Instrument (a–e code-gen)Shipped
Phase 2 (backend)Operate — @EnableScheduling, row-hash-chained governance audit, JIT access grants + scheduled expiry, two-gate retirement, scheduled Merkle integrity manifests + verification, retention ISM contracts; the scorer reads all of theseShipped + live
Phase 2 (infra)The privileged OpenSearch admin user + LOGRITE_PROVISIONER_ENABLED=true (flips OS writes from fail-soft to real), a snapshot repo, at-rest encryption, NTP attestationNeeds infra
Phase 3Active Collector-Agent coverage → the scorer + the CDM/HWAM/SWAM asset-feed connector (/api/v1/inventory/*) → Inventory Visibility, both shipped; only third-party CDM data-format adapters deferredShipped
Memo completenessThe compliance clock (L1/120d · L2/180d · L3/320d from the LRA), the Log Access (CISA/FBI) release, and HVA / High-Impact risk banding — three memo requirements the dashboard previously scored around rather than implementedShipped

MaturityScoringService computes each of the five Appendix-C elements to a level 0–4, and overall = MIN(element levels) — the lowest watermark. It records the capping element and the % of the agency’s systems at each level.

ElementSource
Inventory Visibilitylive — % of inventoried assets shipping logs from a CDM/HWAM/SWAM feed (/api/v1/inventory/*) + active Collector Agents; else attested
Collection Coveragelive — % of the agency’s systems bound to a log index / serviceName, plus active heartbeating Collector Agents
Collection Operationslive — % of systems with analyzer rules configured
Data Retentionlive — an active retention contract’s searchable/retrievable window (Phase 2); falls back to attested
Log Managementlive — a verified Merkle integrity manifest (→ L3) + an active JIT access grant (→ L4) (Phase 2); else attested

Each element also carries a measurementSources entry ("live" | "attested") recording whether its final level came from a live signal or a self-attestation. Attested values are stamped selfAttested:true and shown separately from live-measured signals — both on the dashboard (a per-element live/attested badge + a Honesty preserved panel) and in the export. As Phase 2 / Phase 3 signals come online for a tenant they replace the attestations automatically.

API (/api/v1/maturity, JWT-gated, tenant-scoped)

Section titled “API (/api/v1/maturity, JWT-gated, tenant-scoped)”
MethodPathPurpose
GET/assessmentLatest assessment: 5 element levels, overall = lowest, % per level, baseline a–k, attestations, measurementSources (live vs attested, per element)
POST/recomputeRecompute from live signals + attestations
PUT/attestationSubmit a self-attested element level (stored flagged selfAttested)
GET/export?format=json|pdfAgency Logging Plan self-assessment
GET/ntp-statusLive NTP sync status (chronyc/timedatectl, polled every 5 min by NtpAttestationService)
GET/milestonesThe M-26-14 compliance clock — L1/120d, L2/180d, L3/320d from the LRA publication date
GET/log-access-export?grantId=&from=&to=Log Access (CISA/FBI) release — grant-scoped, time-boxed NDJSON with a SHA-256 chain of custody

Live alert coverage. k_automated_alerts in baselineCoverage is now live-measured via AlertCoverageService — queries /_plugins/_alerting/monitors?size=100 and counts enabled monitors. coverage_ok=true when ≥ 2 monitors are enabled. The two agent monitors shipped in Sprint 3 (“Collector Agent - Compliance Flag Raised” and “Collector Agent - Ingest Silence”) are what drive this live signal.

M-26-14 is a selectable compliance framework via the existing POST /api/v1/monitoring/enable (no new endpoint) — MonitoringService resolves the m2614_compliance log type and auto-seeds per-project rows. The analyzer dispatcher emits the a–e event logs (delegating to the matching SOC2 generators) and runs them through the LogLeakDetector gate.

M-26-14 dates the mandate as well as defining it: Level 1 within 120 days of CISA publishing the Logging Reference Architecture, Level 2 within 180, Level 3 (Advanced) within 320. LraMilestoneService computes the three due dates and each milestone’s state — met / pending / at-risk / overdue — against the agency’s current overall level. A level the agency has already reached reports met permanently; it cannot go overdue.

The clock starts at a date CISA sets, which the application cannot discover, so it is configured:

KeyEnvDefaultMeaning
m2614.lra.published-dateM2614_LRA_PUBLISHED_DATE(blank)ISO-8601 LRA publication date
m2614.lra.at-risk-daysM2614_LRA_AT_RISK_DAYS30Warning band before an unmet deadline

Log Access (CISA/FBI) release (/log-access-export)

Section titled “Log Access (CISA/FBI) release (/log-access-export)”

The memo requires an agency to provide logs to CISA/FBI on request during a compromise. LogAccessExportService implements that as a scoped release, not a second way into the store:

  • It runs against an approved, unexpired AccessGrant (FR-15) and can only read that grant’s index pattern. A requested, expired or expiry-less grant is refused (403); another tenant’s grant is invisible (404).
  • The window is mandatory and capped (400 if missing, inverted, or over the maximum), as is the document count.
  • The payload’s first NDJSON line is a _custody record — grant, requester, approver, index pattern, window, document count, and the SHA-256 of the documents — and the same digest is written into the hash-chained governance audit as a cisa_export action. The recipient can recompute it; the agency can prove later exactly what it released.
  • A failed query raises rather than returning an empty “here are your logs” file, and nothing is recorded as released unless it was.
KeyEnvDefault
m2614.log-access.max-docsM2614_LOG_ACCESS_MAX_DOCS10000
m2614.log-access.max-window-daysM2614_LOG_ACCESS_MAX_WINDOW_DAYS31
m2614.log-access.time-fieldM2614_LOG_ACCESS_TIME_FIELD@timestamp

Risk banding — HVA / High-Impact prioritization

Section titled “Risk banding — HVA / High-Impact prioritization”

M-26-14 is explicitly risk-based: agencies are to prioritize High Value Assets and High Impact Systems. asset_inventory.criticality normalizes whatever the feed calls it (criticality, hva, isHva, fismaImpact, impactLevel) into hva / high_impact / standard, and /api/v1/inventory/summary returns byCriticality with per-band coverage.

An absent or unrecognized value leaves the existing band alone rather than defaulting to standard, so a feed refresh cannot quietly demote a known HVA.

Phase 2 Operate API (JWT-gated, tenant-scoped)

Section titled “Phase 2 Operate API (JWT-gated, tenant-scoped)”

These back the Operate signals the scorer reads. All OpenSearch-side writes are fail-soft until LOGRITE_PROVISIONER_ENABLED=true + a privileged admin user are configured, so they never break the request path.

MethodPathPurpose
GET/POST/api/v1/governance/audit /verifyRow-hash-chained governance audit trail + tamper verification
GET/POST/api/v1/access/grantsJust-in-time OpenSearch-role access grants; a @Scheduled sweep expires them
GET/POST/api/v1/retirement/requestsTwo-gate log/role retirement (requester ≠ gate-1 ≠ gate-2; single-approver → 400)
GET/POST/api/v1/integrity/manifests /verify /compute@Scheduled Merkle integrity manifests over ingested windows + re-verification
GET/POST/api/v1/retention/policy /statusRetention-tier (ISM) policy + contracts defining searchable/retrievable windows
POST/GET/api/v1/inventory/assets /summaryCDM/HWAM/SWAM asset-feed ingest (upsert by asset id, governance-audited) → Inventory Visibility

A nightly/periodic @Scheduled job computes new Merkle manifests and re-verifies recent ones; a tampered window flips verify_status:mismatch, which the scorer treats as Log Management L3 unmet. The Phase-2 dashboard cards are shipped — the /m26-14 page has an Operate & Inventory signals row driving Log Integrity (manifests + verify), Access Governance (JIT grants), Data Retention (ISM contract), and the CDM/HWAM/SWAM Inventory Feed coverage.

The /m26-14 page (sidebar, after AI Warden) is organized into three pill tabs (AI-Warden style): Maturity · Operate · Plan. The page header (title + Recompute + Export plan) stays above the tabs. (The earlier 6-stage lifecycle strip was replaced by the tabs.)

  • Maturity — the Enable M-26-14 as a framework card (shows enabled-status derived from baseline a–e coverage + a CTA into the Compliance flow with M-26-14 pre-selected); the lowest-watermark overall badge + L0→L4 stepper; five element level cards each with a live-measured / self-attested source badge (measurementSources) + %-of-systems and, for the GAP elements, an attest level picker; a Honesty preserved panel separating live-measured from self-attested elements (FR-20); the Instrument baseline a–k coverage as a read-only tri-state tile grid (✓ a–e code-gen · ◑ f–k SOC-side · ○ gap); and a sortable Appendix-C thresholds table.
  • Operate — the CEM/THIRF capability map (PRESENT vs BUILD, FR-7 … FR-16); the Operate & Inventory signals row (Log Integrity manifests + verify, Access Governance JIT grants, Data Retention ISM contract, CDM/HWAM/SWAM Inventory Feed coverage); and the OperationsPanel (JIT / two-gate / asset ingest / governance audit).
  • Plan — the Remediate capping-element runbook cards (the “go to controls” button switches to the Operate tab); an Export — Agency Logging Plan card (PDF/JSON, honesty preserved); and the collapsible FR-1…FR-25 traceability grid.

Timestamp Sync has three states, not two. source:"none" means the ingest host exposes neither chrony nor timedatectl, so sync could not be measured — which the card says explicitly, because it is not the same claim as “measured, and out of sync”.

The Log Access release form lists only grants that are approved and still inside their window (the backend refuses the rest, so offering them would only produce a 403 the user cannot act on), takes a window, downloads the NDJSON, and shows the SHA-256 chain-of-custody digest back on screen.

Attest level — downgrades are allowed. The GAP-element level picker now offers all levels L0–L4 (the previous “attest upward only / lower levels disabled” restriction was removed). Selecting a level below the current one is permitted but pops a confirmation modal warning that, since overall is the lowest watermark, the downgrade may reduce the overall grade. Raising a level applies immediately. Both still PUT /api/v1/maturity/attestation.

Export — what the Agency Logging Plan carries

Section titled “Export — what the Agency Logging Plan carries”

GET /export (JSON and PDF) now carries the surrounding controls, not just the score: the deadlines, an operating posture block (NTP attestation, retention windows, integrity manifest counts, access-grant governance + the audit-chain verification, inventory risk bands), and the cross-cutting LRA topics (Zero Trust · AI · IoT/OT · HVA). Every part of the posture block is best-effort — a control that cannot be read reports its error rather than dropping out of the filed document silently.

The PDF renderer wraps and paginates. It previously emitted a single page and stepped the text cursor per line regardless of where it landed, so content past ~46 lines was drawn below the page and past ~92 characters off the right edge — invisible in the byte stream, visible only in the document an agency files.

POST /api/v1/governance/verify failed from the first row in every workspace measured on dev (“0 of 580 verified”). Not tampering: rows were hashed with a nanosecond timestamp and stored in timestamp(6), which rounds to microseconds, so the verifier rehashed a different value. A null actor (hashed as "", stored as “system”) and nested details (jsonb reorders keys) had the same shape.

  • New rows are hashed canonically (v2|…): UTC instant truncated to microseconds, the actor as stored, details sorted at every depth.
  • Existing rows are verified by recovering the rounded-away precision: the original instant is one of the 1,000 values within ±500 ns of the stored microsecond, and only an exact hash match counts (legacyRecovered). A legacy row with nested details cannot be re-derived and is reported as unverifiableLegacy (with ids) — never counted as verified.
  • Forks — two rows chained to the same head, found in both workspaces — came from unserialised appends. Appends are now serialised per workspace with pg_advisory_xact_lock (PostgreSQL only; the integration tests run on SQLite, which has no such function). Existing forks are accepted only when the row’s own hash verifies, and listed (forks, forkIds); the row a fork branched past has no successor committing to it, which is the one gap and is stated.
  • Live on dev (backend 533199a4): workspace 3103 VERIFIED 600 of 600; 4102 265 of 266 (1 unverifiable legacy row); a row written afterwards verifies under the new form.