Script 'mail_helper' called by obssrc
Hello community,

here is the log from the commit of package kargo-cli for openSUSE:Factory 
checked in at 2026-10-09 21:44:24
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/kargo-cli (Old)
 and      /work/SRC/openSUSE:Factory/.kargo-cli.new.3698736 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "kargo-cli"

Fri Oct  9 21:44:24 2026 rev:67 rq:1383652 version:1.12.3

Changes:
--------
--- /work/SRC/openSUSE:Factory/kargo-cli/kargo-cli.changes      2026-10-08 
11:59:37.419564571 +0200
+++ /work/SRC/openSUSE:Factory/.kargo-cli.new.3698736/kargo-cli.changes 
2026-10-09 21:44:31.095133731 +0200
@@ -1,0 +2,6 @@
+Fri Oct 09 05:10:07 UTC 2026 - Johannes Kastl 
<[email protected]>
+
+- Update to version 1.12.3:
+  No CLI-related changes or dependency updates
+
+-------------------------------------------------------------------

Old:
----
  kargo-cli-1.12.2.obscpio

New:
----
  kargo-cli-1.12.3.obscpio

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Other differences:
------------------
++++++ kargo-cli.spec ++++++
--- /var/tmp/diff_new_pack.LdkaMY/_old  2026-10-09 21:44:35.260308999 +0200
+++ /var/tmp/diff_new_pack.LdkaMY/_new  2026-10-09 21:44:35.261309041 +0200
@@ -19,7 +19,7 @@
 %define executable_name kargo
 
 Name:           kargo-cli
-Version:        1.12.2
+Version:        1.12.3
 Release:        0
 Summary:        CLI for the Kubernetes application lifecycle orchestration
 License:        Apache-2.0

++++++ _service ++++++
--- /var/tmp/diff_new_pack.LdkaMY/_old  2026-10-09 21:44:35.292310346 +0200
+++ /var/tmp/diff_new_pack.LdkaMY/_new  2026-10-09 21:44:35.295310472 +0200
@@ -3,7 +3,7 @@
     <param name="url">https://github.com/akuity/kargo.git</param>
     <param name="scm">git</param>
     <param name="exclude">.git</param>
-    <param name="revision">refs/tags/v1.12.2</param>
+    <param name="revision">refs/tags/v1.12.3</param>
     <param name="versionformat">@PARENT_TAG@</param>
     <param name="versionrewrite-pattern">v(.*)</param>
     <param name="changesgenerate">enable</param>

++++++ _servicedata ++++++
--- /var/tmp/diff_new_pack.LdkaMY/_old  2026-10-09 21:44:35.314311272 +0200
+++ /var/tmp/diff_new_pack.LdkaMY/_new  2026-10-09 21:44:35.317311398 +0200
@@ -3,6 +3,6 @@
                 <param name="url">https://github.com/akuity/kargo</param>
               <param 
name="changesrevision">17077e406ed309fcc830d08776d9b9a47fe6e134</param></service><service
 name="tar_scm">
                 <param name="url">https://github.com/akuity/kargo.git</param>
-              <param 
name="changesrevision">2b5f3a2f12c1eb6156253ba71d44818d4262c865</param></service></servicedata>
+              <param 
name="changesrevision">d544d7c6a9d4d99d096df52dcb8357375c26b729</param></service></servicedata>
 (No newline at EOF)
 

++++++ kargo-cli-1.12.2.obscpio -> kargo-cli-1.12.3.obscpio ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/kargo-cli-1.12.2/pkg/promotion/runner/builtin/kustomize_builder.go 
new/kargo-cli-1.12.3/pkg/promotion/runner/builtin/kustomize_builder.go
--- old/kargo-cli-1.12.2/pkg/promotion/runner/builtin/kustomize_builder.go      
2026-10-06 20:24:02.000000000 +0200
+++ new/kargo-cli-1.12.3/pkg/promotion/runner/builtin/kustomize_builder.go      
2026-10-08 17:12:48.000000000 +0200
@@ -181,8 +181,20 @@
                PluginConfig:     buildPluginCfg,
        }
 
+       // Reject remote entries whose Git ref would be interpreted by git as an
+       // option. See kustomizeValidatorFS.
+       validatingFS := newKustomizeValidatorFS(kusFS)
        k := krusty.MakeKustomizer(buildOptions)
-       return k.Run(kusFS, path)
+       rm, err := k.Run(validatingFS, path)
+       if err != nil {
+               // Kustomize obscures validation errors (see 
kustomizeValidatorFS), so
+               // report them in preference to whatever error Kustomize 
returned.
+               if validationErr := validatingFS.Err(); validationErr != nil {
+                       return nil, validationErr
+               }
+               return nil, err
+       }
+       return rm, nil
 }
 
 // fileNameFunc generates a filename for a resource.
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/kargo-cli-1.12.2/pkg/promotion/runner/builtin/kustomize_validator_fs.go 
new/kargo-cli-1.12.3/pkg/promotion/runner/builtin/kustomize_validator_fs.go
--- old/kargo-cli-1.12.2/pkg/promotion/runner/builtin/kustomize_validator_fs.go 
1970-01-01 01:00:00.000000000 +0100
+++ new/kargo-cli-1.12.3/pkg/promotion/runner/builtin/kustomize_validator_fs.go 
2026-10-08 17:12:48.000000000 +0200
@@ -0,0 +1,123 @@
+package builtin
+
+import (
+       "fmt"
+       "maps"
+       "net/url"
+       "path/filepath"
+       "slices"
+       "strings"
+
+       "sigs.k8s.io/kustomize/api/konfig"
+       kustypes "sigs.k8s.io/kustomize/api/types"
+       "sigs.k8s.io/kustomize/kyaml/filesys"
+)
+
+// kustomizeValidatorFS is a filesys.FileSystem that inspects every
+// kustomization file read through it and rejects entries with a query
+// parameter value that git could interpret as an option.
+//
+// Kustomize resolves remote Git references by running the system git binary
+// as `git fetch --depth=1 <repository> <ref>`, passing the ref (taken from the
+// entry's ?ref= or ?version= query parameter) as a literal argument without an
+// end-of-options separator. A ref such as --upload-pack=<cmd> therefore causes
+// git to execute an arbitrary command.
+//
+// Kustomize reads every kustomization file -- including those inside
+// repositories it has cloned to resolve remote entries -- through the
+// filesys.FileSystem passed to it, and it always reads and parses a
+// kustomization file before resolving any of its entries. Validating at this
+// layer therefore rejects a malicious ref before git is ever invoked, and also
+// covers remote entries referenced transitively by other remote entries. See
+// validateQueryValues for which values are rejected.
+//
+// Kustomize treats any error reading a kustomization file as the file being
+// absent, which would obscure the reason for the failure. The first validation
+// error is therefore also recorded, so that callers can report it via Err.
+type kustomizeValidatorFS struct {
+       filesys.FileSystem
+       err error
+}
+
+// newKustomizeValidatorFS wraps the given filesys.FileSystem so that
+// kustomization files containing entries with unsafe query parameter values
+// cannot be read through it.
+func newKustomizeValidatorFS(fs filesys.FileSystem) *kustomizeValidatorFS {
+       return &kustomizeValidatorFS{FileSystem: fs}
+}
+
+// Err returns the first validation error encountered by ReadFile, if any.
+func (r *kustomizeValidatorFS) Err() error {
+       return r.err
+}
+
+// ReadFile implements filesys.FileSystem.
+func (r *kustomizeValidatorFS) ReadFile(path string) ([]byte, error) {
+       content, err := r.FileSystem.ReadFile(path)
+       if err != nil {
+               return nil, err
+       }
+       if !slices.Contains(
+               konfig.RecognizedKustomizationFileNames(),
+               filepath.Base(path),
+       ) {
+               return content, nil
+       }
+       // Parse the kustomization exactly as Kustomize does. If it cannot be
+       // parsed, Kustomize will fail to parse it as well and none of its 
entries
+       // will be resolved, so it is safe to return the content as-is and let
+       // Kustomize report the error.
+       var k kustypes.Kustomization
+       if err = k.Unmarshal(content); err != nil {
+               return content, nil
+       }
+       // Merges deprecated fields (e.g. bases) into their replacements.
+       k.FixKustomization()
+       for _, entries := range [][]string{
+               k.Resources,
+               k.Components,
+               k.Generators,
+               k.Transformers,
+               k.Validators,
+       } {
+               for _, entry := range entries {
+                       if err = validateQueryValues(entry); err != nil {
+                               err = fmt.Errorf("error validating %s: %w", 
path, err)
+                               if r.err == nil {
+                                       r.err = err
+                               }
+                               return nil, err
+                       }
+               }
+       }
+       return content, nil
+}
+
+// validateQueryValues returns an error if the value of any query parameter of
+// the given kustomization entry begins with "-".
+//
+// Kustomize currently passes only the ref (taken from the ref or version query
+// parameter) to git, and a valid Git ref never begins with "-". Every
+// parameter is checked nonetheless, so that a parameter Kustomize starts
+// passing to git in the future cannot silently reintroduce option injection.
+func validateQueryValues(entry string) error {
+       _, query, ok := strings.Cut(entry, "?")
+       if !ok {
+               return nil
+       }
+       // A query that cannot be fully parsed still yields every parameter that
+       // could be parsed. Those are checked too, rather than relying on 
Kustomize
+       // ignoring malformed queries.
+       values, _ := url.ParseQuery(query)
+       for _, param := range slices.Sorted(maps.Keys(values)) {
+               for _, value := range values[param] {
+                       if strings.HasPrefix(value, "-") {
+                               return fmt.Errorf(
+                                       "entry %q specifies query parameter %s 
%q, which must not begin with %q",
+                                       entry, param, value, "-",
+                               )
+                       }
+               }
+       }
+       return nil
+}
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/kargo-cli-1.12.2/pkg/promotion/runner/builtin/kustomize_validator_fs_test.go
 
new/kargo-cli-1.12.3/pkg/promotion/runner/builtin/kustomize_validator_fs_test.go
--- 
old/kargo-cli-1.12.2/pkg/promotion/runner/builtin/kustomize_validator_fs_test.go
    1970-01-01 01:00:00.000000000 +0100
+++ 
new/kargo-cli-1.12.3/pkg/promotion/runner/builtin/kustomize_validator_fs_test.go
    2026-10-08 17:12:48.000000000 +0200
@@ -0,0 +1,338 @@
+package builtin
+
+import (
+       "fmt"
+       "os"
+       "os/exec"
+       "path/filepath"
+       "testing"
+
+       securefs "github.com/fluxcd/pkg/kustomize/filesys"
+       "github.com/stretchr/testify/require"
+       "sigs.k8s.io/kustomize/kyaml/filesys"
+)
+
+func Test_validateQueryValues(t *testing.T) {
+       testCases := []struct {
+               name   string
+               entry  string
+               assert func(*testing.T, error)
+       }{
+               {
+                       name:  "local path",
+                       entry: "../base",
+                       assert: func(t *testing.T, err error) {
+                               require.NoError(t, err)
+                       },
+               },
+               {
+                       name:  "remote without query",
+                       entry: "https://github.com/example/repo//base";,
+                       assert: func(t *testing.T, err error) {
+                               require.NoError(t, err)
+                       },
+               },
+               {
+                       name:  "remote with safe ref",
+                       entry: "https://github.com/example/repo//base?ref=main";,
+                       assert: func(t *testing.T, err error) {
+                               require.NoError(t, err)
+                       },
+               },
+               {
+                       name:  "remote with safe version and other params",
+                       entry: 
"https://github.com/example/repo//base?version=v1.2.3&timeout=90s";,
+                       assert: func(t *testing.T, err error) {
+                               require.NoError(t, err)
+                       },
+               },
+               {
+                       name:  "unparseable query",
+                       entry: "https://github.com/example/repo?ref=%zz";,
+                       assert: func(t *testing.T, err error) {
+                               require.NoError(t, err)
+                       },
+               },
+               {
+                       name:  "value containing but not beginning with -",
+                       entry: 
"https://github.com/example/repo?ref=release-1.0&timeout=1m";,
+                       assert: func(t *testing.T, err error) {
+                               require.NoError(t, err)
+                       },
+               },
+               {
+                       name:  "ref beginning with -",
+                       entry: 
"https://github.com/example/repo?ref=--upload-pack=touch /tmp/pwned",
+                       assert: func(t *testing.T, err error) {
+                               require.ErrorContains(t, err, `specifies query 
parameter ref "--upload-pack=touch /tmp/pwned"`)
+                       },
+               },
+               {
+                       name:  "version beginning with -",
+                       entry: "https://github.com/example/repo?version=-x";,
+                       assert: func(t *testing.T, err error) {
+                               require.ErrorContains(t, err, `specifies query 
parameter version "-x"`)
+                       },
+               },
+               {
+                       name:  "percent-encoded ref beginning with -",
+                       entry: 
"https://github.com/example/repo?ref=%2D%2Dupload-pack%3Dx";,
+                       assert: func(t *testing.T, err error) {
+                               require.ErrorContains(t, err, `specifies query 
parameter ref "--upload-pack=x"`)
+                       },
+               },
+               {
+                       name:  "safe ref followed by unsafe duplicate",
+                       entry: 
"https://github.com/example/repo?ref=main&ref=--upload-pack=x";,
+                       assert: func(t *testing.T, err error) {
+                               require.ErrorContains(t, err, `specifies query 
parameter ref "--upload-pack=x"`)
+                       },
+               },
+               {
+                       name:  "safe ref with unsafe version",
+                       entry: 
"https://github.com/example/repo?ref=main&version=--upload-pack=x";,
+                       assert: func(t *testing.T, err error) {
+                               require.ErrorContains(t, err, `specifies query 
parameter version "--upload-pack=x"`)
+                       },
+               },
+               {
+                       name:  "other known parameter beginning with -",
+                       entry: 
"https://github.com/example/repo?ref=main&timeout=-5";,
+                       assert: func(t *testing.T, err error) {
+                               require.ErrorContains(t, err, `specifies query 
parameter timeout "-5"`)
+                       },
+               },
+               {
+                       name:  "unknown parameter beginning with -",
+                       entry: 
"https://github.com/example/repo?ref=main&future=--upload-pack=x";,
+                       assert: func(t *testing.T, err error) {
+                               require.ErrorContains(t, err, `specifies query 
parameter future "--upload-pack=x"`)
+                       },
+               },
+               {
+                       name:  "local path with parameter beginning with -",
+                       entry: "../base?foo=-bar",
+                       assert: func(t *testing.T, err error) {
+                               require.ErrorContains(t, err, `specifies query 
parameter foo "-bar"`)
+                       },
+               },
+               {
+                       name:  "partially unparseable query with parameter 
beginning with -",
+                       entry: 
"https://github.com/example/repo?bad=%zz&ref=--upload-pack=x";,
+                       assert: func(t *testing.T, err error) {
+                               require.ErrorContains(t, err, `specifies query 
parameter ref "--upload-pack=x"`)
+                       },
+               },
+       }
+       for _, testCase := range testCases {
+               t.Run(testCase.name, func(t *testing.T) {
+                       t.Parallel()
+                       testCase.assert(t, validateQueryValues(testCase.entry))
+               })
+       }
+}
+
+func Test_kustomizeValidatorFS_ReadFile(t *testing.T) {
+       const badRef = "https://github.com/example/repo?ref=--upload-pack=x";
+       testCases := []struct {
+               name     string
+               fileName string
+               content  string
+               assert   func(t *testing.T, content []byte, err, recordedErr 
error)
+       }{
+               {
+                       name:     "file does not exist",
+                       fileName: "",
+                       assert: func(t *testing.T, _ []byte, err, recordedErr 
error) {
+                               require.Error(t, err)
+                               require.NoError(t, recordedErr)
+                       },
+               },
+               {
+                       name:     "non-kustomization file is not inspected",
+                       fileName: "resources.yaml",
+                       content:  "resources:\n- " + badRef + "\n",
+                       assert: func(t *testing.T, content []byte, err, 
recordedErr error) {
+                               require.NoError(t, err)
+                               require.NoError(t, recordedErr)
+                               require.Contains(t, string(content), badRef)
+                       },
+               },
+               {
+                       name:     "unparseable kustomization is passed through",
+                       fileName: "kustomization.yaml",
+                       content:  "unknownField: " + badRef + "\n",
+                       assert: func(t *testing.T, content []byte, err, 
recordedErr error) {
+                               require.NoError(t, err)
+                               require.NoError(t, recordedErr)
+                               require.Contains(t, string(content), badRef)
+                       },
+               },
+               {
+                       name:     "safe kustomization is passed through",
+                       fileName: "kustomization.yaml",
+                       content: `resources:
+- ../base
+- https://github.com/example/repo?ref=main
+`,
+                       assert: func(t *testing.T, content []byte, err, 
recordedErr error) {
+                               require.NoError(t, err)
+                               require.NoError(t, recordedErr)
+                               require.Contains(t, string(content), "ref=main")
+                       },
+               },
+       }
+       for _, field := range []string{
+               "resources",
+               "bases",
+               "components",
+               "generators",
+               "transformers",
+               "validators",
+       } {
+               for _, fileName := range []string{
+                       "kustomization.yaml",
+                       "kustomization.yml",
+                       "Kustomization",
+               } {
+                       testCases = append(testCases, struct {
+                               name     string
+                               fileName string
+                               content  string
+                               assert   func(t *testing.T, content []byte, 
err, recordedErr error)
+                       }{
+                               name:     fmt.Sprintf("unsafe ref in %s of %s", 
field, fileName),
+                               fileName: fileName,
+                               content:  fmt.Sprintf("%s:\n- %s\n", field, 
badRef),
+                               assert: func(t *testing.T, content []byte, err, 
recordedErr error) {
+                                       require.ErrorContains(t, err, "must not 
begin with")
+                                       require.Nil(t, content)
+                                       require.Equal(t, err, recordedErr)
+                               },
+                       })
+               }
+       }
+       for _, testCase := range testCases {
+               t.Run(testCase.name, func(t *testing.T) {
+                       t.Parallel()
+                       memFS := filesys.MakeFsInMemory()
+                       path := filepath.Join("/work", "missing")
+                       if testCase.fileName != "" {
+                               path = filepath.Join("/work", testCase.fileName)
+                               require.NoError(t, memFS.WriteFile(path, 
[]byte(testCase.content)))
+                       }
+                       validatingFS := newKustomizeValidatorFS(memFS)
+                       content, err := validatingFS.ReadFile(path)
+                       testCase.assert(t, content, err, validatingFS.Err())
+               })
+       }
+}
+
+// Test_kustomizeBuild_remoteGitRefInjection is a regression test for
+// GHSA-927c-fm24-cq67. Kustomize passes the ref of a remote entry to
+// `git fetch` as a literal argument, so a ref such as --upload-pack=<cmd>
+// causes git to execute <cmd>. kustomizeBuild must reject such refs -- both
+// directly and in remote entries referenced by other remote entries -- while
+// still resolving remote entries with safe refs.
+func Test_kustomizeBuild_remoteGitRefInjection(t *testing.T) {
+       gitPath, err := exec.LookPath("git")
+       if err != nil {
+               t.Skip("git binary not available")
+       }
+
+       // A script git would execute as its upload-pack helper if an injected
+       // option were honored.
+       markerPath := filepath.Join(t.TempDir(), "pwned")
+       scriptPath := filepath.Join(t.TempDir(), "upload-pack.sh")
+       // The script must be executable.
+       require.NoError(t, os.WriteFile( // nolint: gosec
+               scriptPath,
+               []byte("#!/bin/sh\ntouch "+markerPath+"\nexit 1\n"),
+               0o700,
+       ))
+       maliciousRef := "?ref=--upload-pack=" + scriptPath
+
+       // A repository that generates a single, harmless resource.
+       safeRepo := newTestGitRepo(t, gitPath, `configMapGenerator:
+- name: remote
+  literals:
+  - key=value
+`)
+       // A repository whose own kustomization contains a malicious ref.
+       maliciousRepo := newTestGitRepo(t, gitPath, fmt.Sprintf(
+               "resources:\n- file://%s%s\n",
+               safeRepo, maliciousRef,
+       ))
+
+       testCases := []struct {
+               name     string
+               resource string
+               assert   func(*testing.T, error)
+       }{
+               {
+                       name:     "remote resource with safe ref",
+                       resource: "file://" + safeRepo + "?ref=main",
+                       assert: func(t *testing.T, err error) {
+                               require.NoError(t, err)
+                       },
+               },
+               {
+                       name:     "remote resource with malicious ref",
+                       resource: "file://" + safeRepo + maliciousRef,
+                       assert: func(t *testing.T, err error) {
+                               require.ErrorContains(t, err, "must not begin 
with")
+                       },
+               },
+               {
+                       name:     "remote resource that references a malicious 
ref",
+                       resource: "file://" + maliciousRepo + "?ref=main",
+                       assert: func(t *testing.T, err error) {
+                               require.ErrorContains(t, err, "must not begin 
with")
+                       },
+               },
+       }
+       for _, testCase := range testCases {
+               t.Run(testCase.name, func(t *testing.T) {
+                       workDir := t.TempDir()
+                       require.NoError(t, os.WriteFile(
+                               filepath.Join(workDir, "kustomization.yaml"),
+                               []byte("resources:\n- "+testCase.resource+"\n"),
+                               0o600,
+                       ))
+                       diskFS, err := securefs.MakeFsOnDiskSecureBuild(workDir)
+                       require.NoError(t, err)
+
+                       _, err = kustomizeBuild(diskFS, workDir, nil)
+                       require.NoFileExists(t, markerPath)
+                       testCase.assert(t, err)
+               })
+       }
+}
+
+// newTestGitRepo creates a Git repository with a main branch containing a
+// kustomization.yaml with the given content, and returns its path.
+func newTestGitRepo(t *testing.T, gitPath, kustomization string) string {
+       t.Helper()
+       dir := t.TempDir()
+       require.NoError(t, os.WriteFile(
+               filepath.Join(dir, "kustomization.yaml"),
+               []byte(kustomization),
+               0o600,
+       ))
+       for _, args := range [][]string{
+               {"init", "--initial-branch=main"},
+               {"add", "."},
+               {
+                       "-c", "user.name=test",
+                       "-c", "[email protected]",
+                       "-c", "commit.gpgsign=false",
+                       "commit", "-m", "initial commit",
+               },
+       } {
+               cmd := exec.Command(gitPath, args...)
+               cmd.Dir = dir
+               out, err := cmd.CombinedOutput()
+               require.NoError(t, err, string(out))
+       }
+       return dir
+}

++++++ kargo-cli.obsinfo ++++++
--- /var/tmp/diff_new_pack.LdkaMY/_old  2026-10-09 21:44:39.533488812 +0200
+++ /var/tmp/diff_new_pack.LdkaMY/_new  2026-10-09 21:44:39.538489023 +0200
@@ -1,5 +1,5 @@
 name: kargo-cli
-version: 1.12.2
-mtime: 1791311042
-commit: 2b5f3a2f12c1eb6156253ba71d44818d4262c865
+version: 1.12.3
+mtime: 1791472368
+commit: d544d7c6a9d4d99d096df52dcb8357375c26b729
 

++++++ vendor.tar.gz ++++++
/work/SRC/openSUSE:Factory/kargo-cli/vendor.tar.gz 
/work/SRC/openSUSE:Factory/.kargo-cli.new.3698736/vendor.tar.gz differ: char 
133, line 2

Reply via email to