Conversation
The worker carried its own copy of a rule validation.ts already states, on an import line that already reached that module. The shared function takes the caller's wording, so the refusal text is unchanged. The local describe block went with it: tests/ipc-validation.test.ts already pins every rule for the shared function, including the two extensions the .zip suffix regression cost us.
The same walk was written twice, once in pathPolicy.ts for managed paths and once in workers/extraction.ts for the eight worker call sites. Both came from the fork seed, so neither was a chosen split, and a guard held in two copies is a guard that can be tightened on one side only. It lives in validation.ts now, which both sides already import and which stays Electron-free. The refusal takes the caller's wording, so the path policy keeps its "for managed paths" text, and it is a TypeError on both sides now; nothing anywhere branches on the class.
The Electron and the Node transport each carried their own Content-Length pre-check, status check and streamed byte-cap loop. Two transports is deliberate (#76); two reading loops was not, and they had drifted: only the Electron copy threw the typed BoundedResponseError that #442 added for the release-notes classifier. collectBounded now reads for both. The refusal messages are byte for byte what they were, which netHandlers.fetchModDbListingArchive and the login classifier both match on, and a non-2xx from either transport is now the typed error. loginFailureReason reads the status off that error instead of parsing the digits back out of the message with an anchored regex. Same tokens for every case the transport can produce, including a response with no status line, which still lands on http-other. The module stays Electron-free: the property is read structurally, the way codeOf already reads code. The tests that stood in for the transport build the typed shape now.
loginFailureReason reads statusCode now instead of parsing the message, so the message match alone no longer pins the login chain end to end: a transport that answered a plain Error with the same text would send a 503 outage to unclassified and put the generic "Login failed" on screen where the player should read that the service is down. The 404 case now asserts the property as well as the message.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Three guards in the host layer were written twice each. None of them is relaxed here: every check stays exactly where it ran before, including the defence in depth inside the workers. Only the second copy goes, so none of the three can be tightened on one side and left stale on the other.
What changes
assertSafeFileName(item 3).workers/download.tscarried its own copy of a rulevalidation.tsalready states, on an import line that already reached that module. The shared function takes the caller's wording, soassertSafeFileName(fileName, "download file name")throws the same "Invalid download file name" the worker always threw. The guard stays inside the worker, behind the handler's own check inpathsHandlers.ts. Minus 12 lines indownload.ts.The symlink ancestor walk (item 2). The same walk lived in
pathPolicy.tsfor managed paths and inworkers/extraction.tsfor the eight worker call sites, both inherited from the fork seed rather than chosen. It is one function invalidation.tsnow, which both sides already import and which stays Electron-free.dirnameagainstresolve(current, "..")andbreakagainstreturnat the filesystem root were the only differences, and the root always exists, so that branch was unreachable either way. The refusal takes the caller's wording, so the path policy keeps its "for managed paths" text. Minus 19 lines net across the three files;innoExtraction.tsnow takes the walk fromvalidation.tsinstead of re-exporting it throughextraction.ts.The bounded read (item 1).
network.tswrote the Content-Length pre-check, the status check and the streamed byte-cap loop once per transport. Two transports is deliberate (#76); two reading loops was not, and they had drifted: only the Electron copy threwBoundedResponseErrorwith thestatusCodeandheadersthat #442 added forreleaseNotesFailureReason.collectBoundednow reads for both, with the transport passing its own cancel (request.abortfor Electron,request.destroyfor Node). Minus 27 lines of code innetwork.ts; the helper's own doc comment puts the file at minus 7 overall.The status parse (item 1, second half).
loginFailureReason.tsparsed the status digits back out of the message text with an anchored regex, behind a comment arguing why that was safe. Now that both transports throw the typed error, it readsstatusCodeas a number, the same waycodeOfreadscode. The regex and its justification go; the module stays Electron-free because the property is read structurally rather than withinstanceof.What stays
Nothing was skipped. Every finding in the issue applied as written.
The guards themselves are untouched: the IPC validation at the trust boundary, the path policy, the mutation-tested guards, the defence in depth inside the workers, the hexagonal split. The two-transport split in
network.tsstays two transports.tests/security-boundaries.test.ts,tests/log-provenance.test.ts,tests/text-contrast.test.tsandtests/i18n/i18n-parity.test.tsare unchanged and still pass.The line savings are smaller than the issue estimated, 34 net rather than 65, because the two shared functions carry the doc comments the copies did not have to duplicate. The code they replace is gone as estimated.
Behaviour
Nothing changes for a caller. The refusal texts are byte for byte what they were, which matters in two places the issue named:
netHandlers.fetchModDbListingArchivematches/redirect/on the message to recognise ModDB's counted 302, andloginFailureReasonmaps three literal network messages by exact string. Both still match.Two error classes change, and no caller anywhere branches on either. The symlink walk is a
TypeErroron both sides now (it was a plainErrorin the workers);grep -rn "instanceof TypeError" src testsfinds nothing, and all four pinned tests match by substring on/Symbolic links are not allowed/. A non-2xx from the Node transport is aBoundedResponseErrornow (it was a plainError); its message is unchanged,network.test.tsmatches the Node path by message substring, andBoundedResponseError extends Error.Callers checked by grep before each change:
assertSafeFileName(three call sites plus the two e2e scripts, all already on the shared one),assertNoSymlinkComponents(nine call sites acrossextraction.ts,innoExtraction.tsandpathPolicy.ts),runDownload(downloadWorker.tsandoptimumManifest.ts),requestBoundedBufferandrequestBoundedTextViaNode.Tests changed, all of them fixtures standing in for what the transport throws, none of them relaxed:
tests/ipc/download.test.ts: thedescribe("assertSafeFileName")block is deleted, 10 cases. It re-tested the rulestests/ipc-validation.test.tsalready pins for the shared function, including the two extensions that pin the.zipsuffix regression (vs_client_linux-x64_1.22.6.tar.gzandvs_install_win-x64_1.22.6.exe). TherunDownloadcase asserting/Invalid download file name/stays and is what pins the wording passing through.tests/ipc/loginFailureReason.test.tsandtests/ipc/accountHandlers.test.ts: the hand-builtnew Error("Network request failed with status 503")fixtures now carry the status as a number, which is what the transport actually throws. Same expected tokens for every case, including a response with no status line, which still lands onhttp-other. The "matches the status message whole" case is kept and renamed: with nothing parsed out of the message, an error that only reads like a status refusal still falls through to the class name and still leaks no part of its message.Gate, against
origin/devin the same worktree:git merge-treeagainst all six open PR branches reports no conflict. The only file overlap ispathPolicy.tswithfix/issue-465-worlds, which touches the grant lists and not the lines removed here.Check
The login's HTTP classification had no end-to-end pin. Moving the chain off the message text and onto
statusCodeleft the transport half unpinned:tests/ipc/network.test.tsmatched only/404/, which a plainErrorsatisfies, and the two fixture files build the refusal by hand, so they assert the classifier against their own assumption rather than against the transport. Wrapping the Node transport's settle to replace the typed error with a plain one of the same text kept all 242 files green, while the real transport driven through the real classifier answeredunclassifiedfor a 503 instead ofhttp-unavailable, whichloginFailureFamilymaps tounknownandaccountHandlers.ts:155turns into the generic "Login failed", the #481 regression.The 404 case now asserts the property as well as the message:
tests/ipc/network.test.ts:113-122. It keepsassert.match(error.message, /404/)and addsassert.equal(error.statusCode, 404). Against the mutation above it fails withundefined !== 404; against the branch as pushed it passes. No source file changed, so behaviour is untouched and no test was deleted.Gate after the fix: typecheck pass,
lint:ci0 errors and the same 14 warnings,format:checkpass, 242 files, 4451 passed, 2 skipped, statements 94.62%, branches 91.06%, functions 95.32%, lines 96.28%. Identical to the table above, as expected from a test-only change.Closes #486. Part of #492.