From 2b3e177888f49a63e5bacaaedfa8928e78a0dda9 Mon Sep 17 00:00:00 2001 From: LIghtJUNction Date: Tue, 6 Oct 2026 21:38:12 +0800 Subject: [PATCH 1/3] chore: prepare verified Windows profile installer pins --- .../workflows/prepare-windows-profile-pin.yml | 61 +++++++++++++++++++ 1 file changed, 61 insertions(+) create mode 100644 .github/workflows/prepare-windows-profile-pin.yml diff --git a/.github/workflows/prepare-windows-profile-pin.yml b/.github/workflows/prepare-windows-profile-pin.yml new file mode 100644 index 0000000..de45768 --- /dev/null +++ b/.github/workflows/prepare-windows-profile-pin.yml @@ -0,0 +1,61 @@ +name: Prepare Windows profile installer pin +on: + push: + branches: [fix/windows-profile-installer-20261006] + paths: [.github/workflows/prepare-windows-profile-pin.yml] +permissions: + contents: read +concurrency: + group: prepare-windows-profile-installer + cancel-in-progress: false +jobs: + prepare: + if: github.ref == 'refs/heads/fix/windows-profile-installer-20261006' + runs-on: ubuntu-latest + timeout-minutes: 5 + permissions: + contents: write + steps: + - uses: actions/checkout@v4 + with: + ref: fix/windows-profile-installer-20261006 + fetch-depth: 1 + - name: Update source pin and bind verified launchers + shell: bash + run: | + set -euo pipefail + git config user.name 'github-actions[bot]' + git config user.email '41898282+github-actions[bot]@users.noreply.github.com' + python3 - <<'PY' + from pathlib import Path + import hashlib + import subprocess + + old_provider = 'adad64b7ac77ec997b5be4662c6b6ccf36998357' + new_provider = '62eb05b40f1dda7b844b5430ad2b735e66508aa0' + old_helper = '319d9790ba026583ea226fb92b13b366b738c3ef' + old_digest = '91dcff456f04abb0ef0ff7f95742ff3d332bfb2c93b4b067f4d778008556f69c' + + def replace_once(path, old, new): + file = Path(path) + data = file.read_bytes() + assert data.count(old.encode()) == 1, f'{path}: expected exactly one old pin' + file.write_bytes(data.replace(old.encode(), new.encode())) + + replace_once('codewhale.mjs', old_provider, new_provider) + replace_once('CODEWHALE.md', old_provider, new_provider) + subprocess.run(['git', 'add', 'codewhale.mjs', 'CODEWHALE.md'], check=True) + subprocess.run(['git', 'commit', '-m', 'fix: pin Codewhale Windows environment isolation'], check=True) + helper = subprocess.check_output(['git', 'rev-parse', 'HEAD'], text=True).strip() + digest = hashlib.sha256(Path('codewhale.mjs').read_bytes()).hexdigest() + for path in ['codewhale.sh', 'codewhale.ps1']: + replace_once(path, old_helper, helper) + replace_once(path, old_digest, digest) + replace_once('test-codewhale.mjs', old_helper, helper) + Path('.github/workflows/prepare-windows-profile-pin.yml').unlink() + subprocess.run(['git', 'add', 'codewhale.sh', 'codewhale.ps1', 'test-codewhale.mjs', '.github/workflows/prepare-windows-profile-pin.yml'], check=True) + subprocess.run(['git', 'commit', '-m', 'fix: bind both Codewhale bootstraps to verified helper bytes'], check=True) + print(f'Provider: {new_provider}\nHelper: {helper}\nSHA256: {digest}') + PY + git diff --exit-code + git push origin HEAD:refs/heads/fix/windows-profile-installer-20261006 From cdf32d6d0e612cb2e22ae93f2f25f744d6145617 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 6 Oct 2026 13:38:22 +0000 Subject: [PATCH 2/3] fix: pin Codewhale Windows environment isolation --- CODEWHALE.md | 2 +- codewhale.mjs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CODEWHALE.md b/CODEWHALE.md index 9bef495..d2cfbee 100644 --- a/CODEWHALE.md +++ b/CODEWHALE.md @@ -1,6 +1,6 @@ # Codewhale 一键安装和 LMM 配置 -需要 Node.js 22+ 和 npm。Bash 入口单独下载时还需要 curl。脚本不代装 Node,不改 npm registry,不使用 sudo 或 `--force`。安装使用官方的 `npm install --global codewhale@latest`;二进制选择和校验仍由官方 npm 安装器负责。LMM 适配器安装固定 Git 提交 `adad64b7ac77ec997b5be4662c6b6ccf36998357`,禁用其 npm 生命周期脚本;不依赖尚未发布的 npm 包。 +需要 Node.js 22+ 和 npm。Bash 入口单独下载时还需要 curl。脚本不代装 Node,不改 npm registry,不使用 sudo 或 `--force`。安装使用官方的 `npm install --global codewhale@latest`;二进制选择和校验仍由官方 npm 安装器负责。LMM 适配器安装固定 Git 提交 `62eb05b40f1dda7b844b5430ad2b735e66508aa0`,禁用其 npm 生命周期脚本;不依赖尚未发布的 npm 包。 ## 安装并配置 diff --git a/codewhale.mjs b/codewhale.mjs index a581a46..6e677c9 100755 --- a/codewhale.mjs +++ b/codewhale.mjs @@ -7,7 +7,7 @@ import { createRequire } from 'node:module'; import { createInterface } from 'node:readline/promises'; import { pathToFileURL } from 'node:url'; -export const PROVIDER_REV = 'adad64b7ac77ec997b5be4662c6b6ccf36998357'; +export const PROVIDER_REV = '62eb05b40f1dda7b844b5430ad2b735e66508aa0'; export const PROVIDER_URL = `https://github.com/TokenNotIncluded/codewhale-lmm-provider/archive/${PROVIDER_REV}.tar.gz`; const require = createRequire(import.meta.url); const actions = ['setup', 'install', 'login', 'run', 'models', 'status', 'balance', 'usage', 'logout', 'doctor', 'menu', 'help']; From ed2a44be1146a68d8cff3950a8a67120f02ed1f1 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 6 Oct 2026 13:38:22 +0000 Subject: [PATCH 3/3] fix: bind both Codewhale bootstraps to verified helper bytes --- .../workflows/prepare-windows-profile-pin.yml | 61 ------------------- codewhale.ps1 | 4 +- codewhale.sh | 4 +- test-codewhale.mjs | 2 +- 4 files changed, 5 insertions(+), 66 deletions(-) delete mode 100644 .github/workflows/prepare-windows-profile-pin.yml diff --git a/.github/workflows/prepare-windows-profile-pin.yml b/.github/workflows/prepare-windows-profile-pin.yml deleted file mode 100644 index de45768..0000000 --- a/.github/workflows/prepare-windows-profile-pin.yml +++ /dev/null @@ -1,61 +0,0 @@ -name: Prepare Windows profile installer pin -on: - push: - branches: [fix/windows-profile-installer-20261006] - paths: [.github/workflows/prepare-windows-profile-pin.yml] -permissions: - contents: read -concurrency: - group: prepare-windows-profile-installer - cancel-in-progress: false -jobs: - prepare: - if: github.ref == 'refs/heads/fix/windows-profile-installer-20261006' - runs-on: ubuntu-latest - timeout-minutes: 5 - permissions: - contents: write - steps: - - uses: actions/checkout@v4 - with: - ref: fix/windows-profile-installer-20261006 - fetch-depth: 1 - - name: Update source pin and bind verified launchers - shell: bash - run: | - set -euo pipefail - git config user.name 'github-actions[bot]' - git config user.email '41898282+github-actions[bot]@users.noreply.github.com' - python3 - <<'PY' - from pathlib import Path - import hashlib - import subprocess - - old_provider = 'adad64b7ac77ec997b5be4662c6b6ccf36998357' - new_provider = '62eb05b40f1dda7b844b5430ad2b735e66508aa0' - old_helper = '319d9790ba026583ea226fb92b13b366b738c3ef' - old_digest = '91dcff456f04abb0ef0ff7f95742ff3d332bfb2c93b4b067f4d778008556f69c' - - def replace_once(path, old, new): - file = Path(path) - data = file.read_bytes() - assert data.count(old.encode()) == 1, f'{path}: expected exactly one old pin' - file.write_bytes(data.replace(old.encode(), new.encode())) - - replace_once('codewhale.mjs', old_provider, new_provider) - replace_once('CODEWHALE.md', old_provider, new_provider) - subprocess.run(['git', 'add', 'codewhale.mjs', 'CODEWHALE.md'], check=True) - subprocess.run(['git', 'commit', '-m', 'fix: pin Codewhale Windows environment isolation'], check=True) - helper = subprocess.check_output(['git', 'rev-parse', 'HEAD'], text=True).strip() - digest = hashlib.sha256(Path('codewhale.mjs').read_bytes()).hexdigest() - for path in ['codewhale.sh', 'codewhale.ps1']: - replace_once(path, old_helper, helper) - replace_once(path, old_digest, digest) - replace_once('test-codewhale.mjs', old_helper, helper) - Path('.github/workflows/prepare-windows-profile-pin.yml').unlink() - subprocess.run(['git', 'add', 'codewhale.sh', 'codewhale.ps1', 'test-codewhale.mjs', '.github/workflows/prepare-windows-profile-pin.yml'], check=True) - subprocess.run(['git', 'commit', '-m', 'fix: bind both Codewhale bootstraps to verified helper bytes'], check=True) - print(f'Provider: {new_provider}\nHelper: {helper}\nSHA256: {digest}') - PY - git diff --exit-code - git push origin HEAD:refs/heads/fix/windows-profile-installer-20261006 diff --git a/codewhale.ps1 b/codewhale.ps1 index df13dc1..244a478 100644 --- a/codewhale.ps1 +++ b/codewhale.ps1 @@ -19,8 +19,8 @@ try { $work = Join-Path ([IO.Path]::GetTempPath()) ('lmm-codewhale-' + [guid]::NewGuid()) New-Item -ItemType Directory -Path $work | Out-Null $helper = Join-Path $work 'codewhale.mjs' - Invoke-WebRequest -UseBasicParsing 'https://raw.githubusercontent.com/TokenNotIncluded/lmm-scripts/319d9790ba026583ea226fb92b13b366b738c3ef/codewhale.mjs' -OutFile $helper - if ((Get-FileHash -LiteralPath $helper -Algorithm SHA256).Hash -ne '91dcff456f04abb0ef0ff7f95742ff3d332bfb2c93b4b067f4d778008556f69c') { + Invoke-WebRequest -UseBasicParsing 'https://raw.githubusercontent.com/TokenNotIncluded/lmm-scripts/cdf32d6d0e612cb2e22ae93f2f25f744d6145617/codewhale.mjs' -OutFile $helper + if ((Get-FileHash -LiteralPath $helper -Algorithm SHA256).Hash -ne '4138c5d74f1dee3cf089a90bdeef649d42304de7f5f9cd83e28f7d3302f994fb') { throw 'Codewhale setup script checksum mismatch; nothing was executed.' } } diff --git a/codewhale.sh b/codewhale.sh index e518b1d..d69f505 100755 --- a/codewhale.sh +++ b/codewhale.sh @@ -10,6 +10,6 @@ temp_root=${TMPDIR:-/tmp} if [[ -n ${TERMUX_VERSION:-} || ${PREFIX:-} == */com.termux/files/usr ]]; then temp_root=${TMPDIR:-${PREFIX:-/data/data/com.termux/files/usr}/tmp}; fi work=$(mktemp -d "$temp_root/lmm-codewhale.XXXXXX") trap 'rm -rf -- "$work"' EXIT -curl --proto '=https' --proto-redir '=https' -fsSL 'https://raw.githubusercontent.com/TokenNotIncluded/lmm-scripts/319d9790ba026583ea226fb92b13b366b738c3ef/codewhale.mjs' -o "$work/codewhale.mjs" -node -e 'const fs=require("node:fs"),crypto=require("node:crypto");if(crypto.createHash("sha256").update(fs.readFileSync(process.argv[1])).digest("hex")!==process.argv[2]){console.error("Codewhale setup script checksum mismatch; nothing was executed.");process.exit(1)}' "$work/codewhale.mjs" '91dcff456f04abb0ef0ff7f95742ff3d332bfb2c93b4b067f4d778008556f69c' +curl --proto '=https' --proto-redir '=https' -fsSL 'https://raw.githubusercontent.com/TokenNotIncluded/lmm-scripts/cdf32d6d0e612cb2e22ae93f2f25f744d6145617/codewhale.mjs' -o "$work/codewhale.mjs" +node -e 'const fs=require("node:fs"),crypto=require("node:crypto");if(crypto.createHash("sha256").update(fs.readFileSync(process.argv[1])).digest("hex")!==process.argv[2]){console.error("Codewhale setup script checksum mismatch; nothing was executed.");process.exit(1)}' "$work/codewhale.mjs" '4138c5d74f1dee3cf089a90bdeef649d42304de7f5f9cd83e28f7d3302f994fb' node "$work/codewhale.mjs" "$@" diff --git a/test-codewhale.mjs b/test-codewhale.mjs index b711293..683fdf0 100644 --- a/test-codewhale.mjs +++ b/test-codewhale.mjs @@ -240,7 +240,7 @@ process.exit(Number(process.env.TEST_CURL_EXIT||0));\n`; const result = f.runShell('--help'); assert.equal(result.status, 0, result.stderr); assert.match(result.stdout, /Codewhale \+ LMM/); - assert.ok(f.calls()[0][1].some(arg => /319d9790ba026583ea226fb92b13b366b738c3ef\/codewhale.mjs/.test(arg))); + assert.ok(f.calls()[0][1].some(arg => /cdf32d6d0e612cb2e22ae93f2f25f744d6145617\/codewhale.mjs/.test(arg))); }); test('truncated downloads fail before execution even when bytes were written', t => { const f = shellFixture(t); f.env.TEST_BAD_DOWNLOAD = '1'; f.env.TEST_CURL_EXIT = '18';