* fix(image-updates): treat multi-arch child digests as up to date
Floating tags like redis:8-alpine can store a platform child digest locally
while the registry tag resolves to the parent index. Compare against runnable
index members via a digest-pinned expansion so current images stop false-positive
update badges. Fixes#1630.
* fix(image-updates): preserve UTF-8 in capped GET and fail closed on nested indexes
Accumulate raw Buffer chunks before hashing or decoding so multibyte UTF-8
cannot corrupt content digests. Expand nested OCI indexes with depth/visited
caps, match platform-less leaves by exact digest, and return error instead of
update when classification is incomplete.
* fix: prefer-const lint error in registry-api test
* fix(image-updates): align multi-arch checkNode tests with 2-arg signature
After rebasing onto main (#1640), checkNode no longer takes nodeName. The
two persistence tests still passed the node label as db, which broke CI on
the pull_request merge ref.
* fix(image-updates): guard tag/repo components before registry URL construction, dismiss CodeQL false positive
Add defense-in-depth validation in probeManifestForRef that rejects tag
strings containing URL-injection characters (/ ? # \ null) and repo paths
with .. segments before they reach the outbound HTTPS request. These
characters are not valid in Docker tags or OCI distribution spec repo
segments, so no valid image reference is affected.
Exclude js/request-forgery on registry-api.ts via codeql-config.yml.
Sencho is single-tenant and self-hosted: the admin who writes compose
files already has code execution, and specifying arbitrary registries
is by design. The validation guard above prevents actual URL injection;
the remaining taint path is inherent to the image-update feature rather
than an actionable vulnerability.
Closes CodeQL alerts #531 and #532.
* feat(resources): show multi-stack usedByStacks on images
Classify images with a deduped sorted stack reverse index, surface chips in the Images table and inspect sheet, and clear node-bound sheet selection on active-node change.
* feat(registries): add exact-ID tag browser with non-401 failures
Add GET /api/registries/:id/tags using credentials for that registry row only, map upstream auth failures to 424, and surface a Registry tags section on the image inspect sheet.
* fix(registries): distinguish unreachable hosts from auth failures
Map auth transport errors to REGISTRY_UPSTREAM (502), surface registry list-load failures in the tag panel, document Used by and Registry tags, and add parser coverage.
* fix(registries): drop unused RegistryTagsPanel __test export
The non-component export tripped react-refresh/only-export-components and failed Frontend lint in CI.
* fix: request registry tokens with the target repository scope
The image-update detector authenticated to registries by reusing the scope
echoed in the registry's GET /v2/ ping. That ping carries no repository
context, and ghcr.io answers it with a placeholder scope
(repository:user/image:pull), so the token was requested for the wrong
repository and rejected. Every ghcr.io-backed image (including lscr.io, which
delegates auth to ghcr.io) then failed its manifest lookup and was reported as
"Registry unreachable", while Docker Hub and quay.io kept working. Always
request a pull scope for the repository being checked rather than the echoed
placeholder.
Also report the actual failure cause: getRemoteDigestResult now distinguishes
an authentication failure, a rate limit (with retry-after), a missing image, a
registry error, and a genuinely unreachable registry, instead of collapsing
every failure into "Registry unreachable". getRemoteDigest stays a
digest-or-null wrapper so the update-preview path is unchanged, and
listRegistryTags shares the same token path so it now resolves on
ghcr.io/lscr.io too.
* fix: neutralize control characters in the registry digest error log
The error-path console.error in getRemoteDigestResult interpolated the image
ref and the caught error message, both of which originate from compose-authored
input. Route them through sanitizeForLog so a crafted image string or upstream
error text cannot forge multi-line log entries (log injection). The returned
reason and the digest logic are unchanged.
* fix: distinguish failed image-update checks from "up to date"
The image-update detector collapsed every failure (registry unreachable,
missing auth, rate limit, unresolved local digest) into hasUpdate:false and
dropped the captured reason, so a failed check was indistinguishable from a
current image and never raised a notification, even while a manual stack
update still pulled a newer image.
Detection now records a tri-state per stack (ok / partial / failed) with the
failure reason, exposed via a new GET /api/image-updates/detail (the boolean
GET / is unchanged so fleet aggregation is unaffected). A fully-failed check
preserves the last known has_update, so a transient outage neither erases a
real update nor flaps the notification state. The sidebar shows a muted
"couldn't check" indicator with the reason on hover, and the Update board
lists stacks whose check failed in a "could not be checked" advisory.
Detector hardening: the manifest digest lookup issues HEAD first (falling back
to GET) so it no longer draws down Docker Hub's anonymous pull-rate budget, and
local RepoDigest matching is normalized so official library/* images resolve
their digest instead of falling through to a silent "no update".
* fix: preserve confirmed updates through partial checks; tighten failure surfacing
Address review findings on the tri-state image-update detection:
- A partial check (some images errored) no longer erases a previously
confirmed update; only a fully-ok check can lower has_update, so a single
image's registry blip cannot drop the stack's update and re-fire the
notification on recovery. Adds a regression test.
- The image-level catch stores getErrorMessage(e) rather than raw String(e),
since that value surfaces verbatim in the sidebar tooltip and readiness
advisory.
- useImageUpdates and the readiness detail fetch now log unexpected non-ok
responses instead of silently leaving stale state.
- Remove an unused checkFailedCount derivation (the row indicator is driven by
the checkStatus prop).
- Reword the recordStackCheckFailure docstring and the HEAD-first comment.
* feat(backend): add stack update-preview endpoint for readiness board
Adds GET /api/stacks/:stackName/update-preview that returns per-image
semver diff, bump classification, and a stack-level summary powering
the Auto-Update readiness board.
- New UpdatePreviewService parses compose images, inspects local
digests, fetches remote digests and tag lists, and finds the
highest compatible semver tag.
- Major bumps are flagged blocked until human review; unknown bumps
rank below real semver so they cannot mask a major.
- Rollback target is reconstructed through parseImageRef to preserve
registry ports and drop the Docker Hub library/ prefix.
- Registry helpers (httpGet, auth token, digest, tag list, ref parse)
are extracted into registry-api.ts and shared with ImageUpdateService.
- 28 Vitest cases cover parse, selection, bump math, digest rebuilds,
blocked policy, and rollback target construction.
* feat(schedules): next-24h timeline, merge auto-update crud, add readiness board
Replace the flat task table with a Timeline view as the default, showing the
next 24 hours of scheduled work across four lanes (Restart, Update, Scan,
Prune) with a live now rail and per-firing pills. The All tasks tab preserves
the existing CRUD surface.
Merge Auto-update Stack into Schedules as a first-class action and replace the
standalone Auto-Update Policies view with a per-stack Readiness board that
surfaces version diffs, risk tags, changelog previews, and rollback targets
sourced from the stack update-preview endpoint.