package oplog_test import ( "os" "path/filepath" "strings" "testing" "time" "proxyctl/internal/app" "proxyctl/internal/oplog" ) func TestLoggerRejectsSymlinkedStateAncestor(t *testing.T) { root := t.TempDir() victim := filepath.Join(root, "victim") if err := os.Mkdir(victim, 0o700); err != nil { t.Fatal(err) } if err := os.Symlink(victim, filepath.Join(root, "state")); err != nil { t.Fatal(err) } logger := oplog.New(filepath.Join(root, "state", "proxyctl", "operations.log"), 1024, 1) if err := logger.Write(app.LogEntry{Operation: "reset", Result: "failure"}); err == nil { t.Fatal("logger followed a symlinked state ancestor") } entries, err := os.ReadDir(victim) if err != nil { t.Fatal(err) } if len(entries) != 0 { t.Fatalf("victim changed: %v", entries) } } func TestLoggerRotatesAtBoundAndNeverStoresAuthenticatedURL(t *testing.T) { path := filepath.Join(t.TempDir(), "state", "proxyctl.log") logger := oplog.New(path, 180, 2) for i := 0; i < 20; i++ { err := logger.Write(app.LogEntry{Time: time.Unix(int64(i), 0), Operation: "reset", Target: "fake", Result: "failure", Detail: "request http://alice:secret@proxy.example:8080/private failed"}) if err != nil { t.Fatal(err) } } for _, candidate := range []string{path, path + ".1", path + ".2"} { data, err := os.ReadFile(candidate) if os.IsNotExist(err) { continue } if err != nil { t.Fatal(err) } if len(data) > 180 { t.Errorf("%s has %d bytes, max 180", filepath.Base(candidate), len(data)) } text := string(data) if strings.Contains(text, "secret") || strings.Contains(text, "proxy.example:8080/private") { t.Errorf("log leaked authenticated URL: %q", text) } if !strings.Contains(text, "operation=reset") || !strings.Contains(text, "target=fake") || !strings.Contains(text, "result=failure") { t.Errorf("log lacks operation fields: %q", text) } } matches, err := filepath.Glob(path + "*") if err != nil { t.Fatal(err) } if len(matches) > 3 { t.Fatalf("retained %d logs, want at most 3: %v", len(matches), matches) } }