Skip to content

fix(bake): grant permissions for configured build inputs - #14325

Merged
ndeloof merged 2 commits into
docker:mainfrom
crazy-max:bake-entitlement-grants
Oct 12, 2026
Merged

ndeloof merged 2 commits into
docker:mainfrom
crazy-max:bake-entitlement-grants

Conversation

@crazy-max

@crazy-max crazy-max commented Oct 12, 2026 •

Copy link
Copy Markdown
Member

What this PR does

Fixes Compose builds that fail with missing entitlement grants since Buildx v0.37.2. Compose invokes Bake with --progress rawjson, which now enforces permissions that this mode previously skipped.

Related issue

Changes made

Grants read access to configured Dockerfiles, file secrets, SSH keys and sockets, local cache sources, and OCI layout contexts, plus write access to local cache destinations. Dockerfile grants cover absolute local Dockerfiles used with remote contexts and external relative Dockerfiles used with cwd:// contexts. Each Dockerfile grant targets the file itself.

Grants SSH agent access for pathless build.ssh and --ssh entries, including implicit agent forwarding for SSH Git contexts without explicit SSH configuration. Pathless entries are serialized as bare SSH IDs so Bake applies its agent entitlement checks correctly.

Forwards explicitly requested build entitlements and adds network.host for build.network: host. Identical grants are deduplicated, while environment secrets and privileged builds retain their existing behavior.

Additional context

The raw JSON entitlement enforcement change addresses GHSA-gwr2-q96m-6682. The related GHSA-p54p-jq4x-rc28 covers implicit secret sources and OCI layout paths; it is not the direct cause of the reported explicit file-secret failure.

This change preserves Bake's entitlement enforcement and supplies grants derived from the Compose build configuration and CLI options.

@docker-agent docker-agent left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Assessment: 🟢 APPROVE

Comment thread pkg/compose/build_bake.go
local = append(local, path)
}
}
if buildConfig.Dockerfile != "" && buildConfig.DockerfileInline == "" &&

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[low] Dockerfile directory grant may not deduplicate against a relative context path

When the Dockerfile lives inside a relative context (e.g. context: ./app, dockerfile: Dockerfile), dockerFilePath resolves and EvalSymlinks the path to an absolute directory like /abs/app, but buildConfig.Context was already appended as the raw relative string ./app. Because slices.Compact(slices.Sorted(...)) deduplicates by string equality, both --allow fs.read=./app and --allow fs.read=/abs/app are emitted. Duplicate/overlapping grants are not harmful — buildx accepts them — but the stated intent of the deduplication is not fully achieved for this common case. The existing test uses contextDir = filepath.Join(root, "app") (already absolute), so the relative-path variant is not exercised.

Consider normalising buildConfig.Context with filepath.Abs before appending it to paths, or normalising all collected paths at the end of localBuildPaths before returning them.

Confidence Score
🟡 moderate 57/100

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Compose loader resolves local build contexts to absolute paths before this function runs, so context: ./app won't normally reach this code as ./app. Also, filepath.EvalSymlinks doesn't inherently turn relative paths into absolute ones. The deduplication here removes identical grants; symlink aliases can still produce overlapping grants, but those are harmless. I don't think we need another normalization step here.

Comment thread pkg/compose/build_bake.go Outdated
if buildConfig.Dockerfile != "" && buildConfig.DockerfileInline == "" &&
(!isRemoteContext(buildConfig.Context) || filepath.IsAbs(buildConfig.Dockerfile)) {
// Include the directory so Dockerfile-specific ignore files are covered.
local = append(local, filepath.Dir(dockerFilePath(buildConfig.Context, buildConfig.Dockerfile)))

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[low] Empty context may cause filepath.Dir(dockerFilePath("", dockerfile)) to produce ., granting --allow fs.read=.

When buildConfig.Context is an empty string:

  1. isRemoteContext("") returns false (no ://, not detected as git/remote), so the guard on line 369 does not exclude the empty-context case.
  2. dockerFilePath("", "Dockerfile") computes filepath.Join("", "Dockerfile") = "Dockerfile" (a bare filename).
  3. filepath.Dir("Dockerfile") = ".".
  4. "." is appended to local, emitting --allow fs.read=. — granting read access to the entire current working directory, which is broader than intended.

In practice Compose likely populates Context from the project directory before this function is reached, but the condition has no explicit guard for the empty-context case. Adding buildConfig.Context != "" to the condition (or relying on the fact that a missing context: key is resolved to the project directory in the loader) would make the intent explicit and prevent the fallthrough.

Confidence Score
🟡 moderate 57/100

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

An omitted build context defaults to . and is resolved by the Compose loader before this function runs. Granting the Dockerfile directory is intentional because Bake also needs access to Dockerfile-specific ignore files. Even for an explicitly empty context, the existing context grant already resolves to the working directory in Bake, so this doesn't introduce broader access. I'd keep the current behavior without adding an empty-context guard.

@codecov

codecov Bot commented Oct 12, 2026 •

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 97.50000% with 2 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
pkg/compose/build_bake.go 97.50% 1 Missing and 1 partial ⚠️

📢 Thoughts on this report? Let us know!

@crazy-max
crazy-max force-pushed the bake-entitlement-grants branch 2 times, most recently from 85f0e1f to c95a3fe Compare October 12, 2026 09:55

@docker-agent docker-agent left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Assessment: 🟢 APPROVE

@crazy-max
crazy-max marked this pull request as ready for review October 12, 2026 10:05
@crazy-max
crazy-max requested review from a team as code owners October 12, 2026 10:05
@crazy-max
crazy-max requested review from glours and ndeloof October 12, 2026 10:05

@docker-agent docker-agent left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Assessment: 🟢 APPROVE

Comment thread pkg/compose/build_bake.go
// isRemoteContext reports whether the Dockerfile is resolved within a remote source.
func isRemoteContext(ctxName string) bool {
contextType, _ := build.DetectContextType(ctxName)
return contextType == build.ContextTypeGit || contextType == build.ContextTypeRemote || strings.Contains(ctxName, "://")

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[low] isRemoteContext classifies cwd:// as remote, potentially skipping the Dockerfile directory grant

isRemoteContext (introduced in this PR) returns true whenever the context string contains ://:

func isRemoteContext(ctxName string) bool {
    contextType, _ := build.DetectContextType(ctxName)
    return contextType == build.ContextTypeGit || contextType == build.ContextTypeRemote || strings.Contains(ctxName, "://")
}

This matches cwd://-prefixed contexts, which are actually local filesystem paths. The Dockerfile directory grant in localBuildPaths is gated on this function:

if buildConfig.Dockerfile != "" && buildConfig.DockerfileInline == "" &&
    (!isRemoteContext(buildConfig.Context) || filepath.IsAbs(buildConfig.Dockerfile)) {
    local = append(local, filepath.Dir(dockerFilePath(buildConfig.Context, buildConfig.Dockerfile)))
}

When the main context is cwd://some/path and the Dockerfile is a relative path, both conditions are false (isRemoteContext → true, filepath.IsAbs → false), so the Dockerfile directory is never added to the read-grant list — even though cwd:// resolves to a local path that Bake needs to access.

The context path itself is added to local correctly (the strings.CutPrefix("cwd://", ...) branch in the first loop), so only the Dockerfile's parent directory (for .dockerignore and related files) would be missing from the grant.

If cwd:// can appear as buildConfig.Context with a relative dockerfile: path, the build would fail with an entitlement error. Consider excluding cwd:// from the :// check:

return contextType == build.ContextTypeGit || contextType == build.ContextTypeRemote ||
    (strings.Contains(ctxName, "://") && !strings.HasPrefix(ctxName, "cwd://"))
Confidence Score
🟡 moderate 65/100

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed by stripping cwd:// before resolving the Dockerfile path for the read grant. The Bake target keeps its original context and Dockerfile values so Bake handles their resolution.

@docker-agent docker-agent left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Assessment: 🟡 NEEDS ATTENTION

Lower-confidence findings (not posted inline)

  • [medium] pkg/compose/build_bake.go:721 — isRemoteContext returns true for cwd:// contexts, potentially excluding Dockerfile directory from fs.read grants (confidence: weak 52/100)
Comment thread pkg/compose/build_bake.go
}
}
// Bake implicitly forwards the default agent for SSH Git contexts.
if len(ssh) == 0 {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[medium] SSH agent entitlement not granted when path-based SSH keys coexist with an SSH git context

When a service has explicit path-based SSH socket keys (key.Path != ""), those paths are added to fs.read grants but the "ssh" entitlement is not appended. The guard at line 272 only adds the "ssh" entitlement for SSH-protocol git contexts when len(ssh) == 0 — meaning no SSH config at all. If a user configures explicit SSH socket files and uses an SSH git build context (e.g. context: git+ssh://...), neither branch appends "ssh" to bake.entitlements:

for _, key := range ssh {
    if key.Path == "" {
        bake.entitlements = append(bake.entitlements, "ssh")  // default agent → ✓
    } else {
        bake.localPaths = append(bake.localPaths, strings.Split(key.Path, ",")...)  // socket path → fs.read only, no "ssh" entitlement
    }
}
// Only fires when ssh is completely empty:
if len(ssh) == 0 {
    if u, err := buildkitgit.ParseURL(buildConfig.Context); err == nil && u.Scheme == buildkitgit.SSHProtocol {
        bake.entitlements = append(bake.entitlements, "ssh")  // ← missed when len(ssh) > 0
    }
}

Bake implicitly forwards the SSH agent for SSH git cloning regardless of whether explicit --ssh socket paths are also configured. The missing entitlement would cause a permission denied / missing entitlement error when Bake tries to clone the SSH git context. Consider changing the guard to check for an SSH git context independently of whether ssh is non-empty:

Suggested change
if len(ssh) == 0 {
if u, err := buildkitgit.ParseURL(buildConfig.Context); err == nil && u.Scheme == buildkitgit.SSHProtocol {
bake.entitlements = append(bake.entitlements, "ssh")
}
Confidence Score
🟡 moderate 67/100

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bake only injects the default SSH agent when no explicit SSH configuration is provided for the build context. Our len(ssh) == 0 guard matches that behavior. With explicit key or socket paths, Bake checks filesystem access to those paths, which we already grant. Removing the guard would grant agent access that this configuration doesn't require.

Comment thread pkg/compose/build_bake.go
if key.Path == "" {
bake.entitlements = append(bake.entitlements, "ssh")
} else {
bake.localPaths = append(bake.localPaths, strings.Split(key.Path, ",")...)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[low] SSH key.Path comma-split may produce empty paths in fs.read grants

strings.Split(key.Path, ",") is used to support comma-separated socket paths. If key.Path has a trailing comma or contains consecutive commas (e.g. a malformed or programmatically constructed config), an empty string is appended to bake.localPaths. The slices.Compact(slices.Sorted(...)) deduplication in bakeArgs sorts paths ascending — the empty string sorts to the front and, since it appears only once, survives Compact — and this would emit --allow fs.read= to Bake, which may be rejected or behave unexpectedly.

Consider filtering out empty segments:

Suggested change
bake.localPaths = append(bake.localPaths, strings.Split(key.Path, ",")...)
bake.localPaths = append(bake.localPaths, slices.DeleteFunc(strings.Split(key.Path, ","), func(s string) bool { return s == "" })...)
Confidence Score
🟡 moderate 67/100

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bake's SSH parser also uses strings.Split and preserves empty segments. Filtering them only from the grants would make the grants differ from the SSH configuration passed to Bake, without fixing the malformed input. I'd leave validation to Bake and BuildKit rather than silently filter those segments here.

@ndeloof ndeloof left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, this fixes the cases from #14285. I ran the PR binary with COMPOSE_BAKE=true against buildx v0.37.2 on a Dockerfile outside the context, a file secret, a key-path ssh: and local cache_from/cache_to: all four fail on v5.5.1 with additional privileges requested and build with this PR.

Requesting changes on one point only: the Dockerfile grant should be the file, not its directory (see the inline comment, with the buildx/buildkit references). Everything else looks fine to me.

Comment thread pkg/compose/build_bake.go Outdated
Comment on lines +395 to +398
if buildConfig.Dockerfile != "" && buildConfig.DockerfileInline == "" &&
(!isRemoteContext(buildConfig.Context) || filepath.IsAbs(buildConfig.Dockerfile)) {
// Include the directory so Dockerfile-specific ignore files are covered.
local = append(local, filepath.Dir(dockerFilePath(buildConfig.Context, buildConfig.Dockerfile)))

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The grant here is the Dockerfile's parent directory, but buildx only asks for the Dockerfile file, and a file-level grant is enough. This is broader than needed, and the comment above it ("so Dockerfile-specific ignore files are covered") isn't accurate.

  • What bake checks is the file: collectLocalPaths adds t.DockerfilePath, not its directory (bake/bake.go#L1618-L1627 @ buildx v0.37.2). That's why the error reads pass "--allow=fs.read=../outside/Dockerfile".
  • buildx does mount the whole Dockerfile directory as the dockerfile local (build/opt.go#L977), but the entitlement isn't computed from that mount, and the frontend only pulls two files from it: <filename> and <filename>.dockerignore (dockerui/config.go#L351, read at #L415). A plain .dockerignore is read from the build context, not from the Dockerfile's directory (#L538-L575).
  • I checked it with a real buildx bake (v0.37.2, default builder, context ./app, dockerfile: ../docker/Dockerfile.prod, run from an unrelated working directory): granting only the file builds, and a sibling docker/Dockerfile.prod.dockerignore excluding a file is honored identically with the file grant and with the directory grant. A plain docker/.dockerignore isn't read in either case.

The directory grant can be much wider than the file: dockerfile: /Dockerfile.prod yields fs.read=/, and a Dockerfile kept in $HOME opens the whole home to the build, where before this change only the context was granted.

Suggestion: append dockerFilePath(buildConfig.Context, buildConfig.Dockerfile) as is (drop filepath.Dir) and fix the comment. The tests need the matching change: TestLocalBuildPaths (resolvedDir, dockerfileDir in the "remote context with local Dockerfile" case) and TestPrepareBakeBuildGrants (filepath.Dir(dockerfile)) should expect the file path.

Only checked on buildx v0.37.2 with the docker driver.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah yes you're right, I conflated the directory BuildKit mounts with the path Bake checks for entitlements. Changed the grant to the Dockerfile itself and updated the tests to assert that its parent directory isn't granted.

Buildx 0.37.2 enforces entitlements with rawjson progress, exposing missing
permissions in Compose's Bake invocation. Grant read access to Dockerfiles
and file secrets, including local Dockerfiles used with remote contexts,
and forward requested entitlements and host network access.

Deduplicate grants and preserve environment secrets and privileged builds.
Add regression coverage for the generated Bake configuration and arguments.

Signed-off-by: CrazyMax <1951866+crazy-max@users.noreply.github.com>
@crazy-max
crazy-max force-pushed the bake-entitlement-grants branch from c95a3fe to 58ed333 Compare October 12, 2026 11:00
Grant access to SSH keys and sockets from build configuration and CLI
options, and authorize default agent forwarding, including implicit SSH
Git contexts. Serialize pathless SSH entries without an empty source so
Bake applies the agent entitlement check.

Grant read access to local cache imports and OCI layout contexts, and
write access to local cache exports. Match Bake's path and cache syntax
while excluding service, target and image references from filesystem grants.

Signed-off-by: CrazyMax <1951866+crazy-max@users.noreply.github.com>
@crazy-max
crazy-max force-pushed the bake-entitlement-grants branch from 58ed333 to 83efad5 Compare October 12, 2026 11:04

@docker-agent docker-agent left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Assessment: 🟢 APPROVE

The entitlement grant logic is well-structured and correctly implements the required permissions for Buildx bake. Deduplication via slices.Compact(slices.Sorted(...)) is correct, the ociLayoutPath helper mirrors Buildx's own parsing, isRemoteContext correctly excludes remote schemes from path grants, and pathless SSH entries are serialized as bare IDs to trigger Bake's agent entitlement checks. No medium- or high-severity bugs were found in the changed code.

Lower-confidence findings (not posted inline)

  • [low] pkg/compose/build_bake.go:455 — localCachePaths compares attrs["type"] against the lowercase literal "local" but stores values without lowercasing them, so a cache entry with type=Local (capital L) would be silently skipped with no read/write grant issued. In practice BuildKit only emits lowercase values, so real-world impact is negligible. Consider lowercasing the value when storing: attrs[strings.ToLower(name)] = strings.ToLower(value). (unverified / low severity)
@crazy-max

crazy-max commented Oct 12, 2026 •

Copy link
Copy Markdown
Member Author

Lower-confidence findings (not posted inline)

  • [low] pkg/compose/build_bake.go:455 — localCachePaths compares attrs["type"] against the lowercase literal "local" but stores values without lowercasing them, so a cache entry with type=Local (capital L) would be silently skipped with no read/write grant issued. In practice BuildKit only emits lowercase values, so real-world impact is negligible. Consider lowercasing the value when storing: attrs[strings.ToLower(name)] = strings.ToLower(value). (unverified / low severity)

The current parsing matches Buildx. Attribute keys are case-insensitive, but values are preserved, including the cache type. Buildx and BuildKit both check for exactly type=local. Lowercasing values would also alter case-sensitive src and dest paths, so I'll keep the existing behavior.

@docker-agent docker-agent left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Assessment: 🟢 APPROVE

@ndeloof
ndeloof merged commit 1e1c577 into docker:main Oct 12, 2026
66 checks passed
@crazy-max
crazy-max deleted the bake-entitlement-grants branch October 12, 2026 14:58
tamird added a commit to tamird/gvisor that referenced this pull request Oct 20, 2026
The Compose plugin bundled in the Docker 29 image omitted the
network.host grant when passing build.network: host to Buildx Bake.
Docker-in-gVisor builds consequently failed with a request to allow host
networking.

Build Compose from the merged fix 1e1c577ea [1] and install it in the
image's CLI plugin directory until the official image includes the fix.
Use the upstream Makefile to produce a static plugin for each native
architecture, with a verified source archive and pinned image bases.

[1]: docker/compose#14325

Assisted-by: OpenAI Codex, acting on behalf of Tamir Duberstein.
jrmatherly added a commit to jrmatherly/dotfiles that referenced this pull request Oct 25, 2026
Buildx 0.37.2 enforces the fs.read entitlement under --progress rawjson,
which Compose always uses, and Compose grants no read for the merged
Dockerfile the Dev Containers CLI writes under $TMPDIR, so every
Compose-based dev container failed to build (docker/compose#14285,
devcontainers/cli#1320). The fix, docker/compose#14325, is merged but not
in any release through 5.6.1.

bin/docker-devcontainer exports BUILDX_BAKE_ENTITLEMENTS_FS=0 and execs
docker. The rendered VS Code settings point dev.containers.dockerPath at it
through a new `dotfiles` template variable (the setting is
application-scoped, so a workspace file can't set it), so only Dev
Containers builds lose the check. Delete all of it once a Compose release
carries the fix.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

3 participants