v0.325.0: share + escrow limiters reserve first; family sessions as fingerprints; samba renames apply; path guard widened; gates in a worktree
gates / gates (push) Successful in 45s
gates / gates (push) Successful in 45s
R-1111 reserveAttempt is the one limiter body (share password, escrow re-auth, dashboard sign-in). R-1112 family.json holds fingerprints; old files are converted at load. R-1113 the household's pass reuses its family session. R-1109 the samba compose carries the smb.conf hash. R-1110 TestNoBareDrivePathToPathHelpers. R-1114 shared_scripts.py resolves the felhom.eu sibling from a worktree. Each red-proved. Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
This commit is contained in:
@@ -108,35 +108,15 @@ func (s *Server) validShareCSRF(r *http.Request) bool {
|
||||
return subtle.ConstantTimeCompare([]byte(c.Value), []byte(want)) == 1
|
||||
}
|
||||
|
||||
// ── the share-password brute-force limiter (own per-IP map; same 5-attempts / 1-min window as the
|
||||
// admin login, copied — never shared) ─────────────────────────────────────────────────────────────
|
||||
// ── the share-password brute-force limiter (own per-IP map; same 5-attempts / 1-min window and the same
|
||||
// reserveAttempt body as the admin login — the counter is never shared) ──────────────────────────
|
||||
|
||||
// shareRateLimited reports whether this IP is currently blocked (>= loginMaxAttempts within the
|
||||
// window). An expired window resets the counter first, so a fresh burst starts clean.
|
||||
func (s *Server) shareRateLimited(ip string) bool {
|
||||
s.shareAttemptMu.Lock()
|
||||
defer s.shareAttemptMu.Unlock()
|
||||
a := s.shareAttempts[ip]
|
||||
if a != nil && time.Since(a.lastFail) > loginWindowDuration {
|
||||
delete(s.shareAttempts, ip)
|
||||
a = nil
|
||||
}
|
||||
return a != nil && a.count >= loginMaxAttempts
|
||||
}
|
||||
|
||||
// shareRegisterFailure bumps this IP's failed-attempt counter (lazily allocating the map so
|
||||
// struct-literal test servers that skip NewServer still work).
|
||||
func (s *Server) shareRegisterFailure(ip string) {
|
||||
s.shareAttemptMu.Lock()
|
||||
defer s.shareAttemptMu.Unlock()
|
||||
if s.shareAttempts == nil {
|
||||
s.shareAttempts = make(map[string]*loginAttempt)
|
||||
}
|
||||
if s.shareAttempts[ip] == nil {
|
||||
s.shareAttempts[ip] = &loginAttempt{}
|
||||
}
|
||||
s.shareAttempts[ip].count++
|
||||
s.shareAttempts[ip].lastFail = time.Now()
|
||||
// shareReserve counts one share-password try for this visitor BEFORE the password is compared and reports whether it
|
||||
// may go ahead — check and count in one locked step (reserveAttempt, R-1111), so parallel tries cannot all pass a
|
||||
// check that none of them has counted yet. A right password clears the count (shareClearFailures).
|
||||
func (s *Server) shareReserve(ip string) bool {
|
||||
_, ok := reserveAttempt(&s.shareAttemptMu, &s.shareAttempts, ip)
|
||||
return ok
|
||||
}
|
||||
|
||||
// shareClearFailures clears this IP's counter after a successful gate pass.
|
||||
|
||||
Reference in New Issue
Block a user