Vista Platform 4.5.0 — full list of changes¶
Covers 4.5.0 through its release candidates (rc.1 to rc.7). The date on the final release is stamped when it ships. The release summary — highlights, what to do before upgrading, what each edition includes, and how to verify the signatures — is in CHANGELOG.md. This page is the complete, per-change record behind it.
4.5.0 is a security-hardening and dependency release. The edge, sign-in and sessions, authorization and tenant isolation, outbound requests, request sizes and the chart's defaults were reviewed, and every confirmed, bounded finding was fixed. Each fix was tested through the real router or handler rather than the helper alone, and checked by breaking the fixing line to confirm the test goes red. Where a fix is partial, the entry says what is left.
The release also carries the work that followed the security review: asset classification coverage (the manufacturer of a MAC from the full IEEE registry, a Smart device class, classification from a host's DHCP vendor class, and unknown hosts reclassified as evidence accumulates), lease-fresh matching of probes on DHCP networks, installers for the sensor and the Linux device agent that enroll what they install, the sensor as a Windows service, and scheduled scans set up in plain language. Those entries are under "Added", "Changed" and "Fixed" below.
The fourth release candidate adds a clean-up of the discovery pipeline. A discovery now reaches inventory when it is written rather than at the next poll; one classifier and one writer decide what every discovery becomes, third-party connections included, with the full certificate assessment; crypto found on an asset that is not yet approved is held in one place and replayed on approval; duplicate crypto configurations are collapsed and can no longer recur; a host inventory's endpoints and addresses go through the single identity engine; and a few identity rules were corrected (IPv6 unique-local addresses, interface MACs, two devices that share a name, class proposals). It also moves to Go 1.26.9, which fixes nine standard-library advisories. See "Discovery pipeline and identity clean-up (rc.4)" after "Changed", the matching entries under "Fixed", and "Upgrade notes in full" at the end.
The sixth release candidate adds GitOps-safe chart installs (platform.generateSecrets=false
with pre-created Secrets, and migration Jobs that no longer delete themselves),
Ed25519-signed CBOM artifacts that a third party can verify offline (Enterprise), a
per-collector secret issued to every sensor and device agent so the registration key is no
longer kept on disk, a Forgot password? link on the sign-in page, and a round of
correctness and load fixes: compliance no longer re-evaluates when a re-sighting changes
nothing, the Crypto Risks list reads a persisted judgement, and large maps lay out in
seconds. It removes the unused InfluxDB datastore, changes the chart's storage class default
to the cluster's own, and moves plan assignment to the platform.billing permission. See
"Chart, signing and scale (rc.6)" below and "New in rc.6" under "Upgrade notes in full".
The schema upgrade is additive except for one deletion. It turns on row-level
security for one table and for the audit-log partitions (see "Authorization and
tenant isolation"); adds two bookkeeping tables, oui_vendor_backfill_state and
class_floor_sweep_state, one nullable column on asset_identifiers
(device_confirmed_at) and two columns on pcap_upload_jobs
(truncated_packet_count, snapshot_length); widens the rule kinds
classification_rules accepts to include oui_vendor and dhcp_vendor_class;
and deletes the per-prefix oui classification rules Vista shipped (see
"Classification rules are written against manufacturer names"). The discovery-pipeline
changes also add crypto-evidence bookkeeping and
uniqueness constraints; rc.5 adds tenant certificate-trust policy and evaluation
storage and widens the audit category constraint to accept integration events; rc.6 adds
the persisted crypto-risk judgement table (crypto_risk_judgments), the CBOM signing-key
history (cbom_signing_keys) and signature columns on cbom_artifacts, and the wrapped
per-collector secret on sensors and device_agents.
Because the widened rule-kind constraint is not valid for the 4.4.0
schema, a rollback to 4.4.0 after this upgrade is a restore from your backup, not
a helm rollback.
After the upgrade, two background passes run in inventory-service: a vendor backfill about two minutes after the service starts, and a classification sweep about five minutes after it starts and every six hours afterwards. See "Every manufacturer the IEEE registry knows" and "An asset still shown as an unknown host is reclassified".
Data-path fixes (rc.7)¶
Everything below was merged after the sixth release candidate was cut, from a review of every data source (sensor, device agent, device interrogation, cloud, NetBox) on a live install. None of it needs an upgrade step.
Fixed¶
- QUIC connections carry their crypto. A ClientHello that does not fit one QUIC Initial packet (every browser offering a hybrid post-quantum key share) is reassembled across packets, so QUIC 3rd Party Connections now record the server name, offered ciphers and JA3/JA4 fingerprints that passive TLS already did. Reassembly is bounded: 16 KB per flow, 4096 flows, five seconds. (#2515)
- Crypto seen by a direct probe is recorded on the asset that owns the address. Evidence held for an asset was replayed only when the sighting had itself established identity; a TLS handshake or SSH host key read at an address the monitoring asset already owned, whose sighting only confirmed what was known, was never materialised. Traffic-only sightings still wait for an operator's Link, as before. (#2521)
- A direct probe of a provisional host corroborates it instead of creating a twin. An authorized scan of an address held by exactly one provisional asset, bringing no competing name or interface, attaches its findings to that asset and establishes it. Previously the address yielded to the scan's new asset, leaving two records and a duplicate finding. (#2519)
- A device added with credentials is interrogated right away, and again when credentials
are first added to a device never interrogated; the Interrogate button is no longer the only
way to the first run. Identity enrichment reports
never_interrogatedfor such a device instead of a missing re-check consent. (#2517) - Automatic scans skip hosts no sensor can reach. Addresses in a
cloudsegment, and hosts on a network none of a tenant's sensors is on when the tenant has sensors, are no longer probed from the platform sensor (every port came back filtered). They are counted on Settings → Active Scanning under "Not scanned" as "No sensor on the network" and stay eligible until a sensor covers them. (#2520) - Key Store assets show the key. A cloud KMS key's asset carries provider, account, region, key id and resource id, spec, usage, state, origin, rotation state, creation date and aliases, refreshed on every cloud run. The key posture itself was already in the key inventory; the asset page showed none of it. (#2518)
- Hourly connector syncs run hourly. NetBox and CMDB-sync schedules drifted by one
scheduler tick per run (65 minutes between "hourly" runs). Services that still ran the
HTTP framework in debug mode now run in release mode unless
GIN_MODEis set. (#2516)
Chart, signing and scale (rc.6)¶
Everything below was merged after the fifth release candidate was cut. The upgrade steps it needs are under "New in rc.6" in "Upgrade notes in full".
Added¶
- Chart: GitOps mode for Argo CD / Flux.
platform.generateSecrets=falsemakes the chart render no Secret whose content it would have to generate; you pre-create them and name them (platform.existingSecretName,platform.existingDatastoreSecretName,install.existingSecretName,jwtSigning.existingSecretName,cbomSigning.existingSecretName) and the render fails listing anything missing. Theschema-migration/rls-rolesJobs no longer self-delete, so a GitOps controller does not re-run them. (#2497, #2498) - CBOM artifacts are signed with Ed25519 under a per-install key (Enterprise). The signature covers a canonical envelope (content hash, scope id and version, attestation layers, kind, author); verification names any altered field; current and retired public keys are published (
GET /cbom/signing-keys) for offline verification. The chart generates the key (cbomSigning.keyPEMto pin it); Enterprise Docker Compose installs generate one on first start. (#2498, #2505) - Agents: a per-collector secret issued at enrolment and at each certificate rotation. It replaces the single-use registration key as the root of the key protecting device credentials, and the registration key is no longer kept in the collector's config file. Collectors enrolled earlier migrate at their next certificate rotation with no re-enrolment. (#2499)
- Web UI: "Forgot password?" on the sign-in page emails a password-reset link. (#2468)
Changed¶
- Assigning a plan to a tenant needs
platform.billing. Assigning a plan (and the tier impact-analysis preview) requiredplatform.settings, a catalogue-settings permission, although it is a billing action. It now requiresplatform.billing, and admin-ui shows Assign to tenant… only to operators who hold it; the Plans & Pricing page itself still needsplatform.settings, so an operator who assigns plans needs both. The stock Super Administrator and Platform Admin roles hold both, so they are unaffected; a custom platform role with onlyplatform.settingsloses plan assignment (#2511). - Admin list endpoints validate paging. Tenant invoices, legal acceptances, security events, the catalogues and classification-rule lists answer 400 for a non-numeric, zero or negative page or limit instead of silently using defaults, and an oversize limit is clamped (#2510).
datastores.storageClassNamedefaults to the cluster's default StorageClass. The default waslonghorn, so a stock install on any other cluster left its volume claims Pending. It is now"", which omitsstorageClassNameand uses the cluster default; per-datastore overrides are kept and the chart's install notes warn about the change. Existing releases must pin their class before upgrading (see "Upgrade notes in full") (#2494).- Compliance no longer re-evaluates when a re-sighting changes nothing.
asset.changedis published only for new or materially changed assets (created, history row, crypto/certificate/risk/segment/service/TLS outcome moved),certificate.changedonly when the stored certificate changes, and certificate history no longer records adata_enrichedrow per unchanged sighting; no-op evaluations log one line. (#2490, #2496, #2502) On a busy estate most sightings change nothing, so this removes most of the evaluation load without changing any score. - The Crypto Risks list, summary and export read a persisted judgement (
crypto_risk_judgments, invalidated by triggers on every judge input, kept warm by a 5-minute sweep) and page, sort and search in SQL with trigram indexes, so large estates no longer re-judge every configuration per request. Rows cascade from tenants and configurations. (#2495, #2503) A judgement is recomputed only when the configuration or one of its inputs (catalogue rows, certificates, the prior finding) changes, rather than on every page load.
Fixed¶
- Web UI: large relationship maps lay out in seconds. Above 120 nodes+edges the map uses a seeded, deterministic d3-force layout (dagre below, golden coordinates unchanged); the no-Worker fallback runs the same path. (#2493)
- Findings list totals count a finding as open as soon as its time-boxed suppression expires, matching the control counts, instead of waiting for the expiry sweep. (#2489)
- web-ui and admin-ui no longer log error-level Caddy lines at startup on a read-only root filesystem (
persist_config off, storage on/tmp). (#2491) - Local
make build-distbuilds the web-ui and admin-ui images without a dhi.io login (NODE_BUILDERis passed like the Go builder args), andscripts/stage-chart-sql.shno longer modifies the tracked schema mirror (comment stripping is opt-in via--strip-comments, used by the release workflows). (#2501) - Chart: HTTP-01 challenges work under the default NetworkPolicy;
networkPolicy.externalDatastoreCIDRsfor external Postgres/Redis;backends.<svc>.enabled=falseomits the backend entirely. (#2460) - Chart: HA overlay renders with MSP licence persistence; minimal overlay uses Recreate on every backend; chart doc references point at live core pages (guarded);
values.schema.jsonvalidates the security blocks. (#2462) - Compliance: Frameworks status, the stored score rollup and the framework summary report the same score when overrides exist; duplicate overrides answer 409; status GET no longer writes. Framework activation is atomic and respects the plan cap under concurrency; cancelling the default falls back to Best Practices; tenants cannot set a licence price; the default-framework lookup is read-only. Time-boxed suppressions expire and reopen the finding; request bodies bounded; invalid input answers 400/422; ticket references validated; strict paging. (#2466, #2471, #2474)
- UI:
unwrap/safeHttpUrlshared in primitives; platform change-password and every admin-ui mutation fail on bodiless error responses. (#2467) - Web UI: plan changes refresh entitlements at once; scopes stay in sync between Settings and CBOM; access-check failures show a retry instead of a denial; the Settings rail hides pages you cannot open; invite/reset/SSO pages harden input and strip tokens from the URL; secret fields are masked. Selecting a map node no longer re-renders the whole map; map layout runs in a worker for larger graphs. (#2469, #2476)
- Admin: tenant mutations answer 404/400/409 honestly and audit updates; settings save is atomic and can clear values; integration credentials are masked on read; bounded request bodies and page sizes. (#2470)
- Discovery processor: batch claims are fenced and renewed (no double import across replicas); auth/route errors from inventory are retried instead of rejecting rows; bad findings are isolated by bisecting a refused chunk. (#2479)
- Platform Sensor: rate-limit config can no longer break job creation; the per-tenant target cap is enforced atomically (429 names the limit); a missing Platform Sensor fails the unit so Retry is reachable. (#2480)
- Scans: Cancel reaches legacy sensor commands; manual legacy payloads are re-authorized at dispatch, pickup and retry; jobs with no active scanning carry a note; external-target consent audit is mandatory. (#2481)
- CBOM: SPDX/PDF exports render the real artifact (they were mostly blank); comparisons of pre-internal-view artifacts refuse with a clear 409; requested signing is never silently skipped; duplicate scope names answer 409; artifact list paging. (#2484)
- Chart: Postgres and NATS now reload rotated server certs. With
datastores.postgres.tls.enabled/datastores.nats.tls.enabled, the StatefulSets carry the Stakater Reloader annotation for theirpostgres-mtls/nats-mtlsSecret, so the ~60-day cert renewal restarts them instead of leaving everyverify-fullclient failing at expiry. Requires Reloader, as before. - Chart: a chart-version-only upgrade no longer restarts Postgres, Redis or NATS. Their pod templates carried
helm.sh/chartand the app version labels; they now carry only the selector labels pluspart-of. Selectors are unchanged. - Chart: datastore pod
securityContextrenders each key once (it emittedfsGroup/runAsUsertwice, which strict YAML renderers such as kustomize reject). - Chart: Postgres gets a startup probe, a 120s termination grace period and a fast-shutdown
preStop, so crash recovery after an unclean stop is no longer killed by the liveness probe and a stop with connected clients no longer ends in SIGKILL. - Helm chart:
image.pullSecretsnow reaches every pod, frontends honourimage.digest, frontends get PodDisruptionBudgets, the helm-test image is overridable.image.pullSecretswas applied to backends only, so with a private registry web-ui, admin-ui, the datastores, the schema-migration / rls-roles / seed-data Jobs and the helm-test pod sat in ImagePullBackOff. web-ui and admin-ui ignoredimage.digestand rendered by tag while backends rendered@sha256:(blank the digest for the tag form, as before).values-ha.yamlclaimed PodDisruptionBudgets for both frontends but only backends had them; a frontend withreplicas > 1now gets one. The helm-test pod hardcodedcurlimages/curl:8.22.0; it is nowsmokeTest.image.*(default unchanged, prefixed withimage.registry). A new chart test fails if the rendered schema ConfigMap exceeds 900 KB. - Helm chart: value combinations that rendered green and then failed on the cluster are now refused at render time or fixed.
schemaMigration.enabled=falsewith the defaultserviceRls.enabled=trueno longer hangs every backend inwait-for-schema(therls-rolesJob now runs without the migration Job; your own schema must create thecrypto_app/crypto_bypassNOLOGIN roles). An external Postgres or Redis (datastores.postgres.enabled=false/datastores.redis.enabled=false) now requiresplatform.existingSecretNameholdingdatabase-url/redis-urlinstead of leaving pods inCreateContainerConfigError;datastores.nats.enabled=false(no external-NATS path exists) is refused with a clear message. The documented mTLS opt-out now listsagentMtls.enabled=falseas the fourth toggle (it was left out and the opt-out failed to render).seedData.enabledwith an unstaged, emptyfiles/schema/seed.sqlfails with a pointer toscripts/stage-chart-sql.shinstead of seeding nothing and creating no platform admin. - Helm chart:
datastores.storageClassNamemay now be empty.--set datastores.storageClassName=omitsstorageClassNamefrom the PVCs so a new install on a cluster withoutlonghorncan use the cluster's default StorageClass; previously the schema rejected an empty value. A later change in this release makes empty the default (#2494); see "Upgrade notes in full". - Helm chart: the chart-created Namespace (
createNamespace=true) is now kept onhelm uninstall(helm.sh/resource-policy: keep), so uninstalling no longer deletes the PVCs and Secrets that are themselves marked to be kept. - Web UI: every asset-class rule kind has a label (#2363). The Approvals class-proposal row showed the raw key
os_namefor OS-name rules. Every kind the classifier emits now has a label, pinned against the Go rule-kind list so the next new kind fails a test instead of reaching a reviewer. - Web UI: Dashboard, Findings, Posture, Remediation and Settings now share one cached read per endpoint, and Findings groups open on a narrowed filter. Risk summary, posture trend, PQC progress and the available-frameworks list were each cached under several keys with the request copied into several screens, so moving between screens re-fetched identical data and activating a framework in Settings left the Posture and Dashboard copies stale. Each now has one hook and one cache key. On the Findings page, narrowing the filter could leave newly formed groups collapsed; unseen groups now start open.
- Sensor: exact interface matching, no busy-spin on a lost capture, and BPF failures fail the interface. A configured interface name now matches a device's exact name before any description is tried, so on Linux an interface such as
eth1is no longer captured as the first description-less device in the list. When the packet source for an interface closes (read error, NIC down) the sensor reports a capture error instead of spinning one CPU core at 100% and saying nothing, and logs which interfaces are not being captured. An interface whose BPF filter cannot be applied is no longer captured unfiltered; it fails to start, and the sensor logs a loud "capture is DEGRADED" line when only some configured interfaces came up. Idle TCP flows that never see a FIN or RST are now aged out of every reassembly table (including SMB and Kerberos), and a zero or negative reporting/heartbeat interval falls back to its default instead of crashing the sensor. Dropped TLS enrichment results are re-probed rather than suppressed, and a full disk no longer silences the sensor's stderr log. - Device agent: a hung device no longer wedges the agent, and a finished result survives a network blip. A job now runs under a per-job timeout (5 minutes, or the job's own
timeout_seconds, capped at 1 hour) and fails with a "timed out" reason instead of blocking the only poll loop while the heartbeat kept reporting the agent healthy. A panic inside one interrogator becomes a failed job instead of killing the agent. Result and failure submissions retry five times over about two minutes (4xx rejections are not retried; there is no on-disk spool yet). On SIGTERM/SIGINT the agent stops its loops and waits up to 30 seconds for the job in flight to finish and submit. The local audit log now redacts error text the same way the wire does. - Sensor:
update_configandset_log_levelno longer report success for a no-op.update_configskipped fields of the wrong type or name without a word and answered "success (0 settings changed)"; it now fails naming each rejected field, lists any skipped fields when others applied, and saves what it applied to the sensor's config file (active probing, network discovery, host observation, dedup TTL and reporting interval previously reverted at the next restart). A change that could not be saved is reported as an error.set_log_levelvalidated its payload and changed nothing; it now sets the sensor's log format (debugadds source file and line;info,warnanderroruse the normal one, and the result says which took effect) until the next restart, and rejects unknown levels. - Sensor: a failed interface change can no longer wedge the capture loop. Changing the monitored interfaces stopped the running capture before the replacement was proven, so a failed start (and a failed revert) left the sensor reading closed channels: a nil-discovery panic or a busy loop. The replacement now starts first and is swapped in only on success, and a closed capture channel restarts the sensor instead of spinning.
- Installers: a rejected registration key no longer causes a restart storm, the key is no longer printed or left unquoted, and the sensor unit no longer has a lethal reload. The sensor and device agent give up on a rejected key (and on an unparsable config), but their systemd units restarted them every 10 seconds forever, re-hitting the registration endpoint. They now exit with status 78 (
EX_CONFIG) for conditions only a person can fix, and both units setRestartPreventExitStatus=78.install-sensor.shno longer writesExecReload=/bin/kill -HUP, which killed the sensor (it handles only SIGINT/SIGTERM). Both Linux installers now show only the last four characters of the registration key, accept it fromVISTA_REGISTRATION_KEYor--key-stdin(so it stays out ofps;--keystill works), andinstall-device-agent.shquotes every YAML value it writes (a#or:in the URL or key used to break the config). Thesensor/config-democorp.yamlsample key is now a placeholder. - Admin UI: the first load ships the shell, not the whole console. Every section page was imported eagerly into one bundle. Sections are now route-level code-split (
React.lazy, one Suspense boundary inside the shell), so the main chunk drops from 903.7 kB (234 kB gzip) to 339.6 kB (102 kB gzip) and a section's code downloads when you first open it. Routes, guards and redirects are unchanged. - Sensor and device agent: enrolment and certificate rotation never report success before the credentials are on disk. The sensor now proves it can write its certificate directory and config file before it sends the single-use registration key (as the device agent already did), and a failed save after registration is fatal, naming the path and saying the key is spent, instead of running on in-memory credentials that vanish at the next restart. A rotated certificate that cannot be saved (the platform has already revoked the old one) is retried, kept in memory, reported at error level on every heartbeat and saved as soon as the disk recovers; "rotated successfully" is logged only once it is on disk. Certificate, key and CA files, and the agents' config files, are written atomically (temp file and rename; the certificate and key as a unit), and an unreadable certificate or key named by the config is now a startup error instead of silently running without mTLS.
- Sensor and device agent: mTLS connections honour
HTTPS_PROXY/NO_PROXY. After enrolment the collectors built their TLS transport without proxy support, so a collector behind a corporate proxy went dark as soon as it registered or rotated its certificate. All five transports now share one proxy-aware constructor, and a rotated-out transport's idle connections are closed. - Sensor and device agent: config-file rewrites quote their values. The device agent regenerated its config by hand (dropping
heartbeat_intervaland any comments, withplatform_urlandregistration_keyunquoted); it now marshals with the YAML library and merges into the existing file. The sensor's monitored-interface update quotes each name and rejects names containing control characters, which arrive in a platform-controlled command. The sensor also prefers the data-directory config over a stray./config.yamlin the working directory. - Internal (CI): the standards gate now runs on every pull request and every push to
main(#1799).standards.ymlwaspaths:-filtered, so a change touching only service code, a UI, the chart or docs never ranmake auditor the export leak gate andmainwent red later on an unrelated PR. The filter is gone, and a post-merge sentinel runs the fullmake standards-checkon each push tomain, opening or appending to astandards-brokenissue on failure. - Web UI: server-backed search boxes wait for you to stop typing and cancel superseded requests. The Inventory search (certificates, configurations, connections, stale views), the Software lens and an asset's Software tab, the relationship, SBOM, observation-link and merge asset pickers sent one request per keystroke and let every earlier one run to completion. They now share one debounce (
useDebouncedValue, also used by the Admin UI) and pass the query's abort signal to the request, so a superseded search is cancelled rather than racing the newer one. - Admin UI: the Activity export honours the tenant scope and filters. The CSV/JSON buttons exported the entire cross-tenant log whatever the scope bar and category, user-type, search or impersonation filters said. The export now carries them, and the audit-service export endpoint accepts every filter the list does (including
search,impersonation,requires_attentionandtag, which it previously ignored). The download also works in Firefox. - Admin UI: dialogs and drawers are accessible and no longer discard input on a stray click. Modals, the tenant drawer, the plan builder and the role drawer now share one dialog shell with
role="dialog", an accessible name, a focus trap (focus moves in, Tab cycles, focus returns to where you were), Esc to close and a single body-scroll lock that stacked dialogs release in any order. With unsaved input (a half-written plan change, plan details, role permissions), a scrim click or Esc asks "Discard changes?" instead of closing. Icon-only buttons now carry accessible names. - Web UI: Findings no longer fetches crypto risks on lenses that do not show them, and Approvals and pickers stop truncating silently. The Findings page requested up to five pages of crypto risks, one after another, even on the Producer, Framework and Control lenses; it now fetches them only on the crypto lenses, with pages 2 onward in parallel. The Command Center "Pending approvals" tile shows the real pending-asset total instead of capping at 100, Discovery > Approvals gains Prev/Next for assets beyond the first 100, and the asset drawer's configurations and the remediation plan's finding picker show "Showing N of M" when the list is cut short.
- Web UI: failed reads show errors instead of empty states, and polling stops when nothing is in flight. The ⌘K palette shows "Could not search …" instead of "No results found" when a search request fails; sign-up and complete-registration block submission with a visible error when the terms of service cannot be loaded (previously the checkbox silently disappeared); the sensor registration badge reports a failed status check instead of "Registered — waiting for the first data" and stops polling after an error or 10 minutes; the PCAP page polls only while a job is unsettled and the Posture page only while the re-evaluation cooldown is running. The notification bell has an accessible name,
aria-expanded, closes on Esc, and no longer refetches on every open. - Admin UI: Job Repair and Jobs & Queues now filter and page on the server instead of the newest 100 rows. Job Repair filtered the 100 most recent jobs down to the problem states in the browser, so on a busy fleet recent healthy jobs pushed stuck older ones off the page and it showed "No jobs". Both pages now share one query that sends the status filter and page to the server, and show "Showing N of M jobs" with previous/next controls; Jobs & Queues keeps a fixed list of status chips.
- Admin UI: the ⌘K palette honours permissions and opens tenants. It now offers only the sections the sidebar shows the operator, and choosing a tenant opens that tenant's drawer instead of just the Tenants list.
- Admin UI: Security policy keeps unsaved edits. A settings refetch while you are editing no longer resets the form.
- Admin UI: the notification bell is accessible and reports failures. The bell and tenant switcher have accessible names and expanded state and close on Esc; a failed "mark read" now shows an error toast.
- Admin UI: changing a role refreshes the Staff list. Role create, rename and delete now also refresh staff rows that display the role name.
- The admin console no longer silently drops tenants, staff and tenant-health rows past the first page. The Tenants page (live and Deleted), the tenant switcher, the command palette, Mission Control, the Assign-tier modal, the Staff list and both tenant-health views each asked for one fixed-size page (100 or 200 rows) and ignored the server's total. They now page through the whole list, up to 5,000 rows, and the Tenants and Staff pages show a "List truncated" notice if that ceiling is ever reached. The two tenant-health queries are now one.
- Admin UI catalog and activity searches no longer fire a request per keystroke or flash an empty table. The End-of-life, Vulnerability and Classification-rules catalogues and Security > Activity now wait 250 ms after typing stops before searching, and keep the previous rows visible while the next page or search loads. The Activity category filter now always lists every audit category instead of only those on the current page, so choosing one no longer hides the rest.
- Identity enrichment no longer plans probes of a collector's own address and retries them forever. The planner excluded only the fresh DNS-interface addresses of the observing and executing sensors, while the dispatch guard refuses every address of either, so a sensor without a reported DNS interface (a Windows sensor) or one with a docker bridge or second NIC kept getting probe jobs for its own address, refused with
probe target belongs to a collectorand retried with backoff. The planner now excludes every address of every collector of the tenant, from the same rule the guard uses, and an observation whose only target is a collector settles ascompleted/target_is_collector. - The web UI no longer reports success when a save fails without an explanation, and now shows the server's reason when it gives one. A write that the server rejected with an empty-bodied error (a bare 403 or 500) used to toast "Assets merged", "Added to plan", "Deleted" and the like while nothing had changed, and a failed legal-terms acceptance silently looped the gate. Every create, update and delete in the tenant UI now treats any non-2xx response as a failure, and shows the server's own message (for example why an Active Scan was refused) instead of a fixed "Failed to ..." string.
- A malformed agent or sensor config file is now fatal instead of silently falling back to defaults (#1787). The device agent and the sensor used to log a warning and start against
localhost:8080with no identity when their config file failed to parse, while reporting a healthy start. They now exit non-zero, naming the file and the parse error; the environment-variable fallback still applies when no config file exists.
Removed¶
- Chart and compose: the unused InfluxDB datastore (StatefulSet, PVC, Secret keys, compose service, Grafana datasource, env and Makefile targets). A leftover InfluxDB PVC can be deleted after upgrading. (#2494)
- sensor-manager no longer registers
POST /api/v1/sensor-manager/sensors/:sensor_id/exports(air-gapped export). It wrote to anair_gapped_exportstable that no schema has ever defined, so it failed on every call, and no sensor ever called it. The unbuilt feature's handler, service method and model went with it; thesensors.air_gappedflag is unchanged (#1508). - compliance-engine no longer serves the
/mappingsroutes (#1505). They validatedrule_idagainstcompliance_rules, a table that is never seeded and has no writer, so every create, get or update failed with "compliance rule not found"; no UI or client called them. Thecompliance_rulesandrule_vulnerability_mappingstables are left in place.
Security¶
- Auth: no internal-HMAC bypass on impersonation/tenant-admin routes; PUT /tenant allowlist; PAT scopes enforced (and PAT sessions cannot drive self-service account routes); per-action login/forgot rate limits keyed on email+address;
TRUSTED_PROXY_CIDRS+ charttrustedProxyCIDRs;JWT_ACCEPT_LEGACY_HMACcloses the HS256 window; platform accounts are refused at the tenant sign-in (after the password verifies, so no enumeration) with a pointer to the admin console. (#2461, #2488) - Auth: deactivating, deleting, resetting or revoking a user invalidates their live access tokens in every service; the lockout counter resets after expiry. (#2472)
- Compliance: control details are tenant-scoped (another tenant's custom control id answers 404) and paginated in SQL. (#2477)
- MCP: token exchange is rate-limited with a negative cache; revoked/deactivated users are refused within 60s; tool output is bounded and labelled as untrusted tenant data; trusted proxies; no host-header discovery in production; audit failures withhold reads. (#2478)
- The sensor no longer prints its raw config file, including the registration key, to stdout at startup. Only a secret-safe summary (the key by length) is logged.
- A sensor heartbeat reporting
healthy,degradedorunhealthyno longer fails with a 500; the reported health is mapped onto the stored liveness status (active,active,error) (#1797). - Fresh installs no longer crash-loop: with
serviceRls.enabled(the default), each backend'swait-for-schemainit container now also waits until it can log in as the app role (serviceRls.roleApp), which therls-rolesJob enables just after the schema is applied. Previously backends started in that gap and restarted two or three times withpassword authentication failed for user "crypto_app"before settling (#1413). - Chart: with
schemaMigration.enabled=falsetheseed-dataJob no longer waits ~10 minutes for a schema-migration completion marker that nothing writes, and fails the install or upgrade. It now waits only for thepublic.tenantstable, the same check the backends make in that mode (#2146). - The asset page's Endpoints and Identifiers tabs now follow the asset after a scan, edit or merge instead of keeping the first list they saw. Merging assets no longer refetches every query in the app (session, permissions, branding, notifications), only asset, inventory, findings and dashboard data; and the Inventory "awaiting review" banner reads counts instead of three full proposal lists.
- Activity log no longer fills with sensor and agent heartbeats and job polls. Sensor heartbeats and health reports and discovery-agent heartbeats and job polls were recorded as activity-log rows (about 17,000 a day for two sensors and one agent). They are no longer recorded; data submissions, registration and certificate rotation still are.
Certificate trust and integration fixes (rc.5)¶
Added¶
- Certificate trust is your organisation's decision, evaluated by the platform. A new Settings → Policies → Trusted CAs page records which certificate authorities the organisation trusts or distrusts, and every certificate in inventory carries a trust status — public (the embedded Mozilla root store), trusted by your organisation, self-signed, untrusted, distrusted or chain incomplete — decided centrally rather than by whichever sensor happened to look. Trust or distrust a CA from its own certificate page, filter the Certificates and Assets lenses by trust, and the Certificate Hygiene framework now has a control for certificates issued by a CA nobody trusted. Missing intermediates and roots are completed from the certificate's own issuer URL, the public bundle and your decisions, so "chain incomplete" means the issuer was genuinely unobtainable. Connecting an integration to an internally-signed system no longer needs a pasted certificate: a failed Test connection shows the chain the server presented and offers Trust this CA (#2397).
Fixed¶
- Audit: connector events were discarded. Connector private-endpoint acknowledgements, scan-consent changes (NetBox and CMDB) and CMDB run undo were audited under an
integrationcategory the activity-log schema did not accept, so the middleware dropped every one of them.integrationis now a stored category, the service's request trail files under it too, and a guard holds every hand-written audit call in the service to a storable category. -
Imported assets inherit their segment's environment. An asset a NetBox or CMDB pull (or any other intake) placed in a segment took the segment's location and site but not its environment, so it fell outside every environment filter, the default "Production" CBOM scope included. The asset now takes its own segment's environment when it records none; a stated environment is never overruled. Existing assets catch up on their next observation or sync run.
-
The NetBox connector accepts a pasted
Bearer nbt_…token. NetBox 4.5+ shows a new token once, as the ready-to-paste header line; the client now strips the scheme word and sendsBearerfor these tokens, which NetBox 5.0 will require. -
A transient failure writing a third-party finding to
external_connectionsis retried instead of permanently suppressing it. Since the discovery import moved into inventory-service, a failed route (database error, resolver timeout, failed upsert) left the finding's outcome asrejected, which discovery-processor settles as a terminalsuppressedrow with no retry. inventory-service now reports it as outcomeerrorwith the cause inreason, and discovery-processor records the row as a transient failure (attempt counted, backoff, retried on a later claim). A third-party finding with no source address is still the permanentdropped(#2374).
Changed¶
- Discovery tools are grouped together in the navigation. Approvals, observations, discovery jobs and related discovery tools are reachable from Discovery.
- Windows sensor installation works in Windows PowerShell 5.1. The installer uses ASCII source text, and manual-install guidance now covers macOS and Windows setup.
- Certificate trust verifies every issuer signature. Cached issuer lookups and stored chain links cannot confer trust on a leaf that the named issuer did not sign. An unreadable tenant trust policy stops NetBox and CMDB connections so explicit distrust cannot silently disappear. Concurrent certificate reads initialize one trust evaluator.
Security¶
Sign-in and sessions¶
-
Changing the sign-in email through the profile endpoint is refused.
PUT /auth/melet any valid access token rewrite the account's sign-in address, and the forgot-password flow would then deliver to the new address, so a stolen or borrowed session became a takeover of the account. A request that carries a differentemailnow answers403 email_change_not_supported. No screen used it. (#2321) -
Sign-in no longer reveals whether an address is registered. Before the password was checked,
POST /auth/methods,POST /auth/login,POST /auth/authenticateand the platform-admin sign-in each told a caller whether an address was unknown, deactivated, single-sign-on only, role-less or a platform administrator, and answered unknown addresses measurably faster than real ones. A wrong password now gets the same invalid-credentials answer whatever the account's state, an unknown address spends the same time verifying a password as a real one, and an account's state is disclosed only to someone who supplied its right password.POST /auth/methodsno longer returns the organization's id except where single sign-on needs it. (#2316) -
The email-first password step honours the per-account rate limit, as the other sign-in routes already did; a locked account now answers as locked instead of a server error. The password-reset and forgot-password routes are now rate limited as sign-in routes: the limiter's list of them named paths that do not exist. (#2316)
-
Platform-admin sign-in is rate limited per address and per account. Five attempts a minute per account and twice that per address (change it with
ADMIN_LOGIN_RATE_LIMIT), and staff single sign-on's start is limited per address. A throttled attempt answers429withRetry-After, never reaches password verification and does not count toward the account's lockout, and an address refused for its own rate is not charged to the account, so one noisy source cannot spend the real administrator's budget. The counters live in Redis and fall back to a per-pod counter if Redis is unavailable; the limiter never turns itself off. Not done: five wrong passwords still fit in one window, so one source can lock an account once, and a distributed attacker can keep it locked.forgot-passwordandreset-passwordon the admin console are not throttled by this limiter. (#2322) -
Resetting a platform administrator's password ends all of that administrator's sessions. Setting a password, deactivating and deleting a platform user now do the same, and a failed revoke on set-password is reported as an error instead of passing silently. Platform sign-out now also revokes the access token it was given. Not done: an access token issued before a set-password, deactivate or delete keeps working until it expires (up to one hour), and a tenant user's password reset does not end live access tokens. (#2316, #2322)
-
A cross-site page can no longer submit the MCP OAuth consent decision.
POST /oauth/authorizeis refused (403) when the browser marks the requestSec-Fetch-Site: cross-siteor itsOriginis not the host that served it. Requests that carry neither header, such as non-browser clients, are unaffected. (#2322) -
Single sign-on for platform staff is bound to the browser that started it and uses PKCE. The admin console's staff sign-in kept its
stateonly in a cookie, so nothing tied an attempt to the browser that began it and a state could be used more than once. An attacker could finish the provider's flow with their own account and send the victim the callback address, signing the victim's browser in as the attacker. Starting a sign-in now stores the attempt server-side under itsstatetogether with a hash of a random value, which is also set in an HttpOnly,SameSite=Laxcookie (admin_sso_binding, ten minutes, scoped to the callback path). The callback refuses unless the cookie matches, before it looks up the provider or exchanges the code, and consumes the attempt whether it succeeds or fails. The authorization request sends a PKCEcode_challenge(S256) and the token request the matchingcode_verifier, for every provider. The attempt lives in Redis; where admin-service runs without Redis it is held in the process, and with several replicas an attempt that returns to a different replica is refused rather than accepted. (#2337)
Authorization and tenant isolation¶
-
Pending sensor registration keys need
sensors.create.GET /sensor-manager/sensors/pendinghanded every member of a tenant, a viewer included, the registration key of every sensor waiting to enrol, enough to enrol a rogue sensor. It now needs the same permission that creates sensors. The Pending section of Discovery → Sensors & Agents is already hidden for anyone the request refuses. (#2321) -
Tenant administrators can no longer edit or delete platform-wide alert rules. The rules every tenant sees belong to the platform; the service now refuses a tenant-scoped write to a rule that has no owner tenant. (#2321)
-
The admin console API requires a platform identity. Five admin-service routes were gated only by a valid token;
/admin-service/admin/**now refuses a tenant's token before any handler runs, including a tenant token whose role claim saysplatform_admin. (#2321) -
The sensor registration settings routes are removed.
GET /sensor-manager/admin/settingswas open to every member of a tenant andPUTto any holder ofsettings.update, and thePUToverwrote a process-wide setting that every tenant's key creation and sensor registration reads. No screen or client called either, so both routes are gone and answer404. The limits are fixed at the values they had by default: 50 pending sensors, a 60-minute registration-key lifetime and address validation off. The API description and generated client no longer list them. (#2323) -
PUT /tenant/ui-configauthorizes on identity, not on a role string. The handler trusted a token whose role claim readplatform_admin; the route now needs a platform identity and theplatform.settingspermission, and a tenant token is refused with403whatever its role is called. The per-tenantPUT /admin/tenants/{tenantId}/ui-configof the admin console was already gated this way and is unchanged. (#2323) -
A tenant role can no longer be given a platform role's name. Creating one named
platform_adminis refused. (#2321) -
Usage metering ignores a caller-chosen tenant. An
X-Tenant-IDheader or?tenant_id=on an unauthenticated request could meter usage against any tenant; the tenant now comes only from the authenticated session. (#2321) -
Row-level security is tested against the application's database role. On a Helm install the services connect as a role that is not a superuser, does not bypass row-level security and owns no table. A new test, run against a real PostgreSQL, proves a tenant cannot read, update, delete, re-point or insert across tenants in 16 sensitive tables, and fails if a tenant table has no policy at all. (#2321)
-
Docker Compose installs now enforce row-level security too. Compose services connected to PostgreSQL as the table owner, which PostgreSQL exempts from row-level security, so tenant isolation there rested on per-query filtering alone. Every Postgres-backed service now connects as
crypto_app(row-level security applies; it owns nothing) withBYPASS_DATABASE_URLascrypto_bypassfor the few annotated cross-tenant paths, as the chart does. A one-shotdb-rolesservice, indocker-compose.yml, the production file and the smoke file, gives those roles a login on everydocker compose up, then checks the result and fails ifcrypto_appis a superuser, bypasses row-level security or owns a table, or if either role cannot read the tenants table. It reusesPOSTGRES_PASSWORD, so there is no new secret and an existing.envkeeps working. An existing install upgrades in place withdocker compose up -d.db-rolesexits 0 and stays stopped;make db-rolesruns it by hand. If it exits non-zero,docker compose logs db-rolessays why: when the volume was created from a schema that predates the roles, re-apply the current schema, which is idempotent and keeps your data:
docker compose exec -T postgres psql -U crypto_user -d crypto_inventory \
-v ON_ERROR_STOP=1 -f /docker-entrypoint-initdb.d/01-schema.sql
docker compose up -d
A password authentication failed means POSTGRES_PASSWORD in .env no
longer matches the one the volume was created with. (#2330)
- Alert score history and audit-log partitions are tenant-isolated. Row-level
security is now on
alert_framework_score_snapshots, the compliance score-drop detector's history, which the application role could read across tenants, and the detector now reads and writes it inside a tenant-scoped transaction. Everyaudit.activity_logspartition has row-level security enabled with no policy, so the application role reading a partition by name gets nothing and writes nothing; reads throughaudit.activity_logsare unchanged, and so are the platform's own bypass paths. A partition created later gets the same protection. The schema upgrade enables it on every existing partition once, a metadata change that holds a brief lock on each. Tooling that reads anactivity_logs_y…partition directly as the application role must read the parent instead, or use the bypass role. (#2333)
Platform secrets and session tokens¶
-
In production, services refuse to start on missing or weak platform secrets. With
ENV=production(the chart'sappConfig.envdefault), a service exits at start-up, naming the variable, whenINTERNAL_AUTH_SECRETorENCRYPTION_MASTER_KEYis missing or shorter than 32 bytes (for the services the chart gives each), orJWT_SECRETis set but shorter than 32 bytes. A service that only verifies tokens still starts withoutJWT_SECRETwhen ES256 verification keys are configured (JWT_JWKS_URLorJWT_PUBLIC_KEYS), sojwtSigning.acceptLegacyHmac: falsekeeps working; with noJWT_SECRETand no ES256 source it could verify nothing and refuses.auth-serviceandadmin-service, which issue tokens, always needJWT_SECRET. A fallback written into a service's own configuration can no longer stand in for an absent value. Secrets the chart generates (64 characters) andopenssl rand -hex 32pass. See Secrets management. (#2331) -
Credential encryption can no longer be off in production. Building the credential cipher with an empty key is an error in production instead of a pass-through. Outside production it still works, logs
CREDENTIAL ENCRYPTION IS DISABLED, and the services that store credentials (inventory, integration, notification, cluster-sensor, device-interrogation and admin) report"credential_encryption": "enabled"or"disabled"in their/healthpayloads. (#2331) -
The legacy HS256 session window closes by itself.
jwtSigning.acceptLegacyHmacnow defaults toauto. The chart used to inject the legacy shared secret (JWT_SECRET) into every service that only verifies tokens until an operator turned it off by hand, so any one of them could forge a session for any user and tenant. Withauto, a fresh install starts with it removed; an upgrade removes it once the signing Secret is older thanjwtSigning.legacyHmacWindowHours(192, a refresh-token lifetime plus a day), and an upgrade that first turns ES256 signing on keeps it for that window. The window closes at an upgrade, not on a timer.trueandfalseoverrideauto, andhelm installorhelm upgradeprints which mode was chosen and why.helm templateand Argo CD cannot read the cluster, so they keep the secret; pin the value there. See Secrets management. (#2334)
Outbound requests and scans¶
- Searching treats
%and_as ordinary characters. The user list's search and the external connections' search passed the typed text to aLIKEpattern unescaped, so a search for_or%matched every row. Both now match the characters literally, and so does a backslash. (#2323)
Outbound requests and scans¶
-
Alert webhooks, incident webhooks and platform-staff single sign-on refuse loopback, link-local, metadata and private addresses. The audit service's legacy webhook alert action now uses its own guarded client instead of the one that carries the service's mTLS identity; incident webhooks use the guarded client and read at most a bounded response; and the staff sign-in's token exchange and userinfo request (an
admin_loginprovider can only be Google or Microsoft) use a public-only client, refuse redirects to another origin, read at most 1 MiB and no longer panic on a malformed userinfo URL. A receiver on a private address no longer receives these. (#2325) -
fd00:ec2::254, the EC2 IPv6 metadata address, is refused everywhere. It is a unique-local address, so every private-endpoint opt-in would have allowed it. It is now in the never-reachable list with the IPv4 metadata address. The Alibaba and Oracle metadata addresses are not yet on that list and remain reachable under a private-endpoint opt-in. (#2325) -
Scans run by the Platform Sensor no longer reach the cluster's own pod and Service ranges. A tenant's Discover Assets or Active Scan run from the platform refuses any address inside
networkPolicy.clusterInternalCIDRs, whatever networks the tenant has registered, both when the scan is created and for each host as it runs. Scans run by a tenant's own sensor are unchanged, and the rest of private address space stays scannable. The chart default is RKE2's10.42.0.0/16and10.43.0.0/16; set it to your cluster's real ranges. (#2319)
Edge and network¶
-
The edge Content-Security-Policy is no longer weaker than the application's own. It replaced the single-page app's stricter policy with
connect-src 'self' https:and noobject-srcorbase-uri. In production it is nowconnect-src 'self', pinsobject-srcandbase-uri, and the legacyX-XSS-Protectionauditor is off. The chart template and the compose gateway configuration are regenerated, and a new guard,make edge-csp-test, fails if the weaker policy returns. (#2316) -
The auth tier of the edge rate limit drops from 200 to 50 requests a second on average (burst 100) per source address, and the edge limits are now configurable under
edgeRateLimitin the chart:apiandautheach take anaverageand aburst, andipStrategy.depthoripStrategy.excludedIPschoose which address a request is counted against. Behind a load balancer or a proxy hop the connecting address is the proxy, so every client shares one bucket untildepthis set to the number of trusted proxies; the default is 0, because a depth longer than theX-Forwarded-Forlist makes Traefik answer 500 on every rate-limited route. The Docker Compose gateway takes the same auth numbers and an optionalRATE_LIMIT_IP_DEPTH. A values file written for an earlier release still renders. (#2322) -
The MSP dashboard's WebSocket requires its own origin. An upgrade request whose
Origindoes not name the host that served it, or that carries none, is refused. Neither user interface opens this socket. (#2322) -
The agent mTLS port serves only sensor and agent routes. The listener on
agentMtls.port(default 8444) ofsensor-manageranddevice-interrogation-servicewas handed the service's whole router. Traffic to it reaches the pod by TLS passthrough, so no edge deny, rate limit or body limit applied, and the handshake accepts any client certificate; a self-signed one reached the admin routes, the internal service routes, the tenant API, registration and/health, with the token or signature as the only remaining gate. A route now reaches that port only by being registered through the listener's own list, and everything else answers404before any middleware runs. Sensor-manager serves the eleven sensor calls (heartbeat, discoveries, command acknowledgement, discovery-job completion and unit reports, certificate rotation, config, health, command poll, webhook config and exports); device-interrogation-service serves the five agent calls (jobs, results, heartbeat, certificate rotation and host inventory). Sensors and agents need no change, and enrolment still happens on the public host. One edge case changes: a device agent re-run with--registeragainst a saved configuration whosePlatformURLis already the passthrough address and that still holds its old client certificate used to reach/agents/registerthrough the listener and now gets404; register against the public host. (#2332) -
Seven HMAC-only routes are no longer published at the edge. The audit service's four ingestion routes (
POST /activity-logsandPOST /job-execution-logs/start,/{id}/progressand/{id}/complete) and three inventory routes (POST /assets/auto-approve/agent-host,POST /keys/cloudandPOST /discovery/jobs/{id}/import) are authenticated by the internal service signature, but sat outside every internal path prefix, so the edge forwarded them to the verifier. The edge now denies them by method and exact path on every host, both API versions, with and without a trailing slash, in both the chart and the compose gateway; their tenant-facing siblings (GET /activity-logs,POST /activity-logs/query,GET /keys/cloud) are not affected, and in-cluster callers, which use the service address, are unchanged. The repository's route audit now recognises every spelling of a signed-only route and fails on any middleware that verifies a service signature without being classified. (#2335)
Request limits and data handling¶
-
Anonymous and bulk routes stop reading without limit. Request bodies are capped at 1 MiB on the anonymous sign-in routes of
auth-serviceandadmin-service; at 32 MiB on the agent intakes for discoveries and results, with at most 10,000 discoveries in one batch; and onevaluate/multiple, which also takes at most 100 frameworks and runs 8 evaluations at a time. A request over a limit is refused. (#2319) -
Request logs no longer record query strings.
auth-servicewrote the query twice, through the framework logger and its structured logger, so an OAuth authorizationcode,stateorid_tokenon a callback reached the logs. Both now log the path only, as doesadmin-service; the query is dropped rather than redacted by name. The log written when a handler panics still includes the request line, and other services' request logs are unchanged; none hosts an OAuth redirect target. (#2325) -
A packet-capture job can only open a file in the tenant's own upload directory.
pcap-processoraccepts an absolute path that, once cleaned and with symbolic links resolved, is a regular file inside<PCAP_TEMP_DIR>/<tenant id>/. Anything else, including another tenant's capture, a path that climbs out with.., a link out and a FIFO, is refused permanently, recorded in the audit log aspath_rejected, and deletes nothing.sensor-managerwrites uploads to/tmp/pcap-uploads/<tenant id>/, and neither the chart nor the compose files setPCAP_TEMP_DIR, so nothing changes by default. (#2325) -
Two internal services set HTTP timeouts. The health and fallback servers of
discovery-processor-serviceandresource-tracker-servicenow have read, write and idle timeouts (5, 10, 15 and 60 seconds, as other services use). (#2325) -
A sensor registration key is no longer written to the logs. The key is masked when a sensor registers. (#2319)
-
Database interrogation redacts through every entry point. The connection variants of the database interrogators skipped the settings redaction the registry applies; they no longer do, so no configuration secret reaches stored results. (#2319)
-
Only
httpandhttpsaddresses are rendered as links from stored data. A catalogue or classification-rule URL with another scheme (javascript:, for instance) shows as text in both user interfaces. (#2319) -
A service that verifies internal requests refuses all of them when started without its secret. device-interrogation-service's verifier admitted every request if its secret was empty. (#2316)
-
Chart guard. A test now renders the chart with its defaults and fails if
agentMtls.enabledis nottrue. (#2319)
Added¶
-
Every manufacturer the IEEE registry knows is now recognised from a MAC, and the sensor no longer does the lookup. The platform resolves the vendor at ingestion from the full IEEE registry (all 24-, 28- and 36-bit blocks, about 53,000 prefixes) instead of a 500-row table compiled into the sensor, which left any manufacturer outside it with no vendor at all. A vendor the device itself reported, through an agent, an interrogation, a connector or an import, is never overwritten, and the registry's answer replaces one an older sensor looked up in its own table. A backfill brings every existing asset up to date from its MAC identifiers: it starts about two minutes after inventory-service does, works through each organization in batches, records the registry snapshot it finished under, and is retried hourly for anything it could not complete. A release with a refreshed registry runs it again. Sensors need no upgrade for this.
-
A Smart device class for consumer and office connected devices: TVs and streaming players, smart speakers, doorbells, plugs, bulbs and locks, game consoles and appliances. Roku, Vizio, Ring, Sonos, Bose, Nintendo, PlayStation, Philips Hue, Nest, GE Lighting and Amazon devices are classified from their MAC, and Cast receivers and Matter nodes from what they advertise on the LAN. IoT device keeps its meaning of the enterprise and building estate: cameras, sensors, badge readers and building controls. The learned classifier was retrained with the new class; its answers still go to Approvals for review.
-
Classification from a host's DHCP vendor class. Windows clients, Android devices, Cisco access points and HP JetDirect printers are classified from the vendor class identifier (DHCP option 60) they send when they ask for a lease. Catalog ▸ Classification rules accepts a new
dhcp_vendor_classrule kind, an anchored regular expression over that string, so a platform administrator can add rules beside the shipped ones.
Changed¶
-
The bundled NATS server is upgraded from 2.10 to 2.14. The NATS project no longer patches 2.10;
datastores.nats.image.tagis now2.14-alpine(2.14.7 when built). JetStream streams, durable consumers, their acknowledgement state and stored messages are read in place from the existing volume. A direct 2.10 to 2.14 start was tested on a platform-shaped workload with a durable consumer holding unacknowledged messages, and the stream, acknowledgement floor and backlog came through intact; the NATS project documents upgrading one minor version at a time, so back up before you upgrade. Rolling the chart back afterwards is not clean: NATS 2.10 rebuilds the stream index on its first start and logscorrupt state file, with messages and consumer state intact in testing. Snapshot thenats-data-nats-0volume first if you need a clean rollback. A values file that pinsdatastores.nats.image.tagkeeps the old server until the pin is removed, and an external NATS is unaffected. See Releases & Versioning (Upgrading → Bundled NATS server). The Docker Compose files, nightly integration job and local integration-test script already ran 2.14. (#2314) -
The bundled NATS server is probed over HTTP, not by opening its TLS port. The probes were a TCP connection to port 4222, which speaks TLS by default (
datastores.nats.tls.enabled); each probe closed the connection before the handshake, so NATS loggedTLS handshake error: EOFevery ten seconds. That line came from the probes, not from any client. The StatefulSet now probes/healthzon the monitoring port (8222, plain HTTP in both modes): a startup probe (js-enabled-only, every 5 seconds for up to about ten minutes, for a large JetStream store to recover), a readiness probe (js-enabled-only, every 5 seconds) and a liveness probe (js-server-only, every 10 seconds, so a broken stream or consumer can never restart-loop the pod). ThenatsService had no readiness probe and could be routed to before the server was ready; it no longer is. The pod template changes, so the NATS pod restarts once on upgrade. (#2328) -
The documented signature-verification commands now anchor the signing identity. The
cosign verify --certificate-identity-regexpvalues printed byhelm installand published in the install guide and README were…@.*, which accepts a run of the release workflow from any ref. For Core it is now^https://github\.com/VistaSecurity/VistaPlatform-Core/\.github/workflows/release-core\.yml@refs/tags/v[0-9]+\.[0-9]+\.[0-9]+(-[A-Za-z0-9.]+)?$, a release tag only. The commercial line's regexp accepts a release tag ormain, because the 4.4.0 commercial release was run frommainand carries that identity. Verification of every published 4.x release is unchanged; checked against the 4.3 and 4.4 images and charts. Core images are now built from a Go builder pinned to the exact patch ingo.work, including when the Docker Hardened Image base is used. See Releases & Versioning. (#2329) -
The
helm testpod image is updated tocurlimages/curl8.22.0 (from 8.10.1). No action needed. (#2314) -
Two unused cross-tenant lookups are removed from compliance-engine. Nothing called them. (#2321)
-
Scheduled scans are set up in plain language, not cron. Pick every few hours, daily, weekly on chosen days, or monthly, at a time in your own time zone, and a sentence confirms when it will run; the schedule list reads the same way. A custom cron expression remains available and is validated before saving. Existing schedules keep running exactly as before.
-
Dashboard posture shows its evidence, not a bare percentage. A risk-band donut distinguishes high, medium, low, assessed-clean and unassessed assets; the overview also uses clearer inventory wording and bars for overlapping observed external-connection measures.
-
Discovery navigation groups what you deploy separately from what ran. Discovery Jobs now includes the run outcomes and failure messages that were on Job Logs. Old Job Logs links redirect there, and the rail makes the section hierarchy and activity clearer.
-
An asset still shown as an unknown host is reclassified as evidence accumulates, without a reviewer. A host first seen by ARP and later heard on mDNS, or whose vendor arrives in a later frame, is now classified over everything the inventory holds about it (its MAC identifiers, the services it advertised, its LLDP and CDP capabilities, and a vendor or model from the device itself) rather than the latest frame alone. A sweep runs about five minutes after inventory-service starts and every six hours afterwards, so assets already in the inventory are asked again; each move is recorded in the asset's class history and nothing the sweep decides is added to Approvals. A class a person declared, or a reviewer rejected, is never touched, only a rule's answer is applied (a learned-model answer still goes to Approvals), and an asset that already holds a real class is left as it is. Expect the unknown-host count to fall after the upgrade.
-
Classification rules are written against manufacturer names, not MAC prefixes. Catalog ▸ Classification rules now carries one
oui_vendorrule per manufacturer whose products are all one kind of thing, resolved through the IEEE registry. The 496 per-prefixouirules Vista shipped are retired on upgrade; anouirule an administrator edited or added is kept, and, as the more specific statement about its MAC, still applies before a vendor rule. Prefix rules remain available for administrator overrides.
Discovery pipeline and identity clean-up (rc.4)¶
Each entry below describes what a user or tenant administrator sees; the upgrade consequences are collected under "Upgrade notes in full".
-
One classifier and one external-connections writer for discoveries. The discovery processor no longer classifies network ownership, evaluates the tenant's auto-approval rules or writes 3rd Party Connections itself: it imports every discovery, third-party ones included, and inventory-service classifies each one once (honouring a cloud resource's cloud segment), evaluates the auto-approval rules against that answer, and either resolves it to an asset or records the connection. The import reports per finding what happened and which rule approved it, which is what the discovery row is stamped with; a third-party row is now
observed(it wasauto_approvedwhen the processor wrote the connection itself). The internalnetwork-segments/classify-assetroute, whose only caller was the processor, is removed, and the import no longer reads a caller-suppliedasset_status(#2374). -
3rd Party Connections keep the full certificate assessment, and never invent a source. The one writer now carries the certificate's validation status and quality flags — Certificate Transparency (an explicit "no SCT" stays a no), known-bad issuing CA, EV, OCSP status — read by the same shared reader the certificates table uses, plus the destination name with its provenance and the passive key-exchange label. A third-party discovery with no source address is no longer stored with source
0.0.0.0: it is dropped, reported asdropped/third_party_no_source_ipand counted on the batch's audit record (third_party_dropped_no_source_ip). Certificate validity times in the older sensors' format now parse on every path (#2374). -
A direct scan of a known asset now shows its ports on that asset, and a host's agent report closes the ports it no longer lists through the same identity decision. When a completed TLS or SSH exchange (or a host's own report) is only supporting evidence for exactly one asset, its ports now appear on that asset's Services & Endpoints tab instead of waiting in Discovery → Observations for a Link; its names and addresses are still held for review, and evidence that points at two assets, or at an archived or denied one, attaches nothing. A host inventory's identity and sockets are decided in one place: a report that drops a listening socket marks it closed in the same step that recognises the host (never for a report the platform could not tie to the host), a report without a network-interfaces section is still matched by the address the platform reached it at or the agent reported, and addresses on a host's virtual interfaces (container bridges, tunnels) are kept as evidence rather than used to match other hosts (#2374 WP7, closes the open half of #2205).
-
Hosts on carrier-grade NAT addresses (100.64.0.0/10) are now internal candidates, not 3rd Party Connections. Overlay networks such as Tailscale and ZeroTier hand out these addresses; a host on one that matches no network segment now appears as an Unknown asset for review, as any unregistered private address does, instead of being written to 3rd Party Connections. It reports network type private, so an approval rule on
network.type:privatenow also matches it. Probing is unchanged: automatic scans and sensor enrichment still leave 100.64.0.0/10 alone unless you register the range as a network segment, and a segment learned or imported over it is still public. Behind this, every component now reads one definition of private address space (shared/network/addrscope) instead of four that disagreed — the discovery processor's fallback also now counts loopback as private — andmake auditfails on a new copy (#2374). -
An import reads the tenant's network segments once, not several times per finding. Ownership classification, segment tags, segment enrichment and the identity intake's scoping each used to re-read every network segment of the tenant for every finding; an import of fifty findings now makes one read of the segment list and one of the identity snapshot, and a batch of posted sightings likewise. Fifty cloud resources in one VPC also find (or create) its segment once. The classify route answers from one read instead of two, and Reclassify all reads the segments once for the whole pass. A DHCP lease that changes a segment's posture mid-import makes the next finding read the segments again (#2374).
-
Discoveries reach the processor as soon as they are written (#2374 WP1). Every writer of the
sensor_discoveriesingestion queue — sensor batches, TLS enrichment and self-observation in sensor-manager, scan-plan units from the Platform Sensor and tenant sensors, device-interrogation results, host-inventory connections, cloud discovery, and PCAP uploads — now publishesdiscovery.queue.ready(newDISCOVERY_QUEUEstream) after its transaction commits. discovery-processor wakes on it and drains every claimable batch, instead of claiming one batch per 15-second poll. A writer with no NATS client logs a WARNING once rather than silently relying on the poll. DISCOVERY_POLL_INTERVALnow defaults to 60 seconds and is only the fallback (a lost message, NATS unavailable, a failed row's retry). The poll is no longer tripled when NATS is connected.- discovery-processor no longer wakes on
discovery.jobs.submit, which is cluster-sensor-service's scan-job subject: a stuck-job republish no longer fires processor polls, and a PCAP upload no longer asks cluster-sensor to run a job that does not exist. Its olddiscovery-processor-poll-triggerconsumer is deleted at start-up. - A failed row no longer rejects its whole batch. A failed import chunk or
external-connection upsert now costs only its own rows: they keep
processed_atNULL with the attempt and the error recorded in the newsensor_discoveries.process_attempts/process_errorcolumns, and are retried after a backoff; only they are settledrejected(with the error) once the failure is permanent orDISCOVERY_MAX_RETRIESattempts are spent (default 7, backing off 30 s doubling to 10 min, so a rolling restart of inventory-service is ridden out;DISCOVERY_RETRY_BACKOFF_BASEis honoured exactly). A row that fails to scan, or that landed but could not be stamped processed, is counted the same way, and a TLS-enrichment row folded into a failed survivor shares its failure. Rows that landed are marked processed as before, and the chunks after a failed one are still sent. The processor no longer sleeps between retries while holding the batch and its NATS message. - Compliance evaluates an asset once per discovery import, not once per
event about it. An import announces a changed asset on
compliance.asset.changedand once more for every crypto configuration it created (inventory.lifecycle.crypto.configuration_added), and compliance-engine re-evaluated the asset for each of them with identical results. Requests for the same asset that arrive within two seconds of each other now share one evaluation. A request is only folded into an evaluation that had not started when it arrived, so a late or missing event still gets its own evaluation and a failed evaluation is retried for every request that joined it (#2374). -
A discovered cloud KMS key (AWS KMS, Azure Key Vault, GCP Cloud KMS) now takes one path into inventory. Each key used to be written three times: to the key inventory (Inventory → Keys), to a separate
kms_keystable that no page reads, and into the discovery queue, which classified and identified it as a Key Store Infrastructure Asset with no endpoint. The key inventory now creates or refreshes the Key Store asset itself, with the same identity, class, cloud segment and approval rules as before, so it appears without passing through the discovery queue and existing Key Store assets keep matching.kms_keysis no longer written; the table is kept for now and will be dropped in a later release, together with the experimental endpoints that still read it. Because the key inventory is now the only record, a run whose keys could not be recorded there reports the KMS type as failed instead of logging a warning (#2374). -
Crypto evidence for an unapproved asset is held in one place and replayed by one path (#2374 WP5). Certificates and crypto configurations found on an asset that is not yet monitoring used to wait in three stores — an array in the asset's metadata (replayed only by Discovery → Approvals or an agent install), discovery payloads retained under identity admission
enforce(replayed by the identity sweep), and the observations the enrichment pass re-resolved into them. They now wait in the newdeferred_crypto_findingstable and are replayed by one path on every route tomonitoring: at once on Accept in Approvals and on an agent-driven approval, and within a minute when an auto-approval rule, an Active Scan or an operator's Link on an observation approves the asset — which previously left the crypto parked for good. A replay publishes the same events a live ingest does. At most 50 findings are held per asset; the oldest observed are dropped first.identity_observation_payloadsnow holds passive host observations only. -
IPv6 unique-local and link-local addresses are private. A host seen at an IPv6 unique-local address (
fc00::/7) used to be classified as a public network with a third-party owner, so an approval rule onnetwork.type:privatenever matched and the asset stayed pending. RFC 1918 IPv4, IPv6 unique-local, link-local and loopback addresses are now private; everything else is public. (#2371, #2374) -
Every interface MAC an interrogation reports becomes an identifier. A multi-homed device interrogated through its management interface used to keep only its primary MAC as an identifier, so a sensor later hearing it from another interface's MAC opened a merge proposal. Each usable interface MAC (not empty, malformed, zero, broadcast, locally administered or virtual-router) is now attached to the device. (#2370)
-
Two devices that share a name but not hardware become two assets. When a controller reports two clients (for example two plugs of one model that share a DHCP hostname) with different MACs and addresses, the second used to open a merge proposal, or silently take the first one's MAC. It is now created as its own pending asset, with the shared name staying on the original. Relayed adverts and a changed address on the same MAC are unchanged. (#2372)
-
A class proposal closes when Promote applies that class, and a proposal is never an ancestor of the current class. Approvals no longer asks a reviewer to approve a class the asset already holds, and a measured workstation is no longer offered computer by a vendor rule. Stale proposals leave the queue and the Approvals count. (#2373)
-
A held host inventory no longer closes live ports. When the platform keeps a host report as supporting evidence only, it writes no ports from it and no longer closes the ports the report did not match. (#2366)
Fixed¶
- A newly discovered asset's History shows one "created" entry, not two. The identification engine's creation record and the context the asset was created with (tags, metadata, ownership, attributes) are now one row; before, the asset appeared to have been created twice. Existing duplicate rows are left as they are.
- Ingest reads each identifier's owner once per finding. Discovery ingest built every finding's identity observation twice and looked each identifier up twice — once to decide whether the finding belonged to a denied, elevated or suppressed asset, and again to resolve it. Both decisions now rest on one read, taken under the identifier locks on the same transaction, which also removes the window in which the two reads could disagree. Denied, third-party, suppressed and archived findings behave as before.
-
A provisional asset (a device first heard about across VLANs, #1898) now triggers the 60-second automatic scan sweep as soon as it appears, like any other new asset; it previously waited for the 15-minute full sweep (#2374).
-
Purging a tenant now also removes its rows in
class_floor_sweep_stateandoui_vendor_backfill_state, the two classification state tables that had atenant_idbut no cascading foreign key totenants. -
The asset timeline no longer gets an "updated" entry every time an observation describes itself differently. A host that announces two services, or that two collectors hear in turn, sends a different description of the sighting each time (the capture interface, which mDNS service answered and on which port, whether a reflector relayed it, which collectors heard it). Those were stored as the asset's metadata, replaced as a whole on every observation, and counted as a change, so one quiet host wrote about a hundred entries an hour. They describe the sighting, not the asset, and now never earn an entry on their own, as the batch and sensor ids never did. They are still stored and still show the latest sighting. Entries already written are not removed. (#2289)
-
Passive QUIC is reported once per flow, not once per packet. A QUIC connection opens with a burst of Initial packets, and the sensor sent every decoded one on, so each flow produced about sixteen identical discoveries. QUIC now goes through the sensor's connection cache like passive TLS and VPN detection: one report per flow per dedup window (
capture.dedupTTLMinutes, default 60 minutes), keyed on the destination, the client address, the QUIC version, the server name and the ALPN, and not on the client's ephemeral port.external_connections.observation_countfor QUIC now counts sensor reports, as it does for TLS, not packets. Upgrade the sensor to benefit. (#2338) -
"Scan anyway" scans the rest of the selection. When an Active Scan includes assets outside your registered networks, the platform asks before anything runs; 4.4.0's fix resent only the held assets, so the others in the selection were never scanned while the page said one asset had started. The refusal now carries
pending_asset_ids, the assets a confirmed resend must carry: the whole request normally, because nothing ran, and only the held assets when the platform judged an asset after other batches had already gone out, so a dispatched asset is never sent twice. The dialog resends exactly that set and says which assets are scanned with it. Discover Assets' own "Scan anyway" is unaffected. (#2340) -
A device interrogation that spends its whole time limit no longer fails at the last step. The job ran under one five-minute context and every write after the device answered (findings, discoveries, the completion mark) used it, so a slow device, or a worker shutting down, left the final update to fail with
failed to interrogate device: failed to mark job completed: context deadline exceededand the results unsaved. Those writes, the failure stamp and the discovery-job record now run on their own bounded context (two minutes; thirty seconds for the failure stamp). Stored job errors are redacted and capped at 2,000 characters by eliding the middle, so the failing step stays at the head and the cause at the tail, and an error with an empty message is named by its type. (#2340) -
Probes of a device the platform has already confirmed at a DHCP address attach to that device. An SSH, TLS or QUIC probe of an address whose owner a sensor confirmed by MAC within the last day now matches that owner instead of waiting in Discovery → Observations as "Matches an asset" for a person to link. The address is still a lease: it follows the MAC when another device takes it, and a probe that carries a different NIC, or arrives past the window, is held for review as before. Nothing is back-filled, so an address becomes eligible after its device's MAC is next seen there (minutes on a sensored network), and rows already waiting in Observations stay until you link or dismiss them.
-
A sensor installed with the install command now actually enrolls. The Linux and Windows installers registered the sensor themselves, with a made-up version of 1.0.0, which used up the single-use registration code before the sensor could. The sensor then never sent a heartbeat or submitted a discovery, and the console showed it at v1.0.0 marked as needing an upgrade. The installers now let the sensor enroll itself (its private key never leaves the host), wait until it has, and print its ID and real version. A rejected code stops the install with instructions instead of leaving a service that retries forever. The Windows installer checks for Npcap before it changes anything. A sensor installed with an earlier installer never enrolled and its code is spent: generate a new registration key, run the new installer with it, and delete the old row. Re-running the installer with the key that enrolled a host keeps its identity and upgrades the binary in place.
-
The sensor now runs as a Windows service. The Windows installer registered
crypto-sensor.exeas a service, but the sensor never told the Service Control Manager it had started, so starting the service failed with error 1053 and Windows killed the process. The sensor now reports to the Service Control Manager: it shows as Running once it is capturing, and Stop-Service or a system shutdown sends its last discoveries before it exits. As a service it logs todata\logs\sensor.logunder the install directory, rotated at 10 MB, and writes start and stop events to the Application event log. A restart requested from the console now brings the service back up. The installer shows the sensor's log when registration fails, and stops and disables the service if the registration code was rejected. -
The Linux device-agent installer preserves a registration key unless the host can safely store enrollment. It installs the agent as a hardened service with an owned data directory, and waits until the agent has enrolled. A failed enrollment save is fatal, and a manual start checks that its data path is writable before it contacts the platform, so the key is not spent on an agent that could not keep its certificate. An agent from an earlier release that logged
Failed to save certificates to diskcannot be recovered: delete it in the console, generate a new key and install again. -
Merge proposals now offer "Merge as is", "Review merge" and "Keep separate". The recommended selection is previewed before a one-click merge; conflicts that need a field choice always open review, and merge notes are optional because the audit records the decision details independently.
-
A sensor's own host report can reconcile its physical NICs without treating container bridges as host identity. A verified installation identity keeps its asset match, while a MAC held by another asset opens a reviewable merge proposal; virtual interfaces are excluded unless they carry the control-plane route.
-
Uploaded captures now report truncation and preserve more observations. The packet-capture job identifies packets cut short by its snapshot length (the upload page warns when a capture was truncated, for example by a gateway's built-in capture tool), TLS reassembly handles retransmitted and reordered TCP segments, every observed host is retained, and QUIC conversations are reported once in the same form as live sensor observations. Results are returned to the platform over service-mesh mTLS. Captures processed before the upgrade read as not truncated.
-
Scan jobs no longer stop being picked up after an upgrade or restart. The platform-owned durable consumer remains when a pod drains, where it used to be deleted by the pod that created it, leaving the new pod subscribed to a subject nothing delivered to while queued scans waited. The stuck-job sweep also recreates a consumer that is missing before it republishes queued work. During the upgrade from 4.4.0, whose pods still delete the consumer as they drain, a queued scan can wait until that sweep next runs.
-
A queued scan no longer counts as its own concurrent job. The rate-limit check excludes the job being picked up, so an organization at exactly its concurrent-job limit no longer has its oldest queued scan failed with "too many concurrent jobs" while nothing is running, and Start scan now identifies the exhausted limit in its 429 response.
Dependencies¶
-
Go toolchain 1.26.8 to 1.26.9, which fixes nine standard-library advisories in
crypto/tls,net/httpandmime/multipart(GO-2026-6603, 6605, 6607, 6608, 6610, 6611, 6612, 6613 and 6617). Every builder image and CI job moves with it. This supersedes the next entry's statement that no Go advisories were open: they were fixed in 1.26.9, which arrived after it was written. -
Go toolchain 1.26.6 to 1.26.8, and the public runtime image
alpine3.24.1 to 3.24.2. These are bug-fix patch releases (1.26.7 carriesnet/httpfixes; 1.26.8 carries cgo, compiler, runtime and debug fixes). They fix no Go security advisories: every advisory against Go 1.26 was already fixed in 1.26.6. The bump is taken for the bug fixes and to stay current. (#2312) -
The web server in the
web-uiandadmin-uiimages is rebuilt. Caddy 2.11.4 becomes 2.11.7, built on Go 1.26.8 with newer versions of its dependencies (cel-go, nowcel.dev/cel-go, 0.32.0; OpenTelemetry 1.47.0 andotlploggrpc0.23.0;klauspost/compress1.20.1; chi 5.3.2). This clears every finding the image scanner reported against the 4.4.0 user-interface images: three of Medium severity (GHSA-gcjh-h69q-9w9g, CVE-2026-81871 and CVE-2026-81872) and four of Low severity (CVE-2026-81870).govulncheckon the new binary finds no vulnerability the code calls; the only residual is GO-2026-5932 ingolang.org/x/crypto, which has no upstream fix and is not called. gRPC stays at 1.83.2, because 1.84 is affected by GO-2026-6443. The runtime image is re-pinned to the 2026-10-05 build ofcaddy:2-alpine. The Node builder for these images is nowdhi.io/node:24-alpine3.24-dev; if you build them yourself withNODE_BUILDER, use that or an equivalent Node 24 image. (#2326) -
Go modules are refreshed across every module, including the AWS and Azure SDKs, go-redis 9.23, quic-go 0.63 and gopacket 1.7.4, with every dependency at one version everywhere.
govulncheckreports no vulnerability that the code calls. It lists one in a module the code requires but does not call, GO-2026-5932 ingolang.org/x/crypto, which has no upstream fix. Major versions of the Azure SDK compute, network and storage packages and ofstripe-goare available and not taken. (#2318) -
npm packages are updated to the latest minor and patch releases across the workspaces and the user interfaces, with no major-version changes. The
source-map-jsandpostcss-selector-parseradvisories are cleared. One advisory remains, inbraces(GHSA-vfj7-8cjw-p6xm): no fixed release exists, and it is reached only through Tailwind CSS 3's build-time dependencies, which read static paths in this repository and are not in any image or run at runtime. It is accepted until Tailwind CSS 4 migrates the user interfaces. (#2315) -
Redis, PostgreSQL and InfluxDB images are unchanged. The chart's
redis:7andpostgres:17lines are current in their supported series. InfluxDB stays on 2.8: 2.9 migrates API tokens to a hashed form on first start, which cannot be undone. (#2314) InfluxDB was later removed altogether in rc.6 (see "Removed" under "Chart, signing and scale (rc.6)").
Upgrade notes in full¶
New in rc.6¶
- Pin the datastore storage class before you upgrade a chart install.
datastores.storageClassNamenow defaults to"", which leaves the class to the cluster's default StorageClass, instead oflonghorn. A StatefulSet's volume claim templates cannot be changed, so if your release relied on the old default an unpinned upgrade fails withForbiddenon the Postgres, Redis and NATS StatefulSets. Read the class your volumes use and pass it:
kubectl -n <namespace> get pvc
helm upgrade ... --set datastores.storageClassName=<class>
A release that already sets datastores.storageClassName (or a per-datastore class) needs
nothing. New installs get the cluster default.
- InfluxDB is removed. Nothing in the platform used it. Delete any datastores.influxdb
block from your values file before upgrading: the chart's values schema now refuses it and
the render fails. The upgrade deletes the influxdb StatefulSet; its data volume carries a
keep annotation and stays. Once you are sure you do not need it, delete it with
kubectl -n <namespace> delete pvc <influxdb data claim> (find it with
kubectl get pvc). The influx-* keys drop out of the generated Secret on the next
render. In Docker Compose the influxdb service is gone; remove its leftover volume with
docker volume rm if you want the space back, and the INFLUXDB_* lines in .env are no
longer read.
- Regenerate signed CBOM artifacts (Enterprise). Artifacts signed by earlier releases
used a shared-secret HMAC; they now verify as signature_valid: false with the reason
legacy-hmac; regenerate, and there is no fallback. Generate a new artifact for the same
scope to get an Ed25519 signature, and give recipients the public key from
GET /cbom/signing-keys (pin the kid you send them). The chart generates the signing key
in Secret <release>-cbom-signing and keeps it across upgrades; set cbomSigning.keyPEM
to pin your own, or cbomSigning.existingSecretName with platform.generateSecrets=false.
Enterprise Docker Compose installs generate a key on first start.
- mcp-service now requires Redis. It checks every cached sign-in against the same
revocation list as the other services, which lives in Redis. The chart and the Compose
files already wire REDIS_URL into it; a custom deployment that runs mcp-service on its own
must give it one, or revoked tokens keep working until the cache expires. In production,
mcp-service no longer derives its OAuth discovery address from the request's Host header:
the chart sets OAUTH_CALLBACK_BASE_URL from your hostname, and a Compose production
install must set it (or MCP_ALLOW_HOST_FALLBACK=true), or the discovery endpoint answers
503.
- Plan assignment needs platform.billing. Assigning a plan to a tenant, and the tier
impact-analysis preview, now require platform.billing instead of platform.settings.
The stock Super Administrator and Platform Admin roles hold both and are unaffected. Give
platform.billing to any custom platform role whose holders assign plans; they also still
need platform.settings to reach Plans & Pricing.
- Sensors and device agents migrate to a per-collector secret on their own. A collector
enrolled earlier keeps its registration key until its next certificate rotation, which
issues the secret and removes the key from its config file. No re-enrolment and no action;
a re-run of the same install command still recognises the enrolment.
- GitOps installs are opt-in. platform.generateSecrets defaults to true, so an
existing install renders as before. To move a release under Argo CD or Flux, pre-create the
Secrets the chart would otherwise generate and name them (platform.existingSecretName,
platform.existingDatastoreSecretName, install.existingSecretName,
jwtSigning.existingSecretName, cbomSigning.existingSecretName), copying the current
values out of the generated Secrets so the database passwords and the encryption key do not
change; the render fails, listing what is missing, until they are all named. The
schema-migration and rls-roles Jobs no longer delete themselves ten minutes after they
finish; the next upgrade that changes the schema replaces them.
- The persisted crypto-risk judgement fills itself after the upgrade. Every existing
configuration starts out marked for re-judging; a background sweep works through them every
five minutes, and a Crypto Risks read judges any it has not reached yet, so the first reads
after the upgrade can be slower. No action needed.
New in rc.4¶
- First upgrade to this release moves deferred crypto evidence into
deferred_crypto_findings: the schema migration moves every pending asset's held findings out of its metadata, and every unmaterialized discovery payload out ofidentity_observation_payloads, once. Findings an older pod holds during the rolling upgrade are moved by inventory-service within a minute. Nothing is lost and nothing is replayed twice; an asset already approved whose held findings never replayed has them replayed on the first sweep. - Rolling upgrade: producer pods still on the previous version do not publish
discovery.queue.ready; their rows are picked up by the processor's 60-second fallback poll until they are replaced. No action needed. - The schema adds two columns to
sensor_discoveries(process_attempts integer NOT NULL DEFAULT 0,process_error text); the add is metadata-only, with no table rewrite. - A TLS endpoint the sensor saw passively and then probed is now one discovery, not two. The sensor's TLS enricher reports its probe of an endpoint as a second row next to the passive sighting that triggered it, and both used to be classified, run through the approval rules and imported (or written to 3rd Party Connections) separately. The discovery processor now merges each unambiguous pair (same destination address, port and protocol) into one finding before classification: the probe's measured version, cipher suite, certificate chain and quality flags win, the passive sighting supplies the client address, the requested name and everything the probe did not measure, and an explicit "no" (such as no Certificate Transparency) is kept as a "no". Both rows are marked processed with the same outcome. Pairs that cannot be matched with certainty are left as two discoveries, as before.
- Crypto configurations are now unique in the database, and existing duplicates are collapsed on upgrade (#2374). Ingest has avoided writing a second row for a configuration it already holds since 0.11, but the table never enforced it, so the copies written before then stayed and inflated configuration counts, lists and the PQC-readiness percentage. A configuration is now unique per asset, endpoint, protocol, measured components and discovery method. On upgrade, each set of duplicates is folded into its oldest copy: certificate, algorithm, key and library links and tickets move to it, it keeps the earliest first-seen and latest last-seen times and the most recent evidence, and the other copies are retired (soft-deleted), so their findings resolve on the next evaluation. Merging two assets that share a configuration now folds it the same way instead of keeping two copies.
Upgrade note: the first helm upgrade to this release collapses the
duplicates and builds the new index in the schema-migration Job, which may
take noticeably longer on a large inventory. Discovery writes to crypto
configurations wait while it runs; allow for it in helm upgrade --wait's
timeout. Later upgrades skip the step. As always, pg_dump first.
- Go 1.26.9 (see Dependencies) needs no action; the rebuilt images carry it.
As published with rc.3¶
The summary in CHANGELOG.md now carries a shorter form of these notes. The complete text, unchanged:
Back up your database (pg_dump) first, as always. Upgrade from 4.4.0. The schema upgrade
is additive except for one deletion, described below: it turns on row-level security for the
compliance score history table and every audit-log partition, adds two bookkeeping tables
(oui_vendor_backfill_state, class_floor_sweep_state), one nullable column on
asset_identifiers (device_confirmed_at) and two on pcap_upload_jobs, and widens the rule
kinds classification_rules accepts. As in earlier releases, pass a helm upgrade --timeout
that covers the migration plus the rollout.
- In production a service refuses to start on a missing or weak platform secret. With
ENV=production(the chart default),INTERNAL_AUTH_SECRETandENCRYPTION_MASTER_KEYmust be present and at least 32 bytes, andJWT_SECRET, if set, at least 32 bytes (a token-verifying service may omit it once ES256 verification keys are configured). Secrets the chart generates, andopenssl rand -hex 32, pass. If your platform Secret holds a short custom value, replace it before upgrading: the new pods exit naming the variable, and a rolling upgrade stalls with the old pods still serving. ChangingENCRYPTION_MASTER_KEYhas no in-place re-key, so stored credentials (integrations, devices, connectors) must be entered again afterwards. The Compose production files setENV=productionand follow the same rule. Seesecrets-management.md. jwtSigning.acceptLegacyHmacnow defaults toauto. A fresh install never accepts legacy HS256 tokens; an upgrade stops injectingJWT_SECRETinto every service exceptauth-serviceandadmin-serviceonce the signing Secret is older thanjwtSigning.legacyHmacWindowHours(192). Settrueto keep the old behaviour. With--reuse-valuesthe old default (true) is carried forward; use--reset-then-reuse-valuesto takeauto.helm templateand Argo CD cannot read the cluster and keep the secret until you pinacceptLegacyHmac: false. The noteshelm upgradeprints say which mode was chosen.- Docker Compose installs now connect as
crypto_app, withcrypto_bypassfor the cross-tenant paths, through a one-shotdb-rolesservice. Rundocker compose up -d(with--buildas usual); no new secret is needed. Ifdb-rolesexits non-zero because the volume's schema predates the roles, re-apply the schema as its log describes (the command is in the full list of changes). - Rolling back to 4.4.0 after this upgrade needs your backup, not
helm rollback. The 4.4.0 schema re-adds the narrower rule-kind constraint onclassification_rules, which the newoui_vendoranddhcp_vendor_classrules violate, so its schema job would stop. Together with the NATS note below, plan a rollback as a restore. - The shipped per-prefix MAC classification rules are retired. The upgrade deletes the
496
ouirules Vista shipped and nobody edited, and the seed supplies oneoui_vendorrule per manufacturer in their place. Anouirule an administrator edited or added is kept, and, being the more specific statement about its MAC, still applies before a vendor rule. Catalog ▸ Classification rules shows what remains. - Vendors and classes fill in after the upgrade, in the background. A backfill resolves every existing asset's vendor from its MAC identifiers, and a sweep (after start-up, then every six hours) reclassifies assets still shown as unknown hosts; each move is recorded in the asset's class history. A vendor a device agent, interrogation, connector or import reported is kept. Sensors need no upgrade for this.
- The lease-fresh DHCP rule starts empty. Nothing is backfilled: an address becomes eligible after the platform next sees its device's MAC there (minutes on a sensored network), and a probe is matched this way only within 24 hours of that sighting. Until then probes behave as in 4.4.0, and a row already waiting in Observations stays until you link or dismiss it.
- A sensor or device agent installed by an earlier installer must be installed again,
with a new registration key. Earlier installers spent the single-use code themselves, so
such a sensor shows at v1.0.0 with no heartbeat. Generate a new key (Discovery → Sensors &
Agents → Register), run the new installer and delete the old row; re-running it with the key
that enrolled a host upgrades in place. A device agent that logged
Failed to save certificates to diskmust be deleted and installed again with a new key. Windows sensors need Npcap installed first. - During the rolling upgrade a queued scan can wait briefly. The 4.4.0 pods delete the scan-job consumer as they drain; the new pods own it, and the stuck-job sweep re-creates it before republishing queued work. Nothing is lost, and the next upgrade does not repeat this.
-
Single-sign-on sign-ins in progress during the upgrade must be started again once, for staff sign-in to the admin console and, on Enterprise, tenant and platform sign-in. An external Redis must be 6.2 or later.
-
The bundled NATS server upgrades from 2.10 to 2.14, and rolling back is not clean. JetStream data is read in place. A later rollback to a release on NATS 2.10 rebuilds the stream index on first start and logs
corrupt state file; messages and consumer state were intact in testing. Snapshot the NATS volume (nats-data-nats-0) before upgrading if you need a clean rollback. A values file that pinsdatastores.nats.image.tagkeeps the old server until you remove the pin; use--reset-then-reuse-values. An external NATS is unaffected. The NATS pod also restarts once for its new health probes. See the "Bundled NATS server" note inreleases.md. - Set
networkPolicy.clusterInternalCIDRsto your cluster's real pod and Service ranges. The Platform Sensor now refuses scan targets inside them, whatever networks a tenant has registered. The default is RKE2's (10.42.0.0/16,10.43.0.0/16); on another network plugin the default protects the wrong ranges and may block scans of addresses that are the customer's own. Scans run by a tenant's own sensor are unchanged. - The edge's auth rate limit drops from 200 to 50 requests a second (burst 100) per
source address. If a load balancer or proxy sits in front of Traefik, set
edgeRateLimit.ipStrategy.depthto the number of trusted proxies (1behind a single ALB); otherwise every client shares one bucket and a busy office can throttle everyone. A wrong non-zero depth, longer than theX-Forwarded-Forlist, makes Traefik answer500on rate-limited routes, so check that two clients do not share a bucket.edgeRateLimit.apiandedgeRateLimit.authset the ceilings. - Platform-admin sign-in is rate limited: five attempts a minute per account, twice
that per address (
ADMIN_LOGIN_RATE_LIMIT). A throttled attempt answers429withRetry-After. - Sign-in answers differently. A wrong password gets the same invalid-credentials
answer for an unknown, deactivated or single-sign-on-only account, and
POST /auth/methodsno longer returns the organization's id except where single sign-on needs it. Integrations that read those distinctions from a failed sign-in stop seeing them. PUT /auth/mewith a differentemailanswers403 email_change_not_supported. No screen used it.- Pending sensor registration keys need
sensors.create.GET /sensor-manager/sensors/pendingrefuses viewers and any role without it. - Tenant administrators can no longer edit or delete platform-wide alert rules, the
admin console API (
/admin-service/admin/**) refuses tenant tokens, and a tenant role can no longer be namedplatform_admin. - Tenant single sign-on endpoints must be
https://when saved (Enterprise). A provider saved with anhttp://URL keeps working until its URL is edited; embedded credentials in a URL are refused. ai.allowPrivateEndpoints: trueno longer allows a model on the pod's own loopback (Enterprise), andCONNECTOR_ALLOW_PRIVATE_ENDPOINTS=falsenow also disables private AI endpoints. Private-network and in-cluster addresses are unchanged.GETandPUT /sensor-manager/admin/settingsare removed and answer404. They let a tenant member read, and a holder ofsettings.updateoverwrite, registration limits shared by every tenant, and no screen or client used them. The limits are fixed at their previous defaults (50 pending sensors, a 60-minute key lifetime, address validation off).PUT /tenant/ui-configneeds a platform identity andplatform.settings. A tenant token gets403whatever its role is called; it used to trust the token's role string. The admin console's per-tenantui-configroute is unchanged.- The user list's and external connections' search treat
%and_literally. - Request bodies are capped: 1 MiB on anonymous sign-in routes and
evaluate/multiple(at most 100 frameworks), 32 MiB on agent submissions with at most 10,000 discoveries per batch. A request over a limit is refused. - The edge Content-Security-Policy is
connect-src 'self'in production. A deployment whose user interface calls another origin is blocked from doing so. - Platform-admin sessions: resetting a password, deactivating or deleting a platform
user ends their refresh sessions; an access token already issued keeps working until it
expires (up to one hour). The OAuth consent decision and the MSP dashboard WebSocket
now reject cross-site requests, and the WebSocket requires an
Origin.
Highlights as published with rc.3¶
-
Account takeover through the profile closed.
PUT /auth/melet any valid access token change the account's sign-in email, and the forgot-password flow then delivered to the new address. It now answers403. -
Sign-in tells an attacker nothing. Before the password was checked, the sign-in routes disclosed whether an address was unknown, deactivated, single-sign-on only or a platform administrator, and answered unknown addresses faster. They no longer do. Platform-admin sign-in is rate limited per address and per account, and the edge's auth-tier limit is lower and configurable.
-
Platform-administrator sessions end when they should. Resetting a password, deactivating or deleting a platform user ends that user's sessions, and signing out revokes the access token it was given.
-
Authorization tightened where it was loose. Pending sensor registration keys need
sensors.create; tenant administrators cannot change platform-wide alert rules; the admin console API refuses tenant tokens; the sensor registration settings no tenant member should have been able to change are gone; usage metering ignores a caller-chosen tenant. Row-level security is now tested against the application's database role. -
Outbound requests and scans stay out of the cluster. Scans run by the Platform Sensor refuse the cluster's own pod and Service ranges; webhooks and staff sign-in refuse loopback, metadata and private addresses; and tenant single sign-on and the AI assistant's private-endpoint setting (Enterprise) refuse loopback, metadata and cluster addresses.
-
Bounded requests and a stricter edge. Anonymous sign-in routes, agent intakes and bulk evaluation cap their request bodies; the edge Content-Security-Policy is same-origin for connections; OAuth consent and the MSP dashboard socket reject cross-site requests; sensor registration keys and OAuth codes are no longer logged; packet-capture jobs open only the tenant's own upload directory.
-
Weak platform secrets stop a production install, and legacy tokens stop on their own. In production every service refuses to start when
INTERNAL_AUTH_SECRETorENCRYPTION_MASTER_KEYis missing or under 32 bytes, and credential encryption can no longer be silently off.jwtSigning.acceptLegacyHmacdefaults toauto, so the chart removes the legacy HS256 shared secret from token-verifying services by itself. -
Row-level security reaches Docker Compose, score history and audit partitions. Compose services connect as the application role instead of the table owner, and the compliance score history and the audit-log partitions are tenant-isolated.
-
The agent port and the edge expose less. The sensor and device-agent mTLS port serves only agent routes, and seven service-to-service routes are no longer published at the edge.
-
Single sign-on is bound to the browser and uses PKCE. Staff sign-in to the admin console, and tenant and platform sign-in (Enterprise), close a login-CSRF path.
-
Verification commands are anchored. The documented
cosign verifyidentity regexps accept only the release workflow run from a release tag (the commercial line also frommain). -
Far more of the inventory is classified. MACs resolve against the full IEEE registry (about 53,000 prefixes) on the platform; a new Smart device class covers consumer devices; a host's DHCP vendor class (option 60) classifies Windows, Android, access points and printers; and unknown hosts are reclassified as evidence accumulates. A class a person declared or a reviewer rejected is never touched.
-
Installers that actually enroll. The sensor installers used to spend the single-use registration code themselves, so a sensor installed with the install command never sent a heartbeat. The Linux and Windows installers now let the sensor enroll itself, wait until it has and print its ID and version; the Linux device-agent installer does the same and never spends a key it cannot save; and the sensor now runs as a Windows service.
-
Scheduled scans in plain language. Pick every few hours, daily, weekly on chosen days or monthly, at a time in your own time zone, and a sentence confirms when it will run. Existing schedules keep running as before; a custom cron expression remains available.
-
DHCP addresses a device was just seen at decide a match. An SSH, TLS or QUIC probe of an address whose owner a sensor confirmed by MAC within the last day attaches to that owner instead of waiting in Discovery → Observations as "Matches an asset".
-
Clearer screens. A posture donut on the dashboard, run outcomes on Discovery Jobs (Job Logs links redirect there), and Merge as is / Review merge / Keep separate on merge proposals.
-
Operational fixes. Passive QUIC is reported once per flow instead of about sixteen times; Scan anyway scans the whole selection; a device interrogation that uses its full time limit completes with its results; the bundled NATS server's probes no longer log a TLS error every ten seconds; scan jobs are still picked up after an upgrade or restart, and a queued scan no longer counts as its own concurrent job; uploaded packet captures report truncation and keep more of what they observed; and a sensor's own host report can reconcile its physical NICs without treating container bridges as host identity.
-
The bundled NATS server is 2.14, replacing 2.10, which the NATS project no longer patches.
-
Dependencies are current. The user-interface images' web server (Caddy 2.11.7) is rebuilt and clears every finding the image scanner reported against 4.4.0. Go 1.26.8 and alpine 3.24.2 are bug-fix patch releases and fix no security advisories; Go modules and npm packages are refreshed, with no called Go vulnerability and one accepted, build-time-only npm advisory.
-
The asset timeline no longer grows with each sighting's own description, the remainder of what 4.4.0 began.