1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
|
package game
// Regression: the account name typed at the prompt must be validated with
// player.ValidName BEFORE it reaches the filesystem. The name is later stored
// on the session and reused to build account/character paths, so an unvalidated
// "../../" style name must never be joined into a path, even if such a path
// happens to resolve to an existing file.
import (
"os"
"path/filepath"
"strings"
"testing"
"thehouseoficarus/internal/net"
)
func TestAccountNameValidatedBeforeFilesystemAccess(t *testing.T) {
h := newHarness(t)
h.seedAccount("alice")
// A file that "../escape" resolves to once joined under players/accounts/
// (filepath.Join cleans the traversal). Before the fix this name reached
// FindAccount first and was accepted into the password state.
escapePath := filepath.Join(h.dir, "players", "escape.yaml")
if err := os.WriteFile(escapePath, []byte("name: escape\npassword_hash: x\n"), 0600); err != nil {
t.Fatal(err)
}
conn := newChanConn()
sess := &net.Session{Conn: conn}
sess.SetState(net.StateAccountName)
h.g.RunOnOwner(func() { h.g.handleAccountName(sess, "../escape") })
if got := sess.State(); got != net.StateAccountName {
t.Fatalf("traversal account name accepted: state = %v, want StateAccountName", got)
}
if sess.Account != nil {
t.Fatalf("rejected name bound to the session: %+v", sess.Account)
}
if out := string(conn.Bytes()); !strings.Contains(out, "name must be") {
t.Fatalf("missing name-validation message; output = %q", out)
}
// A valid existing name still reaches the password prompt.
conn2 := newChanConn()
valid := &net.Session{Conn: conn2}
valid.SetState(net.StateAccountName)
h.g.RunOnOwner(func() { h.g.handleAccountName(valid, "alice") })
if got := valid.State(); got != net.StatePassword {
t.Fatalf("valid account name rejected: state = %v, want StatePassword", got)
}
if valid.Account == nil || valid.Account.Name != "alice" {
t.Fatalf("valid account name not bound: %+v", valid.Account)
}
}
|