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

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:
admin committed 2026-10-17 09:36:34 +02:00
1 parent 7d41c2973c
commit 94f44931dd
28 files changed
+945 -190

No files matched your search

+21 -16
View File
@@ -158,8 +158,10 @@ func (s *Server) familyRDHost(rd string) (string, bool) {
// family-gated app's backend receives its cookies, and a session id lifted from one app, set as the family session
// cookie on the dashboard host, minted passes into EVERY family app for 30 days. A derived token cannot be turned back
// into the session, and it opens only its own host. Pinned by TestR976_AnAppCookieCannotBeLiftedIntoAnotherApp.
func (s *Server) familyAppToken(host, sid string) string {
return s.gateMAC("famapp", host, sid)
// R-1112: it is derived from the session's FINGERPRINT (fp) — all the store keeps — so app cookies minted before
// R-1112 (derived from the value) are refused once and the browser passes through the sign-in start again.
func (s *Server) familyAppToken(host, fp string) string {
return s.gateMAC("famapp", host, fp)
}
// familySessionForAppToken finds the session an app token was derived from (still valid, as Store.Valid judges it).
@@ -167,8 +169,8 @@ func (s *Server) familySessionForAppToken(host, at string) (family.Session, bool
if at == "" {
return family.Session{}, false
}
return s.familyStore().Find(func(id string) bool {
return hmac.Equal([]byte(s.familyAppToken(host, id)), []byte(at))
return s.familyStore().Find(func(fp string) bool {
return hmac.Equal([]byte(s.familyAppToken(host, fp)), []byte(at))
})
}
@@ -181,10 +183,10 @@ type familyToken struct {
MAC string `json:"m"`
}
func (s *Server) mintFamilyToken(host, sid, rd string) string {
func (s *Server) mintFamilyToken(host, fp, rd string) string {
nb := make([]byte, 12)
_, _ = rand.Read(nb)
t := familyToken{Host: host, App: s.familyAppToken(host, sid), Exp: s.gateNow().Add(gateTokenLife).Unix(), Nonce: hex.EncodeToString(nb), RD: rd}
t := familyToken{Host: host, App: s.familyAppToken(host, fp), Exp: s.gateNow().Add(gateTokenLife).Unix(), Nonce: hex.EncodeToString(nb), RD: rd}
t.MAC = s.gateMAC("ftoken", t.Host, t.App, strconv.FormatInt(t.Exp, 10), t.Nonce, t.RD)
b, _ := json.Marshal(t)
return base64.RawURLEncoding.EncodeToString(b)
@@ -334,16 +336,18 @@ func (s *Server) ServeFamilyStart(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Cache-Control", "no-store")
se, ok := s.familySessionFromCookie(r)
if !ok && s.hasSession(r) {
// The household's own dashboard session vouches (decision 46's rule) — a household session in the family store,
// never the dashboard session itself.
// The household's own dashboard session vouches (decision 46's rule) — the household's session in the family
// store, never the dashboard session itself. R-1113: its live one, not a new one on every pass.
if st := s.familyStore(); st != nil {
if sid, err := st.NewSession(""); err == nil {
se, ok = family.Session{ID: sid}, true
if hs, err := st.HouseholdSession(); err == nil {
se, ok = hs, true
} else {
s.logger.Printf("[ERROR] [web] family start: the household's family session could not be saved: %v", err)
}
}
}
if ok {
http.Redirect(w, r, "https://"+host+familyCallbackURI+"?"+url.Values{"t": {s.mintFamilyToken(host, se.ID, rd)}}.Encode(), http.StatusFound)
http.Redirect(w, r, "https://"+host+familyCallbackURI+"?"+url.Values{"t": {s.mintFamilyToken(host, se.FP, rd)}}.Encode(), http.StatusFound)
return
}
s.renderFamilyPage(w, r, rd, "", http.StatusOK)
@@ -409,9 +413,9 @@ func (s *Server) ServeFamilyLogin(w http.ResponseWriter, r *http.Request) {
s.renderFamilyPage(w, r, "", s.msg(r, "family_gate.msg.signed_in"), http.StatusOK)
}
// familyLogoutToken is the sign-out form's token, bound to the family session it ends (R-983, v0.324.0). The sign-in
// form's token (familyFormToken) is the same for every visitor, so a stranger could fetch one and use it; this one
// names the session, which another page never sees.
// familyLogoutToken is the sign-out form's token, bound to the family session it ends (R-983, v0.324.0) — to the
// cookie value the browser holds (sid). The sign-in form's token (familyFormToken) is the same for every visitor, so a
// stranger could fetch one and use it; this one names the session, which another page never sees.
func (s *Server) familyLogoutToken(sid string) string {
return s.gateMAC("famlogout", sid)
}
@@ -448,9 +452,10 @@ func (s *Server) renderFamilyPage(w http.ResponseWriter, r *http.Request, rd, no
}
}
}
if se, ok := s.familySessionFromCookie(r); ok {
if _, ok := s.familySessionFromCookie(r); ok {
c, _ := r.Cookie(familySessionCookie) // present: familySessionFromCookie found it
data["SignedIn"] = true
data["LogoutToken"] = s.familyLogoutToken(se.ID)
data["LogoutToken"] = s.familyLogoutToken(c.Value)
}
w.Header().Set("Content-Type", "text/html; charset=utf-8")
w.Header().Set("Cache-Control", "no-store")