Skip to content

Pro content

The licensing page says that part of what Pro sells lives on the server rather than in the binary. Since 0.39 that part exists. This page is the catalogue: three things, all behind the entitlement token, all off without a licence, none of them able to stop the free core.

Content Licence feature Signed with Valid for Free installation gets
Threat-intel feed bundle feeds the feed key nyx-feed-2026a 14 days the local refresh from the abuse.ch sources
Model packs model_packs the release key nyx-release-2026a 180 days the built-in model set
Analyst quota analyst carried in the entitlement token the token’s lifetime no licence quota; the analyst is gated by the daily budget alone

The design rule the three share with the updater: verify first, install last, and never let a Pro artefact stop the free core. A bundle that does not verify, does not parse or does not carry what was signed never becomes a candidate; an installation that cannot reach the key server keeps what it has.

A Pro installation whose licence carries feeds pulls one curated, signed indicator bundle a day instead of assembling the list from the public sources itself. The publisher is an unattended daily job on our side that fetches the abuse.ch Feodo Tracker, SSL Blacklist and URLhaus lists (all CC0), normalises them to the appliance’s {domains, ips, cidrs} file under 100,000 entries, gzips, signs and publishes the result. Spamhaus DROP is not a source — it is free for non-commercial use only, and a commercial product cannot redistribute it.

Property Value
Id threat-intel
Version the UTC publication date as a semantic version, 2026.9.18
Channel stable or beta; the appliance follows NDR_CONTENT_CHANNEL, or its update channel when that is empty
Signature a manifest signed with the feed key nyx-feed-2026a, verified on the appliance against /etc/ndr/feed-pubkeys.json
Validity 14 days from publication; a feed that has not been renewed is served flagged as expired and refused by the appliance
Size at most 32 MiB; the signed manifest carries the byte count, the SHA-256 and the number of indicators

The manifest is verified before a single byte of the bundle is fetched, the bundle is checked against the signed size and checksum, parsed with the same strict parser the API reads the installed file with, and its indicator count is compared with the signed entries. Only then does it become a candidate for /etc/ndr/threat-intel.yaml. How the daily unit runs, what its root half refuses and how a Pro installation falls back to the local sources is on threat intelligence.

Why a separate key. The feed publisher is the most exposed process in the product: it runs unattended every day and fetches third-party lists. Its key can therefore push indicators and nothing else. A compromise of it can never sign a release or a model pack, because the artefact type is inside the signature and the appliance verifies feeds against a third keyring (NDR_FEED_KEYRING) that is neither the licence ring nor the release ring.

A Pro installation whose licence carries model_packs pulls signed detection-model packs — the YAML catalogue, curated and versioned on the key server — and the analyzer loads them over the built-in model set. They are additive: a pack can add models, replace a built-in model of the same id, and add roles for its own models. It can never weaken a shipped policy, never set the aggregation section, and never carry code — every model in a pack goes through the analyzer’s own strict validator before anything is installed, and a detector or parameter the running binary does not know is refused whole.

Property Value
Id network-core today; a pack id is [a-z0-9][a-z0-9-]{0,31}
Version a real semantic version, 1.2.0
Signature a manifest signed with the release key — a person publishes a pack from the release host the way a release is cut — verified against /etc/ndr/release-pubkeys.json (NDR_UPDATE_KEYRING)
Validity 180 days from publication
Size at most 8 MiB; a deterministic tar.gz of plain *.yaml files, nothing else
min_appliance_version the oldest appliance that understands the pack; a newer requirement than the running version keeps the installed version

The pull, the precedence between the built-in file and one or more packs, the current.json that names the version in use, the models health row and the rollback are on model packs.

The analyst spends real money against your OpenAI key. A Pro licence with the analyst feature carries a quota for it in the entitlement token: a per-installation budget per UTC calendar month, in micro-dollars.

Plan Default quota per installation and month
Basic 5 USD
Tier 35 10 USD
Tier 55 20 USD

The figures are the key server’s catalogue defaults and can be overridden per key in the portal; a key issued before the quota existed carries the plan default from its next renewal. The token fields are analyst_quota_microusd and analyst_quota_period (month), and GET /api/v1/license shows both.

It is a product limit, best effort — not a proxy. Three sentences say exactly what that means:

  • The appliance meters its own ledger. Immediately before it reserves the daily budget for a run it adds the month’s spend across every tenant to the run’s reservation and refuses the run when that crosses the quota — 429 quota_exhausted on the API, distinct from budget_exhausted, your own daily cap, which applies unchanged beside it.
  • The key server sees the usage the appliance reports in its daily heartbeat, shows it against the quota in the portal and flags an installation that is over. It cannot enforce anything: the ledger is the customer’s, and a patched appliance spends what its own key allows.
  • No request is proxied. The OpenAI key stays on the appliance and the analyst’s only outbound connection is the one to the provider you configured.

Where the quota shows on the appliance — the analyst health row, the usage report in the heartbeat, the two ndr telemetry show lines — is on the analyst page.

Nothing on this page changes what the free core does, and nothing here is required for it:

  • The daily threat-intel refresh builds the same kind of file from the abuse.ch sources locally — Feodo Tracker, SSLBL and URLhaus, fetched by the appliance itself, validated and installed fail-closed. Since 0.39 that set no longer includes Spamhaus DROP. A free installation’s ndr threat-intel pull ends with exit code 3 and opens no connection; the health row says source: local.
  • The analyzer runs the built-in model set alone; ndr models status prints packs: none (the built-in model runs alone) and the models health row is unavailable, which is a fact and not a fault.
  • The analyst, if you switch it on, is bounded by your daily budget alone. The five quota fields on the health row are null.

A Pro installation that loses its entitlement — the licence lapses, feeds is removed from the key, licensing is switched off — drops to exactly this behaviour at its next refresh, with the reason in the state file and the health row. It is never worse off than a free one.

The content endpoints are GET /v1/feeds/{id}/latest?channel= and GET /v1/feeds/{id}/{version}, and the same shape under /v1/model-packs/. Every one of them wants Authorization: Bearer <entitlement token>.

Answer Meaning
200 with manifest the signed manifest; manifest: null is an empty channel, not a failure
302 the download, a redirect to the content storage; the token never travels to the storage host
401 invalid_token no token, or not a valid one. A free installation holds no token and never asks
402 not_entitled a valid token whose licence lacks the feature, with a sentence naming the plan and the feature. The appliance treats it as not entitled and applies the local sources or the built-in model
403 revoked, 403 expired the licence itself, as everywhere else on the key server
410 a withdrawn bundle
429 rate_limited the manifest lookups are metered per licence and day — 500 for feeds, 200 for packs; the appliance asks once a day

The bundle bytes live in a content bucket separate from the release bucket. The bucket allows anonymous reads of a single object and must deny listing; the object’s name is the signed bundle field of the manifest, which must carry 16 random hex characters (128 bits) and is refused at signing and at verification without them; and that name is handed only to the holder of an entitled token. The redirect carries the family prefix and the signed name alone — never the id and the version, which anybody can guess.

So the gate is exactly you cannot guess the name. An entitled installation can share a URL, as it could share the bundle itself, and a bucket that lists would give everything away. That is phase one, stated once rather than dressed up. A presigned, licence-bound URL is the upgrade, and it would need storage credentials on the key server, which holds none today.

The stronger guarantees are the ones that matter for you and do not depend on the bucket at all: a bundle is only ever accepted with a valid signature from the right key for its type, and the appliance never executes anything it downloaded — a feed is a list, a pack is a set of YAML files the analyzer validates before it loads one model.