feat(miner-extension): reject oversized pasted ranked-candidates JSON before saving - #5530
Conversation
… before saving The extension doesn't request the unlimitedStorage permission, so chrome.storage.local is capped at its default ~10 MiB quota with no guard against an unbounded paste silently failing to save. Reject a paste over a conservative size bound with a clear error, before ever attempting to parse or save it. Fixes #4863
…N size guard A UTF-16 character-length check undercounts any multibyte content, so a payload full of non-ASCII characters could pass the size guard added in the previous commit yet still exceed chrome.storage.local's real quota once serialized, recreating the exact silent-failure bug that guard exists to prevent. Measure the actual UTF-8 byte size via TextEncoder instead.
|
Superagent didn't find any vulnerabilities or security issues in this PR. |
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #5530 +/- ##
=======================================
Coverage 94.79% 94.79%
=======================================
Files 566 566
Lines 45065 45065
Branches 14675 14675
=======================================
Hits 42718 42718
Misses 1613 1613
Partials 734 734
Flags with carried forward coverage won't be shown. Click here to find out more. 🚀 New features to boost your workflow:
|
|
Tip 🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩 ✅ Gittensory review result - approve/merge recommendedReview updated: 2026-07-13 01:56:25 UTC
✅ Suggested Action - Approve/Merge
Review summary Nits — 5 non-blocking
Linked issue satisfactionAddressed Review context
Contributor next steps
Signal definitions
[BETA] Chat with GittensoryAsk Gittensory 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://gittensory.aethereal.dev/docs/gittensory-commands 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 Gittensory, a quiet PR intelligence layer for OSS maintainers.
|
Summary
textarea with no size bound at all. The extension does not request the
unlimitedStoragepermission, sochrome.storage.localis capped at its default ~10 MiBQUOTA_BYTESquota shared across every key — anoversized paste could silently fail to save (Chrome rejects the
set()call) or leave storage in a partialstate, with no clear feedback to the contributor about why.
options.js'sparseRankedCandidatesJsonnow measures the pasted text's real UTF-8 byte size vianew TextEncoder().encode(trimmed).lengthand rejects anything over a newMAX_RANKED_CANDIDATES_JSON_BYTESbound (8 MiB) before attempting
JSON.parse— so an oversized-but-invalid paste fails with a clear"too large" error rather than a confusing JSON syntax error, and a valid-but-oversized paste never reaches
chrome.storage.local.setat all.TextEncoderis a standard Web API available in both the real (unbundled) extension runtime and, onceinjected into the sandbox context, this repo's
node:vm-based unit-test harness for these scripts — it isnot present in a bare
vm.createContext({})by default, which the test helpers now account for.Fixes #4863
Scope
type(scope): short summaryConventional Commit format, for examplefix(api): restore profile access checks.CONTRIBUTING.mdand does not reintroduce GitHub Pages, VitePress,site/, orCNAME.Closes #123) — a linked open issue is required for every contributor PR.Validation
git diff --checknpm run actionlintnpm run typechecknpm run test:coveragelocally — this change lives entirely underapps/gittensory-miner-extension/**and its dedicated test filetest/unit/miner-extension-content.test.ts, outside vitest's rootcoverage.includeglob (only rootsrc/**is Codecov-measured), socodecov/patchcannot see this diff.npm run test:workersnpm run build:mcpnpm run test:mcp-packnpm run ui:openapi:checknpm run ui:lintnpm run ui:typechecknpm run ui:buildnpm audit --audit-level=moderateRan the full local gate:
npm run test:ci(798 test files, 0 failures) andnpm audit --audit-level=moderate(0 vulnerabilities), both clean on the final commit.Test coverage added to
test/unit/miner-extension-content.test.ts(now 22 tests total, up from 18): rejecting a payload one byte over the bound with a clear "too large" error, and specifically asserting the size check fires beforeJSON.parseruns (proven with a non-JSON oversized string, checking the thrown error is not a JSON syntax error); accepting a payload of exactly the boundary byte length; a regression test driving the real form-submit save flow end-to-end with an oversized paste, asserting the error surfaces throughshowStatusandchrome.storage.local.setis never called (zero partial writes); and a dedicated regression test using"é"-character padding (1 UTF-16 code unit but 2 UTF-8 bytes each), sized so it would have wrongly passed a naive character-length check while its real byte size exceeds the bound — proving multibyte content can't sneak past the guard.Safety
chrome.storageonly).chrome.storage.localwrite path.parseRankedCandidatesJson/showStatuscode, not a mock of the validation itself.UI Evidencesection below.apps/gittensory-miner-extension/README.md's "Local ranked cache" section.UI Evidence
This is a browser extension options page — there is no hosted/public deployment to screenshot from this
environment (no browser screenshot tooling available here). Verified functionally instead:
npm run test:ciincludes the full
test/unit/miner-extension-content.test.tssuite (22/22 passing), including a regression testthat drives the real options-page save flow with an oversized paste and asserts the visible status message
matches
/too large/iwhilechrome.storage.local.setis never invoked.Notes
pasted text's UTF-16
.length(character count) instead of actual byte size, so a payload full of multibytecharacters could pass the check yet still exceed the real
chrome.storage.localquota once serialized,recreating the exact silent-failure bug the guard exists to prevent. This PR fixes that by measuring real
UTF-8 bytes via
TextEncoderinstead, with the"é"-padding regression test above proving the fix — thatsame input would have passed the old, incorrect check.
unlimitedStoragepermission as an alternative fix; the issue's proposal was a bounded,validated paste path with a clear error, not expanding the extension's storage footprint.