Fix accuracy defects from the review follow-up batch - #75
Merged
Conversation
The lower-severity half of the same adversarial review as #74. None of these crashes or claims a false success, but each reports something that is not quite true. Login item names are now read one per line instead of relying on osascript's ", " list serialization. That default is ambiguous: an item named "Backup, Inc." serialized exactly like two items "Backup" and "Inc.", so it could never be matched and was permanently unremovable through this tool; and a surviving "Foo, Bar" kept producing a "Foo" fragment, so removing a genuinely separate "Foo" re-matched on the post-delete recheck and reported failed after a successful removal. lsof escapes every byte it deems unprintable, not just the space that motivated the earlier fix -- a tab arrives as \x09 and a non-ASCII name as a run of \xNN. Both the awk and the JXA decoders now handle whole runs, so a multi-byte UTF-8 name reassembles instead of leaking literal escapes into the UI and into anything matching on the name; control bytes become a space because they would break the TSV. Pinned Homebrew packages were dropped outright by the devtoolUpdates regex, which anchored the latest version to end-of-line while brew appends " [pinned at X]". The row vanished while the collector's line count still included it, so the displayed total disagreed with the list and a pinned package's update was invisible. Rows now survive and say they are pinned. SpaceGoalSelection's documented "same set regardless of scan order" promise did not hold when size and label both tie -- label falls back to kind, so two same-size rows of one kind tied completely and the result depended on emit order. Path breaks the remaining tie. The goal comparison also gained a tolerance: 2.4 + 0.3 + 0.3 is exactly 3.0 in decimal but accumulates to 2.9999999999999996, so the greedy walked past the set that met the goal, took an extra item, and then called the result short. A non-finite sizeGB is clamped at the parse boundary, where it used to poison every sum and trip a range precondition. Login item removal and the observation window now respect isBusy in both directions: removal could previously start a second concurrent ScanPipeline over the same output files during a scan (untracked by scanTask, so 검사 취소 could not stop it), and a scan started under an observation landed in that observation's own results. Mothball used scan() rather than scanReport(), discarding inspection failures -- MothballCore's own API comment warns that this makes "found but could not inspect" indistinguishable from "nothing found". The page now says which it is.
Same class of miss as the network_watch skipif: the probe runs the real regex through /usr/bin/osascript, which Linux runners lack.
4 tasks
heznpc
added a commit
that referenced
this pull request
Aug 13, 2026
Six items the #74/#75 reviewers classified low/informational -- nothing here is reachable in a shipped build today; each is a seam a future change could silently reopen. privacy.sh's PCH_TCC_DB_PATH test seam now opens only under PCH_TEST_MODE=1, matching PCH_TEST_BREW_BIN and PCH_TEST_OSASCRIPT_BIN. It was the one injection point in the new modules without the gate, and ungated it doubles as an arbitrary-sqlite-read redirect for any future caller that forwards the variable. login_items.sh now refuses a manifest whose protocol version differs from its own -- fields this version reads could mean something else under another one. cleanup.sh cross-checks its whole manifest; this is the minimum equivalent. The bundled-runtime completeness guard now also covers non-.sh runtime dependencies (rules/*.json, data/*.json, report_i18n, *.jxa.js, and shipped .py), with an explicit build/reference-only exclusion list mirroring the .sh guard's. Proven the same way: removing rules/network.json from RUNTIME_FILES trips it. RuntimeWorkspace's bundled branch now demands the development opt-in whenever the injected resourceURL is not the running app's own signed bundle. That combination skips the code-signature payload comparison and is constructible only through parameter injection; without the gate, a future refactor decoupling the parameters would run unsigned code with no test noticing. The revalidation tests declare the opt-in they were implicitly relying on. Raw pinned-file values now refuse on key collision instead of silently clobbering whichever module payload merged first -- the exact asymmetry behind the approval_token/approval_token_module near-miss fixed in #67. Observation timeout slack grows from 20s to 60s past the window (the snapshots run exactly when the machine is busiest), and a timeout now says it timed out instead of "status 124". Co-authored-by: Heznpc <heznpc@users.noreply.github.com>
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.
The lower-severity half of the same adversarial review as #74. None of these crashes or claims a false success, but each reports something that is not quite true. All were reproduced before being fixed.
Login item names: comma ambiguity
osascriptserializes a list as", "-joined text, so an item namedBackup, Inc.is byte-identical to two itemsBackupandInc.. Two consequences, both confirmed:FooandFoo, Barpresent, removingFoosucceeded but the post-delete recheck still matched theFoofragment of the survivor → reportedfailedafter a successful removal.Names are now read one per line (
text item delimiters→ linefeed), which cannot collide since names carrying tab/newline are rejected upstream.lsof escapes beyond
\x20lsofescapes every byte it deems unprintable. #73 decoded only\x20, so a tab arrived as a literalev\x09iland a non-ASCII name as a run of\xNN. Both the awk (network_watch.sh) and JXA (scanner_helper.jxa.js) decoders now handle whole runs — verified that\xed\x95\x9c\xea\xb8\x80reassembles to한글in both engines rather than becoming per-byte mojibake. Control bytes become a space, since they would break the TSV the protocol rides on.Pinned Homebrew packages were silently dropped
brewappends[pinned at X]for a pinned formula or cask; the regex anchored the latest version to end-of-line, so those lines returnednulland the row disappeared. The collector's ownwc -lstill counted them, so the displayed "N개 업데이트" total disagreed with the rows shown, and a pinned package's available update was invisible. Rows now survive and carrypinned, surfaced in the UI. A pytest guard drives the real regex through JavaScriptCore against all five real brew output shapes.SpaceGoalSelection
labelfalls back tokind, so two same-size rows of one kind tied completely and the result depended on emit order. Path breaks the remaining tie.2.4 + 0.3 + 0.3is exactly 3.0 in decimal but accumulates to2.9999999999999996, so the greedy walked past the set that met the goal, took an extra item, and then labelled the result short of the goal it had reached. (I searched for a genuine case rather than trusting the reviewer's example, which did not actually reproduce.)ClosedRangeprecondition.Concurrency guards
Login item removal and the observation window now respect
isBusyin both directions. Removal could previously start a second concurrentScanPipelineover the samescan_result.json/report files during a scan — and since that run isn't held inscanTask, 검사 취소 could not stop it. Conversely a scan started under an observation landed in that observation's own results (the scanner'sdu/lsofas top "real CPU use", VirusTotal lookups as "new connections").Mothball reported inspection failures as "nothing to archive"
It used
scan()instead ofscanReport(), discarding failures — and MothballCore's own API comment warns that this exact thing makes "found but could not inspect" indistinguishable from "nothing found". The page now distinguishes them.Test plan
pytest tests/— 369 passedswift test— 168 passedrelease_smoke.py --check-only— okpinned, and no escaped process names remain in the network section.Follows #74.
🤖 Generated with Claude Code