mirror of
https://github.com/QwenLM/qwen-code.git
synced 2026-07-09 17:19:02 +00:00
* feat(cli): add standalone auto-update support Standalone installs (via install-qwen-standalone.sh) previously fell through to npm global detection, causing auto-update to run `npm install -g` which doesn't update the standalone binary. Now the CLI detects standalone installs by looking for manifest.json in ancestor directories, then performs a self-update: download archive from OSS/GitHub, verify SHA256, extract, and atomically replace the installation directory. Includes: input validation (semver + target allowlist), lockfile for concurrency, Windows deferred replacement via helper script, and tar path traversal protection. Closes #4627 * feat(cli): harden standalone update — smoke test, rollback, signature - Smoke test: spawn new binary with --version after extraction, before replacing the installation. Rejects broken/wrong-arch packages. - Rollback: keep .old directory after updates. Export rollbackStandaloneUpdate() for future /doctor rollback integration. - Signature: Ed25519 verification of SHA256SUMS via node:crypto. Embeds public key; CI signs with scripts/sign-release.sh. Graceful degradation when .sig is not yet published (transition period). Set QWEN_REQUIRE_SIGNATURE=1 to enforce strict mode. * fix(cli): address CI + review findings on standalone update - Fix TS4111: use bracket notation for process.env index signature - Fix CodeQL: validate paths for cmd.exe metacharacters before bat script - Fix Windows lock race: keep lock alive during deferred swap, release in bat script after move completes * fix(cli): use event payload in update UI + add rollback tests - handleAutoUpdate: handleUpdateFailed/handleUpdateSuccess now read the message from the event payload instead of hardcoding text. Windows deferred users now see the correct "applied after exit" message. - Add standalone-update.test.ts with 4 tests covering rollback scenarios. * fix(cli): address review findings — 5 bugs in standalone-update 1. SHA256SUMS: handle GNU coreutils binary-mode `*` prefix in filename 2. acquireLock: treat NaN (empty/corrupt lock file) as stale, not held 3. tryFetch: consume response body on non-OK status to release sockets 4. Windows bat: add 30s timeout to PID wait loop preventing infinite hang 5. Finally block: clean orphaned .new directory on Windows error path * fix(cli): update handleAutoUpdate tests for payload-driven messages Tests previously asserted hardcoded handler text. After making handlers read messages from event payload, the assertions need to reflect the emitted payload values instead. * feat(cli): auto-migrate npm users to standalone when prefix is not writable When a global npm install requires sudo (prefix not writable), the auto-update now falls back to the standalone path instead of spawning a doomed `npm install -g` that always EACCES. - installationInfo.ts: detect writable prefix with fs.accessSync; return isStandalone=true with fallback dir when not writable - standalone-update.ts: support first-time migration (no existing manifest), detect platform target, create bin wrapper, refuse to overwrite unmanaged directories Closes #4643 * fix(cli): remove unused _mockedAccessSync (TS6133) * fix(cli): address wenshao review — Windows deferred, PATH, rollback, headers - Fix Windows deferred update: move pendingDir cleanup to catch block so it only runs on error, not on successful deferred path - Add ensurePathInShellRc: auto-append ~/.local/bin to shell rc on npm→standalone migration so the wrapper is actually discoverable - Wire rollbackStandaloneUpdate into /doctor rollback subcommand - Add license headers to all 4 new source files - Fix spawnAndCapture double-settle with settled flag - Add tests for ensureBinWrapper and ensurePathInShellRc - Clarify signature verification comments (test key, not production) * fix(cli): add i18n translations for /doctor rollback command The mustTranslateKeys test requires zh-CN and zh-TW translations for all built-in command descriptions. Add translations for the new rollback subcommand and its user-facing messages. * fix(cli): skip Unix-only wrapper tests on Windows The ensureBinWrapper Unix-wrapper test asserts on POSIX file mode bits (mode & 0o111) which Windows NTFS does not implement. Skip Unix-specific tests when running on Windows runners. * fix(cli): address CodeQL + test coverage on standalone-update - Add assertPathWithin() and resolve install paths before spawning the smoke-test process. Closes the CodeQL "shell command built from environment values" finding by guaranteeing nodeBin/cliBin stay inside the freshly extracted install directory. - Add performStandaloneUpdate test coverage for the four pre-flight failure cases (bad version, missing manifest, unknown target, concurrent lock) so the core update path is no longer untested. * fix(cli): address wenshao review round 2 — bat error handling, rollback safety, shell injection, lock race, test coverage Five issues raised in PR #4629 review (2026-06-01): 1. Windows bat: add errorlevel checks after each move, rollback on second-move failure, timestamped log to qwen-update.log 2. Unix rollback: inner try/catch on recovery rename; compound error with manual mv instructions if both renames fail 3. Shell injection: assertSafeForShellEmbed() validates paths before embedding in /bin/sh wrappers and shell rc (platform-aware: allows backslash on Windows) 4. Lock-file race: bat writes .swap sentinel before rename; acquireLock refuses to reclaim stale-PID lock while sentinel exists 5. Test coverage: 4 new handleAutoUpdate standalone-path tests (done, deferred, error, npm-not-entered) + 3 shell-safety rejection tests * fix(cli): prevent symlink attack on staging directory via mkdtempSync Replace fixed-name .qwen-code-update-staging with mkdtempSync random suffix — an attacker who can write to parentDir can no longer pre-create a symlink to hijack recursive deletion. Also adds lstat guard before cleanup rmSync (defense-in-depth) and fixes a potential tempDir leak if mkdtempSync for extractDir fails. * fix(cli): harden standalone-update — download size guard, shell-meta coverage, cleanup logging - Add MAX_DOWNLOAD_BYTES (512 MB) streaming size guard in downloadToFile to prevent resource exhaustion from oversized responses - Extend UNSAFE_SHELL_META_UNIX to also reject semicolon and single-quote - Add debugLogger.warn when extractDir cleanup is skipped (unexpected type) - Add Windows backslash acceptance test for assertSafeForShellEmbed * fix(cli): harden standalone-update — tar preservePaths, timeout signal, shell-rc validation Defense-in-depth improvements from independent code review: - Explicitly set preservePaths:false on tar extract to prevent symlink path traversal - Check err.signal in spawnAndCapture for cross-platform timeout detection - Validate binDir in ensurePathInShellRc independently of calling context * fix(cli): address review round 3 — archive timeout, stderr capture, rollback result type 1. Separate ARCHIVE_TIMEOUT_MS (5 min) from FETCH_TIMEOUT_MS (30s) so large archive downloads on slow networks don't time out prematurely 2. Capture stderr from smoke-test execFile and include it in the error message so users see the actual crash reason (missing lib, etc.) 3. Change rollbackStandaloneUpdate return from boolean to discriminated result type (ok/reason/detail) so doctorCommand shows accurate messages for each failure mode instead of a generic "not found" * fix(test): drop pre-mkdir in shell-safety double-quote test The 'rejects standaloneDir with embedded double-quote' test called fs.mkdirSync on a path containing `"`, which ENOENTs on Windows since NTFS forbids `"` in path components. The validator assertSafeForShellEmbed runs synchronously before any FS access in ensureBinWrapper, so the pre-mkdir was vestigial — removing it makes the test pass on Windows without weakening the assertion. * refactor(standalone-update): remove over-engineered defenses Remove theoretical security mechanisms that solve near-impossible attack scenarios but introduce real complexity and new bug surface: - sentinel mechanism (solves a <2s race window requiring 3 independent low-probability events to align) - assertSafeForShellEmbed + UNSAFE_SHELL_META regexes (default paths are ~/.local/lib/qwen-code, never contain metacharacters) - assertPathWithin (execFile doesn't invoke shell; CodeQL false positive) - lstatSync symlink guard (mkdtempSync random naming already prevents predictable symlink pre-creation) Retains unsafeCmdChars in atomicReplace (bat script interpolation is a real context), bat error handling, archive timeout, and stderr capture. * fix(cli): harden standalone-update error paths and lock release * fix(cli): address multi-model review findings on standalone-update Security: - Preserve per-mirror error details in downloadWithFallback diagnostics - Deduplicate SHA256SUMS.sig download via downloadWithFallback - Upgrade signature-skip log from info to warn with enforcement hint - Add post-extraction path traversal validation for Windows ZIP - Add symlink target validation in tar extraction filter - Add shell metacharacter validation (assertSafeForShellEmbed) for binDir in ensurePathInShellRc and standaloneDir in ensureBinWrapper - Validate cmd.exe metacharacters BEFORE filesystem mutation in atomicReplace Windows path Correctness: - Fix spawnAndCapture treating string error codes as exit 0 - Release lock when mkdtempSync for extractDir fails - Add concurrent lock protection to rollbackStandaloneUpdate - Fix misleading manual-recovery message after auto-recovery succeeds - Fix t() dynamic i18n key — split into translated prefix + raw detail - Reject unsupported architectures in detectTarget instead of silent fallback to linux-x64 Performance: - Compute SHA256 hash during download stream (single-pass), eliminating the second full read of the 50-150 MB archive Quality: - Fix macOS bash shell rc priority (.bash_profile over .bashrc) - Rename npmPrefixDir to npmPackageDir for accuracy - Extract magic number 3 to MAX_MANIFEST_SEARCH_DEPTH constant Tests: - Add rollback concurrent lock protection tests (live + dead PID) - Add fish shell ensurePathInShellRc test - Add shell metacharacter rejection test for ensurePathInShellRc * fix(cli): move shell validation after platform branch in ensureBinWrapper assertSafeForShellEmbed rejects backslashes, which are normal in Windows paths. Move the call into the Unix-only else branch so Windows wrapper creation uses only the cmd.exe metacharacter check. * fix(cli): add post-extraction path validation for Unix tar Matches the Windows zip path's validateExtractedPaths call. The tar filter's string-level path.resolve check cannot detect chained symlink attacks (symlink A → ".", then A/payload → "../../etc"). The post-extraction realpath walk catches these. * fix(cli): fix standalone auto-update reachability and rebase conflicts - Add missing isStandalone/standaloneDir fields to getStandaloneInstallInfo so handleAutoUpdate and /doctor rollback can detect standalone installs - Remove redundant findStandaloneDir (conflicted with stricter detection) - Fix race condition: move mkdirSync after lock acquisition - Ensure parent directory exists before lock file creation - Clean up empty .old directory after first-time npm→standalone migration - Align bash RC file selection with install script (Linux → .bashrc) - Use ?? instead of || for manifest.target fallback - Upgrade ensureBinWrapper failure logging from debug to warn - Add debug logging for SHA256SUMS.sig download failures
42 lines
1.4 KiB
Bash
Executable file
42 lines
1.4 KiB
Bash
Executable file
#!/usr/bin/env bash
|
|
# Signs SHA256SUMS with an Ed25519 private key for release integrity verification.
|
|
#
|
|
# Usage:
|
|
# RELEASE_SIGNING_KEY_PEM=/path/to/key.pem ./scripts/sign-release.sh dist/releases/vX.Y.Z/SHA256SUMS
|
|
#
|
|
# Output:
|
|
# Creates SHA256SUMS.sig alongside the input file (base64-encoded 64-byte signature).
|
|
#
|
|
# Key generation (one-time):
|
|
# openssl genpkey -algorithm Ed25519 -out release-signing-key.pem
|
|
# openssl pkey -in release-signing-key.pem -pubout -outform DER | base64
|
|
# # Embed the base64 public key in standalone-update-verify.ts
|
|
|
|
set -euo pipefail
|
|
|
|
if [[ -z "${RELEASE_SIGNING_KEY_PEM:-}" ]]; then
|
|
echo "ERROR: RELEASE_SIGNING_KEY_PEM environment variable must point to the Ed25519 private key." >&2
|
|
exit 1
|
|
fi
|
|
|
|
if [[ ! -f "${RELEASE_SIGNING_KEY_PEM}" ]]; then
|
|
echo "ERROR: Key file not found: ${RELEASE_SIGNING_KEY_PEM}" >&2
|
|
exit 1
|
|
fi
|
|
|
|
SHA256SUMS_FILE="${1:-}"
|
|
if [[ -z "${SHA256SUMS_FILE}" || ! -f "${SHA256SUMS_FILE}" ]]; then
|
|
echo "Usage: $0 <path/to/SHA256SUMS>" >&2
|
|
exit 1
|
|
fi
|
|
|
|
SIG_FILE="${SHA256SUMS_FILE}.sig"
|
|
|
|
# openssl pkeyutl -rawin signs raw content with Ed25519, outputs 64-byte signature
|
|
openssl pkeyutl -sign -rawin \
|
|
-inkey "${RELEASE_SIGNING_KEY_PEM}" \
|
|
-in "${SHA256SUMS_FILE}" \
|
|
-out /dev/stdout 2>/dev/null | base64 > "${SIG_FILE}"
|
|
|
|
echo "Signed: ${SIG_FILE}"
|
|
echo "Signature (base64): $(cat "${SIG_FILE}")"
|