fix(miner-extension): guard the live-fetch sync path against the storage quota#7062
Conversation
…age quota syncRankedCandidatesFromMinerUi wrote fetched ranked candidates to chrome.storage.local with no size check, unlike options.js's manual-paste flow which measures the real serialized UTF-8 byte size against the shared 10 MiB QUOTA_BYTES limit (JSONbored#4863). A large live-fetch result could silently write a partial payload past the quota via this path. Apply the same TextEncoder byte-size guard before the storage write, returning the function's existing typed { ok: false, error, minerUiUrl } shape on an over-quota result rather than writing a partial payload. Closes JSONbored#7006
|
🚨 Contributor flagged. Click here for more info: Superagent Dashboard |
|
Superagent didn't find any vulnerabilities or security issues in this PR. |
|
Warning ⏸️ LoopOver review result - manual review recommendedReview updated: 2026-07-17 20:22:22 UTC
Review summary Nits — 2 non-blocking
Flagged checks (non-blocking)
Decision drivers
Context & advisory signals — never blocks the verdict
Review context
Contributor next steps
Signal definitions
🧪 Chat with LoopOverAsk LoopOver a question about this PR directly in a comment — grounded only in the same cached, public-safe facts shown above, never a new claim.
Full command reference: https://loopover.ai/docs/loopover-commands 🧪 Experimental — new and may change. Visual preview
Click any thumbnail to open the full-size screenshot. Before = production · After = this PR's preview deploy. 🟩 Safe / merged · 🟦 Advisory · 🟨 Held for review · 🟥 Blocked / closed 💰 Earn for open-source contributions like this. Gittensor lets GitHub contributors earn for the work they already do — register to start earning →. Checked by LoopOver, a quiet PR intelligence layer for OSS maintainers.
|
…ve-fetch vm sandbox node:vm's createContext() is a fresh, isolated realm with none of the outer process's globals. background.js's live-fetch path bounds its fetch with AbortSignal.timeout(...) (#4c0b19f4) and measures the real serialized byte size via TextEncoder for the quota guard (JSONbored#7062), but the test harness never injected either global into the sandbox -- every call on the success path threw "<Global> is not defined", caught by the function's own try/catch and silently misreported as a typed failure result instead of a real one. Confirmed this isn't CI flakiness: reproduced deterministically outside CI, on every run, for any candidates payload that reaches the success path (all 4 of the 7 originally-failing tests exercise that path; the other 3 return early on a non-2xx/malformed/thrown-fetch branch before ever touching either global).
What
syncRankedCandidatesFromMinerUi(apps/loopover-miner-extension/background.js) — the live-fetch replacement for manual paste — wrote fetched ranked candidates tochrome.storage.localwith no size check. The manual-paste flow inoptions.jsalready guards against this: it measures the real serialized UTF-8 byte size (viaTextEncoder, not JS string.length) against the shared 10 MiBQUOTA_BYTESlimit, because an unbounded write can silently fail or leave storage in a partial state (#4863). The live-fetch path never got the equivalent guard, so the same#4863failure mode could still happen through it.How
Measure the serialized byte size of the fetched
candidatesbefore thechrome.storage.local.set(...)call, against the same 8 MiB ceilingoptions.jsenforces. On an over-quota result, return the function's existing typed{ ok: false, error, minerUiUrl }shape (matching every other failure branch and its documented "never throws" contract) rather than writing a partial payload.Validation
Tests added to the existing
test/background.test.ts: an over-quota live-fetch payload is rejected withok: falseand nochrome.storage.local.setcall (localSetCallsstays empty), while a within-quota payload still writes normally. Confirmed the over-quota test fails against the unfixed code (it writes the partial payload) and passes with the fix.miner-extension:lint/typecheckand the extension test suite (26 tests) pass; the guard's lines are fully covered.Closes #7006