From 77e783bdf9478371fdbf6b5e17a679d63171cf06 Mon Sep 17 00:00:00 2001 From: Aravindhan Ayyanathan Date: Mon, 22 Jun 2026 10:02:17 +0100 Subject: [PATCH 1/2] validate resource file paths in package operations Signed-off-by: Aravindhan Ayyanathan --- pkg/engine/engine.go | 4 ++ pkg/engine/engine_test.go | 37 +++++++++++++----- pkg/repository/update.go | 16 +++++--- pkg/repository/update_test.go | 28 +++++++++++++- pkg/task/replace_test.go | 35 ++++++++++++++++- pkg/task/replaceresources.go | 7 +++- pkg/{engine => util}/safejoin.go | 19 ++++++++-- pkg/{engine => util}/safejoin_test.go | 54 +++++++++++++++++++++++++-- test/e2e/api/rpkg_edit_test.go | 26 ++++++++++++- 9 files changed, 200 insertions(+), 26 deletions(-) rename pkg/{engine => util}/safejoin.go (62%) rename pkg/{engine => util}/safejoin_test.go (60%) diff --git a/pkg/engine/engine.go b/pkg/engine/engine.go index e563db22e..f8172fd2e 100644 --- a/pkg/engine/engine.go +++ b/pkg/engine/engine.go @@ -558,6 +558,10 @@ func (cad *cadEngine) UpdatePackageResourcesWithoutRender(ctx context.Context, r return nil, err } + if err := util.ValidateResourcePaths(newRes.Spec.Resources); err != nil { + return nil, err + } + prr := &porchapi.PackageRevisionResources{ Spec: porchapi.PackageRevisionResourcesSpec{ Resources: newRes.Spec.Resources, diff --git a/pkg/engine/engine_test.go b/pkg/engine/engine_test.go index 8819049d4..c14d2f719 100644 --- a/pkg/engine/engine_test.go +++ b/pkg/engine/engine_test.go @@ -898,6 +898,7 @@ func TestUpdatePackageResourcesWithoutRender(t *testing.T) { lifecycle porchapi.PackageRevisionLifecycle oldRV string newRV string + resources map[string]string closeErr error expectError bool errorContains string @@ -941,6 +942,15 @@ func TestUpdatePackageResourcesWithoutRender(t *testing.T) { expectError: true, errorContains: "git push failed", }, + { + name: "failure - path traversal rejected", + lifecycle: porchapi.PackageRevisionLifecycleDraft, + oldRV: "1", + newRV: "1", + resources: map[string]string{"../../etc/config": "content"}, + expectError: true, + errorContains: "path traversal not allowed", + }, } for _, tt := range tests { @@ -963,31 +973,40 @@ func TestUpdatePackageResourcesWithoutRender(t *testing.T) { ResourceVersion: tt.oldRV, }, } + resources := tt.resources + if resources == nil { + resources = map[string]string{"Kptfile": "test"} + } newRes := &porchapi.PackageRevisionResources{ ObjectMeta: metav1.ObjectMeta{ Name: "test-pkg", ResourceVersion: tt.newRV, }, Spec: porchapi.PackageRevisionResourcesSpec{ - Resources: map[string]string{"Kptfile": "test"}, + Resources: resources, }, } mockPkgRev.On("Lifecycle", mock.Anything).Return(tt.lifecycle).Maybe() mockPkgRev.On("Key").Return(repository.PackageRevisionKey{}).Maybe() - // Only expect repo open + draft flow when we pass validation - needsDraft := tt.newRV != "" && tt.oldRV == tt.newRV && tt.lifecycle == porchapi.PackageRevisionLifecycleDraft + // Only expect repo open + draft flow when we pass all pre-draft validation + needsDraft := tt.newRV != "" && tt.oldRV == tt.newRV && + tt.lifecycle == porchapi.PackageRevisionLifecycleDraft if needsDraft { mockCache.On("OpenRepository", mock.Anything, repositoryObj).Return(mockRepo, nil) mockRepo.On("UpdatePackageRevision", mock.Anything, mockPkgRev).Return(mockDraft, nil) - mockDraft.On("UpdateResources", mock.Anything, mock.Anything, mock.Anything).Return(nil) - closeRet := mockrepo.MockPackageRevision{} - if tt.closeErr != nil { - mockRepo.On("ClosePackageRevisionDraft", mock.Anything, mockDraft, 0).Return(nil, tt.closeErr) - } else { - mockRepo.On("ClosePackageRevisionDraft", mock.Anything, mockDraft, 0).Return(&closeRet, nil) + // Only expect write+close when resources pass validation + if tt.resources == nil { + mockDraft.On("UpdateResources", mock.Anything, mock.Anything, mock.Anything).Return(nil) + + closeRet := mockrepo.MockPackageRevision{} + if tt.closeErr != nil { + mockRepo.On("ClosePackageRevisionDraft", mock.Anything, mockDraft, 0).Return(nil, tt.closeErr) + } else { + mockRepo.On("ClosePackageRevisionDraft", mock.Anything, mockDraft, 0).Return(&closeRet, nil) + } } } diff --git a/pkg/repository/update.go b/pkg/repository/update.go index 3d33b1fdc..ec575cb4b 100644 --- a/pkg/repository/update.go +++ b/pkg/repository/update.go @@ -1,4 +1,4 @@ -// Copyright 2022-2025 The kpt Authors +// Copyright 2022-2026 The kpt Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -23,6 +23,7 @@ import ( "github.com/kptdev/kpt/pkg/lib/update" updatetypes "github.com/kptdev/kpt/pkg/lib/update/updatetypes" + "github.com/kptdev/porch/pkg/util" ) const LocalUpdateDir = "kpt-pkg-update-*" @@ -99,13 +100,16 @@ func (m *DefaultPackageUpdater) do(_ context.Context, localPkgDir, originalPkgDi func writeResourcesToDirectory(dir string, resources PackageResources) error { for k, v := range resources.Contents { - p := filepath.Join(dir, k) - dir := filepath.Dir(p) - if err := os.MkdirAll(dir, 0750); err != nil { - return fmt.Errorf("failed to create directory %q: %w", dir, err) + p, err := util.FilepathSafeJoin(dir, k) + if err != nil { + return fmt.Errorf("invalid resource path %q: %w", k, err) + } + d := filepath.Dir(p) + if err := os.MkdirAll(d, 0750); err != nil { + return fmt.Errorf("failed to create directory %q: %w", d, err) } if err := os.WriteFile(p, []byte(v), 0600); err != nil { - return fmt.Errorf("failed to write file %q: %w", dir, err) + return fmt.Errorf("failed to write file %q: %w", p, err) } } return nil diff --git a/pkg/repository/update_test.go b/pkg/repository/update_test.go index 4c96ecd8e..0e2a8e04d 100644 --- a/pkg/repository/update_test.go +++ b/pkg/repository/update_test.go @@ -1,4 +1,4 @@ -// Copyright 2022, 2024 The kpt Authors +// Copyright 2022, 2024, 2026 The kpt Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -149,3 +149,29 @@ func TestDefaultPackageUpdaterdo(t *testing.T) { assert.NoError(t, err) assert.Equal(t, "upstream content", updatedResources.Contents["file1.txt"]) } + +func TestWriteResourcesToDirectoryRejectsInvalidPaths(t *testing.T) { + dir := t.TempDir() + + tests := []struct { + name string + key string + }{ + {"relative path escapes base", "../escape.txt"}, + {"nested relative path escapes base", "a/../../escape.txt"}, + {"absolute path", "/etc/file"}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + resources := PackageResources{ + Contents: map[string]string{ + tt.key: "content", + }, + } + err := writeResourcesToDirectory(dir, resources) + assert.Error(t, err) + assert.Contains(t, err.Error(), "invalid resource path") + }) + } +} diff --git a/pkg/task/replace_test.go b/pkg/task/replace_test.go index 9bb937994..3c66043da 100644 --- a/pkg/task/replace_test.go +++ b/pkg/task/replace_test.go @@ -1,4 +1,4 @@ -// Copyright 2022, 2024 The kpt Authors +// Copyright 2022, 2024, 2026 The kpt Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -91,3 +91,36 @@ func removeCommentsFromFile(t *testing.T, name, contents string) string { return nocomment.String() } + +func TestReplaceResourcesRejectsInvalidPaths(t *testing.T) { + ctx := context.Background() + + replace := &replaceResourcesMutation{ + newResources: &porchapi.PackageRevisionResources{ + Spec: porchapi.PackageRevisionResourcesSpec{ + Resources: map[string]string{ + "../../../etc/config": "content", + }, + }, + }, + oldResources: &porchapi.PackageRevisionResources{ + Spec: porchapi.PackageRevisionResourcesSpec{ + Resources: map[string]string{ + "Kptfile": "existing", + }, + }, + }, + } + + input := repository.PackageResources{ + Contents: map[string]string{"Kptfile": "existing"}, + } + + _, _, err := replace.apply(ctx, input) + if err == nil { + t.Fatal("expected error for invalid path, got nil") + } + if !bytes.Contains([]byte(err.Error()), []byte("path traversal not allowed")) { + t.Errorf("unexpected error message: %v", err) + } +} diff --git a/pkg/task/replaceresources.go b/pkg/task/replaceresources.go index b59013727..0cd5b6af8 100644 --- a/pkg/task/replaceresources.go +++ b/pkg/task/replaceresources.go @@ -1,4 +1,4 @@ -// Copyright 2024 The kpt Authors +// Copyright 2024, 2026 The kpt Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -20,6 +20,7 @@ import ( porchapi "github.com/kptdev/porch/api/porch/v1alpha1" "github.com/kptdev/porch/pkg/repository" + "github.com/kptdev/porch/pkg/util" "go.opentelemetry.io/otel/trace" ) @@ -34,6 +35,10 @@ func (m *replaceResourcesMutation) apply(ctx context.Context, resources reposito _, span := tracer.Start(ctx, "mutationReplaceResources::apply", trace.WithAttributes()) defer span.End() + if err := util.ValidateResourcePaths(m.newResources.Spec.Resources); err != nil { + return repository.PackageResources{}, nil, err + } + old := resources.Contents newRes, err := healConfig(old, m.newResources.Spec.Resources) if err != nil { diff --git a/pkg/engine/safejoin.go b/pkg/util/safejoin.go similarity index 62% rename from pkg/engine/safejoin.go rename to pkg/util/safejoin.go index 9896da586..f99f96964 100644 --- a/pkg/engine/safejoin.go +++ b/pkg/util/safejoin.go @@ -1,4 +1,4 @@ -// Copyright 2022 The kpt Authors +// Copyright 2022, 2026 The kpt Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package engine +package util import ( "fmt" @@ -22,7 +22,9 @@ import ( // Relevant: https://github.com/golang/go/issues/20126 -func filepathSafeJoin(dir string, relative string) (string, error) { +// FilepathSafeJoin joins dir and relative, returning an error if the result +// would escape dir via path traversal. +func FilepathSafeJoin(dir string, relative string) (string, error) { p := filepath.Join(dir, relative) p = filepath.Clean(p) @@ -35,3 +37,14 @@ func filepathSafeJoin(dir string, relative string) (string, error) { } return p, nil } + +// ValidateResourcePaths checks that none of the keys in a resource map +// contain path traversal sequences. Returns an error on the first invalid key. +func ValidateResourcePaths(resources map[string]string) error { + for k := range resources { + if _, err := FilepathSafeJoin(".", k); err != nil { + return fmt.Errorf("invalid resource path %q: path traversal not allowed", k) + } + } + return nil +} diff --git a/pkg/engine/safejoin_test.go b/pkg/util/safejoin_test.go similarity index 60% rename from pkg/engine/safejoin_test.go rename to pkg/util/safejoin_test.go index 3c2e711fb..c251baa4a 100644 --- a/pkg/engine/safejoin_test.go +++ b/pkg/util/safejoin_test.go @@ -1,4 +1,4 @@ -// Copyright 2022 The kpt Authors +// Copyright 2022, 2026 The kpt Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package engine +package util import ( "fmt" @@ -21,7 +21,7 @@ import ( // Relevant: https://github.com/golang/go/issues/20126 -func TestSafeJoin(t *testing.T) { +func TestFilepathSafeJoin(t *testing.T) { grid := []struct { base string relative string @@ -82,7 +82,7 @@ func TestSafeJoin(t *testing.T) { for _, g := range grid { t.Run(fmt.Sprintf("%#v", g), func(t *testing.T) { - got, err := filepathSafeJoin(g.base, g.relative) + got, err := FilepathSafeJoin(g.base, g.relative) if g.wantError { if err == nil { t.Errorf("got %q and nil error, want error", got) @@ -95,3 +95,49 @@ func TestSafeJoin(t *testing.T) { }) } } + +func TestValidateResourcePaths(t *testing.T) { + tests := []struct { + name string + resources map[string]string + wantError bool + }{ + { + name: "valid simple path", + resources: map[string]string{"Kptfile": "content", "subdir/file.yaml": "content"}, + wantError: false, + }, + { + name: "path traversal with ..", + resources: map[string]string{"../etc/config": "content"}, + wantError: true, + }, + { + name: "path traversal nested", + resources: map[string]string{"a/../../etc/file": "content"}, + wantError: true, + }, + { + name: "absolute path", + resources: map[string]string{"/etc/cron.d/job": "content"}, + wantError: true, + }, + { + name: "empty map is valid", + resources: map[string]string{}, + wantError: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + err := ValidateResourcePaths(tt.resources) + if tt.wantError && err == nil { + t.Error("expected error, got nil") + } + if !tt.wantError && err != nil { + t.Errorf("unexpected error: %v", err) + } + }) + } +} diff --git a/test/e2e/api/rpkg_edit_test.go b/test/e2e/api/rpkg_edit_test.go index 1997483cf..822a0e088 100644 --- a/test/e2e/api/rpkg_edit_test.go +++ b/test/e2e/api/rpkg_edit_test.go @@ -1,4 +1,4 @@ -// Copyright 2025 The kpt Authors +// Copyright 2025-2026 The kpt Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -216,6 +216,30 @@ metadata: t.Logf("ConfigMap content matches expected result after KRM function processing") } +func (t *PorchSuite) TestUpdateResourcesRejectsInvalidPaths() { + const ( + repository = "invalid-path-test" + packageName = "invalid-path-pkg" + workspace = defaultWorkspace + ) + + t.RegisterGitRepositoryF(t.GetPorchTestRepoURL(), repository, "", suiteutils.GiteaUser, suiteutils.GiteaPassword) + pr := t.CreatePackageDraftF(repository, packageName, workspace) + + var prResources porchapi.PackageRevisionResources + t.GetF(client.ObjectKey{ + Namespace: t.Namespace, + Name: pr.Name, + }, &prResources) + + // Attempt to add a resource with a relative path that escapes the package directory + prResources.Spec.Resources["../../etc/config"] = "content" + + err := t.Client.Update(t.GetContext(), &prResources) + t.Require().Error(err, "update with invalid resource path should be rejected") + t.Require().ErrorContains(err, "path traversal not allowed") +} + func (t *PorchSuite) TestUpdateResourcesEmptyPatch() { const ( repository = "empty-patch-test" From 08cdf5a6c1e21a3838586243c90fb75e4c7cc528 Mon Sep 17 00:00:00 2001 From: Aravindhan Ayyanathan Date: Mon, 22 Jun 2026 10:23:54 +0100 Subject: [PATCH 2/2] Address review comments Signed-off-by: Aravindhan Ayyanathan --- pkg/engine/engine.go | 8 +++--- pkg/engine/engine_test.go | 50 ++++++++++++++++------------------ pkg/task/replace_test.go | 2 +- pkg/util/safejoin.go | 19 ++++++++----- pkg/util/safejoin_test.go | 20 ++++++++++++++ test/e2e/api/rpkg_edit_test.go | 2 +- 6 files changed, 62 insertions(+), 39 deletions(-) diff --git a/pkg/engine/engine.go b/pkg/engine/engine.go index f8172fd2e..91b8d245b 100644 --- a/pkg/engine/engine.go +++ b/pkg/engine/engine.go @@ -549,6 +549,10 @@ func (cad *cadEngine) UpdatePackageResourcesWithoutRender(ctx context.Context, r return nil, fmt.Errorf("cannot update a package revision with lifecycle value %q; package must be Draft", lifecycle) } + if err := util.ValidateResourcePaths(newRes.Spec.Resources); err != nil { + return nil, err + } + repo, err := cad.cache.OpenRepository(ctx, repositoryObj) if err != nil { return nil, err @@ -558,10 +562,6 @@ func (cad *cadEngine) UpdatePackageResourcesWithoutRender(ctx context.Context, r return nil, err } - if err := util.ValidateResourcePaths(newRes.Spec.Resources); err != nil { - return nil, err - } - prr := &porchapi.PackageRevisionResources{ Spec: porchapi.PackageRevisionResourcesSpec{ Resources: newRes.Spec.Resources, diff --git a/pkg/engine/engine_test.go b/pkg/engine/engine_test.go index c14d2f719..7f562cfdf 100644 --- a/pkg/engine/engine_test.go +++ b/pkg/engine/engine_test.go @@ -894,14 +894,15 @@ func TestUpdatePackageResourcesRenderFailure(t *testing.T) { func TestUpdatePackageResourcesWithoutRender(t *testing.T) { tests := []struct { - name string - lifecycle porchapi.PackageRevisionLifecycle - oldRV string - newRV string - resources map[string]string - closeErr error - expectError bool - errorContains string + name string + lifecycle porchapi.PackageRevisionLifecycle + oldRV string + newRV string + resources map[string]string + closeErr error + skipWriteClose bool + expectError bool + errorContains string }{ { name: "success - draft lifecycle", @@ -943,13 +944,14 @@ func TestUpdatePackageResourcesWithoutRender(t *testing.T) { errorContains: "git push failed", }, { - name: "failure - path traversal rejected", - lifecycle: porchapi.PackageRevisionLifecycleDraft, - oldRV: "1", - newRV: "1", - resources: map[string]string{"../../etc/config": "content"}, - expectError: true, - errorContains: "path traversal not allowed", + name: "failure - path traversal rejected", + lifecycle: porchapi.PackageRevisionLifecycleDraft, + oldRV: "1", + newRV: "1", + resources: map[string]string{"../../etc/config": "content"}, + skipWriteClose: true, + expectError: true, + errorContains: "invalid resource path", }, } @@ -991,22 +993,18 @@ func TestUpdatePackageResourcesWithoutRender(t *testing.T) { mockPkgRev.On("Key").Return(repository.PackageRevisionKey{}).Maybe() // Only expect repo open + draft flow when we pass all pre-draft validation - needsDraft := tt.newRV != "" && tt.oldRV == tt.newRV && + needsDraft := !tt.skipWriteClose && tt.newRV != "" && tt.oldRV == tt.newRV && tt.lifecycle == porchapi.PackageRevisionLifecycleDraft if needsDraft { mockCache.On("OpenRepository", mock.Anything, repositoryObj).Return(mockRepo, nil) mockRepo.On("UpdatePackageRevision", mock.Anything, mockPkgRev).Return(mockDraft, nil) + mockDraft.On("UpdateResources", mock.Anything, mock.Anything, mock.Anything).Return(nil) - // Only expect write+close when resources pass validation - if tt.resources == nil { - mockDraft.On("UpdateResources", mock.Anything, mock.Anything, mock.Anything).Return(nil) - - closeRet := mockrepo.MockPackageRevision{} - if tt.closeErr != nil { - mockRepo.On("ClosePackageRevisionDraft", mock.Anything, mockDraft, 0).Return(nil, tt.closeErr) - } else { - mockRepo.On("ClosePackageRevisionDraft", mock.Anything, mockDraft, 0).Return(&closeRet, nil) - } + closeRet := mockrepo.MockPackageRevision{} + if tt.closeErr != nil { + mockRepo.On("ClosePackageRevisionDraft", mock.Anything, mockDraft, 0).Return(nil, tt.closeErr) + } else { + mockRepo.On("ClosePackageRevisionDraft", mock.Anything, mockDraft, 0).Return(&closeRet, nil) } } diff --git a/pkg/task/replace_test.go b/pkg/task/replace_test.go index 3c66043da..a50cfa9fe 100644 --- a/pkg/task/replace_test.go +++ b/pkg/task/replace_test.go @@ -120,7 +120,7 @@ func TestReplaceResourcesRejectsInvalidPaths(t *testing.T) { if err == nil { t.Fatal("expected error for invalid path, got nil") } - if !bytes.Contains([]byte(err.Error()), []byte("path traversal not allowed")) { + if !bytes.Contains([]byte(err.Error()), []byte("invalid resource path")) { t.Errorf("unexpected error message: %v", err) } } diff --git a/pkg/util/safejoin.go b/pkg/util/safejoin.go index f99f96964..5d4b1521c 100644 --- a/pkg/util/safejoin.go +++ b/pkg/util/safejoin.go @@ -22,9 +22,12 @@ import ( // Relevant: https://github.com/golang/go/issues/20126 -// FilepathSafeJoin joins dir and relative, returning an error if the result -// would escape dir via path traversal. -func FilepathSafeJoin(dir string, relative string) (string, error) { +// FilepathSafeJoin joins dir and relative, returning an error if relative is +// not a clean, canonical relative path within dir. It rejects path traversal +// (.. sequences), absolute paths, the bare "." and ".." entries, and any path +// that would be altered by filepath.Clean (e.g. leading "./", redundant +// separators, or internal "a/../b" segments). +func FilepathSafeJoin(dir, relative string) (string, error) { p := filepath.Join(dir, relative) p = filepath.Clean(p) @@ -32,18 +35,20 @@ func FilepathSafeJoin(dir string, relative string) (string, error) { if err != nil { return "", fmt.Errorf("invalid relative path %q", relative) } - if rel != relative || strings.HasPrefix(rel, ".."+string(filepath.Separator)) || strings.HasPrefix(rel, "."+string(filepath.Separator)) { + if rel == "." || rel == ".." || rel != relative || strings.HasPrefix(rel, ".."+string(filepath.Separator)) || strings.HasPrefix(rel, "."+string(filepath.Separator)) { return "", fmt.Errorf("invalid relative path %q", relative) } return p, nil } -// ValidateResourcePaths checks that none of the keys in a resource map -// contain path traversal sequences. Returns an error on the first invalid key. +// ValidateResourcePaths checks that all keys in a resource map are valid +// relative file paths within a package. It rejects path traversal sequences, +// absolute paths, and non-canonical paths (e.g. leading "./", bare "." or ".."). +// Returns an error on the first invalid key. func ValidateResourcePaths(resources map[string]string) error { for k := range resources { if _, err := FilepathSafeJoin(".", k); err != nil { - return fmt.Errorf("invalid resource path %q: path traversal not allowed", k) + return fmt.Errorf("invalid resource path %q: %w", k, err) } } return nil diff --git a/pkg/util/safejoin_test.go b/pkg/util/safejoin_test.go index c251baa4a..9c2abd8fd 100644 --- a/pkg/util/safejoin_test.go +++ b/pkg/util/safejoin_test.go @@ -63,6 +63,16 @@ func TestFilepathSafeJoin(t *testing.T) { relative: "../foo", wantError: true, }, + { + base: "/tmp", + relative: "..", + wantError: true, + }, + { + base: "/tmp", + relative: ".", + wantError: true, + }, { base: "tmp/", relative: "a/../foo", @@ -112,6 +122,16 @@ func TestValidateResourcePaths(t *testing.T) { resources: map[string]string{"../etc/config": "content"}, wantError: true, }, + { + name: "bare .. without trailing path", + resources: map[string]string{"..": "content"}, + wantError: true, + }, + { + name: "dot refers to directory not a file", + resources: map[string]string{".": "content"}, + wantError: true, + }, { name: "path traversal nested", resources: map[string]string{"a/../../etc/file": "content"}, diff --git a/test/e2e/api/rpkg_edit_test.go b/test/e2e/api/rpkg_edit_test.go index 822a0e088..47d1afd1f 100644 --- a/test/e2e/api/rpkg_edit_test.go +++ b/test/e2e/api/rpkg_edit_test.go @@ -237,7 +237,7 @@ func (t *PorchSuite) TestUpdateResourcesRejectsInvalidPaths() { err := t.Client.Update(t.GetContext(), &prResources) t.Require().Error(err, "update with invalid resource path should be rejected") - t.Require().ErrorContains(err, "path traversal not allowed") + t.Require().ErrorContains(err, "invalid resource path") } func (t *PorchSuite) TestUpdateResourcesEmptyPatch() {