MCPcopy
hub / github.com/filebrowser/filebrowser / TestSharePostHandlerDoesNotLeakSecrets

Function TestSharePostHandlerDoesNotLeakSecrets

http/share_test.go:99–147  ·  view source on GitHub ↗

Regression for the share secret exposure (GHSA-833g-cqhp-h72j): the share API must not serialize the bcrypt password hash or the bypass token, while still persisting them server-side so password-protected shares keep working.

(t *testing.T)

Source from the content-addressed store, hash-verified

97// must not serialize the bcrypt password hash or the bypass token, while still
98// persisting them server-side so password-protected shares keep working.
99func TestSharePostHandlerDoesNotLeakSecrets(t *testing.T) {
100 root := t.TempDir()
101 userScope := filepath.Join(root, "user")
102 if err := os.MkdirAll(userScope, 0o755); err != nil {
103 t.Fatal(err)
104 }
105 if err := os.WriteFile(filepath.Join(userScope, "file.txt"), []byte("x"), 0o600); err != nil {
106 t.Fatal(err)
107 }
108
109 key := []byte("test-signing-key")
110 perm := users.Permissions{Share: true, Download: true}
111 st := scopedUserStorage(t, userScope, perm, key)
112 signed := signToken(t, perm, key)
113
114 body := `{"password":"ShareSecret123!","expires":"24","unit":"hours"}`
115 req, _ := http.NewRequest(http.MethodPost, "/file.txt", strings.NewReader(body))
116 req.Header.Set("X-Auth", signed)
117 rec := httptest.NewRecorder()
118 handle(sharePostHandler, "", st, &settings.Server{Root: root}).ServeHTTP(rec, req)
119
120 if rec.Code != http.StatusOK {
121 t.Fatalf("expected 200, got %d body=%q", rec.Code, rec.Body.String())
122 }
123
124 var resp map[string]any
125 if err := json.Unmarshal(rec.Body.Bytes(), &resp); err != nil {
126 t.Fatalf("decode: %v", err)
127 }
128 if _, ok := resp["password_hash"]; ok {
129 t.Errorf("VULNERABLE: response leaks password_hash: %s", rec.Body.String())
130 }
131 if _, ok := resp["token"]; ok {
132 t.Errorf("VULNERABLE: response leaks token: %s", rec.Body.String())
133 }
134 if resp["hasPassword"] != true {
135 t.Errorf("expected hasPassword=true, got %v", resp["hasPassword"])
136 }
137
138 // The secrets must still be persisted server-side (storm uses the JSON codec,
139 // so the storage struct's tags must keep emitting them).
140 stored, err := st.Share.GetByHash(resp["hash"].(string))
141 if err != nil {
142 t.Fatalf("share not stored: %v", err)
143 }
144 if stored.PasswordHash == "" || stored.Token == "" {
145 t.Fatalf("server-side secrets not persisted: hash=%q token=%q", stored.PasswordHash, stored.Token)
146 }
147}
148
149func signShareTestToken(t *testing.T, id uint, username string, perm users.Permissions, key []byte) string {
150 t.Helper()

Callers

nothing calls this directly

Calls 6

scopedUserStorageFunction · 0.85
signTokenFunction · 0.85
handleFunction · 0.85
MkdirAllMethod · 0.80
GetByHashMethod · 0.65
StringMethod · 0.45

Tested by

no test coverage detected