The sqv method checks each signature against next year's policy first
(methods/sqv.cc, VerifyGetSigners). It builds that date as
strprintf(yearAheadDate, "%d-%d-%d", tm->tm_year + 1900 + 1, tm->tm_mon
+ 1, tm->tm_mday);
On 29 February this gives 29 February of a year that does not have one.
sqv refuses the argument:
$ sqv ... --policy-as-of 2029-2-29 ...
error: invalid value '2029-2-29' for '--policy-as-of <TIMESTAMP>':
Malformed timestamp: 2029-2-29.
and exits 2. The method treats any failure of the year-ahead run as the
policy rejecting the signature, runs sqv again at today's policy, and
warns. So on 29 February every repository gets
W: .../InRelease: Policy will reject signature within a year, see
--audit for details
with the error above as its audit reason, and the year-ahead check does
not happen that day.
I reproduced it with apt-get update on a local file:// repository
signed by an ed25519 key, under a clock set to 2028-02-29 (an
LD_PRELOAD shim on clock_gettime/gettimeofday/time, inherited by the
method and sqv). With the clock at 2028-02-28 or 2028-03-01 there is no
warning, and with APT::Key::GPGVCommand set there is none on 29 February
either. The code is the same on main.
Two things would avoid it: computing the date with a calendar, or
passing "+1y", which sqv accepts as an offset (--policy-as-of +1y
exits 0 here). Separately: sqv's exit 2 is a usage error, and the
method reports it as a policy rejection.
Hi, that is an interesting find! (And some sad code) Am Wed, Oct 07, 2026 at 11:47:19PM -0600, schrieb Christian Noé Ramos López: with what is available via <chrono> in C++20 and above (nowadays). The second option seems (at least for now) to be a usage error itself as the documentation (sqv manpage) for --policy-as-of says | The time is expressed as an ISO 8601 formatted timestamp. And earlier it expressed in general: | The TIMESTAMPs must be given in ISO 8601 format I presume that +1y works stems from --time which says: | Set the reference time as an ISO 8601 formatted timestamp or | relative offset. […] This time is also used as the default | value for --not-after and --policy-as-of […] "relative offset" does not seem defined through. Aside: I /suppose/ "PY1" would be a valid ISO 8601 (duration), but I haven't dared to try how sqv would react. Maybe its just me, but I would prefer the former (even if documentation of sqv is changed) as the wording --policy-as-of highly suggests an exact date for me… even if I realize as of 5 seconds later that this might be a valid way of speaking, too. Best regards David Kalnischkies
Yes we actually want to use midnight here. I'm not sure if the best approach is to the the 1 year from now or to truncate the day field until it produces a valid date, such that 02-29 truncates to 02-28 rather than extends to 03-01 (semantically preserving the end of month). I guess it's easier though to do the chrono change and perhaps that's what we should do yeah.
One thing that outlives whichever lands. In my rows sqv exits 1 when the policy rejects a signature and 2 on a usage error, and the year-ahead pass does not separate them -- both arrive as a policy rejection and warn on every repository. The leap day is one way to reach exit 2 there; any other usage-level failure in that pass would look the same. Is separating them worth doing? Christian Ramos Norte Software chris@nortesoftware.dev El lun, 12 oct 2026 a la(s) 1:15 p.m., Julian Andres Klode (jak@debian.org) escribió: