- user: prevent possible DoS via unbounded parsing of user and group
database files in GHSA-mjcv-p78q-w5fw. This fixes a similar issue
as CVE-2026-47262 in containerd.
- user: prevent falling back to looking up numeric usernames
Improve handling of numeric user/group to prevent looking up numeric
values as usernames. This fixes a similar issue as [CVE-2026-46680] in
containerd.
- user: update minimum go version to go1.18
- assorted testing and linting fixes.
[CVE-2026-46680]: https://github.com/advisories/GHSA-fqw6-gf59-qr4w
full diff: https://github.com/moby/sys/compare/user/v0.4.0...user/v0.4.1
Signed-off-by: Sebastiaan van Stijn <[email protected]>
runPreStart executes a service's pre_start hooks sequentially as
ephemeral containers that share the first non-running replica's
volumes via VolumesFrom and attach to the same networks. A non-zero
hook exit gates service start.
per_replica: false is the only currently supported mode; per_replica:
true is rejected up front. The donor replica is the lowest-numbered
one so the choice is deterministic. ContainerWait uses
WaitConditionNextExit, and the wait loop deterministically handles
the daemon's clean-close (nil on Error + exit code on Result) and
transport-error races to avoid spurious hook failures.
The log stream is opened before ContainerStart to avoid racing
AutoRemove on fast-exiting hooks, and runs under a derived context
so a daemon that keeps the connection open cannot deadlock the call.
Hook containers carry project/service/version labels; the two
cleanup paths force-remove the never-started container explicitly
and warn when removal fails.
pre_start runs once per service when no replica is already running
(initial up, force-recreate or spec change), and is skipped on
scale-up so additional replicas don't re-trigger the hooks.
Coverage: 11 unit tests (including scheduler-race stress) with
goroutine-leak verification via goleak, plus 10 E2E tests (success
path, hook failure gating start, build-image inheritance, idempotent
re-up, spec change, force-recreate, mid-sequence failure, ordering,
scale-up, scaled service).
Signed-off-by: Guillaume Lours <[email protected]>
The reconciler hashed the raw service config while the executor
hashed the form with network_mode/ipc/pid/volumes_from references
resolved to container IDs. Persisted hash and recomputed hash
never matched, so dependents were recreated on every `up`.
Resolve references against observed containers before hashing,
and cascade recreation to namespace-sharing dependents when a
parent is replaced — otherwise the dependent would keep a stale
"container:<old_id>" reference. Also dedup stops in
planStopDependents via stoppedByPlan: with the cascade restored,
the dependent would otherwise receive two Stop nodes.
Fixes#13878.
Signed-off-by: Guillaume Lours <[email protected]>
emitRunningEvents iterated the full ObservedState.Containers map,
which is intentionally broader than the operation scope (it covers
DisabledServices for orphan classification). compose run --no-deps
SERVICE leaves project.Services empty and moves every other service
to DisabledServices, so their running containers were reported as
Running even though this command must not manage them.
Filter the iteration by project.Services, matching the reconciler
scope, and document the contract on the function.
Fixes#13882
Signed-off-by: Guillaume Lours <[email protected]>
The transport passed to NewResolver was applied only via
docker.WithClient. Pass it via docker.WithAuthClient too so the
authorizer's OAuth token fetches use it instead of http.DefaultClient.
Signed-off-by: Guillaume Lours <[email protected]>
Output was aligned with `docker ps --format json` in v2.21.0 (#10918)
but the docs were never updated.
Fixes#13850
Signed-off-by: Guillaume Lours <[email protected]>
When a service declares an env var without a value (e.g. `- KEY` or
`KEY:`), MappingWithEquals stores it as a nil *string. The previous
condition `existing != nil && ...` skipped the warning for this case,
allowing silent overwrites. Change to `existing == nil || ...` so the
warning fires for both nil (shell-inherit) and value-mismatch cases.
Add e2e tests for both list-style (`- KEY`) and map-style (`KEY:`)
YAML forms to lock in the behavior.
Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
Signed-off-by: Yohta Kimura <[email protected]>
The stop section only mentioned setenv being ignored; rawsetenv
is handled identically by the code but was missing from the docs.
The mermaid sequence diagram also lacked a rawsetenv step.
Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
Signed-off-by: Yohta Kimura <[email protected]>
rawsetenv injects provider variables without the service-name prefix, so
a key can collide with a value already set on the dependent service,
whether declared by the user in environment or emitted by another
provider. Log a warning and overwrite on collision, document the
precedence and the non-deterministic ordering between concurrent
providers, and cover the user-environment override with an e2e test.
Signed-off-by: Yohta Kimura <[email protected]>
Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
Providers can now send rawsetenv messages to inject environment
variables into dependent services without the automatic service name
prefix. This enables use cases where applications require exact
variable names that cannot be altered.
Closes#13727
Signed-off-by: Yohta Kimura <[email protected]>
Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
The run parameter was always passed as false at the single call site
and the run==true branch was dead code. Remove it so unparam stops
flagging callers added by PR #13742.
Signed-off-by: Guillaume Lours <[email protected]>
This fixture was not a valid JWT; the first 2 elements decode, but the last
one is malformed;
echo 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9' | base64 -d
{"alg":"HS256","typ":"JWT"}⏎
echo 'eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ' | base64 -d
{"sub":"1234567890","name":"John Doe","iat":1516239022⏎
echo 'SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw' | base64 -d
I�J�IHNJ(]�O��lj~�:N�%_�u
,⏎
This causes problems if the JWT parser is strict and rejecting invalid
JWT's.
It was added in 55b5f233c2, and probably copied
from an example, like https://github.com/knottx/JWTCodable#example-jwt-token,
but the last 2 bytes were truncated.
Signed-off-by: Sebastiaan van Stijn <[email protected]>
`compose up --build` populates BuildOptions.Deps=true so the initial
startup also builds images for depends_on services. The watch rebuild
path reused the same BuildOptions pointer, only resetting Build.Services
to the watched service. Build.Deps stayed true, so s.build() switched
back to IncludeDependencies and rebuilt the upstream dependency too.
Fix it by working on a local copy of BuildOptions in rebuild() and
explicitly setting Deps=false. Using a local copy also removes the data
race on the shared pointer when concurrent file events fire.
Also fix a related leak in doBuildBake: the loop populating bake
configuration iterates over every service in the project (needed so
additional_contexts: service:xxx references can resolve), but it was
emitting the "Image X Building" progress event and tracking expected
images for services that were not part of serviceToBeBuild. Filter
those side-effects to the actual build set so the watch rebuild log
shows only the watched service.
Adds an e2e test reproducing the bug.
Fixes#13853
Co-Authored-By: Claude Opus 4.7 <[email protected]>
Signed-off-by: Nicolas De Loof <[email protected]>
`docker compose publish` routed all registry traffic through Docker
Desktop's HTTP proxy. Publishing to a registry on localhost therefore
failed on Windows with:
proxyconnect tcp: open ./pipe/dockerHttpProxy: The system cannot
find the path specified.
even though `docker push`/`docker pull` worked against the same registry.
Two bugs in internal/desktop/proxy.go:
1. No loopback bypass. ProxyTransport forced every request through the
Docker Desktop proxy and its DialContext always dialed the proxy
socket, so loopback targets could never connect directly. Proxy
selection now bypasses the proxy only for loopback targets
(localhost, 127.0.0.0/8, ::1); all other registry traffic stays
routed through Docker Desktop's PAC-aware proxy so Desktop keeps
ownership of proxy decisions (e.g. enterprise-managed proxies). The
local process NO_PROXY/no_proxy is deliberately not honored, so a
broad value such as * or .corp cannot bypass centrally managed
proxy policy.
2. Malformed Windows pipe path. The proxy named-pipe endpoint was
hardcoded as npipe://./pipe/..., yielding the relative path
./pipe/dockerHttpProxy. It is now derived from the engine endpoint,
preserving its namespace. Docker Desktop reports the backslash form
npipe://\\.\pipe\docker_cli, so the derivation uses LastIndexAny to
handle both backslash and forward-slash forms.
Publishing to localhost now connects directly like `docker push`, while
every non-loopback registry still goes through the Docker Desktop proxy.
Fixes#13824
Co-Authored-By: Claude Opus 4.8 <[email protected]>
Signed-off-by: Domantas Petrauskas <[email protected]>
maxBeforeStatusWidth used len(l.taskID) (bytes) while applyPadding
used utf8.RuneCountInString (runes). For ASCII task IDs the two
agree and no symptom surfaces, but a taskID containing multi-byte
UTF-8 chars (CJK, emoji, accented Latin) reported a width larger
than its visual columns. computeOverflow then triggered truncation
where none was needed, and truncateLongestTaskID's byte-indexed
slice could land mid-multibyte sequence, corrupting the displayed
string.
Align the two measurements on rune count.
Signed-off-by: Guillaume Lours <[email protected]>
In narrow terminals (e.g. tmux panes), the TTY progress UI emitted
lines wider than terminalWidth because adjustLineWidth could shrink
details and taskID but never the progress field. When progress
carried the "X.XMB / Y.YMB" size suffix, the truncation loop exited
with overflow > 0 and applyPadding's max(timerPad, 1) floor pushed
the line one char over. tmux then wrapped the line visually while
print() kept counting logical lines, desyncing aec.Up() on the next
render and producing the mangled "[+] pull X/Y" header overwriting
prior task lines.
Track the size suffix byte length on lineData and let
adjustLineWidth drop it as an intermediate truncation step before
abbreviating the taskID.
Fixesdocker/compose#13595
Signed-off-by: Guillaume Lours <[email protected]>
The progress UI writes to dockerCli.Err() but the auto-mode selector
was probing dockerCli.Out().IsTerminal(), introduced when the
EventProcessor was moved to the CLI layer. Any context that pipes
stdout while keeping stderr attached — `docker compose up | tee log`,
some CI wrappers, PowerShell native-command capture — silently
dropped to plain mode.
Align the detection with the stream the renderer actually targets,
restoring v4 behavior. Extract the switch into selectEventProcessor
so the auto-mode logic can be unit-tested with a real pty pair.
Fixesdocker/compose#13570
Signed-off-by: Guillaume Lours <[email protected]>
follow-up to 7eeb7de7a2, adding more
links now that the CLI reference for docker logs has anchors for them.
Signed-off-by: Sebastiaan van Stijn <[email protected]>
This release include 3 security fixes following the security policy:
- mime: quadratic complexity in WordDecoder.DecodeHeader
Decoding a maliciously-crafted MIME header containing many invalid
encoded-words could consume excessive CPU.
The MIME decoder now better handles this case.
Thanks to p4p3r (https://hackerone.com/p4p3r_hak) for reporting this issue.
This is CVE-2026-42504 and Go issue https://go.dev/issue/79217.
- net/textproto: arbitrary input are included in errors without any escaping
When returning errors, functions in the net/textproto package would
include its input as part of the error, without any escaping. Note that
said input is often controlled by external parties when using this
package naturally. For example, a net/http client uses ReadMIMEHeader
when parsing the headers it receive from a server.
As a result, an attacker could inject arbitrary content into the error.
Practically, this can result in an attacker injecting misleading
content, terminal control bytes, etc. into a victim's output or logs.
This is CVE-2026-42507 and Go issue https://go.dev/issue/79346
- crypto/x509: split candidate hostname only once
(*x509.Certificate).VerifyHostname previously called matchHostnames in a loop
over all DNS Subject Alternative Name (SAN) entries. This caused
strings.Split(host, ".") to execute repeatedly on the same input hostname.
With a large DNS SAN list, verification costs scaled quadratically based on the
number of SAN entries multiplied by the hostname's label count. Because
x509.Verify validates hostnames before building the certificate chain, this
overhead occurred even for untrusted certificates.
Thanks to Jakub Ciolek (https://ciolek.dev) for reporting this issue.
This is CVE-2026-27145 and https://go.dev/issue/79694.
View the release notes for more information:
https://go.dev/doc/devel/release#go1.26.4
Signed-off-by: Sebastiaan van Stijn <[email protected]>
External reviewer noted that the alreadyStopped branch adds createNode
to removeDeps while the !alreadyStopped branch does not — semantically
correct but fragile, since it relies on the implicit invariant that
stopNode.DependsOn contains createNode in the !alreadyStopped path.
Spell out the invariant in a comment so a future maintainer who edits
the stop → create edge in the normal path knows they must also add
createNode unconditionally in the remove deps.
Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
Signed-off-by: Guillaume Lours <[email protected]>
Two coverage gaps surfaced by an external review:
- TestReconcileContainers_DependsOnChain: asserts that a service B with
depends_on: [A] produces a CreateContainer for B that depends on A's
last plan node (the serviceNodes mechanism in infrastructureDeps).
This was the only depends_on-via-plan-DAG behavior untested before.
- TestReconcileContainers_DependsOnScaleDown: companion test that
exercises the scale-down → dependent path specifically, verifying
that the previous commit's lastNode-on-scale-down fix actually wires
the dependency through.
- TestOperationTypeString: adds OpRunProvider to the table; all other
OperationType values were already covered.
Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
Signed-off-by: Guillaume Lours <[email protected]>
Three fixes surfaced by an external code review of the new reconciler:
1. Scale-down now propagates through serviceNodes. When a service is
scaled down (all containers in excess), reconcileService used to
continue without assigning lastNode, leaving r.serviceNodes[svc]
unset. Dependent services then declared no edge on the scale-down
ops and could start before the cleanup finished. Track the
RemoveContainer node as lastNode so depends_on chains pick it up.
2. mustRecreate errors are no longer silently ignored by sortContainers.
The comparator used `obsi, _ := r.mustRecreate(...)`, falling back
to false on any hashing error. Pre-compute obsolescence into a map
keyed by container ID before sorting and propagate the error to
reconcileService.
3. A container is no longer Stopped twice when its network and its
config both diverge. planRecreateNetwork already stops the affected
container as part of the disconnect/remove/recreate dance; the
subsequent planRecreateContainer (triggered via hasNetworkMismatch)
used to add another OpStopContainer against the now-stopped target.
Track stops in r.stoppedByPlan; planRecreateContainer reuses an
existing Stop node when present, and chains its Remove on both that
Stop and the replacement Create.
Two golden tests (TestReconcileNetworks_Diverged*) are updated to
reflect the new, dedupe'd plan shape (one Stop instead of two per
recreated container).
Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
Signed-off-by: Guillaume Lours <[email protected]>