Skip to content

v1.7.0-rc.5

Pre-release
Pre-release

Choose a tag to compare

@github-actions github-actions released this 28 Aug 03:11
0f41c0a

v1.7.0-rc.5

Full Changelog: v1.6.1-rc.5...v1.7.0-rc.5

[1.7.0-rc.5] — 2026-08-27

Security

  • An unauthenticated Portwing hello could throw outside the callback error boundary. A non-string compatibility value in the hello payload reached .split() before anything validated it. The payload is now validated before parsing. (#904)
  • An authenticated agent could write to containers it does not own. Update and removal accepted globally keyed container IDs belonging to another agent or to the controller; ownership is now enforced at the ingestion and removal boundaries. (#904)
  • Runtime environment redaction missed *_PAT values and credentials embedded in URLs. Both are now detected and redacted in the debug dump. (#904)
  • Rejected-origin diagnostics could be used to amplify debug logging outside the route limiter. The diagnostic path is bounded and rate-limited. (#904)
  • Credentials survived redaction in scheme-relative container URLs. A value like //user:pass@host/path has no scheme to anchor it, so parsing threw and the raw string was returned unredacted. Those values now parse and redact like any other URL. (#904)

Accessibility

  • Dark themes now meet the WCAG 2.2 contrast minimum for normal text. Secondary and muted text, the tone colors (warning, caution, danger, success, info) and the two accent colors all failed 4.5:1 against surfaces they are actually painted on, across all six dark themes. Worst measured pairs were muted text on an elevated surface at 1.57:1 and secondary text in a warning dialog at 1.33:1. Token values are raised to clear 4.5:1 against the worst real pairing for each, keeping each palette's hue. Colors used only for borders, focus rings, icons and toggle fills are held to the 3:1 non-text target instead of being raised needlessly. (#850, #865)
  • Text in the update-status condition rows is readable again. Each nested condition row painted the same muted tone background its parent section had already painted, compounding two tints into a surface no text color could clear without washing out the whole muted/secondary hierarchy. The rows now use the card surface they already carried, which no value change could have fixed. (#850)
  • Toast messages meet the contrast minimum. Toast surfaces mixed their tone color at 25%, leaving the tone-colored text on them between 3.65:1 and 3.94:1 in five of the six dark themes. They now use the same 15% mix as every other tone surface in the app. (#850)
  • Primary button labels are no longer white on a light accent. The login and notification-test buttons hardcoded white text on the primary color, measuring as low as 1.45:1. A new --dd-primary-fg token picks black or white per theme, whichever clears 4.5:1 against that theme's primary. (#850)

Fixed

  • Startup recovery could mark an untouched container as a successfully updated one. A prepare-phase operation interrupted before Docker did anything was reconciled as though the replacement had happened, so a container that never changed was recorded as updated. Recovery now compares the persisted old and new container identities: an untouched original fails recovery, a real replacement still succeeds. Startup also stopped expiring destructive in-progress Docker updates before anything had inspected the runtime, and recovered dependency groups rehydrate their completed-upstream context instead of running a restart-only dependent as a full update. (#904)
  • A healthy agent with a large inventory could never reconnect. When the cached watcher replay exceeded 256 KiB the controller rejected the agent before acknowledgement, permanently, so the biggest deployments were the ones that could not come back. The stream now stays open and sends only the acknowledgement, and the authenticated full-inventory handshake supplies the state. Latest-only coalescing also no longer overwrites a protocol-critical snapshot belonging to a different watcher. (#904)
  • Backups could be selected or pruned across unrelated containers that shared a name. Ownership keyed on the container name alone, so the same service name under two agents, two watchers or two compose projects collided. Backups now carry a stable scoped identity, and legacy identity-less backups stay selectable only while a single active identity owns that name. (#904)
  • Rollback restored a mutable tag instead of the digest recorded with the backup, so a rollback could land on whatever that tag pointed at by then rather than the image the container was actually running. (#904)
  • A slow SSE client could make drydock retain unbounded queued bytes. The main broadcast stream, the container and summary stats streams, and the agent controller fan-out all ignored res.write returning false. All three now deliver bounded, drain-aware latest-state updates and clean up on close, and the agent fan-out has a connection cap. (#904)
  • Concurrent scans could cancel each other. The digest single-flight entry captured only the first caller's cancellation state and transient-retry option, so one caller aborting could kill an unrelated caller's scan, and a later waiter could join an entry whose controller was already aborted. Waiter cancellation is isolated, retry requirements are aggregated across live waiters, and aborted entries are retired before a new waiter is admitted. A cancelled scheduled scan also now propagates cancellation into the scanner instead of rejecting the wrapper and letting later runs accumulate. (#904)
  • Docker event chunks were parsed concurrently against a shared buffer, racing both the parser state and the writes that followed. Parsing and application are now serialized. (#904)
  • A successful container action returned 500 when the follow-up refresh failed. The best-effort post-action inspect or store refresh was allowed to overwrite the outcome of an action that had already succeeded. Success is now authoritative and refresh degradation is reported separately. (#904)
  • Paginated container lists were only sorted within a page. The default name sort ran after the store had already paginated, so ordering was not global across pages. (#904)
  • A restart suppressed batch-completion events for updates that were still in flight. Volatile batch membership was cleared on startup, so recovered operations completed silently; membership now rehydrates from the persisted operations. (#904)
  • The system-log stream limiter fell back to an empty identity instead of the client IP when no configured key applied. (#904)
  • An unsupported agent transport was admitted and then failed asynchronously at watcher lookup during a dependency restart. It is now rejected at admission. (#904)
  • A backpressured controller SSE client lost every event but the newest. Only one pending payload was retained while the socket drained, so anything that arrived behind it was silently replaced. Pending payloads are now queued in order and flushed on drain, with the byte cap applied across the queue so a genuinely slow client is still dropped. (#904)
  • Tearing a watcher down while it was setting up its Docker event listener could resurrect it. Deregistration landing during the client-recreation or auth preflight, or during the getEvents request itself, left a late callback to fire against already-cleaned-up state: it registered a stream and attached handlers to a watcher that was being removed, or scheduled a reconnect for one that was already gone. Superseded and deregistered listener setups now bail out at each of those points, and a stream that arrives too late is destroyed rather than adopted. (#904)
  • An update that never started was reported as a failed update. An operation that could not be resolved before the runtime was reachable was marked failed rather than expired, so users saw a failure for an update that had not run. (#904)
  • The Crowdin sync workflow failed on every push to a dev branch that was not the newest one. The base resolver always picked the highest dev/vX.Y on origin regardless of which ref triggered the run, but the Crowdin action had already downloaded translations into the working tree by then, so checking out a different branch over them died with Your local changes to the following files would be overwritten by checkout. A push to a dev/vX.Y branch now targets that branch directly; scheduled and manually dispatched runs keep the highest-wins lookup and its default-branch fallback. (run 33047712284)

Documentation

  • The homepage FAQ described DD_TRIGGER_* as still deprecated, past its actual v1.7.0 removal. It said the prefix was "a deprecated alias scheduled for removal", but v1.7.0 removed it outright: any DD_TRIGGER_* environment variable now fails startup with an error naming every offending variable and its exact DD_ACTION_*/DD_NOTIFICATION_* replacement (see DEPRECATIONS.md). The FAQ answer now says so, and a new test asserts the copy can't drift back to describing an already-removed feature as merely pending removal.
  • Archived changelog snapshots kept relative ./DEPRECATIONS.md links that don't resolve on the docs site. content/docs/v1.x/changelog/index.mdx files are frozen copies of the root CHANGELOG.md, which uses relative links, and the docs sync script's per-version rewrite only handled absolute /docs/... targets. So the relative-link rewrite that already ran for the freshly generated current-version changelog never reached the frozen archives. rewriteDocsLinksInDirectory now routes changelog files through the same rewrite, fixing the two broken links in the v1.5 archive, and any future archive with the same pattern, without touching non-changelog content.

Chore

  • Two real production modules were excluded from the coverage gate, and a third exclude entry never matched anything. query-values.ts and sorting.ts are used by maturity-filter.ts, container.ts and crud-context.ts, but were carved out of the 100% coverage requirement the repo otherwise enforces. Both are back in: sorting.ts was already fully covered indirectly, and query-values.ts needed a new direct test for the repeated-query-param (array) path, which is real Express qs behavior nothing exercised before. The third entry named Trueforge.ts while the file on disk was trueforge.ts, so it was a silent no-op on case-sensitive CI, and dropping it surfaced what the wrong case was hiding. registerComponent() resolves a provider's module by testing for a Provider.ts-cased file, and on a case-insensitive filesystem that test also matched trueforge.ts, loading it under a second differently-cased specifier the direct unit tests never touch, so full-suite coverage read it as partly uncovered even though the module is fully tested. Every other provider file already follows the PascalCase convention, so the file and its test are renamed to match and the convention check now resolves identically on every filesystem.
  • Removed the now-stale CVE-2026-14456 (openssl/libssl3/libcrypto3) Grype suppression. It was scoped to the Alpine 3.24 openssl pin at 3.5.7-r0 and named its own removal trigger: bump the Dockerfile's openssl= pin to 3.5.8 or newer. That bump already landed in #881, so openssl is now pinned at 3.5.8-r0, and a live Grype scan of that exact package and version confirms zero matches for this CVE or any other finding. The suppression no longer applies.