Skip to content

v1.7.0-rc.12

Pre-release
Pre-release

Choose a tag to compare

@github-actions github-actions released this 07 Sep 01:15
767a578

v1.7.0-rc.12

Full Changelog: v1.6.1-rc.10...v1.7.0-rc.12

[1.7.0-rc.12] — 2026-09-06

Fixed

  • The demo site did not send Cross-Origin-Opener-Policy, so the weekly DAST scan failed on ZAP rule 90004 every run. apps/demo/vercel.json now sends same-origin next to the existing Cross-Origin-Embedder-Policy header.
  • The arm64 pass of the image arch check failed on every multi-platform cut with docker: cannot overwrite digest sha256:<index>. scripts/check-image-arch.sh ran once per platform against the same index-digest reference (ghcr.io/codeswhat/drydock:release-staging-N@sha256:<index>), and docker's classic image store cannot hold two platform variants under one digest, so the amd64 pass succeeded and the arm64 pass that followed it always failed. This killed the v1.6.1-rc.9 cut. The script now resolves each platform's own manifest digest out of the index via docker buildx imagetools inspect --raw and jq before it runs docker, skipping attestation entries, and falls back to running the reference unchanged when it isn't an index at all.
  • Two more prunes cleared a container's update policy the same way the startup prune used to. The startup prune stashes a departing record's update policy under its Docker id so the record that replaces it inherits it, but the agent-removal prune and the agent's own stale-container prune still called deleteContainer plainly. An agent removed from config or renamed, or a container handed from one agent to another (or back to the controller's own watcher), lost its snooze, maturity mode and minimum age, and skipped tags or digests the same way the startup case did before that fix. Both prunes now pass identityChangeExpected: true as well, so the same Docker-id stash carries the policy across either hand-off.
  • A manual "check now" (the dashboard, the API, a webhook, or the controller polling an agent) could fire the same notification twice if it landed while a scan was already running. watch() has no re-entrancy guard of its own, so POST /api/v1/containers/watch, POST /api/v1/webhook/watch, and the agent's own watcher API each started a second, fully independent scan on top of one already in progress from the cron schedule or another manual request, and under once=true both passes could read "not yet notified" before either had finished writing its own history, so a trigger like Slack or Telegram sent the same update twice. All three now route through the same single-flight scan orchestration the cron schedule uses: a call that lands mid-scan is folded into that scan's one follow-up instead of starting an independent scan, and the response reports it (result.coalesced in the JSON body for the first two, an X-Drydock-Watch-Coalesced header for the agent endpoint, which keeps its existing bare-array body for compatibility across controller/agent version skew). Manual and API scans still run regardless of a configured maintenance window, exactly as before; only automatic installation is deferred by it. Separately, handleMaturityGateClearedEvent's own once=true check read notification history with a bare, unreserved lookup, so two overlapping evaluations of the same maturity-cleared event could both pass it before either recorded a result; it now takes the same reservation the generic update-available path does before dispatching.
  • A once-notification reservation that never settled held its dedup key for the process lifetime. once=true reserves a triggerId::containerId::eventKind::resultHash key before sending so an overlapping evaluation of the same result cannot send twice, and releases it once the send settles. runHandlerWithTimeout detaches a handler that misses its 30-second deadline instead of waiting on it forever, so a provider that never resolves or rejects left its reservation held forever too, and every later scan for that exact result read it as still in flight and silently skipped the send. The reservation now carries its own expiry, four times the handler timeout, that releases it with a warn log naming the key if nothing released it first.
  • DR-121: the session store and the main store wrote the same /store/dd.json, and whichever one saved last erased the other's data. express-session's connect-loki store opened its own independent LokiJS instance on the exact file the main store already used, and LokiJS's saveDatabase() always serializes the whole in-memory database, so a session autosave (every 5 seconds, and armed by nothing more than an authenticated request touching its session) reverted every container, setting, and audit row the main store had written since boot, while the main store's own autosave (every 5 minutes) deleted the Sessions collection out from under active logins. In practice this meant containers or settings written after startup could vanish from dd.json after a crash or a hard stop, and a restart could log every user out. The session store now writes to its own sibling file, dd-sessions.json by default (derived from DD_STORE_FILE, so a custom filename still gets a distinct sibling rather than colliding with anything else on the volume), and the main store drops and logs a stale Sessions collection left behind in dd.json by an older build instead of continuing to re-save it. Backups remain scoped to the main store file; sessions are not included and are expected to be dropped on restore, which just logs everyone out rather than corrupting anything.

Documentation

  • The agents page's paired Gitea registry example had the controller talking HTTPS to an agent serving plain HTTP. The controller block set DD_AGENT_REMOTE1_CAFILE=/certs/agent-ca.pem, but the agent block above it had no DD_SERVER_TLS_* variables or certificate mounts, so the example copied as written could never connect. The agent block now mounts agent.pem/agent-key.pem and sets DD_SERVER_TLS_ENABLED, DD_SERVER_TLS_CERT, and DD_SERVER_TLS_KEY, with a comment noting the certificate must be signed by the agent-ca.pem the controller mounts and be valid for the host the controller dials.