Vista Platform 4.1.0 — full list of changes¶
Released 2026-09-30. 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.
Added¶
- CMDB schedules run. A CMDB profile set to Hourly, Daily or Weekly is now synced on that schedule (Enterprise). Before, the schedule was saved and shown and nothing ran it. A scheduled run follows the platform's direction — pull for SolarWinds, pull then push for ServiceNow, Device42 and Oomnitza — and is skipped for disabled profiles, suspended or canceled tenants and tenants without CMDB sync. One run per profile at a time across replicas: Sync or Pull during a run answers 409 "a sync for this profile is already running". Runs are limited to 60 minutes, and a run cut short by a restart is closed as failed ("interrupted (service restarted)"). Pulls are now recorded in the sync history too.
- CMDB field mapping, incremental sync and retirement (Enterprise; Oomnitza
first). Each CMDB platform now ships a default field mapping that a profile can
override through
GET/PUT /cmdb/profiles/{id}/mapping. A mapping is checked on save, before every run and at every start; one that no longer checks out pauses the profile ("Mapping invalid" on its card) instead of syncing wrongly. Runs send and write only what changed (full re-sync daily, or with?full=true); a record retired or removed in the CMDB marks the asset absent from that source instead of being deleted; records Vista pushed are recognised when a pull reads them back, so they are neither duplicated nor bounced back. A pull reads only mapped fields, and mapped personal data is recorded on the asset for data-subject requests. - ServiceNow sync works the way a ServiceNow CMDB expects (Enterprise). Pushes go through ServiceNow's Identification and Reconciliation API under a discovery source you name, so a server its own Discovery already found is updated rather than duplicated, its classification is kept, and your reconciliation rules decide which source owns which field. Hardware is sent as its standard CI class (servers, computers, PCs, switches, routers, firewalls, load balancers, access points, storage, printers). Approved relationships between synced CIs are sent as standard ServiceNow relationships. An asset archived in Vista retires its CI (install status Retired). A pull reads every page of every class it covers — Linux, Windows and other servers, PCs, network gear, VMs, applications and business services — classifies each by its ServiceNow class, reads only mapped fields, and fetches only what changed since the last pull.
-
CMDB profiles can reach an on-premises CMDB. New per-profile settings: "CMDB is on our own network" (private addresses, by opt-in, audit-logged) and a CA bundle for a CMDB behind an internal CA. Both are checked on save. Redirects to another host are refused, since credentials travel with them.
-
NetBox connections accept a custom CA certificate. A NetBox whose HTTPS certificate your own CA issued can now be reached by pasting that CA's certificate (PEM) into the connection. It is added to the trust store for that connection only; it is checked on save, and a pasted private key is refused. There is deliberately no "skip verification" option.
-
SIEM export shows delivery health and reaches on-premises receivers (Enterprise). Each integration in Security & Trust → SIEM Export now shows its status, last success, last error, and events sent / failed. An integration can opt in to a private-network receiver and carry a custom CA bundle, and Send test event reports the receiver's own status and message on failure.
-
Generic webhooks can authenticate and are signed. The connection form now offers none / bearer token / basic / one custom header, and every delivery carries
X-Vista-Event-Id(identical across retries — an idempotency key),X-Vista-Timestampand, when the channel has a signing secret,X-Vista-Signature: sha256=<HMAC-SHA256(secret, timestamp + "." + body)>. A new webhook gets a generated signing secret, shown once; rotate it by entering a new one. Verification recipes are in the notification-provider guide. - The Email connection card says when email can't be sent. A new
GET /tenant/delivery-statusreports whether an SMTP host is configured. - A dedicated integration service (Enterprise). A new backend that owns every exchange with a third-party system of record. SIEM export runs in it from this release (see Changed); NetBox and CMDB sync follow. It deploys with the commercial chart only (Deployment, Service, service-mesh certificate, gateway routes) and reports its version and edition to an authenticated caller. The commercial image set grows to 19. Core installs are unchanged — the service does not exist there, and the Core admin console no longer offers what it serves.
- Identity: two records the rules are sure are one device are merged for you (default on, turn it off under Settings → Policies → Identification rules). The same MAC address or serial number seen directly on the device (or reported by a controller that manages it), both records on the same network segment, nothing contradicting it and neither ever marked keep separate — never an address alone, never a generic or synthetic name. A record you typed in is the survivor and keeps your values. Each merge is listed under Discovery → Approvals → Merged automatically as Merged by rule, with its evidence. Turned off, the same cases become ordinary merge proposals.
- Identity: a MAC worked out rather than seen. A device whose IPv6 address is built from its network card's MAC (EUI-64), or whose 12-hex-digit serial number is its MAC, now carries that MAC as an inferred identifier, so it matches the record a sensor or controller built from the MAC. Inferred identifiers vote only after a stated one of the same kind.
- Identity: DHCP is understood per network segment. A network segment's DHCP setting (Settings → Network segments) can be set by an operator, measured by a controller that answered, or inferred from DHCP traffic; a lower source never overwrites a higher one. On a DHCP segment the address follows the MAC that now holds it, and an address alone no longer creates a provisional record.
- Identity: generic names never decide. A host name many unrelated devices carry
(
iphone,android,localhost,printer, or any name three or more live assets already hold) is still recorded, but it is never the reason two records match, and a record that matched only on such a name is not offered as a merge candidate. - Merge proposals show how alike the two records are. The Approvals card adds "The two records themselves score N%", and an identifier with more than one value (a second MAC) now matches when any value agrees.
- Exports say when an asset was never assessed. The inventory CycloneDX xBOM
carries
vista:asset:risk_assessedand omitsvista:asset:risk_scorefor an asset nobody has assessed; OCSF omitsdevice.risk_score; the CSV cell is blank.
Changed¶
- The NetBox connector runs in the integration service (Enterprise). It moved out of the inventory service; connections, tokens, schedules and run history carry over, and an import produces the same segments, assets and counts. The connector now writes the inventory only through the inventory service's identity engine, approval rules and plan limits, over an internal service-to-service API — never its tables.
- ServiceNow profiles: API token removed; certificates and keys are no longer pushed (Enterprise). ServiceNow's APIs take a username and password or OAuth client credentials; the "API token" option could never authenticate, and a profile saved with it now has to be edited to one of the two. Test now reads one CI instead of an admin-only table, so a least-privilege integration user passes. Certificates, keys and crypto configurations are no longer sent as ServiceNow CIs (they went to classes a standard instance does not have); the crypto posture stays on each asset's description. Records a previous version created in those classes are left as they are. Pushes now need a discovery source value on the instance — see the ServiceNow section of the CMDB guide.
- SIEM export no longer loses events when a pod dies (Enterprise). It moved
from the audit service into the integration service, and it no longer
buffers events in memory: each integration reads the stored audit log
forward from its own position, kept in the database, and moves that position
only after its receiver accepts a batch. Delivery is now at-least-once
across pod loss (was at-most-once): a batch in flight when a pod dies is
re-sent, so receivers should de-duplicate on the event
id. A receiver that is down, or that refuses events outright (a bad token, an untrusted certificate), now holds the integration's position instead of dropping events, and delivery catches up once it is fixed; the 10,000-event buffer and its drop-oldest overflow are gone. Events refused for their content (an Elasticsearch mapping conflict) are still passed over, and are now shown as Refused. One replica delivers a given integration at a time. The list shows Delivered through — how far each integration has got. - A new integration starts at "now" and never floods its receiver with the retained trail; Start from sends up to 168 hours of recent history first. Re-enabling a disabled integration starts it at "now" again.
- The SIEM API moved to the integration service's own route prefix (only the admin console calls it; no compatibility route).
- Events reach the SIEM about five seconds after they are stored.
- The SIEM API is denied on the tenant (web-UI) host; the admin console
reaches it on the admin host. And, like the NetBox and CMDB connectors,
the integration service's dial guard is given the cluster's own pod and
Service ranges (
networkPolicy.clusterInternalCIDRs), so a destination allowed onto a private network still cannot reach in-cluster addresses. - The Core admin console no longer shows SIEM Export. SIEM export is an Enterprise capability served by a service Core does not have, so the entry is gone rather than showing an upgrade notice.
- The audit service stamps each event's
created_atwith the database clock, and serves an internal, service-to-service feed of stored events (HMAC-signed calls only; denied at the edge on every host). - Channel Test reports the real reason. A channel that did not deliver now
answers 422 with a sanitized
reasoninstead of a generic 500 ("Test failed"), covering every channel type including refusals of internal addresses. The reason is drawn from a fixed vocabulary and never contains the URL, query string, headers or tokens. The same reason is stored in Delivery History. - Email with no SMTP configured is an explicit, permanent outcome. It used to
fall back to
localhost:587and retry every alert five times. It now fails once, with "Email delivery isn't configured by the platform operator", and is not retried. An explicitSMTP_HOST(dev/compose) still works. - PagerDuty events carry a stable
dedup_key(from the alert id, else the event id), the alert engine's auto-resolve is sent as aresolve, and Test triggers and immediately resolves a throwaway incident instead of leaving one open. - Routing-rule Alert source now offers
discovery,ticketing,remediation_plansandbilling, and a test fails if a producer publishes a source the list lacks. - Slack messages escape
&,<and>; email subjects use the alert's title and bodies list readable fields instead of a JSON dump;GET /platform/channelsmasks credentials like the tenant endpoints; digest-only rows in Delivery History read "queued for digest". - Platform integrations of type Slack, PagerDuty, Datadog and Splunk are
refused. Creating, editing or testing one through the admin API now answers
400and names where the job is done (notification channels, or the SIEM export page). Nothing ever acted on these rows, and their "test" only checked that a credential field was filled in before reporting success.
Fixed¶
- The connector catalogue says only what the code does. Settings →
Integrations → Available connectors is generated from the connector registry,
and the registry had drifted from the platform (integrations review M32–M35).
Cloud: no provider claims serverless functions any more (no Lambda, Azure
Functions or Cloud Functions collector exists), and each lists the classes its
collectors really produce, including CDN distributions, API gateways, key
stores, virtual networks and subnets. Notifications: Slack and PagerDuty are
listed where they are delivered (notification channels), joined by email,
generic webhook and the in-app bell; Microsoft Teams stays absent. SIEM: Splunk,
Datadog, Elasticsearch and the generic webhook are shown as Enterprise and
Configured by your platform operator, with no Add button for a tenant.
SBOM upload is live (CycloneDX 1.4–1.7 and SPDX 2.2/2.3; with no target asset it
creates an application that waits in Approvals). The
customtype, which nothing reads, is no longer a card that points at the wrong form: it is listed as not yet available. CMDB and NetBox entries state the classes their pulls can create (taken from each platform's mapping template; NetBox lists every class its role table yields) and the real conflict rule for NetBox. New tests derive those class lists from the collectors, the CMDB mapping templates and the NetBox role table, so they cannot drift again; the schema check now covers the notification-channel and SIEM tables and every value each CHECK accepts. - Oomnitza pulls classify assets and keep numeric ids. Every pulled Oomnitza
asset became a
server; it is now classified from its type (a Laptop is a laptop, an unknown type is generic hardware), and anequipment_idreturned as a number is no longer dropped. - Device42 sync sends devices, correctly, and never by name alone (Enterprise).
Certificates, keys and crypto configurations are no longer pushed into Device42
as devices (one bogus "TLS 1.2" device per host, and Device42 matched them by
name, so they collapsed into one). Only approved infrastructure assets are sent,
with the calls Device42 documents (form-encoded
POST/PUT /api/1.0/device/; the old JSONPOSTto/devices/was not the create endpoint). A device is matched by its Device42 id, then its serial number; a record with neither whose name Device42 already uses is skipped with the reason instead of overwriting that device. Device42 owns a device's name, serial number, UUID, hardware and OS after creation; Vista rewrites only the Notes. Pull reads every device in pages of 1000 (it stopped at 1000), classified by Device42 type. Sign-in is Username & password: the "API token" option sent a Bearer token Device42 does not accept, and a profile saved with it (or an API key, or OAuth2) is now refused at save. - SolarWinds is pull only for real, and pulls every node. The push code that
wrote certificates, keys and configurations into Orion as nodes is gone. Pull
reads all of
Orion.Nodes(it stopped at 1000) and classifies each node from its MachineType / description (a switch is a switch, not a server); unrecognised nodes are generic hardware. Sign-in is Username & password — the "API key" option sent a Bearer header SWIS rejects; saving one is now refused. Orion's self-signed certificate is trusted with the profile's CA bundle. - Oomnitza pulls every asset and no longer receives certificates and keys.
The pull stopped at the first 1000 assets; it now pages (
limit/skip). Only infrastructure assets are pushed; certificates, keys, crypto configurations and libraries are not assets in an asset register (assets already created that way stay in Oomnitza). - Scheduled cloud discovery delivers buckets, databases and key stores. A scheduled run dropped every S3 bucket, RDS instance, Azure storage account and SQL database, GCP bucket and Cloud SQL instance, and KMS key store, and the rows it did write lacked the provider resource id, certificate quality flags, OCSP and region. Scheduled runs now write exactly what a clicked Discover run writes, report their counts from what landed, and complete (or fail) the identity refreshes they serve — partial runs included.
- Azure and GCP Test Connection contacts the provider. Azure's test
reported "validated" for any three strings and GCP's only parsed the key.
Azure now obtains a token and reads the subscription from Resource Manager;
GCP exchanges the key and reads the project. Failures show the provider's own
error (
AADSTS…,AuthorizationFailed,invalid_grant,PERMISSION_DENIED). A GCP key whosetoken_uriis not Google's OAuth endpoint is refused. - Azure and GCP runs report each resource type. One refused collector used to abort the whole run (load balancers, proxies) or read as "success, 0 found" (Key Vault, storage, SQL, KMS, Cloud SQL). Every type is now collected independently and its outcome, with the provider's error code, is on the job.
- Azure Key Vault, storage and SQL, and GCP Cloud KMS, storage and Cloud SQL can be chosen in the Discover dialog, and scheduled runs collect them.
- A refused
ec2:Describe*call in a region is shown on the job rather than reading as a region with no instances. - The cloud integration's Enabled switch works: runs of a disabled integration are refused. Jobs queued before a tenant was suspended no longer run.
- A credential saved under a retired encryption key says "re-enter it" in Test Connection instead of being sent to the provider, and editing an integration no longer re-encrypts such a credential into a usable-looking but wrong value.
- One list of cloud secret keys. Platform-shared cloud integrations created through admin-service now encrypt the AWS external ID, Azure client ID and GCP service-account key, and the legacy device-credential job payload no longer hands the agent an encrypted username.
- CMDB push sends only configuration items. Assets pending approval, denied, archived (including from the Stale lens), merged away or deleted were pushed to the customer's CMDB, along with certificates, keys and crypto configurations not in use on any approved asset and destroyed certificates and keys. A push now sends approved assets and what is in use on them.
- SolarWinds is pull-only. Sync is no longer offered for SolarWinds and the API refuses it: it pushed certificates, keys and crypto configurations into Orion as nodes.
- CMDB Test, Sync and Pull say why they failed. Test used to answer "Connection test failed" and Sync "Invalid request" whatever went wrong; both now give the sanitized reason (private address, untrusted certificate, rejected credentials, disabled profile, pull-only platform, …) without URLs, headers or response bodies. Pull no longer echoes the raw error. The sync history shows each item the CMDB refused, and the Pull message counts failed and held-for-review records.
- CMDB push honours the batch size, and records each batch before sending
the next, so a push that stops part-way does not create the same CIs again on
the next run.
batch_size(1–1000) andscheduleare validated on save. -
The CMDB sync history (
GET …/cmdb/profiles/{id}/jobs) now requires View Settings, like the profile reads. Deleting an unknown CMDB profile answers 404 instead of 500. A disabled CMDB profile can no longer be pulled from. -
NetBox connector: seven smaller defects. The same prefix in two VRFs is imported once and the second copy is reported as skipped (with both VRF names) instead of counted as matched. The connection card shows the last error and Drift shows the reason a comparison failed, rather than a generic message. The Import response carries the run's real start time. A run left
in_progressby a service restart is closed as failed ("interrupted (service restarted)"). Only one import of a connection runs at a time across replicas; pressing Import during one answers 409. New connections default to a manual schedule. A sensor-discovered asset that NetBox matches keeps its original discovery source. - SIEM export integrations are saved and survive restarts (Enterprise).
They were held in memory only: every restart or
helm upgradelost them, and with more than one audit-service replica each pod had its own list. They are now stored in the database with credentials encrypted and write-only, every replica picks up a change within 10 seconds, failed deliveries are retried with backoff,batch_sizeandflush_interval_secare honoured, and a graceful shutdown flushes buffered events. (Delivery was then made durable in this same release — see "SIEM export no longer loses events when a pod dies" under Changed.) -
SIEM export sends what each destination actually accepts (Enterprise). Splunk now receives HEC event objects with
Authorization: Splunk <token>and the event's own epoch time (it was sent a JSON array with a Bearer token); Datadog receives Logs API v2 logs withDD-API-KEYat the intake for the chosen site, split to Datadog's per-request limits; Elasticsearch receives_bulkNDJSONcreateactions (index or data stream) with API-key or basic auth, and a 200 with per-document errors is counted per document, retrying only the documents Elasticsearch deferred; the generic webhook receives a JSON array with none / bearer / basic / custom-header auth and the same HMAC signature as notification webhooks. Splunk and Elasticsearch are verified end to end against the real products; Datadog is checked against its documented API only. The form shows only what is implemented — the LEEF and CEF format choices and the unimplemented "Basic Auth" option are gone, and the API refuses them. Audit events arriving over the message bus (including batched entries) are now forwarded too; only directly-posted ones were before. -
Enterprise and MSP service images no longer crash under load. The commercial backend images were obfuscated with garble's
-tinyoption, which hides function names the Go runtime relies on to decide when it may safely interrupt a running goroutine. Under heavy work (large host-observation imports ininventory-service, the startup re-evaluation sweep incompliance-engine) a service could corrupt its own scheduler and exit with code 2, printing nothing — and an import in flight when it did could be dropped after its retries ran out. All commercial images now build without-tiny; obfuscation of names and string literals is unchanged. Core images were never affected. - Cloud storage, databases and key stores now become assets when identity admission is enforced — the default for new tenants. S3 buckets, RDS, Azure storage and SQL, GCP storage and Cloud SQL, and KMS / Key Vault keys stayed in Discovery → Observations as "a name was observed without a confirmed device or address binding" and never reached Inventory. A resource listed by the platform's cloud collector is now admitted on the provider's resource id and matched on it by the next run; observations parked by earlier runs are linked by the next discovery run, with no data migration. A name reported by anything else is still not enough.
- Outbound integrations work behind an egress proxy. With
HTTPS_PROXYset, the SSRF guard judged the proxy's address instead of the destination, so a proxy on a private address made every webhook, Slack, PagerDuty, SIEM, NetBox and CMDB call fail with "refusing to connect to internal address". The guard now judges each request's destination (every redirect hop included) and trusts the configured proxy; loopback, link-local/metadata and the platform's own cluster ranges stay refused. See Outbound connections for what the guard can and cannot see behind a proxy. networkPolicy.egressEnabled=trueno longer cuts off the Enterprise integrations.inventory-service(NetBox and CMDB connectors) and the integration service (SIEM export) are now innetworkPolicy.externalEgressBackends;audit-servicestays there for S3 archival. So arecompliance-engine(framework authoring, remediation drafts) andcbom-service(the comparison narrative), which call the configuredai.providerendpoint.- Suspended, canceled and deleted organizations are no longer notified.
- device-interrogation-service's HTTP fallback to notification-service now presents its client certificate under mTLS.
-
Security-incident notifications now reach someone. monitoring-service raised incidents from log patterns (PII in a log line, a critical security event, an authentication-failure pattern) and tried to tell operators through a channel table nothing ever populated, so they went nowhere. They are now published to the notification service as platform notifications (
monitoring/security_incident) and delivered through the platform channels and rules under Settings → Notification Delivery — including the default pack on a fresh install. They are platform-scoped: a tenant's own rules never see them, even when the log line belonged to that tenant. -
Sensor and device-agent install steps point at downloads that exist. Discovery → Sensors & Agents → Register used to send operators to a download "from the platform" that the platform does not have, and to an
install-sensor.shthat releases do not ship. The steps now fetch the binary and installer from the public release matching the running platform version (the version shown on the About page; on a development build with no release version they link the releases page instead of guessing). Serving binaries from the platform for air-gapped installs remains out of scope. - Partial delivery is recorded as partial. A notification delivered in-app but
not by email used to be written to Delivery History as
sent, and a retry that finally worked or was abandoned changed nothing the tenant could see. Status is nowsent,partialorfailedfrom the per-channel outcome, the retry worker updates the row when a retry finishes, and the page lists each channel's result ("in_app ✓, email ✗ (retrying)"). A row whose rule matched but whose channels all failed no longer reads "no rule matched". Error text is deliberately not stored — a transport error can carry a webhook URL, which is a credential. - Azure cloud integrations run. Every Azure integration saved through the UI
stored its tenant and subscription ids in a form the client could not read, so
every run failed with
failed to decrypt subscription_id. Existing integrations work unchanged, with no re-entry. GCP key names (service_account_key,credentials_json) are now recognised as secrets (see Security). - Scheduled cloud runs collect what a clicked Discover run collects. A schedule created in the UI named no resource types, so every scheduled run refreshed only compute enumeration and still reported success. A schedule with no resource types now uses the same set a manual run pre-selects for that provider; a source-refresh run is never widened.
- Two tenants can name a cloud integration the same. The uniqueness rule
covered every tenant, so when one tenant called an AWS integration "Production" no
other tenant could, and the dialog said only "Failed to create integration". The
name is now unique per tenant (and among platform-shared integrations), a clash
answers 409
An integration named "X" already exists, and the dialog shows it. - NetBox imports read every record. The connector stopped as soon as a page came
back with fewer than 250 records; NetBox clamps the page size to its own
maximum, so with a cap of 100 every import read 100 records per kind and reported
success. It now follows the response's
nextlink, and an empty page that links onward is an error, not a silent truncation. - A long NetBox import no longer reports "failed" while it succeeds. Run now and Drift can take minutes, and the server dropped the connection at its two-minute write timeout, so the browser saw a 502 for an import that finished. Both now extend their own write deadline.
- NetBox scheduled imports skip tenants that cannot use them. Suspended, canceled and deleted tenants, and tenants that lost the NetBox entitlement, were still imported on schedule — and could not even disable the connection, because every route answered 402. A reactivated tenant's overdue connections run on the next tick.
- A deleted NetBox connection or CMDB profile name can be used again. The uniqueness rule also counted soft-deleted rows, so re-creating a deleted name answered 500. It now applies to live rows only and a genuine clash answers 409 with the name.
- CMDB profiles can be created and edited. The Settings → Integrations dialog
never sent the two mapping fields, the server bound them as SQL
NULL, and every create and edit failed with 500. Omitted mappings are stored as empty on create and kept on update. - CMDB sync jobs finish. Completing a job failed on an untyped SQL parameter and
the error was thrown away, so every job stayed
in_progressforever. Jobs now complete or fail with a recorded reason, per-item errors reach the job summary (first 50, each cut to 500 characters, with totals), a profile'ssync_errorreads "N of M items failed; first error: …", and the profile card shows it as a tooltip. An invalidplatform_type, blank or over-long name or malformed connection settings answers 400, and an unknown id answers 404 instead of 500 (or a panic on Pull). - ServiceNow OAuth2 sign-in works. The connector sent the client secret as a
bearer token and never exchanged it. It now requests an access token from
/oauth_token.do, caches it until shortly before it expires, and exchanges once more on a 401. - Audit alert rules validate their input. Creating or editing a rule with a
missing or unknown
rule_typeorseverityreached the database constraint and answered 500; it now answers 400 naming the field. - Exports stop reporting "never assessed" as risk 0.
risk_score0 in the database means both "assessed clean" and "never assessed"; three exports and two CSVs asserted the first. The inventory CSV, the inventory CycloneDX xBOM and OCSF now carry no risk score for an unassessed asset (the xBOM addsvista:asset:risk_assessed = false), the Data Protection CSV leaves the cell blank when the drawer says "not assessed", and the map's GraphML and Cytoscape exports omit it — for which the neighbourhood and impact nodes now carryrisk_assessed. An asset assessed at 0 still exports 0. - Core CBOM comparison answers 402. Core mounted no comparison routes, so
/cbom/compareanswered a bare 404 and an AI assistant could conclude the artifacts did not exist. It now answers 402 "not included in your subscription", and the MCP compare tool reportsavailable: falsewith reasonedition. - A compliance summary for a framework you have not activated is an answer, not a
500. The API returns 403
framework_not_activated, and the MCP tool reports it as unavailable rather than as a tool error. - An SBOM uploaded to a merged-away asset lands on the survivor. The upload stored software on the archived tombstone, where nothing shows it. It now follows the merge to the surviving asset (bounded at 8 hops) and says so in a warning.
- Merge proposals stay tidy. Merging left the proposal that launched it pending; a proposal whose live candidates collapse to one is now resolved; a single owner of every identifier is a supporting sighting, not a question; a link made on an address alone carries no device; and there is one pending proposal per pair, with new evidence folded in.
- Identical evidence that now resolves to a different asset no longer fails ingest. After a segment was switched to DHCP, the same sighting could resolve to a different asset than before and the observation link was refused, rolling back the whole ingest for that finding on every recurrence. The observation is now split instead.
- Personal API tokens are for AI assistants (MCP) only. Nothing about how a
token behaves changed; what the product told you did. Only the MCP server
exchanges a token for a session; the inventory and audit APIs answer 401 to one,
so the SBOM-upload-from-CI and scripted audit-export examples are gone, and the
documented Claude.ai / ChatGPT / Gemini "paste the URL and sign in" flow is
withdrawn until hosted assistants can register (they cannot complete sign-in
yet). My Profile → API Tokens now offers only the scopes an MCP tool uses
(
assets.read,compliance.read,reports.read); existing tokens keep working. Header-capable MCP clients (Claude Code, scripts) connect withAuthorization: Bearer <token>. - Tenant notification channels no longer show their credentials. Every channel
response masks them — a URL keeps its scheme and host (
https://hooks.example.test/••••), other secrets keep their last four characters — and the edit dialog shows a blank field with a "Leave blank to keep the current value" hint. On update a credential that is absent, blank or the masked form we sent keeps the stored value; an omitted header is a deleted header. Delivery and Test use the stored values. See Security. - Dependency updates. Docker base images,
github/codeql-action/upload-sarif4.38.1 → 4.38.2, the npm minor/patch group (8 updates across 3 directories), the Go modules group (5 updates across 2 directories), andbrace-expansion2.1.4 → 2.1.7 (two directories). - Build. The Go toolchain guard now dry-runs a dist image target and asserts it
is built with the exact
go.workpatch release, not a floating minor tag.
Removed¶
-
The CMDB profile's On conflict setting (platform wins / CMDB wins / skip). Nothing read it: every push upserts. The documented Vault credential references (
api_token_ref,client_secret_ref) and "Sync Deletions" option are removed from the docs; nothing implemented them. -
The Audit alert rules list and enable/disable toggle under Settings → Alert Rules. Nothing evaluated those rows, so the toggle did nothing; the alert catalog on that page is unchanged.
- The SMS option in the admin console's channel form (there is no SMS sender).
- monitoring-service's own notification delivery (Slack, webhook, PagerDuty and
email senders reading
monitoring_notification_channels) and the unused email plumbing in cluster-sensor-service's alert service. Alerts from both go out through the notification service. Themonitoring_notification_channelstable is no longer created, and is dropped on upgrade when it is empty; a table an operator filled by hand is left in place, unread.
Security¶
- Any signed-in tenant member could read notification-channel credentials.
GET /tenant/channelsand/tenant/channels/{id}had no permission gate and the service decrypted the configuration into the response: the Slack webhook URL (the URL is the secret), the PagerDuty routing key, and webhook tokens, passwords and header values. Both reads now needsettings.read, and — because the seededviewerrole holds it — every channel response is masked regardless (see Changed). Rotate any credential that mattered. - Any signed-in tenant member could read CMDB profile credentials.
GET /cmdb/profilesand/cmdb/profiles/{id}had only the entitlement gate and returnedpassword,api_tokenandclient_secretin plaintext, to viewers, billing admins and read-only tokens alike. Both reads now needsettings.read, no response carries a secret value (has_password,has_api_tokenandhas_client_secretsay whether one is stored;extra_headersvalues are blank), and an update with an omitted or empty secret keeps the stored one. Rotate any CMDB credential that mattered. - GCP service-account keys were stored unencrypted. The cloud-integration handler
encrypted a fixed list of keys that did not include
service_account_keyorcredentials_json, though the client decrypted them. The list now comes from one place per provider, so it cannot drift from what the client reads. The upgrade does not rewrite keys already saved — rotate the key and save the integration with the new one. - A NetBox URL could reach the cluster's own pods and Services. The NetBox
connector's private-endpoint opt-in defaults on, but the guard that refuses the
platform's pod and Service ranges was configured only for device interrogation, so
a tenant could reach in-cluster addresses, and Test's distinct errors ("refused"
versus timeout versus "not NetBox") worked as a port scanner. The chart now gives
inventory-servicethe samenetworkPolicy.clusterInternalCIDRs(and refuses anextraEnvoverride of them), as it already did for device interrogation. - The SIEM API is denied on the tenant host, and the audit service's new internal event feed answers only HMAC-signed service calls and is denied at the edge on every host.