aboutsummaryrefslogtreecommitdiff
path: root/internal/admin/api_entity_path_test.go
diff options
context:
space:
mode:
authorhistoria <[not public]>2026-09-16 02:14:02 -0400
committerhistoria <[not public]>2026-09-16 02:14:02 -0400
commitad60843fbf577eda80105e3916822d663aaefcb3 (patch)
tree68092a6403fdb2992ef92cfe0e2dd62b479935aa /internal/admin/api_entity_path_test.go
parent61881fd0c1a7066293f552be1a747143f70d03ff (diff)
downloadthehouseoficarus-ad60843fbf577eda80105e3916822d663aaefcb3.tar.gz
fix: economy recovery double-refund/erased-liability/cap-drops, entity editor path traversal
Diffstat (limited to 'internal/admin/api_entity_path_test.go')
-rw-r--r--internal/admin/api_entity_path_test.go62
1 files changed, 62 insertions, 0 deletions
diff --git a/internal/admin/api_entity_path_test.go b/internal/admin/api_entity_path_test.go
new file mode 100644
index 0000000..7abf65f
--- /dev/null
+++ b/internal/admin/api_entity_path_test.go
@@ -0,0 +1,62 @@
+package admin
+
+import (
+ "net/http"
+ "net/http/httptest"
+ "os"
+ "path/filepath"
+ "strings"
+ "testing"
+
+ "thehouseoficarus/internal/config"
+ "thehouseoficarus/internal/player"
+)
+
+// TestEntityAPICannotEscapeData: entity create/rename IDs and directory
+// targets join into file paths, so a traversal ID ("../../x") must be
+// rejected instead of writing outside the data directory.
+func TestEntityAPICannotEscapeData(t *testing.T) {
+ root := t.TempDir()
+ dir := filepath.Join(root, "data")
+ for _, sub := range []string{"objects", "players/accounts"} {
+ if err := os.MkdirAll(filepath.Join(dir, sub), 0o700); err != nil {
+ t.Fatal(err)
+ }
+ }
+ accounts := player.NewAccountStore(dir)
+ if err := accounts.SaveAccount(&player.Account{Name: "auditor", Admin: true}); err != nil {
+ t.Fatal(err)
+ }
+ s, err := NewServer(config.Default(), false, accounts, nil, nil, nil, nil, dir,
+ nil, nil, nil, nil, filepath.Join(dir, "config.yaml"),
+ func(fn func()) bool { fn(); return true })
+ if err != nil {
+ t.Fatal(err)
+ }
+ cookieRec := httptest.NewRecorder()
+ s.setAuthCookie(cookieRec, "auditor")
+ cookie := cookieRec.Result().Cookies()[0]
+
+ post := func(path, body string) *httptest.ResponseRecorder {
+ req := httptest.NewRequest(http.MethodPost, path, strings.NewReader(body))
+ req.AddCookie(cookie)
+ rec := httptest.NewRecorder()
+ s.ServeHTTP(rec, req)
+ return rec
+ }
+
+ // Traversal entity ID via the structured create route.
+ if rec := post("/api/objects", `{"id":"../../outside","name":"Escaped object"}`); rec.Code != http.StatusBadRequest {
+ t.Fatalf("traversal object id: status %d body %s", rec.Code, rec.Body.String())
+ }
+ // Traversal new ID via the entity rename route: the rename handler
+ // must reject the request, not rename anything.
+ rec := post("/api/objects/rename", `{"id":"test","new_id":"../../outside"}`)
+ if !strings.Contains(rec.Body.String(), "error") {
+ t.Fatalf("traversal rename id not rejected: status %d body %s", rec.Code, rec.Body.String())
+ }
+ // 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")
+ }
+}