mirror of
https://github.com/runbear-io/beardrive.git
synced 2026-08-25 08:08:08 +02:00
fix(hub): the blob seal compared two clocks that need not agree
The verification cache is sound on one argument: once a stored blob is older than the presign TTL, no live URL for it can exist, so the bytes cannot change again and the hash need not be recomputed. That argument is about time, and the two times came from different machines — o.Modified is the object store's clock, time.Since is the hub's. A hub running ahead of storage overstates the object's age and seals it while a minted URL is still live; a replay through that URL is then served from cache for the rest of the process's life. NTP makes it unlikely and a container without it, or a VM resumed from suspend, makes it reachable. Seal after the TTL plus an hour instead. Sealing early buys nothing — the blob is immutable either way — so the margin costs a few extra hashes on a young blob and removes a dependency on two clocks agreeing that nothing in the process can verify. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DgF8JsoeNPVShGYWdooE72
This commit is contained in:
co-authored by
Claude Opus 5
parent
963843832f
commit
b1e9d20a57
@@ -378,13 +378,30 @@ func (r *RemoteSource) verify(ctx context.Context, sha string) error {
|
||||
if hex.EncodeToString(h.Sum(nil)) != sha {
|
||||
return fmt.Errorf("stored content does not hash to its key")
|
||||
}
|
||||
// sealAfter, not presignTTL: o.Modified is the STORAGE service's clock and
|
||||
// time.Since is the hub's. A hub whose clock runs ahead of storage would
|
||||
// otherwise overstate the object's age and seal it while a minted URL is
|
||||
// still live — after which a replay is served from cache for the life of
|
||||
// the process. Waiting a multiple of the TTL costs a handful of extra
|
||||
// hashes on a young blob and removes the dependency on the two clocks
|
||||
// agreeing, which nothing here can check.
|
||||
if o, ok, err := r.blobStat(ctx, sha); err == nil && ok &&
|
||||
!o.Modified.IsZero() && time.Since(o.Modified) > r.presignTTL() {
|
||||
!o.Modified.IsZero() && time.Since(o.Modified) > r.sealAfter() {
|
||||
r.sealed.Store(sha, struct{}{})
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// sealAfter is how old a stored blob must be before its verification may be
|
||||
// cached: the presign TTL plus a margin for clock skew between the hub and the
|
||||
// object store. The margin is the whole point — the correctness argument is
|
||||
// "no live URL can exist any more", and that is a claim about time measured on
|
||||
// two machines the hub cannot reconcile.
|
||||
func (r *RemoteSource) sealAfter() time.Duration {
|
||||
const skewMargin = time.Hour
|
||||
return r.presignTTL() + skewMargin
|
||||
}
|
||||
|
||||
// Identity is the device identity uploads are journaled under.
|
||||
type Identity struct {
|
||||
ID, Name, Author string
|
||||
|
||||
Reference in New Issue
Block a user