Unvalidated sortProperty on GET /api/iot-hub/installedItems was passed straight into Spring Data's Sort.by, so a client could trigger a 500 PropertyReferenceException by naming an unknown entity field. Add ALLOWED_SORT_PROPERTIES on IotHubInstalledItemEntity (createdTime, itemName, itemType, version) and call validatePageLink in the service so the failure is a 400 with a clear message.
If iotHubInstalledItemService.save throws after the local entity was already created, the entity was orphaned — visible in the tenant but invisible to IoT Hub update/delete. Extracted the per-type entity-deletion dispatch from deleteInstalledItem into a deleteEntityForDescriptor helper and call it from doInstallVersion and registerDeviceInstall on tracking-save failure. registerDeviceInstall now also wraps reportVersionInstalled best-effort to match doInstallVersion.
The post-install Thread.sleep was driven by installTimeoutMs from the uploaded solution.json with no upper bound — a malicious or misconfigured template could pin an HTTP worker thread indefinitely. Cap it at a configurable max (default 60s) exposed as iot-hub.max-install-timeout-ms with an IOT_HUB_MAX_INSTALL_TIMEOUT_MS env override.
Stream entries through a fixed buffer and abort with IOException when the archive crosses any of three configurable thresholds: total uncompressed bytes, per-entry uncompressed bytes, or entry count. Defaults — 200 MiB total, 50 MiB per entry, 10 000 entries — and env-var overrides IOT_HUB_MAX_UNCOMPRESSED_ARCHIVE_BYTES / IOT_HUB_MAX_UNCOMPRESSED_ENTRY_BYTES / IOT_HUB_MAX_ARCHIVE_ENTRY_COUNT are exposed under iot-hub in thingsboard.yml.
Add youtubeUrl to CreatorView and render it alongside the other creator social links. Reindents the link block so it nests correctly inside the surrounding flex column.
Root entries other than WIDGET or SOLUTION_TEMPLATE should always be installed even when a prior version is already installed — only widgets and solution templates are deduplicated by item id at install-plan resolution time.
Switch from base64 data URLs to URL.createObjectURL for images extracted from the device package zip. Revokes them in ngOnDestroy to avoid leaking memory, and avoids embedding multi-megabyte data URLs in the DOM.
Split the detail dialog close button into desktop (inline in header) and mobile (absolute, top-right) variants. Switch meta-grid first column to minmax(0, …) so the creator section can shrink, and truncate the creator name with a tooltip. Add 8px right margin to the card type label.
Move the zip fetch from IotHubActionsService into TbDeviceInstallDialogComponent.ngOnInit so the dialog opens immediately and shows its own loading spinner instead of blocking the caller. Drops the now-unused fetchZipData helper and zipData field from DeviceInstallDialogData.
Drop the (optionSelected) handler and route navigation through the option's (click) instead. Setting [value]="searchText" makes Material write the typed string back through ngModel (a no-op) rather than the MpItemVersionView object, so the input keeps the user's query after the detail dialog closes and searchText.trim() in seeAllResults() stays safe.
Route all parse-failure throws through a single parseFailure(action, itemType[, section], cause) helper so the user-facing wording lives in one place and update paths share the template with install paths.