aboutsummaryrefslogtreecommitdiff
path: root/internal/admin/api_entity_path_test.go
diff options
context:
space:
mode:
Diffstat (limited to 'internal/admin/api_entity_path_test.go')
-rw-r--r--internal/admin/api_entity_path_test.go21
1 files changed, 21 insertions, 0 deletions
diff --git a/internal/admin/api_entity_path_test.go b/internal/admin/api_entity_path_test.go
index 75fed7a..3a0f87b 100644
--- a/internal/admin/api_entity_path_test.go
+++ b/internal/admin/api_entity_path_test.go
@@ -56,10 +56,31 @@ func TestEntityAPICannotEscapeData(t *testing.T) {
if !strings.Contains(rec.Body.String(), "error") {
t.Fatalf("traversal rename id not rejected: status %d body %s", rec.Code, rec.Body.String())
}
+ // Traversal SOURCE id via the rename and move routes: the source is
+ // joined into a path too, so it must be rejected before it can move a
+ // file outside data/ (e.g. the server's own config.yaml).
+ sentinel := filepath.Join(root, "sentinel.yaml")
+ if err := os.WriteFile(sentinel, []byte("name: Sentinel\n"), 0o600); err != nil {
+ t.Fatal(err)
+ }
+ for _, tc := range []struct{ path, body string }{
+ {"/api/objects/rename", `{"id":"../../sentinel","new_id":"stolen"}`},
+ {"/api/objects/move", `{"id":"../../sentinel","dir":"sub"}`},
+ } {
+ if rec := post(tc.path, tc.body); !strings.Contains(rec.Body.String(), "error") {
+ t.Fatalf("traversal source id not rejected at %s: status %d body %s", tc.path, rec.Code, rec.Body.String())
+ }
+ }
+ if _, err := os.Stat(sentinel); err != nil {
+ t.Fatalf("traversal source id moved a file outside data/: %v", err)
+ }
// Nothing may exist outside the data directory.
if _, err := os.Stat(filepath.Join(root, "outside.yaml")); !os.IsNotExist(err) {
t.Fatal("entity API escaped the data directory")
}
+ if _, err := os.Stat(filepath.Join(root, "stolen.yaml")); !os.IsNotExist(err) {
+ t.Fatal("traversal rename escaped the data directory")
+ }
}
// TestTechModuleCreateRejectsTraversalID: POST /api/techs and /api/modules