Supply-chain hardening: sign the setup lists/binaries, verify Updater.cs downloads, lockfile for release tooling #1679
Labels
No labels
accessibility
autorelease: pending
autorelease: tagged
bug
dependencies
documentation
duplicate
enhancement
github_actions
good first issue
help wanted
invalid
question
testing wanted
wontfix
No milestone
No assignees
1 participant
Due date
No due date set.
Dependencies
No dependencies set
Reference
MagicBeanDev/Hookshot#1679
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
The idea
Harden the update / release supply chain. I did a static review of the installer, the updaters, the CI workflows and the host code that touches the network or starts processes (commit
1a429f6). The code is careful and I found nothing malicious, but every player's PC currently trusts whatever is attached to the latest GitHub release, with nothing that ties a download to the maintainers. This would also help with the antivirus reports in #1003.Findings (medium)
hookshot-setup.txt/hookshot-linux.txt, which is downloaded from the same release as the parts. That protects against a broken download, not against a tampered release: anyone who gets the account orRELEASE_TOKENcan push code that runs on every PC that presses Update.builder/setup/setup.c:574-663,builder/linux/linux_update.py:440HookshotBuilder.exe.Updater.csruns downloads with no hash check at all.RunLatestSetupandUpdateFolderdownloadHookshotBuilder.exeand start it, checking only the size, and only when there is a Content-Length. TheHookshotBuilder.zipfallback is unzipped and its.exestarted the same way.builder/HookshotBuilder/Updater.cs:97-104,:172-180,:186-214windows-builder.yml:197keeps the CI release job off. Users cannot check that the published.exematches the source. Suggestion: publish from CI and addactions/attest-build-provenance, sogh attestation verifyworks.release.ymlrunsnpm installwithout a lockfile while holding a write token. The direct devDependencies are pinned, but the transitive ones are not, andsemantic-releaseruns withRELEASE_TOKEN(contents: write).--ignore-scriptsonly covers install scripts. Suggestion: commitpackage-lock.jsonand usenpm ci..github/workflows/release.yml:41,:58-63Findings (low)
setup.c:145(mem_sink):m->p = realloc(m->p, ...)loses the old pointer on failure, and there is no size limit on the manifest.setup.c:639-640: the BCrypt return values are not checked, sofs.his used uninitialised if they fail.setup.c:316-318and:756-760: the tag read fromcurrent.txtbuilds paths without going throughlist_name_ok. It is a local file, so the risk is low; it is about consistency.linux_update.py:417reads each part fully into memory (up to the 4 GiB limit). urllib also follows https→http redirects; the hash check covers that, but a redirect handler that refuses them would make it explicit.HOOKSHOT_SETUP_LIST,HOOKSHOT_SETUP_BASEandHOOKSHOT_LINUX_LISTcan point the setup at another manifest, including over http. This only matters to someone who already controls the environment. Requiring https, or a dev build, would close it.Anything else
Already done well: actions pinned by SHA,
pull_request(not_target), minimalpermissions,persist-credentials: false, Dependabot cooldown, name validation in the setup lists, zip-slip and symlink protection in the Linux updater,SetDefaultDllDirectories, andsafe_tagbefore tags reach a command line. I found no committed secrets. There is noSECURITY.md; a private reporting channel (GitHub private vulnerability reporting) would help with future reports.Follow-up: I went over the parts I had not read the first time, at
b2ed731(mod loader,nn_fs.c,hookshot_env.txt, DLL loading,nup.py, the build scripts and the Python builder inbuilder/py). Again static only, nothing was run. Five more items, none serious:scripts/linux/deps.shchecks a SHA-256 for almost everything, butfetchis called without one for dav1d (:135) and for the Vulkan-Headers, SPIRV-Headers and VulkanMemoryAllocator tag archives (:171). dav1d is compiled and linked into the game executable the AppImage ships, and a tag archive can change at its source. Suggestion: pin a SHA-256 for these four like the rest.builder/py/hookshot_builder/setup_parts.py:147-152takestag,dest,frm,toandshaas they come, whilesetup.c:592-597passes them throughlist_name_ok. With atagthat is an absolute path or has..,:215-216(rmtree(work),rmtree(final)) delete that folder before any part's checksum is looked at.:179also matches the checksum as a substring ofparts.txt. It needs a tampered list, so it is the same trust root as item 1; this is about the port following the same rules as the C setup.pip installwithout hashes (low).deps.sh:102pins versions (requirements.txt) but does not use--require-hashes; that Python goes inside the AppImage.scripts/release_upload.sh:21uploads with--clobber, so a published file can be swapped with no trace. GitHub's immutable releases would close this, together with the attestation in item 3.pythonon the PATH (low). When the package'stools\python\python.exeis missing,Runner.cs:19starts whateverpythonWindows finds first. Stopping with the "restore it from quarantine" message thatCheckalready has would be safer.Also done well, from this pass: the mod loader does not follow folder links and has a depth limit,
guest_path_okinnn_fs.c, thehookshot_env.txtfilter (env_line_allowed), system DLLs loaded withLOAD_LIBRARY_SEARCH_SYSTEM32, andextraction_pathplus the size checks innup.py. The symlink checks added tolinux_update.pysince my first pass are good too.Not covered:
third_party/and the shader recompiler undergpu/vk/eden.