fix(update): install @latest instead of a range-bound update -g - #170
Merged
Conversation
`threatcrush update` ran `pnpm update -g` / `npm update -g`, which only moves a package within the semver range recorded at first install. A box that installed 0.2.0 is pinned to `^0.2.0` and can never cross a 0.x minor, so the update walked 0.2.0 -> 0.2.2 while pnpm printed "(0.11.5 is available)" in the same output, and reported success. Every upgrade path is now a plain install of an explicit `@latest`, which ignores both the recorded range and pnpm's global lockfile pin. Also: - Update (and remove) every package manager that holds a global copy, not just the first one detected. A second install under another manager kept shadowing the new binary on PATH. - After the install, compare the registry's `latest` against what PATH actually resolves to and print the offending path plus `hash -r` when they differ, instead of a bare "updated successfully". - install.sh installs `pkg@latest` too - a bare name let pnpm's global lockfile hand back the copy already on disk. - install.sh reads the installed version from pnpm's global root as well as npm's; after a pnpm install `npm root -g` was empty, which silently disabled the shadowed-install warning on the machines that needed it. The package-manager command builders move to apps/cli/src/upgrade.ts with 19 unit tests, including a regression test that no upgrade command contains `update -g`. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QrdMghdnykcfDVSr7tdKDG
ThreatCrush Security Scan12 finding(s) HIGH/CRITICAL: 1 | MEDIUM: 6 | LOW: 5
Snippets are redacted; ThreatCrush never prints matched credential material. |
`pkgName.replace("/", "%2F")` replaces only the first occurrence
(js/incomplete-sanitization, high). The registry serves a scoped name
unescaped — `https://registry.npmjs.org/@scope/name/latest` returns 200 —
so the encoding was never needed. Verified the lookup still resolves
0.11.5 against the live registry.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QrdMghdnykcfDVSr7tdKDG
…g 21 The GitHub webhook tests signed with a 64-hex `const SECRET`, which gitleaks reads as a leaked webhook secret. It was never issued by anything — the same test stubs it into GITHUB_APP_WEBHOOK_SECRET a line later, so both halves of the signature check are the fixture — but the literal reached master directly in 20cc664, and because the job scans all refs with fetch-depth 0 it has failed every PR opened since. Both halves of the house remedy: - The test generates the secret per run with randomBytes, so no literal survives in the tree for any scanner to find. - A fingerprint for 20cc664 in .gitleaksignore, since history keeps its copy. Placed at the head of the file: two branches both appending at the foot conflict on the second merge. Verified with the exact CI command (gitleaks 8.21.2, `detect --source . --redact --verbose --no-banner --exit-code 1`) scoped to remote refs: 456 commits, no leaks, exit 0. Unscoped, a local scan also reads refs/stash, which CI never checks out — that accounts for the 474 vs 455 commit counts and one finding that exists only in a stash entry. Webhook tests: 9/9 pass. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QrdMghdnykcfDVSr7tdKDG
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 bug
threatcrush updateon a machine still running 0.2.0 upgraded it to 0.2.2 and printed✓ ThreatCrush updated successfully!— while pnpm, in the same output, said(0.11.5 is available).Why
The update shelled out to
pnpm update -g/npm update -g. Both only move a package within the semver range recorded when it was first installed. A 0.2.0 install is recorded as^0.2.0, and^0.xcannot cross a minor — so that box is mathematically incapable of reaching 0.11.x viaupdate -g, no matter how many times it is run. It will report success every time.The fix
Every upgrade path is now a plain install of an explicit
@latest, which ignores both the recorded range and pnpm's global lockfile pin:npm update -g pkgnpm i -g pkg@latestpnpm update -g pkgpnpm add -g pkg@latestyarn global upgrade pkgyarn global add pkg@latestbun update -g pkgbun add -g pkg@latestAlongside that, three things that let the same symptom survive a correct install:
updateandremovenow run against each package manager that reports a global copy. A second install under another manager was left sitting on PATH, so the user reranupdate, watched it succeed, and still got the old binary.latestagainst whatthreatcrush --versionon PATH actually reports; on a mismatch it names both versions, the shadowing path, andhash -r— instead of an unconditional "✓ updated successfully".install.shinstalls@latesttoo. A bare package name let pnpm's global lockfile (and npm's satisfying tree) hand back the copy already on disk. Itsinstalled_versionalso now reads pnpm's global root as well as npm's — after a pnpm installnpm root -gwas empty, which silently disabled the shadowed-install warning on exactly the machines that needed it.Structure
The package-manager command builders move out of
apps/cli/src/index.ts(which runs the program on import and so is untestable) intoapps/cli/src/upgrade.ts.Testing
apps/cli/src/__tests__/upgrade.test.ts, including a regression test asserting no upgrade command for any package manager containsupdate -gorglobal upgrade.install.shassertions covering the@latestspec and the two-root version read.tsc --noEmitclean,sh -n install.shclean.npm i -g @profullstack/threatcrush@latest, reads registry latest0.11.5, and stays quiet when PATH is already current.Note for anyone currently stuck
This fix ships in a new release, so a box running the old
updatestill cannot upgrade itself out of it. That machine needs a one-time manual escape:After that,
threatcrush updateworks normally.🤖 Generated with Claude Code
https://claude.ai/code/session_01QrdMghdnykcfDVSr7tdKDG