Repository navigation
fix(base): reject sub-second key and download-authorization lifetimes - #283
Conversation
**Breaking:** Positive lifetimes below one second were converted to zero. The key methods then omitted the expiration, while download authorization sent an invalid zero-second lifetime. They now reject those values rather than rounding up. Callers must pass a lifetime of at least one second; key callers may instead pass zero for a non-expiring key. Closes #282
sophiecarreras
left a comment
There was a problem hiding this comment.
This fixes the positive sub-second case cleanly and the tests are solid (the request is asserted not to be sent). One hole remains in the other direction: validDurationSeconds only rejects valid > 0 && secs == 0, so -500ms (and -1ns) still returns 0, which omitempty turns into a never-expiring key. It is reachable through b2.Deadline(t) when the deadline passed less than a second ago, because it computes t.Sub(time.Now()) (b2/key.go). Please reject valid < 0 too, with tests for -500ms, -1s and Deadline(past).
Related:
int(valid.Seconds())is unbounded, so a very large duration wraps on 32-bit builds (linux/386 and arm are in the CI matrix). Consider rejecting anything abovemath.MaxInt32seconds, or above the API's maximum key lifetime (please confirm the documented limit).- A sentinel error (for example
ErrInvalidDuration) would let callers useerrors.Is; the message prefix says "blazer/base:" while neighbouring errors use "b2:", worth checking against the package convention. - A
valid == 0download authorization is still sent and left for the server to reject; rejecting it client-side would be friendlier, but optional. - Tests worth adding at the
b2level:Lifetime(500ms)andAuthURLwith a sub-second duration, negative values, the 1-second boundary on the multi-bucket path, and a huge duration.
The area-owner-approvals status is the only non-green item and is only waiting on an approval; the rest of CI is green.
sophiecarreras
left a comment
There was a problem hiding this comment.
Review from Sofia, with Claude's help. Read only, not run. No blocking findings.
The helper rejects positive sub-second lifetimes before any request, and the tests check both the error and that no request was sent, plus the one-hour and zero (non-expiring key) cases. Marking it Breaking in the changelog is right.
Small gaps:
validDurationSecondsonly rejectsvalid > 0 && secs == 0. A negative duration is sent as negative seconds, and1.5sis silently truncated to1. Worth a test and a decision for both.- Overlap with our open Blazer PR #281: both edit
b2/b2.goandbase/base.go. Each merges cleanly on its own today; the second to merge may need a rebase.
Comment only, not an approval.
…cond-lifetime-truncation
**Breaking:** Reject negative and fractional-second key and download-authorization lifetimes before sending a request. Past deadlines can otherwise become zero and omit key expiration, while fractional seconds silently shorten the requested lifetime. Enforce the documented key lifetime of less than 1000 days before integer conversion, which also prevents overflow on 32-bit targets. Preserve zero for non-expiring keys and expose base.ErrInvalidDuration through the existing b2 call paths for errors.Is. Cover negative, fractional, maximum and one-second boundaries with unit and recording-server tests, and update the public documentation and Changed fragment. Closes #282
|
Thanks @sophiecarreras — all addressed in
Break-proofs: each guard removed turns its test red; restored → green. Re-requesting your review. |
david-santacruz-backblaze
left a comment
There was a problem hiding this comment.
Mate, let's check this before merge.
Deadline breaks for any future time. It works out the lifetime as t.Sub(time.Now()), which is never a whole number of seconds, so the new whole-second check in validDurationSeconds rejects it:
CreateKey(ctx, "key", Deadline(time.Now().Add(time.Hour)))
// invalid duration 59m59.999999834s: must be a whole number of secondsThat worked on main. #282 only asks us to reject durations under 1s, so I'd cut the check back to 0 < valid < time.Second, and add a test with a future Deadline.
Accept lifetimes of at least one second and truncate their fractional seconds, preserving the sub-second, negative and upper-bound guards. This keeps future Deadline values usable while preventing zero-second credentials. Closes #282
|
Fixed in Added a future- |
A future one-hour deadline can cross the whole-second boundary between clock reads, making either 3599 or 3600 seconds valid.
|
Windows CI flake fixed in |
Context
base/base.goconverted a key or download-authorization lifetime to the wirevalidDurationInSecondswithint(valid.Seconds()), which truncates. A positive sub-second duration became0:CreateKey/CreateKeyMultiBucket:validDurationInSecondsisomitempty, so a0was dropped and B2 created a never-expiring key.GetDownloadAuthorization: the field is required, so a0was sent and rejected by B2.Change
Add a
validDurationSecondshelper that returns an error for a positive duration below one second, applied before the request at all three call sites. Document the behavior on the exported methods andb2.Lifetime(validis whole seconds; a positive value below 1s errors;0means no expiration).Verification
New tests in
base/base_v4_test.go: a 500ms lifetime is rejected before any request at each call site;1ssends1;0is unchanged. Removing the guard fails the reject tests.make cipasses.Closes #282