mirror of
https://github.com/pbakaus/impeccable.git
synced 2026-09-14 23:26:39 +03:00
npm shim: refuse a download with no verifiable sidecar
The skill launcher and `impeccable install` both fail closed when a release binary's `.sha256` sidecar cannot be fetched or carries no hash: they refuse rather than cache an unverified binary. The npm shim did not. It only compared when a hash was present, so a 404, an empty sidecar, or a truncated one all wrote the payload straight into `~/.impeccable/bin/<version>/` and exec'd it. It now refuses in the same cases, with wording that matches the launcher, and writes nothing until the hash matches, so a refusal leaves the cache dir empty. IMPECCABLE_BIN and the optional-dependency lookup are untouched: neither downloads. tests/cli-shim.test.mjs runs the real shim against a throwaway HTTP server and covers missing, empty, and mismatched sidecars, plus the matching-sidecar and IMPECCABLE_BIN paths. The two refusal cases fail against the old shim. Co-Authored-By: Claude Code <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Vau2X53xGTjjTCXWMVBoNY
This commit is contained in:
co-authored by
Claude Code
parent
a91c226b2b
commit
884c9aaf3d
+19
-4
@@ -33,15 +33,30 @@ async function download() {
|
||||
const res = await fetch(URL, { redirect: 'follow' });
|
||||
if (!res.ok) return null;
|
||||
const buf = Buffer.from(await res.arrayBuffer());
|
||||
// Fail closed, like the skill launcher and `impeccable install`: a sidecar
|
||||
// that cannot be fetched, or that carries no hash, refuses the download
|
||||
// instead of caching an unverified binary. Nothing is written until the
|
||||
// hash matches, so a refusal leaves the cache dir untouched.
|
||||
const sum = await fetch(`${URL}.sha256`, { redirect: 'follow' }).then(r => (r.ok ? r.text() : ''), () => '');
|
||||
const expected = sum.trim().split(/\s+/)[0];
|
||||
if (expected && createHash('sha256').update(buf).digest('hex') !== expected) {
|
||||
const expected = sum.trim().split(/\s+/)[0].toLowerCase();
|
||||
if (!expected) {
|
||||
throw new Error(
|
||||
`cannot verify ${URL} against ${URL}.sha256 (sidecar unavailable or empty); `
|
||||
+ 'refusing the unverified download',
|
||||
);
|
||||
}
|
||||
if (createHash('sha256').update(buf).digest('hex') !== expected) {
|
||||
throw new Error(`checksum mismatch downloading ${URL}`);
|
||||
}
|
||||
fs.mkdirSync(path.dirname(CACHED), { recursive: true });
|
||||
const tmp = `${CACHED}.part.${process.pid}`;
|
||||
fs.writeFileSync(tmp, buf, { mode: 0o755 });
|
||||
fs.renameSync(tmp, CACHED);
|
||||
try {
|
||||
fs.writeFileSync(tmp, buf, { mode: 0o755 });
|
||||
fs.renameSync(tmp, CACHED);
|
||||
} catch (err) {
|
||||
try { fs.rmSync(tmp, { force: true }); } catch { /* best effort */ }
|
||||
throw err;
|
||||
}
|
||||
return CACHED;
|
||||
}
|
||||
async function locate() {
|
||||
|
||||
Reference in New Issue
Block a user