exposure-trending · git:20260504.2a64374 · 2026-05-04 · sha256 e60109fc48c24106

exposure-trending git:20260504.2a64374A

Immutable. This exact content is served forever at /api/v1/blob/e60109fc48c24106.

---
name: exposure-trending
description: Compare BHE posture between two points in time — what got better, what got worse, and which specific findings drove the change. Use when the caller asks "how have we improved", "what changed since last week", "produce a trend report", or at the close of a remediation cycle.
---

# Exposure trending

The single most stakeholder-relevant artefact a BHE deployment produces is a delta: the exposure index dropped from 72 to 54 over the last two weeks, driven by remediation of 8 ESC1 findings and the certification of 12 new Tier Zero members. This skill produces that delta.

## Preconditions

`bhe-bootstrap` has run.

## Workflow

### 1. Pin the time window

The caller usually supplies two dates. Common shapes:

- "since last review" → a 30-day window.
- "this sprint" → 14 days.
- "this incident" → ad-hoc range tied to known events.

If the caller doesn't specify, default to a 30-day rolling window ending today. Format both dates as RFC3339 (`YYYY-MM-DDTHH:MM:SSZ`).

### 2. Pull the per-domain posture history

For each domain SID, call `posture_history(domain_sid, from_date, to_date)`. The response is a time series of (captured_at, exposure_index, tier_zero_count, critical_count). Compute the delta — first vs last datapoint — and the trajectory (monotonically improving, oscillating, regressing).

### 3. Pull the cross-domain finding trends

Call `attack_path_trends(from_date, to_date)`. The response shows per-(environment, finding) deltas: how many of each finding type were active at the start of the window vs. now. The big movers are usually a handful of categories — surface them.

### 4. Drill into the drivers

For the top 3–5 categories with significant negative deltas (more findings now than at start), inspect what drove the increase:

- Call `domain_attack_path_details(domain_sid, finding=<category>)` and look at `accepted_until` — were findings accepted but the acceptance expired?
- Call `tag_history(tag_id=<tier_zero>)` and look for additions in the window — new Tier Zero members typically multiply findings against everything that can reach them.
- Call `audit_logs(action="...")` for the window — were ingest jobs failing, leaving the graph stale?

For categories with significant positive deltas (fewer findings), confirm the cause via the same audit signals — risk acceptance versus actual remediation.

### 5. Build the narrative

Assemble:

- **Headline numbers**: exposure index Δ, tier-zero count Δ, critical-risk count Δ.
- **Top movers**: per-finding-category deltas, ordered by absolute change.
- **Drivers**: 1–2 sentences per top mover explaining why it changed.
- **Outliers**: domains where the trend diverged from the deployment-wide trajectory (one domain getting worse while others improve is signal).
- **Open recommendations**: findings still active that have been accepted with imminently-expiring acceptances.

### 6. Output

```
{
  "window": { "from": "...", "to": "..." },
  "headline": {
    "exposure_index": { "before": 72, "after": 54, "delta": -18 },
    "tier_zero": { "before": 23, "after": 27, "delta": +4 },
    "critical_risk": { "before": 11, "after": 4, "delta": -7 }
  },
  "domains": [
    { "domain_sid": "...", "exposure_delta": -22, "trajectory": "monotone" },
    ...
  ],
  "findings": {
    "improved": [
      { "finding": "ESC1", "before": 8, "after": 0, "driver": "remediation" },
      ...
    ],
    "regressed": [
      { "finding": "Kerberoastable", "before": 3, "after": 6, "driver": "Tier Zero growth" },
      ...
    ]
  },
  "open": [
    { "finding": "DCSync", "principal": "...", "accepted_until": "...", "concern": "expires in 5 days" }
  ]
}
```

Keep the narrative tight — stakeholder reports get worse with length, not better.

## Cost budget

- One `posture_history` per domain.
- One `attack_path_trends` per session.
- ≤5 `domain_attack_path_details` for top movers.
- One `tag_history` for Tier Zero (drift attribution).
- One `audit_logs` query for the window if drift attribution doesn't explain a regression.

## What NOT to do

- Don't extrapolate the trajectory into the future. Two datapoints don't make a forecast; communicate the observed change, not projections.
- Don't conflate "fewer findings" with "better security". Findings can drop because risk was accepted, not because the underlying issue was fixed — the audit-log driver attribution is what distinguishes the two.
- Don't rerun the analysis pass during the report cycle (`start_attack_path_analysis`). Trend stability requires the comparison to be against analysis cycles the caller already trusted.