feat: DownloadTriage — inspect installers before you double-click them - #3
Merged
Merged
Conversation
ShellGuard guards the shell prompt. That is ONE execution path. A double-clicked .command, a .pkg preinstall running as root, an .app inside a DMG, a .scpt in Script Editor — none of them touch a zsh prompt, so none could ever be caught at accept-line. The kit had no coverage of the deliver stage at all. THE .pkg CASE IS THE POINT A package's preinstall/postinstall runs as ROOT, and the user is conditioned to type an admin password into Installer.app because that is what every legitimate install looks like. GuestMode's "a phished password can't escalate" framing does not cover it: the escalation is the installer's documented behaviour, not an exploit. So the tool expands the package and runs its install scripts through the SAME grammar that guards the shell prompt — if it would be blocked at your terminal, it is flagged in the installer, and the script is shown to you. pkgutil --expand-full unpacks; it does not execute. tests/test-downloadtriage.sh asserts that with a fixture whose postinstall would create a marker file: the marker never appears. FALSE POSITIVES FOUND BY RUNNING IT FOR REAL Running it against a genuine 237-item Downloads folder caught a bad one in my own tool: it reported the OFFICIAL SIGNAL INSTALLER as "REJECTED — unsigned". `spctl -a` with no type argument assumes an executable, so it returns "no usable signature" for legitimate .dmg and .zip files. A security tool that tells you Signal is unsigned is worse than no tool. Gatekeeper verdicts are now rendered only where Gatekeeper actually judges: .app, .pkg (with -t install), and Mach-O binaries. For a .dmg the tool says plainly that the signature lives on the app inside and was not checked, and offers --mount. Archives and shell scripts are never assessed — scripts are READ through the grammar instead. Real-folder flag count: 15 -> 3. SHARED GRAMMAR BUG `local` re-declared inside a loop makes zsh echo "name=value" to stdout. clickfix_check did this in three places, so `d=socat` leaked into the output of anything sourcing the grammar. Invisible in ShellGuard (writes to /dev/tty) and in the corpus runner (reads only the verdict), but it corrupted DownloadTriage's report. All loop-scoped locals hoisted to a single declaration. DMGs are not mounted without --mount; mounting is itself a delivery step in current campaigns. Kit goes to seven tools. README, install.sh menu and the kill-chain table updated. New macOS CI job. Honest prior art: Objective-See's WhatsYourSign is the better everyday "who signed this?" tool and is linked; the additive sliver is connecting .pkg install scripts to the ClickFix grammar. Tests: 12/12 downloadtriage · 78/78 corpus · 10/10 baseline · 72 exposurescan · 4/4 pty integration. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AcTJUv94F34MdtGZCGyyur
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.
ShellGuard guards the shell prompt. That is one execution path, and macOS has many. A double-clicked
.command, a.pkgpreinstall running as root, an.appinside a DMG, a.scptin Script Editor — none of them touch a zsh prompt, so none could ever be caught ataccept-line. The kit had no coverage of the deliver stage at all.Human
What it looks like on a hostile installer:
Agent
Why
.pkgis the case that matters. A package'spreinstall/postinstallruns as root, and the user is conditioned to type an admin password into Installer.app — that is what every legitimate install looks like. GuestMode's "a phished password can't escalate" framing does not cover this, because the escalation is the installer's documented behaviour, not an exploit. So the tool expands the package and runs its install scripts through the same grammar that guards the shell prompt. If it would be blocked at your terminal, it is flagged in the installer too, and the script is shown to you.pkgutil --expand-fullunpacks; it never executes. There is a test asserting exactly that, with a fixture whosepostinstallwould create a marker file — the marker never appears.A false positive I only found by running it for real. Against a genuine 237-item Downloads folder, an early build reported the official Signal installer as "REJECTED — unsigned."
spctl -awith no type argument assumes an executable, so it returns "no usable signature" for legitimate.dmgand.zipfiles. A security tool that tells you Signal is unsigned is worse than no tool.Gatekeeper verdicts are now rendered only where Gatekeeper actually judges:
.app,.pkg(with-t install), and Mach-O binaries. For a.dmgit says plainly that the signature lives on the app inside and was not checked, and offers--mount. Archives are never assessed; shell scripts are read through the grammar instead. Real-folder flag count went 15 → 3.A bug in the shared grammar, found the same way.
localre-declared inside a loop makes zsh echoname=valueto stdout.clickfix_checkdid this in three places, sod=socatleaked into the output of anything sourcing the grammar. Invisible in ShellGuard (writes to/dev/tty) and in the corpus runner (reads only the verdict) — it took a consumer that prints to stdout to surface it. All loop-scoped locals hoisted.Scope decision. Ships as a standalone tool rather than an ExposureScan flag.
--tccwent in as a flag because it answers ExposureScan's question ("what would a stealer take from me"). This answers a different one ("is this file hostile") with a different invariant. Kit goes to seven tools; README, installer menu and kill-chain table updated.Honest prior art. Objective-See's WhatsYourSign is the better everyday "who signed this?" tool and is linked in the README.
spctl/codesign/xattrship with macOS and are what this shells out to. The additive sliver is narrow and stated as such: connecting.pkginstall scripts to the ClickFix grammar.Deliberately not done: DMGs are not mounted without
--mount, because mounting is itself a delivery step in current campaigns.Tests:
12/12downloadtriage ·78/78corpus ·10/10baseline ·72ExposureScan ·4/4pty.🤖 Generated with Claude Code
https://claude.ai/code/session_01AcTJUv94F34MdtGZCGyyur