unapply base path for resolver inbound requests (#4478)

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>
This commit is contained in:
Alex Goodman 2025-12-16 08:28:12 -05:00 committed by GitHub
parent e0b61a3ae3
commit beb70891e5
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 108 additions and 9 deletions

View File

@ -14,10 +14,11 @@ import (
// the user given root, the base path (if any) to consider as the root, and the current working directory.
// Note: this only works on a real filesystem, not on a virtual filesystem (such as a stereoscope filetree).
type ChrootContext struct {
root string
base string
cwd string
cwdRelativeToRoot string
root string
rootRelativeToBase string
base string
cwd string
cwdRelativeToRoot string
}
func NewChrootContextFromCWD(root, base string) (*ChrootContext, error) {
@ -40,10 +41,26 @@ func NewChrootContext(root, base, cwd string) (*ChrootContext, error) {
return nil, err
}
// we need to track the relative path from root to base (if set) so that request paths can un-apply the base path
// changes from any incoming requests.
var rootRelativeToBase string
if cleanBase != cleanRoot && cleanBase != "" {
absRoot := cleanRoot
if !filepath.IsAbs(cleanRoot) {
absRoot = filepath.Join(cwd, cleanRoot)
}
rootRelativeToBase, err = filepath.Rel(absRoot, cleanBase) // validate that base is within root
if err != nil {
return nil, fmt.Errorf("base path %q is not within root path %q: %w", cleanBase, cleanRoot, err)
}
}
chroot := &ChrootContext{
root: cleanRoot,
base: cleanBase,
cwd: cwd,
root: cleanRoot,
rootRelativeToBase: rootRelativeToBase,
base: cleanBase,
cwd: cwd,
}
return chroot, chroot.ChangeDirectory(cwd)
@ -125,8 +142,8 @@ func (r ChrootContext) ToNativePath(chrootPath string) (string, error) {
responsePath := chrootPath
if filepath.IsAbs(responsePath) {
// don't allow input to potentially hop above root path
responsePath = path.Join(r.root, responsePath)
// don't allow input to potentially hop above root path (and still un-apply any base paths)
responsePath = path.Join(r.root, r.rootRelativeToBase, responsePath)
} else {
// ensure we take into account any relative difference between the root path and the CWD for relative requests
responsePath = path.Join(r.cwdRelativeToRoot, responsePath)

View File

@ -452,6 +452,25 @@ func Test_ChrootContext_RequestResponse(t *testing.T) {
expectedNativePath: absRelOutsidePath,
expectedChrootPath: "to/the/rel-outside.txt",
},
// base path within root cases...
// note: for absolute input paths, rootRelativeToBase is used to resolve the native path
// note: for relative input paths, cwdRelativeToRoot is used (base does not affect relative path resolution)
{
name: "relative root, abs request, with base",
root: relative,
base: filepath.Join(relative, "path", "to"),
input: "/the/file.txt",
expectedNativePath: absPathToTheFile,
expectedChrootPath: "/the/file.txt", // ToChrootPath trims base prefix without adding separator
},
{
name: "abs root, abs request, with base",
root: absolute,
base: filepath.Join(absolute, "path", "to"),
input: "/the/file.txt",
expectedNativePath: absPathToTheFile,
expectedChrootPath: "/the/file.txt", // ToChrootPath trims base prefix without adding separator
},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
@ -480,6 +499,69 @@ func Test_ChrootContext_RequestResponse(t *testing.T) {
}
}
func TestNewChrootContext_BaseValidation(t *testing.T) {
testDir, err := os.Getwd()
require.NoError(t, err)
relative := filepath.Join("test-fixtures", "req-resp")
absolute := filepath.Join(testDir, relative)
tests := []struct {
name string
root string
base string
cwd string
expectedRootRelativeToBase string
wantErr require.ErrorAssertionFunc
}{
{
name: "base within root",
root: absolute,
base: filepath.Join(absolute, "path", "to"),
cwd: testDir,
expectedRootRelativeToBase: filepath.Join("path", "to"),
},
{
name: "base equals root",
root: absolute,
base: absolute,
cwd: testDir,
expectedRootRelativeToBase: "",
},
{
name: "empty base",
root: absolute,
base: "",
cwd: testDir,
expectedRootRelativeToBase: "",
},
{
name: "relative root with base",
root: relative,
base: filepath.Join(absolute, "path", "to"),
cwd: testDir,
expectedRootRelativeToBase: filepath.Join("path", "to"),
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if tt.wantErr == nil {
tt.wantErr = require.NoError
}
ctx, err := NewChrootContext(tt.root, tt.base, tt.cwd)
tt.wantErr(t, err)
if err != nil {
return
}
assert.Equal(t, tt.expectedRootRelativeToBase, ctx.rootRelativeToBase)
})
}
}
func TestToNativeGlob(t *testing.T) {
tests := []struct {
name string