Skip to content

πŸ› fix(watcher): anchor digest comparison to repo-matched RepoDigests candidates - #670

Merged
scttbnsn merged 3 commits into
dev/v1.6from
fix/v1.6-repo-digest-anchor
Aug 7, 2026
Merged

πŸ› fix(watcher): anchor digest comparison to repo-matched RepoDigests candidates#670
scttbnsn merged 3 commits into
dev/v1.6from
fix/v1.6-repo-digest-anchor

Conversation

@scttbnsn

@scttbnsn scttbnsn commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

Fixes #669 β€” found by the adversarially-verified digest-pipeline audit behind discussion #657.

The bug

getRepoDigest() blindly took RepoDigests[0]. A local Docker image can carry multiple repo@digest entries for one Image ID (pull/retag accumulation, no ordering guarantee), so a stale or foreign-repo entry at index 0 anchored digest.repo β†’ digest.value to the wrong manifest and produced a persistent digest-update false positive that survived applying the update β€” the refresh path re-derived from the same index every cycle, so it never self-healed.

The fix

  • getOrderedRepoDigests (docker-helpers): returns RepoDigests entries whose repo component matches the container's own image reference (reusing the imgset docker.io/library//registry-1 aliasing normalization), in original order; falls back to the full list when nothing matches so behavior never regresses to undefined.
  • image.digest.repoDigests (optional, additive model field): carries the ordered candidate list, re-derived from the live inspect at discovery and on every refresh cycle. Old stored containers lack it and keep single-anchor behavior.
  • handleDigestWatch walks the candidates: cheap raw-equality check first (no registry call), then a poll-cycle-cached normalize-and-compare per anchor, skipping anchors whose manifest fetch fails (a GC'd manifest is itself evidence the anchor is stale). It re-anchors digest.repo to the candidate that matched, so an already-poisoned store self-heals. A genuine same-tag republish (no candidate matches) still flags an update exactly as before; if every candidate fails to normalize, the failure propagates (findNewVersion rejects, watchContainer keeps the previous verdict) instead of coercing into a false "no update" β€” deliberately avoiding the Manifest "created" fetch failures are swallowed, making result.created nondeterministicΒ #606 anti-pattern.
  • Refresh keeps a stored anchor that is still among the fresh candidates, so the watch-cycle re-anchor doesn't ping-pong with the refresh path.

Deliberately untouched: getRawDigestUpdate/getRawUpdateKind/hasRawUpdate, the watch-decision logic, registry providers, UI.

Also in this PR

πŸ”’ Pinned js-yaml 3.15.1 in e2e (override) for GHSA-5p4m-2wfm-xmqj, published mid-soak and turning the qlty medium+ gate red repo-wide β€” same handling as the rc.12 brace-expansion/ip-address pins. e2e is the only workspace carrying js-yaml.

Verification

  • Full app suite: 12,564 tests green, 100% statements/branches/functions/lines.
  • New coverage: foreign-repo-first selection, same-repo stale-first (raw and normalized match), genuine republish still flags, per-anchor failure skip, all-anchors-fail rejection, legacy no-repoDigests container unchanged, self-heal of a poisoned store, refresh anchor stickiness, version !== 2 fallback unchanged.
  • Full pre-push gate green (qlty, sharded coverage, builds, e2e, playwright, zizmor).

Not GA-gating: 1.5.x-era bug, ships in v1.6.1.

Changelog

  • πŸ› Fixed Docker digest selection for stale and foreign RepoDigests entries.
  • ✨ Added ordered repository-digest candidates and persisted them across discovery and refresh cycles.
  • πŸ”§ Added digest-anchor re-anchoring for valid candidates and poisoned stored state.
  • πŸ”§ Filtered malformed repository-digest entries with empty repository or digest components.
  • πŸ”§ Preserved fallback behavior, legacy-container behavior, and non-v2 manifest handling.
  • πŸ”§ Preserved digest-fetch error propagation when all candidates fail.
  • ✨ Added coverage for candidate selection, refresh, republishing, failures, and anchor repair.
  • πŸ”’ Pinned js-yaml to 3.15.1 in the e2e workspace for GHSA-5p4m-2wfm-xmqj.

Concerns

  • Verify that getImageReferenceCandidates uses the same normalization rules as image-set matching.
  • Verify that refresh cycles preserve the selected valid anchor when candidate ordering changes.
  • Verify that all-candidate manifest failures remain errors and do not become false β€œno update” results.
  • Verify that the repoDigests schema remains compatible with legacy containers that lack the field.

…candidates

A local Docker image can carry multiple repo@digest entries for one Image
ID (pull/retag accumulation, no ordering guarantee). getRepoDigest blindly
took RepoDigests[0], so a stale or foreign-repo entry landing first
anchored digest.repo -> digest.value to the wrong manifest and produced a
persistent digest-update false positive that survived applying the update.

- πŸ› getOrderedRepoDigests filters RepoDigests to entries whose repo
  component matches the container's own image reference (reusing the
  imgset docker.io/library aliasing normalization), falling back to the
  full list when nothing matches
- ✨ image.digest.repoDigests (optional, additive) carries the ordered
  candidate list, re-derived from the live inspect every discovery and
  refresh cycle
- πŸ› handleDigestWatch walks every candidate (cheap raw match first, then
  a poll-cycle-cached normalize-and-compare per anchor, skipping anchors
  whose manifest fetch fails) and re-anchors digest.repo to the candidate
  that matched, so a poisoned store self-heals; a genuine same-tag
  republish still flags an update, and total normalization failure still
  propagates instead of coercing into a false "no update"
- πŸ› refresh keeps a stored anchor that is still among the fresh
  candidates, so it does not ping-pong with the watch-cycle re-anchor

Fixes #669
Fresh advisory (CVE-2026-59870 backport gap, quadratic CPU in !!omap
resolution) published against js-yaml 3.x < 3.15.1 / 4.x < 4.3.1 turned
the qlty medium+ gate red repo-wide. e2e is the only workspace carrying
js-yaml (3.15.0, transitive via artillery); exact-version override edited
per the established pattern since npm update cannot move overrides.
@vercel

vercel Bot commented Aug 7, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
drydock-website Ready Ready Preview Aug 7, 2026 3:52pm
drydockdemo-website Ready Ready Preview Aug 7, 2026 3:52pm

@coderabbitai

coderabbitai Bot commented Aug 7, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. πŸŽ‰

ℹ️ Recent review info
βš™οΈ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: a5db72cd-5a52-416f-a9c3-1f441fa17d1c

πŸ“₯ Commits

Reviewing files that changed from the base of the PR and between 8ff3403 and 8f24069.

πŸ“’ Files selected for processing (2)
  • app/watchers/providers/docker/docker-helpers.test.ts
  • app/watchers/providers/docker/docker-helpers.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • app/watchers/providers/docker/docker-helpers.ts
  • app/watchers/providers/docker/docker-helpers.test.ts

πŸ“ Walkthrough

Walkthrough

Docker image metadata now stores ordered repository digests. Helpers match digest entries to image-reference candidates and retain fallback ordering. Image orchestration refreshes and preserves valid digest anchors during discovery and updates. V2 digest comparison checks multiple raw and normalized anchors, re-anchors matches, and retains legacy behavior for older data and non-v2 manifests. The E2E package pins js-yaml to version 3.15.1.

Possibly related PRs

  • CodesWhat/drydock#610: Incorporates the Docker multi-anchor digest and repository-digest identity fixes into the v1.6.0-rc.7 release.
  • CodesWhat/drydock#547: Modifies Docker image orchestration and digest reconciliation behavior.
  • CodesWhat/drydock#536: Modifies stored Docker image state during container rebuilds.
πŸš₯ Pre-merge checks | βœ… 2
βœ… Passed checks (2 passed)
Check name Status Explanation
Linked Issues check βœ… Passed The changes filter malformed and foreign RepoDigests, preserve fallback behavior, store candidates, and self-heal stale anchors as required by issue #669.
Out of Scope Changes check βœ… Passed All code changes support issue #669 or the explicitly documented js-yaml security pin; tests cover the implemented Docker behavior.
✨ Finishing Touches
πŸ“ Generate docstrings
  • Create stacked PR
  • Commit on current branch
πŸ§ͺ Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/v1.6-repo-digest-anchor

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❀️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

πŸ€– Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@app/watchers/providers/docker/docker-helpers.ts`:
- Around line 430-440: Update the mapping/filtering flow around the fullDigest
parser to reject entries when either the repository portion or digest portion is
empty, including repo@ and `@sha256`:...; only return entries with both non-empty
components, and add coverage for both cases.
πŸͺ„ Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
βš™οΈ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 13fc5e6b-9f10-49d4-9452-90dc6dc3f0df

πŸ“₯ Commits

Reviewing files that changed from the base of the PR and between e31db2e and 8ff3403.

β›” Files ignored due to path filters (2)
  • CHANGELOG.md is excluded by !CHANGELOG.md
  • e2e/package-lock.json is excluded by !**/package-lock.json, !**/package-lock.json
πŸ“’ Files selected for processing (8)
  • app/model/container.ts
  • app/watchers/providers/docker/docker-helpers.test.ts
  • app/watchers/providers/docker/docker-helpers.ts
  • app/watchers/providers/docker/docker-image-details-orchestration.test.ts
  • app/watchers/providers/docker/docker-image-details-orchestration.ts
  • app/watchers/providers/docker/image-comparison.test.ts
  • app/watchers/providers/docker/image-comparison.ts
  • e2e/package.json

Comment thread app/watchers/providers/docker/docker-helpers.ts
…component

CodeRabbit: repo@ parsed to an empty digest, which passes the model's
!== undefined guards and would compare as a phantom change; @sha256:...
parsed to an empty repo. Both now filtered with the other malformed shapes.

@biggest-littlest biggest-littlest left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed the digest-anchor selection change and the CodeRabbit round: candidate filtering falls back safely, the re-anchor path is registry-verified, and failure propagation is preserved. LGTM.

@scttbnsn
scttbnsn merged commit 3118850 into dev/v1.6 Aug 7, 2026
25 checks passed
@scttbnsn
scttbnsn deleted the fix/v1.6-repo-digest-anchor branch August 7, 2026 16:15
scttbnsn added a commit that referenced this pull request Aug 9, 2026
17-file identity roll-forward from rc.12 to rc.13, dated 2026-08-08.

- CHANGELOG: new `[1.6.0-rc.13]` section from Unreleased content β€” #670
digest re-anchoring, #672 self-hosted Star History, #673 nanoid pin,
#682 node 24.19.0 + trivy 0.73.0 base-image bumps, #683 icon-bundle
alias resolution β€” plus compare-link rotation
- README: version badge + rc.13 highlights block
- Demo mocks, site config/content, docs API pages, quickstart, updates
highlights, identity-test constants, changelog-link pairs

Guarded by `scripts/release-identity.test.mjs` (4/4) and the web scripts
suite (56/56). Once merged: dev→main wholesale-tree sync, then dispatch
`release-cut.yml` for `v1.6.0-rc.13`. GA candidate clock restarts β€”
earliest GA β‰ˆ Aug 16.

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Changelog

πŸ”§ **Changed**
- Advanced the release identity from `1.6.0-rc.12` to `1.6.0-rc.13`,
dated August 8, 2026.
- Updated README, demo mocks, site configuration, roadmap content, API
examples, quickstart, and update highlights.
- Updated changelog comparison links and release identity test
constants.
- Added release notes for digest re-anchoring, self-hosted Star History,
`nanoid` pins, Node 24.19.0 and Trivy 0.73.0 base images, and
icon-bundle alias resolution.

πŸ”’ **Security**
- Documented workspace-wide `nanoid` security pinning.
- Documented base-image CVE remediation.

## Concerns

- Sync the `dev` tree to `main` after merge.
- Dispatch `release-cut.yml` for `v1.6.0-rc.13`.
- Confirm the expected GA date remains approximately August 16.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
scttbnsn added a commit that referenced this pull request Aug 9, 2026
Wholesale-tree sync ahead of the v1.6.0-rc.13 cut (same pattern as
#667). The single commit carries the exact tree of `dev/v1.6` at
b98808e onto `main` β€” after merge, `git diff --quiet origin/main
origin/dev/v1.6` holds.

Content on dev since rc.12:
- #669/#670 β€” release-cut digest re-anchoring
- #672 β€” self-hosted Star History chart
- #673 β€” NAS acceptance follow-ups
- #681 β€” v1.7 dependency ordering (forward-ported)
- #682 β€” CVE base-image bumps + digest-pinned Node base guard test
- #683 β€” icon-bundle alias resolution (fixes blank Audit/registry icons
shipped in rc.12)
- #684 β€” rc.13 identity roll-forward (17 files, CHANGELOG, README)

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Changelog

- ✨ Added repository-aware Docker digest selection and multi-anchor
comparison.
- ✨ Added self-hosted, theme-aware Star History SVG API with pagination,
caching, deadlines, and fallback rendering.
- ✨ Added icon alias resolution and bundle coverage tests.
- ✨ Added required icon bundle entries and updated icon mappings.
- πŸ”§ Updated Node and Trivy image digests.
- πŸ”’ Pinned `nanoid` to `3.3.18` across workspaces.
- πŸ”’ Added a digest-pinned Node 24 Alpine base-image guard test.
- πŸ”§ Updated `js-yaml` override to `3.15.1`.
- πŸ”§ Updated release documentation, examples, mocks, badges, and tests to
`1.6.0-rc.13`.
- πŸ”§ Removed the external Star History CSP allowlist and embed.

## Concerns

- Verify the Star History route stays within GitHub API rate limits
under concurrent requests.
- Verify cache headers do not serve incomplete or repository-specific
fallback responses incorrectly.
- Verify Docker digest re-anchoring preserves legacy behavior for stored
containers without `repoDigests`.
- Verify icon aliases with rotation or flipping have acceptable fallback
behavior.
- Run the Docker, Star History, icon-bundle, release-identity, and
workspace dependency tests.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants