OSPulse

Alerts & severity

An alert is OSPulse deciding that something in your dependency estate has changed enough to be worth a human's attention. Everything interesting about alerting is in that decision — what counts as a change, how badly it matters, and how you stop the volume from drowning the signal.

An alert is a state change, not a fact

OSPulse continuously holds a picture of every package you depend on: its health score, its maintainers, its release cadence, its known vulnerabilities, its licence. That picture is a fact base. Facts are not alerts. If a package has scored 62 for eight months and still scores 62 today, there is nothing to say.

An alert is raised when a fact changes in a direction that matters to you, or when the world changes underneath a fact you already had. The distinction is the whole design:

  • A dashboard answers "what is true right now?"
  • An alert answers "what became true since you last looked, and should you act?"

This is why alerts are not simply a filtered view of your vulnerability list. A five-year-old CVE in a package you have shipped for five years is a fact. That same CVE appearing on a package you imported yesterday, or newly gaining a public exploit, is an event.

Where alerts come from

Alerts are generated from several independent pipelines. Understanding which one produced an alert tells you a great deal about how to respond.

New vulnerability affecting a package you usesourceOptional

An advisory lands in an upstream feed and correlates to a package version present in one of your repositories. This is the classic case, and the one most likely to be urgent — the vulnerability is new to the world, not merely new to you.

Compromise signalsourceOptional

Behavioural indicators on the package or its publishing account: a release published outside the normal cadence, a maintainer account change followed immediately by a release, install-time scripts appearing where there were none, a namespace takeover pattern. These carry the highest urgency because the window between publication and detection is where supply-chain attacks do their damage. See Threat intelligence.

Health-score degradationsourceOptional

A package's health score drops materially, or a component of it does — the last maintainer goes quiet, the release cadence stalls, the bus factor falls to one. Rarely urgent, frequently important. These are the alerts that prevent next year's incident rather than today's.

Policy violationsourceOptional

A dependency crosses a line you drew yourself: a banned licence, a health score below your floor, an ecosystem you do not permit. See Policies & gates. Unlike the other sources, the threshold here is entirely yours, so the alert is definitionally actionable — you already decided it mattered.

Drift threshold breachsourceOptional

Cumulative dependency drift on a repository passes a level you consider unacceptable — too many majors behind, too much of the tree unmaintained. Drift alerts are about accumulating cost of upgrade, not about immediate risk.

Severity, and why it is not urgency

Every alert carries a severity. Severity is a property of the finding — how bad the underlying issue is in the abstract. For vulnerability-derived alerts it follows the usual CVSS-style bands inherited from the upstream advisory; for policy-derived alerts it comes from the severity you assigned to the rule.

Severity is deliberately context-free. A critical remote-code-execution advisory is critical whether it lands in your payment gateway or in a build-time formatter you invoke twice a year. That context-freedom is a feature: it means severity is stable, comparable across tenants, and not silently rewritten by your own configuration.

Urgency is what you actually need, and urgency is severity multiplied by exposure:

text
urgency ≈ severity × reachability × blast radius × exploit maturity

None of those multipliers live in the advisory. They live in your estate. A critical finding on a transitive dev-dependency that never reaches a production artefact can reasonably wait for the next scheduled upgrade. A medium finding on a directly imported HTTP client that sits on every request path may not.

Sorting your queue by severity alone is a known failure mode

Teams that work strictly top-down by severity spend their first hours on the loudest finding rather than the most exposed one. Use severity to group, and exposure to order within the group.

Exposure: the object that carries context

OSPulse models the intersection of "this vulnerability" and "your code" as a distinct first-class object: a vulnerability exposure. A vulnerability is global and shared. An exposure is yours — it knows which repository, which package version, and which path through your dependency graph brought it in.

bash
curl "https://api.ospulse.app/api/v1/vulnerability-exposures?take=20" \
  -H "Authorization: Bearer $OSPULSE_TOKEN"
json
{
  "items": [
    {
      "id": "3f1c9c8e-2d0a-4a1e-9a0b-7c5f0f0c2b11",
      "vulnerabilityId": "b6e2f4a0-11c9-4d2e-8b3a-5a1d9e4c7f22",
      "severity": "High",
      "status": "Open"
    }
  ],
  "totalEstimate": 47,
  "take": 20,
  "skip": 0
}

Because the exposure is the object that carries context, it is also the object you triage. Triage state belongs to the exposure, never to the vulnerability — you are not asserting that CVE-XXXX-NNNNN is harmless in general, only that this instance in this repository has been assessed.

bash
curl -X PATCH https://api.ospulse.app/api/v1/vulnerability-exposures/{exposureId} \
  -H "Authorization: Bearer $OSPULSE_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{ "status": "Mitigated", "note": "Not reachable — build-time only" }'

Fetch a single exposure with GET /api/v1/vulnerability-exposures/{exposureId} to see the full picture, including the underlying vulnerability record from GET /api/v1/vulnerabilities/{vulnerabilityId}.

Two different verbs, two different meanings

Patching an exposure records a security judgement about a finding. Patching an alert records a workflow judgement about a notification. They are separate on purpose: closing your inbox is not the same as fixing the problem.

The alert lifecycle

Alerts move through three logical states.

Raised

A pipeline produced a finding that matched an alert rule. The alert is delivered to whatever destinations that rule targets and appears in GET /api/v1/alerts.

json
{
  "items": [
    {
      "id": "d2a6b1f4-8e35-4a77-bb90-1f0c33aa4e5d",
      "title": "New high-severity advisory affecting lodash 4.17.20",
      "severity": "High",
      "status": "Open",
      "raisedAtUtc": "2026-07-21T09:14:22Z"
    }
  ],
  "totalEstimate": 12,
  "take": 20,
  "skip": 0
}

Triaged

Someone has looked at it and taken ownership. Acknowledge through the alerts endpoint — this stops re-notification without hiding the alert or implying the underlying issue is resolved.

bash
curl -X PATCH https://api.ospulse.app/api/v1/alerts/{alertId} \
  -H "Authorization: Bearer $OSPULSE_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{ "acknowledged": true }'

Acknowledgement is the single most useful signal in the system. An alert that nobody has acknowledged after a week is telling you something about your rules, not about your dependencies.

Resolved or suppressed

Two very different endings.

Resolved means the condition is no longer true — the package was upgraded, the advisory was withdrawn, the offending dependency was removed. Resolution normally happens by itself: the next scan finds the condition gone and the alert closes.

Suppressed means the condition is still true and you have decided to accept it. For policy-derived alerts, express this as a time-boxed policy exception rather than a permanent mute. For vulnerability-derived alerts, transition the underlying exposure so the reasoning is recorded against the finding.

Alert rules: deciding what deserves an interruption

Alert generation is fixed; alert delivery is yours. Alert rules bind a class of finding to a destination and a threshold.

bash
curl https://api.ospulse.app/api/v1/alert-rules \
  -H "Authorization: Bearer $OSPULSE_TOKEN"

Create, update and delete them through POST /api/v1/alert-rules, PATCH /api/v1/alert-rules/{alertRuleId} and DELETE /api/v1/alert-rules/{alertRuleId}. Rules typically narrow on three axes:

  • What — which alert sources and which severity floor.
  • Where — which repositories, teams or business-criticality bands. A rule scoped to repositories marked business-critical is worth ten rules scoped to everything.
  • How — the destination. Alerts are delivered through configured integrations: Slack, Microsoft Teams, email, or an outbound webhook to your own system.

Alert fatigue is the real failure mode

A dependency-intelligence system that misses a compromise has failed once. A system that emits four hundred alerts a week has failed permanently, because within a month nobody reads any of them and the compromise gets missed anyway.

Design your rules around that asymmetry.

Practical noise control

  • Route by criticality, not by severity. Send everything about your top ten repositories somewhere people watch. Send everything else to a digest.
  • Give every rule an owner. A rule delivering to a channel where nobody is accountable is a rule generating noise by construction.
  • Separate "wake someone" from "put on the backlog". Compromise signals and new critical advisories on production paths belong in the first bucket. Health degradation and drift belong in the second, reviewed on a cadence.
  • Treat unacknowledged volume as a defect. If a rule's alerts are routinely ignored, the rule is wrong. Tighten its scope or lower its priority — do not leave it running.
  • Prefer exceptions with expiry over permanent suppression. A muted rule is invisible; an expiring exception comes back and asks again.

The goal is not zero alerts. It is a volume where every alert still gets read.

What alerts deliberately do not do

  • They do not change your code. Remediation guidance is available, but OSPulse does not open pull requests on your behalf without you wiring that up.
  • They do not block anything. Blocking is the job of policy gates in CI, where a decision can actually be enforced.
  • They do not re-derive severity from your context. Exposure informs your ordering; it does not silently rewrite the advisory.

Next steps