Repository navigation
Description
Summary
pkgs/pkarr-worker: RETENTION_SECONDS is parsed with a bare Number(...) and no validity guard in PkarrStore.putPacket (src/index.ts:67-68), and the resulting timestamp is handed straight to setAlarm, whose handler unconditionally deletes the stored record. Any value that is not a plain positive decimal silently breaks retention for every record in that object:
"0","-1", or a whitespace-only" "(truthy, so the|| "604800"fallback does not fire;Number(" ")is 0) → the deletion alarm is scheduled at or before now, so the record a PUT just stored (and returned 204 for) is deleted again almost immediately; later GETs 404."7d"or any non-numeric spelling →retentionMsisNaNand the alarm time isNaN— the expiry behaviour is whatever the runtime does with an invalid alarm time, instead of the documented 7 days."1e9"→ silently reinterpreted as 1,000,000,000 seconds (~31,700 years): records effectively never expire, contradicting the header contract that "records expire after RETENTION_SECONDS without refresh".
The same file already shows the house pattern for exactly this: DNS_TTL is parsed in handleDoh (src/index.ts:224-226) with Number.isFinite(ttl) && ttl > 0 ? ttl : 30 — a guarded parse with a sane fallback. The retention parse has no equivalent guard.
Evidence (main f4f800e7)
src/index.ts:67-68—const retentionMs = Number(this.env.RETENTION_SECONDS || "604800") * 1000; await this.ctx.storage.setAlarm(Date.now() + retentionMs);NoisFinite, no positivity check, no fallback beyond the empty-string||.src/index.ts:86-88—alarm()runsDELETE FROM record WHERE id = 1unconditionally, so a past-due alarm removes the current record, including one stored seconds earlier.src/index.ts:224-226— the guardedDNS_TTLcontrast in the same file.wrangler.jsonc:18sets"RETENTION_SECONDS": "604800"— the value is operator-edited config (a var, not code), so a units slip ("7d","604800 "variants,"0"while testing) reaches the parse unvalidated.- Distinct from pkarr-worker(deploy): --dry-run is ignored on the --deploy-version path — a rollback preview creates a real 100% deployment #3252 (deploy.mjs
--dry-runon the deploy-version path — deploy tooling, not the Worker's runtime config) and Deployment standard: Nix flake deploys with SOPS + vault-backed keys (pilot: pkarr Worker) #1828/P7.2 implement + deploy: Cloudflare Workers pkarr endpoint, under the Nix deployment standard #1829 (the deployment standard this Worker pilots).
Verified (laptop probe, exact expressions from index.ts:67)
Probe: ~/git/fleet/probe-2026-10-31-laneA-pkarr-retention/ on the laptop — src/index.ts sha256 a751bb4d… identical in the laptop clone and the sandbox checkout at main f4f800e7; the probe evaluates the verbatim :67 expression:
undefined -> retentionMs = 604800000 | alarm in past / invalid: false
"604800" -> retentionMs = 604800000 | alarm in past / invalid: false
"0" -> retentionMs = 0 | alarm in past / invalid: true
"-1" -> retentionMs = -1000 | alarm in past / invalid: true
"7d" -> retentionMs = NaN | alarmAt = NaN
" " -> retentionMs = 0 | alarm in past / invalid: true
"1e9" -> retentionMs = 1000000000000 | ~31,700-year retention
"3600.5" -> retentionMs = 3600500 | fractional seconds accepted silently
(The Durable Object alarm itself was not executed — no node_modules on the laptop for the workers vitest pool; the claim verified here is the parse layer plus the unconditional-delete alarm handler in the source. A past-due setAlarm firing promptly is documented Durable Object behaviour.)
Why it matters
Retention is the only expiry mechanism for stored pkarr packets: the store has one row per key and getPacket performs no age check, so the alarm is the whole contract. A config slip therefore fails in the worst two directions at once — live records vanishing right after a successful publish (clients see 204, then 404), or records never expiring at all — with no error anywhere: putPacket returns "stored" in every case above except a possible runtime throw on the NaN alarm.
Acceptance criteria
-
RETENTION_SECONDSis parsed with the same discipline asDNS_TTL: a non-finite, non-positive, or non-plain-decimal value falls back to the 604800s default (or is rejected loudly at startup), never reachingsetAlarmas 0/negative/NaN. - With
RETENTION_SECONDSset to"0","7d", and" "(one test each), a PUT still stores the packet and a subsequent GET returns it, with the alarm scheduled ~7 days out. - Scientific/fractional spellings (
"1e9","3600.5") are either rejected-to-default or explicitly documented as accepted — not silently reinterpreted. - Regression tests in the workers vitest suite cover the bad-value cases alongside the existing
"604800"behaviour, which is unchanged.
Activity
dezren39 commentedon Oct 31, 2026
🤖 Agent note · state: filed
9 remaining items
dezren39 commentedon Nov 2, 2026
agent-note:v1
dezren39 commentedon Nov 2, 2026
🤖 Agent note · picked up
dezren39 commentedon Nov 2, 2026
agent-note:v1
dezren39 commentedon Nov 2, 2026
agent-note:v1
Merge pull request #4352 from developing-today/queue/5686-3261-retent…