Skip to content

feat(native): pin a BuildKit client and scan Dockerfile image references - #583

Closed
roodboi wants to merge 8 commits into
nextfrom
claude/native-build-groundwork
Closed

roodboi wants to merge 8 commits into
nextfrom
claude/native-build-groundwork

Conversation

@roodboi

@roodboi roodboi commented Nov 5, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Groundwork for building Compose build: images on the native backend. Nothing builds images yet. Native up still refuses build: services, so no user-visible behavior changes.

Pinned BuildKit client (buildx)

  • provider-pins.json now pins buildx 0.34.1, the darwin-arm64 release file, by SHA-256. This is the client release from the same weeks as the pinned Docker 29.5.2 engine. The file is Developer ID signed. The SHA-256 matches the digest GitHub publishes for the release file.
  • runtime prepare-builder --archive <file> works like prepare and prepare-engine:
    • It checks that the file is a regular file and that its SHA-256 matches the pin before it creates any state.
    • It takes the provider lock, copies the file into private candidate state with an exclusive create, hashes the copy again, runs codesign --verify --strict on it, records a tree digest, and publishes by rename.
    • If a prepared client already exists, it checks it again and reuses it; it never overwrites it. If an earlier run was interrupted, the staging directory is kept and the command refuses.
  • runtime probe reports builder_prepared and refuses if the prepared tree changed. That covers content, file mode, extra files, a symlinked executable, and an aliased root.
  • The client is meant to run on the host, outside the VM. This follows the design's preferred placement. If the host spike shows the guest socket forward cannot carry BuildKit's session upgrade, the pin moves to the linux-arm64 file and the client runs inside the VM.

Dockerfile image-reference scanner (provider/build_scan.rs)

build_scan.rs is a pure function. It reads a Dockerfile (it never runs it) and reports the images that a build of the selected target can pull:

  • FROM lines
  • COPY --from=<image>
  • RUN --mount=...,from=<image>
  • the same forms inside ONBUILD triggers of the Dockerfile's own stages

It follows the Dockerfile frontend that is built into the pinned engine (BuildKit 0.30):

  • It reads parser directives, the escape token, line continuations (skipping comments and blank lines), and heredoc bodies of RUN, COPY and ADD (including whitespace between << and the delimiter) the way that frontend does.
  • FROM names and --platform values expand $NAME and ${NAME} from the global ARGs. The build's literal arguments override them. For a linux/arm64 build, the known values of TARGETOS, TARGETARCH, BUILDOS and BUILDARCH are also available.
  • --from and mount from= values are literal, because the frontend itself refuses variables there. A value that names a stage, or a COPY --from stage index, means a stage. scratch means an empty source. Anything else is an image.
  • Only stages that the target can reach contribute images.
  • Each image is returned with its normalized public Docker Hub reference and the short name the frontend uses to match named build contexts.

The scanner refuses anything it cannot resolve exactly:

  • # syntax=, // syntax= and JSON-form frontend selection
  • ${NAME:-...} and other variable modifiers
  • quotes or escapes in references
  • [-prefixed exec-form arguments that strict JSON rejects but the frontend's Go decoder could accept (a \u escape such as a lone surrogate). As in the frontend, only the first JSON value counts, and text after it is ignored.
  • unknown or uncertain variables: *PLATFORM, *VARIANT, *OSVERSION, TARGETSTAGE, SOURCE_DATE_EPOCH
  • --platform values other than linux/arm64 or the build/target platform variables. A global ARG default that overrides BUILDPLATFORM or TARGETPLATFORM must itself resolve to linux/arm64
  • ARG names that are not plain [A-Za-z_][A-Za-z0-9_]* identifiers
  • an image name that equals a stage name (a pinned context with that name would replace the stage)
  • duplicate or invalid stage names, a stage named scratch, unknown instructions and flags, instructions other than ARG before the first FROM, and unterminated heredocs
  • non-Docker-Hub references
  • ADD sources the engine would fetch remotely, which would bypass the build network's isolation: any source with ://, an scp-style user@host: git ref, or a variable. This applies to shell and JSON forms, every source of a multi-source ADD, and ONBUILD triggers. ADD --checksum and --keep-git-dir are refused because they apply only to remote sources. Local sources, local archives and heredocs are still allowed.
  • build arguments that would set BUILDKIT_* or platform arguments
  • Dockerfiles over 1 MiB, more than 256 stages, or more than 32 distinct images
  • expanded values over 4 KiB, or global argument values over 256 KiB in total (such defaults are unresolved, so they refuse when a reference uses them)

Error messages give line numbers only. They never repeat Dockerfile text or argument values.

Source policy (provider/build_policy.rs): not applied yet

Nothing in this PR applies this policy. ONBUILD triggers stored in a base image's configuration are not enforced until the build step applies it, which #606 does. The scanner cannot see those triggers. They could add remote sources or name unpinned images.

build_policy::document(pinned) takes the build's digest-pinned base references and returns a BuildKit source policy:

  1. deny every docker-image:// and oci-layout:// source;
  2. allow exactly each pinned base, as BuildKit identifies a docker-image://<reference> named context (docker-image://docker.io/<namespace>/<repository>[:tag]@sha256:<digest>, normalized like llb.Image);
  3. deny http, https, git and docker-image+blob sources.

Later matching rules win, and unmatched sources (the local:// build context and Dockerfile) are allowed. A reference that is not a digest-pinned public Docker Hub image is refused.

The pinned buildx 0.34.1 reads the policy file named by EXPERIMENTAL_BUILDKIT_SOURCE_POLICY for bake and build. The engine's BuildKit v0.30.0 then checks every source operation against it in the solver, including operations added by base-image triggers. The tests evaluate the document with a model of BuildKit's matching rules (exact identifier, * wildcard, last match wins). Only a host build can confirm the identifiers BuildKit actually produces, so that check belongs to the build step.

Validation

  • RUSTUP_TOOLCHAIN=1.97.1 bun run check:local (rustfmt and Clippy with -D warnings, all targets): pass
  • cargo +1.97.1 clippy --all-targets --all-features -- -D warnings (with Zig 0.15.2): pass
  • Focused tests: 41 passed (provider::build_scan 31, provider::build_policy 3, provider::artifact 7). All fixtures are synthetic:
    • multi-stage builds, global and chained ARG substitution with overrides, stage ARG scoping
    • platform arguments, modifiers and unknown variables
    • stage names, indexes, scratch and images in COPY --from
    • literal-only from, mount sources, target reachability, ONBUILD triggers, --platform rules
    • syntax and escape directives, continuations, heredocs (including <<-, quoted delimiters, several on one line, JSON forms)
    • unknown instructions and flags, Docker Hub-only admission, equivalent spellings sharing one pin
    • size, count and encoding bounds, reserved arguments, and messages that never repeat input
    • artifact tests: pin and constant parity, no state on mismatch or non-regular source, copy rather than move, reuse, tamper refusals, aliased root, retained interrupted staging
  • RUSTUP_TOOLCHAIN=1.97.1 bun run test:local: pass, 21 test binaries, 1282 passed, 0 failed, 109 ignored (run from a short-path checkout of the same diff, because of the Unix socket path length limit)
  • Host check on Apple Silicon with the real release file:
    • a wrong file refuses and creates no state
    • prepare-builder succeeds, and running it again returns the same tree digest
    • the prepared docker-buildx version reports v0.34.1
    • runtime probe reports builder_prepared: true, refuses after chmod 755 on the executable, and passes again after the mode is restored
  • bun run --cwd packages/cli typecheck: pass. bun run docs:prepare && bun run docs:check: pass. tests/prerelease-plan.test.ts and tests/native-candidate-authored-artifact.test.ts (which read the native candidate guide): 64 pass. This PR changes no TypeScript.

Remaining gaps

  • The scanner has not been checked against a real build yet. Its rules come from the BuildKit 0.30 frontend source. They still need to be confirmed against a real build in the host spike, including whether named build contexts match the short image names.
  • Base images can carry their own triggers, and nothing enforces against them yet. An ONBUILD trigger stored in a base image's own configuration can add remote sources or pull an unpinned image. build_policy::document denies both at solve time, but only once the build step applies it (feat(native): build Compose images in the guest engine (runtime build-image) #606). The build step should also refuse pinned bases whose configurations have triggers. That would fail closed even if the policy were not applied.
  • File reading is not part of this change. The build step will read the Dockerfile: a bounded, regular, single-link file inside the context.
  • Nothing calls the scanner, the source policy or the client yet. The build runtime operation, the build network and the frontend integration come in later changes.

Refs HACK-1270

🤖 Generated with Claude Code


See it in action
Open in [code]smith · Need help on this PR? Tag @codesmith-bot with what you need.

Groundwork for native image builds; no command builds images yet and native
up still refuses build services.

- provider-pins.json pins buildx 0.34.1 (darwin-arm64, Developer ID signed)
  by SHA-256. `runtime prepare-builder --archive <file>` checks the pin
  before creating any state, copies the file into private candidate state,
  rehashes the owned copy, verifies its code signature and records a tree
  digest. An existing tree is re-verified and reused; an interrupted
  preparation is retained and refused. `runtime probe` reports
  `builder_prepared` and refuses a changed tree.
- provider/build_scan.rs is a pure scanner that reports the images a build
  of the selected target can pull (FROM, COPY --from, RUN --mount from=,
  ONBUILD triggers of internal stages). It follows the Dockerfile frontend
  embedded in the pinned engine: directives, escape token, continuations,
  heredoc bodies, global ARG substitution with literal build-argument
  overrides, stage names and indexes, target reachability. It refuses what
  it cannot resolve exactly (syntax directives, variable modifiers, quotes,
  uncertain platform arguments, non-arm64 platforms, stage/image name
  collisions, unknown instructions and flags, non-Docker-Hub references,
  size and count bounds) and never echoes Dockerfile text or values.

Refs HACK-1270

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@roodboi

roodboi commented Nov 5, 2026

Copy link
Copy Markdown
Contributor Author

@codex review

@linear-code

linear-code Bot commented Nov 5, 2026

Copy link
Copy Markdown

HACK-1270

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Nov 5, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-11-06T04:51:12.850517Z f21d5a0 Manual request
🔒 Security Review ✅ Completed 2026-11-06T04:54:32.976650Z f21d5a0 Manual request
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: dbcefdc01b

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@chatgpt-codex-connector

Copy link
Copy Markdown

🛡️ Codex Security Review · Automatically triggered

Security review completed. No security issues were found in this pull request.

Reviewed commit: dbcefdc01b

View security finding report

Only the user who started this review can view the report in Codex.

ℹ️ About Codex security reviews in GitHub

This is an experimental Codex feature. Security reviews are triggered when:

  • You comment "@codex security review"
  • A regular code review gets triggered (for example, "@codex review" or when a PR is opened), and you’re opted in so security review runs alongside code review

Once complete, Codex will leave suggestions, or a comment if no findings are found.

A global `ARG BUILDPLATFORM=...` or `ARG TARGETPLATFORM=...` default
replaces the frontend's platform value, and the frontend expands
`--platform` against the global arguments. The scanner accepted
`--platform=$BUILDPLATFORM` by spelling alone, so such a stage could
select another platform. The spelling is now accepted only while no
global default overrides the argument; an overridden one must expand to
linux/arm64 like any other value. A bare global ARG still keeps the
frontend's value.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@roodboi

roodboi commented Nov 5, 2026

Copy link
Copy Markdown
Contributor Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: c88d84dfa1

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@chatgpt-codex-connector

Copy link
Copy Markdown

🛡️ Codex Security Review · Automatically triggered

Security review completed. No security issues were found in this pull request.

Reviewed commit: c88d84dfa1

View security finding report

Only the user who started this review can view the report in Codex.

ℹ️ About Codex security reviews in GitHub

This is an experimental Codex feature. Security reviews are triggered when:

  • You comment "@codex security review"
  • A regular code review gets triggered (for example, "@codex review" or when a PR is opened), and you’re opted in so security review runs alongside code review

Once complete, Codex will leave suggestions, or a comment if no findings are found.

Global ARG defaults expand eagerly, so repeating `ARG A=$A$A` doubled the
value each time and a few hundred bytes of Dockerfile could exhaust
memory. Expansion now checks a 4 KiB cap before each append, and all held
global values share a 256 KiB budget. A default past either cap is
unresolved and refuses only when a reference uses it, so an oversized
platform override still refuses.

ARG names that are not plain identifiers are refused. The frontend would
unquote, unescape or expand such a name, so a literal compare could miss
an override of BUILDPLATFORM or TARGETPLATFORM.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@roodboi

roodboi commented Nov 5, 2026

Copy link
Copy Markdown
Contributor Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 78a22c2b2c

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@chatgpt-codex-connector

Copy link
Copy Markdown

🛡️ Codex Security Review · Automatically triggered

Security review completed. No security issues were found in this pull request.

Reviewed commit: 78a22c2b2c

View security finding report

Only the user who started this review can view the report in Codex.

ℹ️ About Codex security reviews in GitHub

This is an experimental Codex feature. Security reviews are triggered when:

  • You comment "@codex security review"
  • A regular code review gets triggered (for example, "@codex review" or when a PR is opened), and you’re opted in so security review runs alongside code review

Once complete, Codex will leave suggestions, or a comment if no findings are found.

BuildKit links a later `FROM scratch` to an earlier stage of that name
instead of the empty image. The scanner treated it as empty, so it
missed that stage's image and skipped its platform check. A stage alias
of `scratch` (in any case) is now refused; plain `FROM scratch` is still
the empty image.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@roodboi

roodboi commented Nov 5, 2026

Copy link
Copy Markdown
Contributor Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 62f6112b37

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@chatgpt-codex-connector

Copy link
Copy Markdown

🛡️ Codex Security Review · Automatically triggered

Security review completed. No security issues were found in this pull request.

Reviewed commit: 62f6112b37

View security finding report

Only the user who started this review can view the report in Codex.

ℹ️ About Codex security reviews in GitHub

This is an experimental Codex feature. Security reviews are triggered when:

  • You comment "@codex security review"
  • A regular code review gets triggered (for example, "@codex review" or when a PR is opened), and you’re opted in so security review runs alongside code review

Once complete, Codex will leave suggestions, or a comment if no findings are found.

BuildKit's shell lexer keeps tabs, carriage returns and spaces after an
unquoted `<<` in the same word, so `RUN true << CMD` starts a heredoc.
The scanner split `<<` from the delimiter, missed the heredoc, and read
its body as instructions, so a body line like `FROM scratch` could hide
the real stage's image. The heredoc word lexer now absorbs that
whitespace the same way. `<<- CMD` is still two words and no heredoc, as
in BuildKit.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@roodboi

roodboi commented Nov 5, 2026

Copy link
Copy Markdown
Contributor Author

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. 👍

Reviewed commit: 4f6841a473

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@chatgpt-codex-connector

Copy link
Copy Markdown

🛡️ Codex Security Review · Automatically triggered

Security review completed. No security issues were found in this pull request.

Reviewed commit: 4f6841a473

View security finding report

Only the user who started this review can view the report in Codex.

ℹ️ About Codex security reviews in GitHub

This is an experimental Codex feature. Security reviews are triggered when:

  • You comment "@codex security review"
  • A regular code review gets triggered (for example, "@codex review" or when a PR is opened), and you’re opted in so security review runs alongside code review

Once complete, Codex will leave suggestions, or a comment if no findings are found.

The engine fetches ADD URLs and git refs from the guest's own network
namespace, so they would bypass the build network's isolation. The
scanner now refuses any ADD source that BuildKit could treat as remote:
after dropping quotes and escapes, a source with `://`, an scp-style
`user@host:` ref, or a `$` that could expand to either. This covers
shell and JSON forms, every source of a multi-source ADD, ONBUILD
triggers and all flags. `--checksum` and `--keep-git-dir` apply only to
remote sources and are refused outright. Local sources, local archives,
heredocs and COPY are unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@roodboi

roodboi commented Nov 6, 2026

Copy link
Copy Markdown
Contributor Author

Security fix in 1973e92: ADD sources the engine would fetch remotely are now refused.

The engine fetches ADD URLs and git refs from the guest's own network namespace, so those fetches would bypass the build network's isolation. Following the pinned BuildKit v0.30.0 rules (dfgitutil.ParseGitRef, isHTTPSource and the scp-style regex in util/sshutil), a source is remote when it starts with scheme:// or is a user@host:path ref. The scanner refuses a superset: after dropping quotes and escapes, any ADD source with ://, with @ followed by :, or with a $ that could expand to either.

  • It applies to the shell form (split on whitespace, as the frontend does), the JSON form, every source of a multi-source ADD, and ONBUILD triggers, whatever flags are set.
  • --checksum and --keep-git-dir are refused outright, since BuildKit accepts them only for HTTP(S) or git sources.
  • Local sources, local archives, heredocs, github.com/org/repo (which BuildKit treats as a local path), destinations that use variables, and COPY are unchanged.

Test: provider::build_scan::tests::add_refuses_remote_sources. It fails on the previous head. The existing flag test now uses a local ADD in place of a remote one with --checksum.

@codex review

🤖 Addressed by Claude Code

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. What shall we delve into next?

Reviewed commit: 1973e9225e

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@chatgpt-codex-connector

Copy link
Copy Markdown

🛡️ Codex Security Review · Automatically triggered

Security review completed. No security issues were found in this pull request.

Reviewed commit: 1973e9225e

View security finding report

Only the user who started this review can view the report in Codex.

ℹ️ About Codex security reviews in GitHub

This is an experimental Codex feature. Security reviews are triggered when:

  • You comment "@codex security review"
  • A regular code review gets triggered (for example, "@codex review" or when a PR is opened), and you’re opted in so security review runs alongside code review

Once complete, Codex will leave suggestions, or a comment if no findings are found.

BuildKit decodes exec-form arguments with Go's json.Decoder: it reads
only the first value, ignores trailing text, and maps lone surrogate
escapes to U+FFFD. The scanner required strict whole-text JSON, so for
`ADD ["\uD800https://x/y", "/z"]` or `RUN ["echo"] <<EOF` it fell back
to shell form and could miss a remote source or skip real instructions
as a heredoc body. JSON form now decodes the first value the same way,
and `[`-prefixed text with a \u escape that strict JSON rejects refuses.
Bracketed shell text such as `RUN [ -d /x ]` stays shell form.

The scanner cannot see ONBUILD triggers stored in a pinned base image's
configuration. `provider::build_policy` adds the BuildKit source policy
the build step passes through EXPERIMENTAL_BUILDKIT_SOURCE_POLICY: it
denies http, https, git and docker-image+blob sources at solve time, so
a base image's triggers cannot fetch them either.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@roodboi

roodboi commented Nov 6, 2026

Copy link
Copy Markdown
Contributor Author

Security fixes in de13309:

JSON form now matches the frontend. BuildKit's parseJSON decodes exec-form arguments with Go's json.Decoder. It reads only the first value, ignores trailing text, and maps lone surrogate escapes to U+FFFD. The scanner had required strict whole-text JSON and fell back to shell form otherwise. That let ADD ["\uD800https://x/y", "/z"] hide a remote source, and let RUN ["echo"] <<EOF skip real instructions as a heredoc body. json_words now decodes the first value the same way, trailing text included, and then applies the remote-source checks to the decoded words. If [-prefixed text has a \u escape that strict JSON rejects, the scanner refuses. Bracketed shell text such as RUN [ -d /x ] stays shell form, as it does in the frontend. Test: provider::build_scan::tests::json_form_is_read_as_the_frontend_reads_it. It fails on the previous head.

ONBUILD triggers in base images. The scanner never sees a pinned base image's configuration, so it cannot check the triggers stored there. provider::build_policy::document() is the BuildKit source policy for native builds. It denies http://*, https://*, git://* and docker-image+blob://* sources. Checked against the pinned versions:

  • buildx 0.34.1 bake reads EXPERIMENTAL_BUILDKIT_SOURCE_POLICY (bake/bake.go, build.ReadSourcePolicy) and sends it in SolveOpt.SourcePolicy.
  • Engine 29.5.2 vendors BuildKit v0.30.0. There, control.Solve hands the policy to the solver, and llbsolver.Load evaluates every source op against it, including source ops added by a base image's triggers.
  • The JSON uses the sourcepolicy/pb field tags, with string enum names, which json.go decodes.

This PR runs no builds, so the build step must write this document to a private file and set the variable. Test: provider::build_policy::tests::the_policy_denies_every_network_source_scheme_and_nothing_else.

@codex review

🤖 Addressed by Claude Code

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. 🎉

Reviewed commit: de133091f4

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Unmatched sources are allowed, so the policy let a base image's ONBUILD
trigger pull any unpinned image. `build_policy::document` now takes the
build's digest-pinned base references and denies every docker-image and
oci-layout source except exactly those, normalized the way BuildKit's
llb.Image names a `docker-image://` named context. The http, https, git
and docker-image+blob denials stay last. References that are not
digest-pinned public Docker Hub images refuse.

The docs now say plainly that nothing applies the policy yet: ONBUILD
triggers in base images are not enforced until the build step passes it
to buildx.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@roodboi

roodboi commented Nov 6, 2026

Copy link
Copy Markdown
Contributor Author

Follow-up in f21d5a0 to the source-policy review:

  • No enforcement claim. Nothing in this PR applies build_policy::document. The module doc, the scanner doc and the PR description now say plainly that ONBUILD triggers stored in base images are not enforced until the build step passes the policy to buildx through EXPERIMENTAL_BUILDKIT_SOURCE_POLICY. feat(native): build Compose images in the guest engine (runtime build-image) #606 does that.

  • Only pinned images. document(pinned) now takes the build's digest-pinned base references. It denies every docker-image:// and oci-layout:// source first, then allows each pinned base with an EXACT rule, then denies http, https, git and docker-image+blob. The allow identifier is docker-image:// plus the reference normalized the way BuildKit v0.30.0's llb.Image normalizes it (ParseNormalizedNamed then TagNameOnly): for example docker-image://docker.io/library/alpine@sha256:…, with any tag kept. That is the identifier of a docker-image://<reference> named context. A reference that is not a digest-pinned public Docker Hub image is refused.

  • Tests. They model BuildKit's evaluatePolicy (an equal identifier matches, WILDCARD means ^…$ with * as any text, the last match wins):

    • only_the_pinned_images_and_local_sources_are_allowed: the pinned digests and local:// sources are allowed. An unpinned tag, another digest, another repository with the same digest, ghcr.io, oci-layout, http, https, git and docker-image+blob are denied.
    • the_rule_list_has_the_documented_order_and_shape
    • only_digest_pinned_docker_hub_references_are_accepted

    Only a host build can confirm the identifiers BuildKit actually produces, so that check stays with the build step.

@codex review

🤖 Addressed by Claude Code

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. You're on a roll.

Reviewed commit: f21d5a04a1

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@chatgpt-codex-connector

Copy link
Copy Markdown

🛡️ Codex Security Review · Automatically triggered

Security review completed. No security issues were found in this pull request.

Reviewed commit: f21d5a04a1

View security finding report

Only the user who started this review can view the report in Codex.

ℹ️ About Codex security reviews in GitHub

This is an experimental Codex feature. Security reviews are triggered when:

  • You comment "@codex security review"
  • A regular code review gets triggered (for example, "@codex review" or when a PR is opened), and you’re opted in so security review runs alongside code review

Once complete, Codex will leave suggestions, or a comment if no findings are found.

roodboi added a commit that referenced this pull request Nov 7, 2026
, #612, #628, #629, #630) (#639)

## What this is

Merge train batch 6 into `next`: the native image builds stack and its
fixes, as one batch. Builds stay off by default: they run only with
`HACK_NATIVE_BUILDS=1`, which native `up`, restart and doctor all check.
Each component's reviewed head is merged with `git merge --no-ff`, in
stack order. Integration conflicts are resolved in the batch's merge
commits, described below. Hosted CI on this branch is the full-suite
gate for the combined result.

| PR | Title | Linear | Head included | Review |
| --- | --- | --- | --- | --- |
| #583 | feat(native): pin a BuildKit client and scan Dockerfile image
references | HACK-1270 | `f21d5a04` | Codex code + security: no
findings; 0 open threads |
| #606 | feat(native): build Compose images in the guest engine (runtime
build-image) | HACK-1270 | `460ddff2` | Codex code + security: no
findings; 0 open threads |
| #607 | feat(native): admit build storage, budget build cache and
reclaim superseded build images | HACK-1270 | `9f33a1d0` | Codex code +
security: no findings; 0 open threads |
| #612 | feat(native): build Compose build services in native up behind
HACK_NATIVE_BUILDS | HACK-1270 | `7f42e328` | Codex clean at `8ec7460e`;
`7f42e328` adds #628's squash (re-review requested) |
| #628 | fix(native): admit native build fields and name services in
build refusals | HACK-1925 | `f07ea583` | Codex code + security clean;
already squashed into #612's branch |
| #629 | fix(native): keep the VM responsive during image builds and
stop a silent guest | HACK-1924 | `a0e3e797` | Codex code + security: no
findings; 0 open threads |
| #630 | fix(native): restore a sourceless graph after its project files
were touched | HACK-1914 | `a93db842` | Codex clean at `009989c7`;
`a93db842` only merges #629 into it (re-review requested) |

## Security properties in the merged code

- **Build arguments:** they reach the BuildKit client only through a
private 0600 bake definition, never its environment. The client
environment is cleared to an allowlist. Names must be plain identifiers,
and BuildKit-interpreted names such as `BUILDKIT_*`, `BUILDX_*` and
`SOURCE_DATE_EPOCH` are refused. A build argument equal to a managed
secret value is refused without echoing the value. Build arguments are
not secrets, matching Docker.
- **Source policy:** a source policy from `build_policy::document`, set
through `EXPERIMENTAL_BUILDKIT_SOURCE_POLICY` in a private file, applies
to every build. It allows only the scanner's digest-pinned base images
and denies other images, `oci-layout`, `http`, `https`, `git` and blob
sources. Pinned bases whose config has `OnBuild` triggers are refused.
- **Dockerfile scanner:**
- remote `ADD` sources are refused, including the JSON form, multiple
sources and variables;
- JSON-form arguments are read as BuildKit reads them, and a
lenient-decode differential fails closed;
- heredocs are recognized as BuildKit's lexer does, including whitespace
after `<<`;
  - a stage named `scratch` is refused;
  - `ARG` names must be plain identifiers;
  - platform arguments must resolve to `linux/arm64`;
  - expanded values are size-bounded.
- **Build output:** forwarded output is sanitized and then redacted,
including Go-quoted and JSON-escaped forms and values split across a
line cut. It is bounded by size.

## Integration changes

- **#606 with #605 / #609:** `resolve_build_base` runs on #605's guarded
registry client, so the resolver and address policy, origin-bound
authentication and the realm rules apply. It uses #609's native platform
selection, so builds stay Docker Hub and linux/arm64 only. The older
Docker Hub-only fetch helpers are dropped.
- **#606 / #607 with #605 / #609 / #615:** the `build-image` and
`reclaim-build-images` commands sit beside the image-policy and
emulation options. The build error helpers take `impl
Into<ErrorMessage>`.
- **#607:** the native-build-retention TLA+ contract is registered
beside the restart-policy, pool-emulation and multi-root contracts. Its
model-coverage row now says an orphaned build client keeps the fence and
per-key lock until it exits (`9f33a1d0`).
- **#612 with batch 5:**
- The builds-enabled classification flows through `classifyAdapted`, the
Compose-backend refusal and doctor.
- With the gate off, native `up` refuses a build with a message that
names `HACK_NATIVE_BUILDS`, so doctor's basic-build advice names it too;
one test expectation changed to match.
- Startup and restart keep the dependency-file selection, host aliases,
emulation policy and the inactive-profile skip alongside the build
services.
- **New (#609 x #612):** a build service whose adaptation selects amd64
emulation is refused before any effect, because native builds are
linux/arm64 only. `prefer-native` passes, and a test covers it.
- **#629:** the socket cleanup after a clean stop is kept inside #629's
new guest stop. #629's two flaky relay tests are fixed on its branch
(`a0e3e797`): each test gets its own fixture root, and EINVAL from
`set_read_timeout` after the peer closes is accepted.
- **`next` merge:** the last merge brings in Kit's CI shard and SIGKILL
harness changes (#624, #627). The commit hook formatted
`tests/native-compose-generation.test.ts` with Biome, a whitespace-only
change to that Kit file.

## Validation on this branch (`6337e0ed`)

- Clean CLI typecheck: pass. Biome on the 20 changed files: no errors.
- `RUSTUP_TOOLCHAIN=1.97.1 bun run check:local`: pass. All-features
clippy `-D warnings` (Zig 0.15.2): pass.
- Cargo `--lib`: 1346 pass. `--features native-config-plan`
`provider::graph`: 673 pass. Contracts: `project_contract` 66,
`compose_fields_contract` 10, `interpolation_budget_contract` 1,
`local_contract` 14.
- `bun run test:models` (TLA+ 1.7.4): exit 0, 172 controls verified.
- Focused bun tests, bounded runner, the changed test files: 242 pass, 0
fail.
- `docs:prepare` + `docs:check`, `privacy:check`, gitleaks over
`origin/next..HEAD`: pass.

Host evidence: 11 of 11 builds passed on an M5 host with the relay
(#629). Still open: the source-policy host check (a pinned build
succeeds, and a base trigger that pulls an unpinned image or fetches
https fails with "denied by policy") and the bake `--allow fs.read` /
metadata shape on the pinned buildx 0.34.1. Both are listed in #606.

After this merges, each component PR is closed with a link to the merge
commit.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

<!-- codesmith:footer -->
---
<a
href="https://backend.blacksmith.sh/track/run-proof?expires=1796625833&installation_model_id=17053&pr_number=639&ref=codesmith_pr_footer&repository=hack-dance%2Fhack&return_to=https%3A%2F%2Fgithub.com%2Fhack-dance%2Fhack%2Fpull%2F639&variant=see_it_in_action&signature=c3ba624f6f34813ba26773c5b4c3caf941bd59515e8c3016c92df8f0e2a6048f"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://pr-comments-assets.blacksmith.sh/codesmith/proof-see-it-in-action-dark.svg"><source
media="(prefers-color-scheme: light)"
srcset="https://pr-comments-assets.blacksmith.sh/codesmith/proof-see-it-in-action-light.svg"><img
alt="See it in action"
src="https://pr-comments-assets.blacksmith.sh/codesmith/proof-see-it-in-action-dark-3x.png"
width="132" height="28"></picture></a>
<sup><a
href="https://app.blacksmith.sh/hack-dance/codesmith/hack/pr/639?autoLogin=true&ref=codesmith_pr_footer">Open
in [code]smith</a> · Need help on this PR? Tag
<code>@codesmith-bot</code> with what you need.</sup>

<!-- codesmith:autofix:disabled -->
<!-- /codesmith:footer -->

---------

Co-authored-by: Dimitri Kennedy <344842+roodboi@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
@roodboi

roodboi commented Nov 7, 2026

Copy link
Copy Markdown
Contributor Author

Merged into next through the train batch PR #639 (squash commit e59fcc2). This PR's reviewed head f21d5a04a1b75044b804452cbae5fe07b7a9f4a0 was included. #639 describes the integration resolutions and the batch security fixes: Go-style image config key reading, non-ASCII key refusal, the build base size bound (unpacked size tracked in HACK-1945), build-base descriptor validation and case-insensitive stage names. Closing as merged by batch; the branch is kept.

🤖 Addressed by Claude Code

@roodboi roodboi closed this Nov 7, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant