Conversation
…ksum parsing - Switch getLatestVersion to GitHub REST API JSON response instead of redirect parsing - Replace identity maps in getBinaryAssetName with Set-based validation - Parse checksum line once and reuse; compute Buffer.from(binary) once
ky sends the runtime's default User-Agent automatically; GitHub only rejects requests with an empty header, not a missing one.
Single-use method with no reuse benefit; inlining reduces indirection.
Fetch checksums before the binary to validate platform support dynamically instead of hardcoding allowed platforms/archs.
Both npm and binary paths now run automatically. Removes the manual_update_required status — all updates return status: updated.
Ship install.sh as a GitHub release asset and fetch it from the release URL instead of raw.githubusercontent.com, so the update command uses the same versioned script that users install with.
|
|
||
| try { | ||
| const script = await ky.get(scriptUrl).text(); | ||
| await writeFile(scriptPath, script); |
Check failure
Code scanning / CodeQL
Insecure temporary file High
Show autofix suggestion
Hide autofix suggestion
Copilot Autofix
AI 2 days ago
In general, to fix insecure temporary file creation you should avoid constructing temp paths manually with os.tmpdir() and a static or predictable filename. Instead, use a library or API that creates the file atomically with secure permissions and a unique path, and then use that path. For Node.js, the tmp package is a well-known option that provides file() / fileSync() helpers that do this securely.
For this file, the best minimal fix is:
- Import the
tmplibrary. - In
updateBinary, replaceconst scriptPath = join(tmpdir(), "jup-install.sh");with creation of a secure temp file viatmp. - Use the returned
name(path) to write and execute the script. - Keep the existing
try/finallycleanup, but lettmpalso track the file;rmremains harmless redundancy and preserves current behavior.
Concretely, in src/commands/UpdateCommand.ts:
- Add
import tmp from "tmp";near the other imports. - Replace the
scriptPathline with:so we still have aconst tmpFile = tmp.fileSync({ prefix: "jup-install-", postfix: ".sh" }); const scriptPath = tmpFile.name;
scriptPathstring as before, but now it is a securely created, unique file.
No other logic in updateBinary needs to change; writeFile, execSync, and the rm in the finally block can all continue to use scriptPath.
| @@ -5,6 +5,7 @@ | ||
|
|
||
| import chalk from "chalk"; | ||
| import ky from "ky"; | ||
| import tmp from "tmp"; | ||
| import type { Command } from "commander"; | ||
|
|
||
| import { version as currentVersion } from "../../package.json"; | ||
| @@ -153,7 +154,8 @@ | ||
| private static async updateBinary(): Promise<void> { | ||
| const scriptUrl = | ||
| "https://github.com/jup-ag/cli/releases/latest/download/install.sh"; | ||
| const scriptPath = join(tmpdir(), "jup-install.sh"); | ||
| const tmpFile = tmp.fileSync({ prefix: "jup-install-", postfix: ".sh" }); | ||
| const scriptPath = tmpFile.name; | ||
|
|
||
| try { | ||
| const script = await ky.get(scriptUrl).text(); |
| @@ -39,7 +39,8 @@ | ||
| "cli-table3": "^0.6.5", | ||
| "commander": "^14.0.3", | ||
| "ky": "^1.14.3", | ||
| "micro-key-producer": "^0.8.5" | ||
| "micro-key-producer": "^0.8.5", | ||
| "tmp": "^0.2.5" | ||
| }, | ||
| "devDependencies": { | ||
| "@types/bun": "latest", |
| Package | Version | Security advisories |
| tmp (npm) | 0.2.5 | None |
There was a problem hiding this comment.
Addressed in a follow-up. The updater now creates a unique temp directory with mkdtemp(...), writes install.sh inside that directory with mode 0o700, and removes the directory in finally, so the predictable temp-file path is gone.
Avoid predictable temp file path by creating a unique directory with mkdtemp, preventing symlink attacks flagged by GitHub code scanning.
The script is already in the repo — import it as text so Bun bundles it into the compiled binary. Removes the runtime network fetch.
Revert the text import approach — fetching from the release URL is cleaner than importing a shell script as a TS text module.
Remove install method detection, package manager commands, and binary update logic. The update command now fetches and runs install.sh which handles volta/npm/binary fallback internally.
macOS 15+ ships a native /sbin/sha256sum, so the shasum -a 256 fallback for older versions is unnecessary.
Summary
Adds
jup update— a self-update command that fetches and runsinstall.shfrom the matching release tag, which handles volta/npm/binary detection and installation internally.Update command
jup update— fetchesinstall.shfrom the release tag URL, writes it to a secure temp directory (mkdtemp), and executes itjup update --check— reports whether an update is available without installing/repos/jup-ag/cli/releases/latest)install.sh hardening
1. Secure temp file creation
The original script wrote to a predictable path (
/tmp/jup), which is vulnerable to symlink attacks — an attacker could place a symlink at/tmp/juppointing to another file, and the script would overwrite it. The fix usesmktemp -dfor a unique temp directory andtrapfor cleanup:2. Checksum grep error handling
Under
set -e, ifgrepfinds no matching checksum line it returns exit code 1, killing the script with no useful message. The fix suppresses the exit and provides an actionable error: