v1.5.1-rc.1
Full Changelog: v1.5.0...v1.5.1-rc.1
[1.5.1-rc.1] — 2026-06-26
Added
- Remote agents now report their log level and watcher schedule. The agent handshake (
dd:ack) includeslogLevelandpollInterval(the watcher's cron), so theGET /api/v1/agentsresponse and the Agents view populate these fields for connected agents instead of leaving them blank.
Changed
-
Coverage reporting moved from Codecov to Qlty Cloud. Part of the org-wide consolidation onto Qlty (one vendor for code quality and coverage). CI now publishes the normalized app/ui lcov reports to Qlty Cloud via GitHub OIDC — no stored coverage token — replacing the Codecov upload and
codecov.yml. The vitest 100% coverage thresholds in the app/ and ui/ test suites remain the enforced gate; the README coverage badge now points at Qlty. -
Maturity gate counts from the registry publish date when trustworthy. For Docker Hub and GHCR (including lscr.io), the gate now measures elapsed time from the real image push date (
last_updated/updated_at) instead of from when drydock first detected the update. An image that has been public longer thanmaturityMinAgeDaysclears the gate immediately on the first scan that finds it. All other registries expose only the OCI image build date, which is not a reliable push signal, so drydock falls back to its own first-detection timestamp (updateDetectedAt) for those. A trusted publish date is skipped if it fails to parse or is in the future (clock-skew protection).
Fixed
-
Maturity gate (
maturityMode: 'mature') never triggered; the first-detection timestamp was never computed. The function that stampsupdateDetectedAtreturnedundefinedimmediately wheneverupdateAvailablewasfalse, which is always the case while maturity suppression is active, so the timestamp was never computed and the maturity clock never started. Containers blocked by the maturity gate remained permanently "maturing" and never became update-available. -
Maturity clock reset on every container recreation. When a container was stopped and recreated (Portainer stack redeploy,
docker compose down && up), its new Docker container ID caused drydock to treat it as a fresh container, resettingupdateDetectedAtto zero. Containers that are frequently redeployed could never accumulate enough age to clear the gate. The clock is now keyed on a stable container identity and survives recreation as long as the same update remains pending, including the common case where a slow image pull means the replacement container isn't yet visible when the old one is pruned. -
In-memory container cache key collision. The cache key joined watcher name and container name with
_, somy_prod+nginxandmy+prod_nginxproduced the same key. A wrong cache hit could apply a stale security scan result or maturity timestamp from one container to a different container, most visibly after a recreation event. The separator is now::. -
A changed update candidate now restarts the maturity soak. When a new image digest or tag is published while an earlier update is still inside the maturity window, the gate restarts the clock for the new candidate instead of letting it inherit the previous candidate's elapsed time. A freshly pushed image always soaks for the full
maturityMinAgeDaysrather than being treated as already mature. (Local watches previously kept the original detection time here; remote-agent containers already behaved this way.) -
Docker and Docker Compose actions can pull private GCR and Google Artifact Registry images again.
getAuthPull()for the GCR and GAR providers returned the raw service-account email as the username and the private key as the password, whichdocker loginrejects, so any action trigger targeting a privategcr.ioor*-docker.pkg.devimage failed to authenticate and could not apply the update. It now returns the_json_keyusername with the service-account JSON as the password, the format Google's registry auth expects (and the same one the token-exchange path already used). -
Quay tag pagination no longer breaks on standard
Linkheaders. Thenext_pagecursor was matched with a greedy pattern that swallowed the trailing>; rel="next"from an RFC 5988Linkheader and corrupted the cursor, so repositories with more than one page of tags could silently stop paginating. The parser now readsnext_pageandlastcursors correctly from both bare-URL and RFC 5988 header forms, and still URL-encodes them to block scope injection. The inherited TrueForge provider gets the same fix.
Security
-
Custom registry TLS settings now apply to every registry request.
DD_REGISTRY_*_CAFILE,_INSECURE, and_CLIENTCERTwere honored only on the credential handshake for the GAR, GitLab, Mau, DHI, ACR, and ECR providers; the follow-up tag-list, manifest, and blob calls fell back to the system CA bundle. The custom TLS agent is now propagated to those calls, including the anonymous (no-credential) code paths in GAR, GCR, and Quay, so a private CA or aninsecuresetting is enforced end to end rather than only while fetching the token. -
Hook command environment values are sanitized against shell injection. Lifecycle hook commands run through
/bin/sh -c, and registry-controlled values (image name, tag, update digest) flowed into the hook environment unsanitized, so a crafted tag could inject shell commands into a hook script that expanded those variables unquoted. The values are now scrubbed of shell metacharacters, matching the sanitization thecommandaction already applies. -
DD_SESSION_SECRET__FILEis now honored. The session secret was read straight fromprocess.env, bypassing the__FILEsecret-file resolution every other secret uses, so the documented file form was silently ignored and the instance fell back to a generated secret on every restart. It now reads the resolved value, so a secret supplied viaDD_SESSION_SECRET__FILEis used (and, as with every other secret, the file form wins when both the file and the bare variable are set). -
Secret files are checked for unsafe permissions and trailing newlines. When a
DD_*__FILEsecret is readable by group or others, drydock logs a non-fatal warning recommendingchmod 600(skipped on Windows, where the mode bits are not meaningful). File-sourced secret values are also trimmed of a trailing newline so an editor- orecho-added\ncan't corrupt a credential, matching the common Docker*_FILEconvention. -
Debug dump and container environment no longer leak credentials. The debug dump's redaction missed SMTP passwords (
*_PASSkeys) and webhook URLs with embedded secrets, both of which appeared in the dumped environment for any authenticated user. They are now redacted, and the same*_PASSgap is closed for the container runtime environment shown via/api/containers. Registry usernames and service hostnames stay visible by design, since they aren't secrets and aid debugging.
Upgrade Notes
- One-time notification burst on first scan after upgrade (Docker Hub / GHCR containers only). Containers on Docker Hub or GHCR whose pending update is already older than
maturityMinAgeDayswill clear the maturity gate immediately on the first poll after upgrading. Notification triggers inalwaysmode will fire once for each such container. Action triggers (docker,docker-compose,command) will also fire, so containers previously held by the gate may be updated automatically on that first poll. Review your active action-trigger configuration before upgrading if you want to control the timing. This is expected behavior: those images were already mature; the gate was simply unaware of it.
Warning
Upgrade notes — behavioral changes, please read before updating. Releases 1.4.6 and the entire 1.5 line ship security-hardening fixes that change runtime behavior. These are not deprecations: there is no compatibility shim or grace period, so a previously-working deployment can change behavior on upgrade.
- OIDC login now requires
authorization_endpointin your provider's discovery metadata. The authorization-redirect allowlist no longer falls back to a broad same-origin match. Mainstream identity providers (Keycloak, Authentik, Authelia, Okta, Google, Entra/Azure AD, Zitadel, …) publish this field and are unaffected. If your/.well-known/openid-configurationdoes not advertiseauthorization_endpoint, OIDC sign-in will now fail closed — make sure the discovery document exposes it. - Unauthenticated rate-limit buckets now key on the TCP peer address instead of
X-Forwarded-For. Behind a reverse proxy (nginx / Traefik / Caddy), all unauthenticated clients now share a single bucket (the proxy's address), regardless ofDD_SERVER_TRUSTPROXY. Internet-facing or multi-user instances may begin to see unexpected429 Too Many Requestson unauthenticated endpoints. Authenticated requests are keyed per session and are unaffected. - HTTP-trigger
proxyURLs must now use thehttp://orhttps://scheme. Any other scheme (e.g.socks5://) is rejected at config load. Such values were previously accepted but only ever treated as an HTTP proxy — switch to anhttp(s)://proxy URL.