From dbceb5fd225060ffe7fcfec6ddd22fb3f7ef526f Mon Sep 17 00:00:00 2001 From: Rune Soerensen Date: Tue, 14 Apr 2026 21:10:20 -0400 Subject: [PATCH 1/4] Add failing test for `IsTrustedBuilder` not matching tagless known builders Signed-off-by: Rune Soerensen --- internal/builder/trusted_builder_test.go | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/internal/builder/trusted_builder_test.go b/internal/builder/trusted_builder_test.go index 90e7599cd8..a537814d99 100644 --- a/internal/builder/trusted_builder_test.go +++ b/internal/builder/trusted_builder_test.go @@ -30,6 +30,23 @@ func trustedBuilder(t *testing.T, when spec.G, it spec.S) { }) when("IsTrustedBuilder", func() { + it("trusts known trusted builders", func() { + // Known builder with exact tag match + isTrusted, err := bldr.IsTrustedBuilder(config.Config{}, "heroku/builder:24") + h.AssertNil(t, err) + h.AssertTrue(t, isTrusted) + + // Known builder without tag should match any tag + isTrusted, err = bldr.IsTrustedBuilder(config.Config{}, "paketobuildpacks/builder-jammy-base:latest") + h.AssertNil(t, err) + h.AssertTrue(t, isTrusted) + + // Unknown builder should not be trusted + isTrusted, err = bldr.IsTrustedBuilder(config.Config{}, "my/private/builder") + h.AssertNil(t, err) + h.AssertFalse(t, isTrusted) + }) + it("trust image without tag", func() { cfg := config.Config{ TrustedBuilders: []config.TrustedBuilder{ From 85e15fc5b557b0a7f1c4d49ec46b373aa2a032ba Mon Sep 17 00:00:00 2001 From: Rune Soerensen Date: Tue, 14 Apr 2026 21:17:05 -0400 Subject: [PATCH 2/4] Consolidate trust logic in IsTrustedBuilder with smart tag matching Signed-off-by: Rune Soerensen --- internal/builder/trusted_builder.go | 21 ++++++++++++++++++++- internal/commands/build.go | 2 +- internal/commands/builder_inspect.go | 2 +- internal/commands/config_trusted_builder.go | 2 +- 4 files changed, 23 insertions(+), 4 deletions(-) diff --git a/internal/builder/trusted_builder.go b/internal/builder/trusted_builder.go index e7f245789a..d0d579e10b 100644 --- a/internal/builder/trusted_builder.go +++ b/internal/builder/trusted_builder.go @@ -122,11 +122,29 @@ func IsTrustedBuilder(cfg config.Config, builderName string) (bool, error) { if err != nil { return false, err } + + // Collect all trusted builder names + var trustedBuilderNames []string + + // Add known trusted builders + for _, knownBuilder := range KnownBuilders { + if knownBuilder.Trusted { + trustedBuilderNames = append(trustedBuilderNames, knownBuilder.Image) + } + } + + // Add user-configured trusted builders for _, trustedBuilder := range cfg.TrustedBuilders { - trustedBuilderReference, err := name.ParseReference(trustedBuilder.Name, name.WithDefaultTag("")) + trustedBuilderNames = append(trustedBuilderNames, trustedBuilder.Name) + } + + // Check if builder matches any trusted builder + for _, trustedBuilderName := range trustedBuilderNames { + trustedBuilderReference, err := name.ParseReference(trustedBuilderName, name.WithDefaultTag("")) if err != nil { return false, err } + if trustedBuilderReference.Identifier() != "" { if builderReference.Name() == trustedBuilderReference.Name() { return true, nil @@ -137,5 +155,6 @@ func IsTrustedBuilder(cfg config.Config, builderName string) (bool, error) { } } } + return false, nil } diff --git a/internal/commands/build.go b/internal/commands/build.go index d3db7a69b9..2b75bcbb53 100644 --- a/internal/commands/build.go +++ b/internal/commands/build.go @@ -121,7 +121,7 @@ func Build(logger logging.Logger, cfg config.Config, packClient PackClient) *cob if err != nil { return err } - trustBuilder := isTrusted || bldr.IsKnownTrustedBuilder(builder) || flags.TrustBuilder + trustBuilder := isTrusted || flags.TrustBuilder if trustBuilder { logger.Debugf("Builder %s is trusted", style.Symbol(builder)) if flags.LifecycleImage != "" { diff --git a/internal/commands/builder_inspect.go b/internal/commands/builder_inspect.go index 04d014c454..36fa93be93 100644 --- a/internal/commands/builder_inspect.go +++ b/internal/commands/builder_inspect.go @@ -71,7 +71,7 @@ func inspectBuilder( builderInfo := writer.SharedBuilderInfo{ Name: imageName, IsDefault: imageName == cfg.DefaultBuilder, - Trusted: isTrusted || bldr.IsKnownTrustedBuilder(imageName), + Trusted: isTrusted, } localInfo, localErr := inspector.InspectBuilder(imageName, true, client.WithDetectionOrderDepth(flags.Depth)) diff --git a/internal/commands/config_trusted_builder.go b/internal/commands/config_trusted_builder.go index 36bcfb6018..713feaa0fd 100644 --- a/internal/commands/config_trusted_builder.go +++ b/internal/commands/config_trusted_builder.go @@ -55,7 +55,7 @@ func addTrustedBuilder(args []string, logger logging.Logger, cfg config.Config, if err != nil { return err } - if isTrusted || bldr.IsKnownTrustedBuilder(imageName) { + if isTrusted { logger.Infof("Builder %s is already trusted", style.Symbol(imageName)) return nil } From 6bb75db5ddfb0f0fd3611eed5c5f69dc1ac12bea Mon Sep 17 00:00:00 2001 From: Rune Soerensen Date: Thu, 4 Jun 2026 08:03:02 -0400 Subject: [PATCH 3/4] Apply smart matching to IsKnownTrustedBuilder Make IsKnownTrustedBuilder use the same name.ParseReference matching as IsTrustedBuilder so tagless known entries match any tag in the same repository. This fixes the SDK default at pkg/client/build.go and the "can't untrust a known builder" guard in config trusted-builders remove, both of which still relied on exact-string matching. Signed-off-by: Rune Soerensen --- internal/builder/trusted_builder.go | 63 +++++++++++++----------- internal/builder/trusted_builder_test.go | 13 +++-- 2 files changed, 44 insertions(+), 32 deletions(-) diff --git a/internal/builder/trusted_builder.go b/internal/builder/trusted_builder.go index d0d579e10b..f6beca01d5 100644 --- a/internal/builder/trusted_builder.go +++ b/internal/builder/trusted_builder.go @@ -109,12 +109,11 @@ var KnownBuilders = []KnownBuilder{ } func IsKnownTrustedBuilder(builderName string) bool { - for _, knownBuilder := range KnownBuilders { - if builderName == knownBuilder.Image && knownBuilder.Trusted { - return true - } + builderReference, err := name.ParseReference(builderName, name.WithDefaultTag("")) + if err != nil { + return false } - return false + return matchesAnyKnownTrustedBuilder(builderReference) } func IsTrustedBuilder(cfg config.Config, builderName string) (bool, error) { @@ -123,38 +122,44 @@ func IsTrustedBuilder(cfg config.Config, builderName string) (bool, error) { return false, err } - // Collect all trusted builder names - var trustedBuilderNames []string - - // Add known trusted builders - for _, knownBuilder := range KnownBuilders { - if knownBuilder.Trusted { - trustedBuilderNames = append(trustedBuilderNames, knownBuilder.Image) - } + if matchesAnyKnownTrustedBuilder(builderReference) { + return true, nil } - // Add user-configured trusted builders for _, trustedBuilder := range cfg.TrustedBuilders { - trustedBuilderNames = append(trustedBuilderNames, trustedBuilder.Name) - } - - // Check if builder matches any trusted builder - for _, trustedBuilderName := range trustedBuilderNames { - trustedBuilderReference, err := name.ParseReference(trustedBuilderName, name.WithDefaultTag("")) + trustedBuilderReference, err := name.ParseReference(trustedBuilder.Name, name.WithDefaultTag("")) if err != nil { return false, err } - - if trustedBuilderReference.Identifier() != "" { - if builderReference.Name() == trustedBuilderReference.Name() { - return true, nil - } - } else { - if builderReference.Context().RepositoryStr() == trustedBuilderReference.Context().RepositoryStr() { - return true, nil - } + if referencesMatch(builderReference, trustedBuilderReference) { + return true, nil } } return false, nil } + +func matchesAnyKnownTrustedBuilder(builderReference name.Reference) bool { + for _, knownBuilder := range KnownBuilders { + if !knownBuilder.Trusted { + continue + } + trustedBuilderReference, err := name.ParseReference(knownBuilder.Image, name.WithDefaultTag("")) + if err != nil { + continue + } + if referencesMatch(builderReference, trustedBuilderReference) { + return true + } + } + return false +} + +// referencesMatch reports whether builderReference is trusted by trustedBuilderReference. +// Tagless trusted entries match any tag in the same repository; tagged entries require an exact match. +func referencesMatch(builderReference, trustedBuilderReference name.Reference) bool { + if trustedBuilderReference.Identifier() != "" { + return builderReference.Name() == trustedBuilderReference.Name() + } + return builderReference.Context().RepositoryStr() == trustedBuilderReference.Context().RepositoryStr() +} diff --git a/internal/builder/trusted_builder_test.go b/internal/builder/trusted_builder_test.go index a537814d99..16fb047f72 100644 --- a/internal/builder/trusted_builder_test.go +++ b/internal/builder/trusted_builder_test.go @@ -21,10 +21,17 @@ func TestTrustedBuilder(t *testing.T) { func trustedBuilder(t *testing.T, when spec.G, it spec.S) { when("IsKnownTrustedBuilder", func() { - it("matches exactly", func() { + it("matches tagless known builders against any tag in the same repository", func() { h.AssertTrue(t, bldr.IsKnownTrustedBuilder("paketobuildpacks/builder-jammy-base")) - h.AssertFalse(t, bldr.IsKnownTrustedBuilder("paketobuildpacks/builder-jammy-base:latest")) - h.AssertFalse(t, bldr.IsKnownTrustedBuilder("paketobuildpacks/builder-jammy-base:1.2.3")) + h.AssertTrue(t, bldr.IsKnownTrustedBuilder("paketobuildpacks/builder-jammy-base:latest")) + h.AssertTrue(t, bldr.IsKnownTrustedBuilder("paketobuildpacks/builder-jammy-base:1.2.3")) + }) + it("requires an exact tag match for tagged known builders", func() { + h.AssertTrue(t, bldr.IsKnownTrustedBuilder("heroku/builder:24")) + h.AssertFalse(t, bldr.IsKnownTrustedBuilder("heroku/builder")) + h.AssertFalse(t, bldr.IsKnownTrustedBuilder("heroku/builder:99")) + }) + it("does not match unknown builders", func() { h.AssertFalse(t, bldr.IsKnownTrustedBuilder("my/private/builder")) }) }) From 3dc1da2d5e966865ed50216ef0ec1d78ff1588d1 Mon Sep 17 00:00:00 2001 From: Rune Soerensen Date: Thu, 4 Jun 2026 08:03:29 -0400 Subject: [PATCH 4/4] Add caller-level tests for tagged known trusted builders Add regression coverage at the command layer to guard against future refactors that bypass the smart-matching helpers: build, builder inspect, and config trusted-builders add now each verify that a tagged reference (paketobuildpacks/builder-jammy-base:latest) is treated as trusted via the tagless known entry. Signed-off-by: Rune Soerensen --- internal/commands/build_test.go | 13 +++++++++++++ internal/commands/builder_inspect_test.go | 19 +++++++++++++++++++ .../commands/config_trusted_builder_test.go | 12 ++++++++++++ 3 files changed, 44 insertions(+) diff --git a/internal/commands/build_test.go b/internal/commands/build_test.go index 9a9d0ce248..a1aea464f3 100644 --- a/internal/commands/build_test.go +++ b/internal/commands/build_test.go @@ -139,6 +139,19 @@ func testBuildCommand(t *testing.T, when spec.G, it spec.S) { }) }) + when("the builder is a tagged reference of a tagless known trusted builder", func() { + it("sets the trust builder option", func() { + mockClient.EXPECT(). + Build(gomock.Any(), EqBuildOptionsWithTrustedBuilder(true)). + Return(nil) + + logger.WantVerbose(true) + command.SetArgs([]string{"image", "--builder", "paketobuildpacks/builder-jammy-base:latest"}) + h.AssertNil(t, command.Execute()) + h.AssertContains(t, outBuf.String(), "Builder 'paketobuildpacks/builder-jammy-base:latest' is trusted") + }) + }) + when("the image name matches a builder name", func() { it("refuses to build", func() { logger.WantVerbose(true) diff --git a/internal/commands/builder_inspect_test.go b/internal/commands/builder_inspect_test.go index 636ed289a3..620df1fdf9 100644 --- a/internal/commands/builder_inspect_test.go +++ b/internal/commands/builder_inspect_test.go @@ -234,6 +234,25 @@ func testBuilderInspectCommand(t *testing.T, when spec.G, it spec.S) { }) }) + when("image is a tagged reference of a tagless known trusted builder", func() { + it("passes builder info with trusted true to the writer's `Print` method", func() { + builderWriter := newDefaultBuilderWriter() + + command := commands.BuilderInspect( + logger, + config.Config{}, + newDefaultBuilderInspector(), + newWriterFactory(returnsForWriter(builderWriter)), + ) + command.SetArgs([]string{"paketobuildpacks/builder-jammy-base:latest"}) + + err := command.Execute() + assert.Nil(err) + + assert.Equal(builderWriter.ReceivedBuilderInfo.Trusted, true) + }) + }) + when("default builder is configured and is the same as specified by the command", func() { it("passes builder info with isDefault true to the writer's `Print` method", func() { cfg.DefaultBuilder = "the/default-builder" diff --git a/internal/commands/config_trusted_builder_test.go b/internal/commands/config_trusted_builder_test.go index ef6cfd72fc..be0aae5b31 100644 --- a/internal/commands/config_trusted_builder_test.go +++ b/internal/commands/config_trusted_builder_test.go @@ -186,6 +186,18 @@ func testTrustedBuilderCommand(t *testing.T, when spec.G, it spec.S) { h.AssertEq(t, string(oldContents), "") }) }) + + when("builder is a tagged reference of a tagless known trusted builder", func() { + it("does nothing", func() { + h.AssertNil(t, os.WriteFile(configPath, []byte(""), os.ModePerm)) + + command.SetArgs(append(args, "paketobuildpacks/builder-jammy-base:latest")) + h.AssertNil(t, command.Execute()) + oldContents, err := os.ReadFile(configPath) + h.AssertNil(t, err) + h.AssertEq(t, string(oldContents), "") + }) + }) }) })