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.
The threat-intel feed
Section titled “The threat-intel feed”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.
Model packs
Section titled “Model packs”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 quota
Section titled “The analyst quota”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_exhaustedon the API, distinct frombudget_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.
What a free installation gets
Section titled “What a free installation gets”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 pullends with exit code 3 and opens no connection; the health row sayssource: local. - The analyzer runs the built-in model set alone;
ndr models statusprints packs: none (the built-in model runs alone) and themodelshealth row isunavailable, 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.
What the key server answers
Section titled “What the key server answers”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 honest limit
Section titled “The honest limit”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.