Skip to content

NixOS Discourse security advisories ​

Posts from the NixOS Discourse Security category, newest first, read independent of whether a CVE exists yet. This is a dedicated, low-volume category the NixOS security team created specifically to carry these advisories, and createdAt on each entry is the post's own disclosure time, not a CVE record's.

GET /api/v1/nix/discourse-security

The same list is on cve.vulnpatch.dev/signal/nix.

Why this exists ​

The Ahead panel maps a CVE to nixpkgs before the nixpkgs security issue is filed, but it only ever starts from a CVE record, so it cannot surface a disclosure earlier than the CVE itself exists. The NixOS security team can post an advisory to this Discourse category hours or days before MITRE publishes the matching CVE record. Reading this category independent of any CVE closes that gap.

How it is built ​

Every five minutes, beside the delta poll, one small request reads the category's topic list. A topic id not already stored is fetched for its full first post (cooked HTML), up to five new topics a run -- generous headroom for a category that has historically posted only a handful of times a year, and a bound against a malformed or hostile response from discourse.nixos.org turning one poll into dozens of fetches. A failed upstream fetch is reported in the scheduler's own task result, never thrown through the rest of the delta poll.

A topic Discourse itself creates in every category ("About the Security category") is filtered out by title before it is ever considered new; it is not an advisory.

For each new post:

  • products is a first-pass keyword match against known project names (Nix, Lix) in the title and post body. This is deliberately simple -- a first pass, not the corroborated package identity the Ahead panel establishes for nixpkgs attributes -- because the category's posts are prose, not a structured affected-package list. NixOS is deliberately not matched: the category lives on discourse.nixos.org and nearly every post says "NixOS" somewhere regardless of subject, so it carries no signal.
  • relatedCveId / relatedCveIds are set only when a real CVE id (CVE-\d{4}-\d{4,7}, bounded so it cannot match inside a longer token or a longer digit run) is found directly in the post text. A placeholder like CVE-2026-XXXXX, which NixOS advisories sometimes carry before MITRE assigns the real id, does not match. An entry with no CVE id is re-checked, a few per run, while the post is less than 60 days old, since NixOS can and does edit a post after assignment to substitute the real id. Past that age the post is treated as settled and is not re-fetched.
  • The list keeps at most 200 entries; the category's low volume means this is not expected to bind in practice.
  • lastPollIssue is set when the most recent poll's category fetch succeeded but one or more per-topic fetches failed (the two endpoints can be rate-limited independently), so a reader can tell "this run was broken" apart from "nothing new was posted". It clears on the next run that has no failures.

Every upstream fetch is bounded against a hostile or merely oversized response: a 10-second timeout that also covers reading the body (not only establishing the connection), and a 2 MB cap on how much of a response is read regardless of what it claims to contain. A non-200 response's body is always consumed before the error is raised, so a failure never leaves a concurrent stream open.

Cross-linking to a CVE ​

A CVE's own dossier (GET /api/v1/cve/:id) carries a discourseAdvisory field when a stored post is confirmed linked to that CVE, checked two ways, both non-fuzzy:

  1. The post's text directly names the CVE id (matchBasis: "post_mentions_cve_id"). If more than one stored post names it, the earliest is used, since the field's purpose is the original disclosure moment, not a later post repeating the id.
  2. The CVE record's own reference list cites the post's URL (matchBasis: "cited_by_cve_record"). This direction can link a CVE whose record cites a Discourse post even when the post itself was never edited to name that CVE id back.

The field also carries a note stating in words what the match does and does not establish -- a mention or citation does not by itself mean the post is only about this CVE, and products inside it is still an uncorroborated keyword match. No fuzzy matching by product name or publication date alone is performed; a post sharing neither a direct mention nor a reference citation with a CVE stays unlinked on that CVE's dossier. Correlating by product name and date proximity, with an explicitly stated confidence rather than an asserted fact, is a reasonable follow-up and is not yet built.

Response shape ​

json
{
  "success": true,
  "data": {
    "entries": [
      {
        "id": 80547,
        "title": "Security advisory for Nix and Lix - October 2026",
        "slug": "security-advisory-for-nix-and-lix-october-2026",
        "url": "https://discourse.nixos.org/t/security-advisory-for-nix-and-lix-october-2026/80547",
        "createdAt": "2026-10-13T14:05:55.686Z",
        "excerpt": "Nix and Lix are affected by a set of issues...",
        "products": ["Nix", "Lix"],
        "relatedCveId": null,
        "relatedCveIds": []
      }
    ],
    "updatedAt": "2026-10-14T06:05:00.000Z"
  },
  "timestamp": "2026-10-14T06:05:03.120Z"
}

A 503 with code: "NOT_BUILT" means the five-minute poll has not run yet; "STORE_UNAVAILABLE" means the backing KV store could not be read on this request. Neither means no advisories have been posted.