Sicherheit & Robustheit härten

- SQLite-DSN: busy_timeout(5000) + WAL + foreign_keys gegen SQLITE_BUSY
- uid ist nie 0 mehr (0 = Sentinel für gelöscht/anonym), Retry bei Kollision
- scanEntries gibt Scan-Fehler zurück statt Zeilen still zu überspringen
- Session-timeout serverseitig auf 30 Tage gedeckelt (clientgesteuert)
- Bild-Upload: Dimensionen vor Decode prüfen (Decompression-Bomb-Schutz)
- Secure-Cookie via TLS-Erkennung (r.TLS / X-Forwarded-Proto)
- Rate-Limit (httprate, 20/min/IP) auf Login & Registrierung
- Vote nur auf existierende Beiträge (keine Waisen-Votes)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-07 17:07:17 +02:00
parent 5478e92253
commit 1313e9613c
7 changed files with 95 additions and 20 deletions
+24 -11
View File
@@ -12,7 +12,6 @@ import (
)
func createUser(username, password string) error {
uid := int64(rand.IntN(99999999))
now := time.Now().Unix()
hash, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
@@ -20,11 +19,22 @@ func createUser(username, password string) error {
return err
}
_, err = db.Exec(
`INSERT INTO user (uid, created_at, username, password) VALUES (?, ?, ?, ?)`,
uid, now, username, hash,
)
return err
// uid ist nie 0: 0 ist Sentinel für "gelöscht"/"anonym" (siehe handleDeleteEntry,
// uidFromContext). Bei der seltenen UNIQUE-Kollision auf uid neu würfeln.
var lastErr error
for i := 0; i < 5; i++ {
uid := int64(rand.IntN(99999999)) + 1
_, lastErr = db.Exec(
`INSERT INTO user (uid, created_at, username, password) VALUES (?, ?, ?, ?)`,
uid, now, username, hash,
)
if lastErr == nil {
return nil
}
// username ist ebenfalls UNIQUE und wird vom Aufrufer vorab geprüft;
// eine Kollision hier kann also nur die uid sein -> erneut versuchen.
}
return lastErr
}
// idFromUsername liefert die interne id oder 0, wenn der Nutzer nicht existiert.
@@ -169,11 +179,14 @@ func handleUserDelete(w http.ResponseWriter, r *http.Request) {
}
http.SetCookie(w, &http.Cookie{
Name: "session",
Value: "",
Path: "/",
Expires: time.Unix(0, 0),
MaxAge: -1,
Name: "session",
Value: "",
Path: "/",
Expires: time.Unix(0, 0),
MaxAge: -1,
HttpOnly: true,
Secure: isHTTPS(r),
SameSite: http.SameSiteLaxMode,
})
writeJSON(w, http.StatusOK, map[string]string{"status": "deleted"})
}