v1.6.1-rc.7
Full Changelog: v1.7.0-rc.8...v1.6.1-rc.7
[1.6.1-rc.7] — 2026-09-03
Fixed
DD_AGENT_ALLOW_INSECURE_SECRETwas parsed as an agent namedallow.getAgentConfigurations()handed the wholeddEnvVarsmap to the genericdd.agentprefix parser, and that documented flat flag matches the prefix asDD_AGENT_ALLOW_INSECURE_SECRET, producing an{insecure: {secret: 'true'}}agent literally namedallow. Registration then rejected it every boot withAgent allow failed to register ("host" is required), on every install that set the flag regardless of whether any real agents were configured. The flag is now excluded before the map is parsed. Reported by @depuits in #945.- A container seen before its registry was configured stayed stamped
unknownforever.shouldRepairStoredImageReference()only re-derived a stored image reference when the tag wasunknownor digest-shaped, so a container whoseimage.registry.namewas alreadyunknownbut whose tag was otherwise normal never re-entered the repair path on refresh, and only recovered if it was recreated. The repair now also runs when the stored registry name isunknown, re-resolving it from the live image inspect on the next refresh cycle. Reported by @depuits in #945. - The self-update helper could destroy a health-verified replacement when removing the old controller failed. The main Docker update path already treated old-container cleanup as best effort after its health gate, but the helper still sent a 409, timeout, or other removal failure into rollback. That rollback force-removed the healthy replacement first and could then fail to restore an old container that Docker had already reaped. The helper now treats a missing old container as already cleaned up and records every other cleanup failure on the successful operation without rolling back the replacement.
- The watcher-snapshot handler still pruned on an empty container list, and a test had frozen that in place.
AgentClienthas four entry points that ingest a container list, and three of them (handshake(),watch()andhandleContainerSync()) already refuse to prune when that list is empty, the first for the cold-start race in #386 andwatch()most recently in the rc.6 sweep (#922).handleWatcherSnapshotEvent(), which handles thedd:watcher-snapshotserver-sent event, never got one. A reconnecting agent legitimately reports zero containers on this path:filterPendingDiscoveries()only bypasses the discovery-settling delay for container ids already in the agent's own local store, and an agent restart just reset that store, so every genuinely running container is held back on the first cycle. An empty incoming list then made every stored container look like a real removal, sodeleteContainer()ran withoutreplacementExpected,stashUpdatePolicyForReplacement()never fired, and the container's maturity policy override was deleted with nothing left to restore it from. This is why the durability fix in #743 did not cover it: that made the stash survive a process restart, and on this path nothing was ever stashed. Note the existing test asserted the broken behaviour outright, having been written from the implementation back in 1.5.0 before the ambiguity was understood, so it is inverted here rather than extended. (#565) - Debug dumps no longer expose Apprise service URLs, Rocket.Chat user IDs, or Telegram chat IDs. These provider-specific credential fields are now redacted without hiding ordinary
parse.urlsconfiguration flags. - Compose updates ran the post-pull security gate after the service had already been replaced. The Docker-native path pulled, scanned, and enforced policy before stopping the old container, but the Compose path called the same gate only after it had stopped, removed, and recreated the service. A blocked image was therefore already running and already written into the compose file by the time the block verdict arrived. Compose now separates preflight from runtime mutation: it pulls once, captures the exact repository digest, runs signature verification, vulnerability scanning, and SBOM generation against that immutable reference, and only then permits any stop, remove, or create; the per-service platform check still runs before that service is touched. If preflight fails after the candidate compose-file mutation, the existing rollback restores the file before the error is surfaced. Compose-file-once mode preflights every affected service before the first runtime mutation, and a failed preflight terminalizes every queued operation. If the daemon cannot bind a scan to the pulled image, block mode and signature verification fail closed; availability
warnrecords a skipped scan and continues without pretending a mutable-tag scan protected the replacement. Scaled services reuse the same captured identity, private registries are matched exactly, and recovery preserves the operator-facing tag from the hybridtag@digestreference. - The ordinary Docker update pulled by mutable tag, then verified, scanned and deployed whatever that tag pointed at next.
ContainerUpdateExecutorpulledrepo:tagand handed the same mutable reference to signature verification, the vulnerability scan and SBOM, the runtime-config compatibility lookup, and finallyPOST /containers/create. Nothing tied those steps to the image the pull had actually fetched, so a registry retag landing anywhere in that window meant one image was gated and a different one was deployed. The Compose path's equivalent was closed in #952; this is the same hole on the Docker-native path, which is the default for every non-Compose container. The executor now inspects the pulled image once, matches itsRepoDigestsagainst the reference's own repository so a lookalike entry for another registry cannot satisfy it, and pins an immutablerepo:tag@sha256:...that signature verification, the scan, SBOM, the config lookup and the replacement create all use. Signature verification moved from before the pull into the post-pull gate, because verifying a mutable tag ahead of the pull says nothing about what arrived. If the daemon cannot bind the pulled image to a manifest digest, block mode and signature verification fail closed and terminalize the operation before anything is renamed or created, while availabilitywarnrecords a skipped scan and continues rather than pretending a mutable-tag scan protected the replacement. Moving the gate behind the pull moved the pre-update hook and the prune/backup step behind the gate with it, so an image the gate goes on to reject no longer fires an operator hook, deletes cached images or writes a rollback row on its way to being refused, and a container failing the gate on every cycle can no longer push a real rollback point out of history. A reference that is already digest-pinned is left alone, the tag the operator configured is still what the operation record, the compose-file sync and the UI show, and the identity binding is now one implementation shared with the Compose path instead of two. - A Docker Hub image pulled through the
index.docker.ioalias could not be bound to its pulled digest, so the update failed instead of proceeding. The post-pull identity binding matches the daemon'sRepoDigestsagainst the repository forms the pulled reference could legitimately be recorded under, and that candidate list knewdocker.ioandregistry-1.docker.iobut not the legacyindex.docker.iohost. The daemon rewrites that alias todocker.ioand records Hub images under their short name, soindex.docker.io/nginx:1.27was matched against a candidate list holding onlyindex.docker.io/nginxand never matched thenginx@sha256:...entry it had just pulled. Signature verification and block mode then failed the update closed on an image that was perfectly bindable, and availabilitywarnskipped a scan it could have run. This reaches any container whose registry provider resolves through that host, including a custom registry configured atindex.docker.ioand add.registry.lookup.imagediversion that leaves the deploy reference on the alias. Every other Docker Hub alias check in the codebase already covers all three hosts; this one now matches them. - A compose update ran the operator's pre-update hook, pruned images and wrote a rollback row before the security gate had looked at the candidate. Moving signature verification behind the pull put the gate behind those three steps as well, and the compose path kept that ordering when the Docker-native path was changed to defer them alongside the digest pinning. A compose image that failed verification had therefore already fired
dd.hook.pre, deleted cached images underPRUNE=trueand inserted a backup record on its way to being refused, and a service failing the gate every cycle pushed real rollback points out of history through the backup retention prune that runs after a failed update. The compose trigger now defers the same two steps behind its post-pull gate, except in compose-file-once mode, where the preflight has already gated every service in the batch before the first runtime mutation and there is no post-pull hook left to hang them off. The unbound-image path was the other half of it: when the daemon cannot bind the pulled image to a digest and availabilitywarnallows the update anyway, compose skipped the post-pull hook outright, which under the new ordering would have skipped the pre-update hook and the prune/backup step with it, so it now runs that hook with only the gate half suppressed, exactly as the Docker path already did.
Note: this is a maintenance cut, built from dev/v1.6 at 79a971a58e483c0ae22fd90a7be5a34d13a3e399, not from main.
- The container image and release artifact are cosign-signed (identity
release-cut.yml@refs/heads/main, since the workflow run itself always executes at that ref), but carry no SLSA build-provenance attestation — this workflow's own OIDC token always claimsmainHEAD as the build source, which would be false for this artifact, so attestation is skipped rather than publish a false claim.