@modelstatus/cli 0.1.73 → 0.1.74
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/updater.js +29 -2
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@modelstatus/cli",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.74",
|
|
4
4
|
"description": "Track which AI models you use, where, and never get surprised by a retirement. Free offline model-health for any repo (mm status), browser sign-in for cloud inventory + alerts.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"llm",
|
package/src/updater.js
CHANGED
|
@@ -88,6 +88,33 @@ async function fetchJson(url) {
|
|
|
88
88
|
return res.json();
|
|
89
89
|
}
|
|
90
90
|
|
|
91
|
+
// Ed25519 RELEASE public key (committed: packages/cli/release-pubkey.pem). The
|
|
92
|
+
// manifest is signed with the matching private key; verifying that signature is
|
|
93
|
+
// what makes the manifest's sha256 trustworthy on every platform, independent of
|
|
94
|
+
// the CDN. An auto-update that can't verify this signature does not proceed.
|
|
95
|
+
const RELEASE_PUBKEY = `-----BEGIN PUBLIC KEY-----
|
|
96
|
+
MCowBQYDK2VwAyEApu4VP7vB6iwxsPcK5KaosjK7SIp9HrWMt5IvcUwjUOM=
|
|
97
|
+
-----END PUBLIC KEY-----`;
|
|
98
|
+
|
|
99
|
+
/** Fetch the manifest + its detached Ed25519 signature and verify before
|
|
100
|
+
* parsing. Returns the parsed manifest, or throws if the signature is missing or
|
|
101
|
+
* invalid (fail closed — never self-update against an unverifiable manifest). */
|
|
102
|
+
async function fetchVerifiedManifest(base) {
|
|
103
|
+
const [mRes, sRes] = await Promise.all([
|
|
104
|
+
fetch(`${base}/version.json`, { cache: "no-store" }),
|
|
105
|
+
fetch(`${base}/version.json.sig`, { cache: "no-store" }),
|
|
106
|
+
]);
|
|
107
|
+
if (!mRes.ok) throw new Error(`GET version.json -> ${mRes.status}`);
|
|
108
|
+
if (!sRes.ok) throw new Error("manifest signature unavailable — refusing to self-update");
|
|
109
|
+
const manifestBytes = Buffer.from(await mRes.arrayBuffer());
|
|
110
|
+
const sig = Buffer.from((await sRes.text()).trim(), "base64");
|
|
111
|
+
const key = crypto.createPublicKey(RELEASE_PUBKEY);
|
|
112
|
+
if (!crypto.verify(null, manifestBytes, key, sig)) {
|
|
113
|
+
throw new Error("manifest signature INVALID — refusing to self-update");
|
|
114
|
+
}
|
|
115
|
+
return JSON.parse(manifestBytes.toString("utf8"));
|
|
116
|
+
}
|
|
117
|
+
|
|
91
118
|
/** True if we can create/rename files in `dir` (needed for an atomic swap). */
|
|
92
119
|
function dirWritable(dir) {
|
|
93
120
|
try {
|
|
@@ -189,7 +216,7 @@ export async function forceUpdate() {
|
|
|
189
216
|
const key = platformKey();
|
|
190
217
|
if (!key) return { status: "unsupported" };
|
|
191
218
|
|
|
192
|
-
const manifest = await
|
|
219
|
+
const manifest = await fetchVerifiedManifest(`${CDN}/cli/${CHANNEL_PATH}`);
|
|
193
220
|
writeCache({ ...(readCache() || {}), last_check: Date.now(), latest_known: manifest.version });
|
|
194
221
|
if (!manifest.version) return { status: "error", message: "manifest has no version" };
|
|
195
222
|
if (compareVer(manifest.version, BUILD_VERSION) <= 0) return { status: "latest", version: dispVer(BUILD_VERSION) };
|
|
@@ -223,7 +250,7 @@ export async function maybeCheckForUpdate(flags = {}) {
|
|
|
223
250
|
const key = platformKey();
|
|
224
251
|
if (!key) return null;
|
|
225
252
|
|
|
226
|
-
const manifest = await
|
|
253
|
+
const manifest = await fetchVerifiedManifest(`${CDN}/cli/${CHANNEL_PATH}`);
|
|
227
254
|
// Always update the cache so we don't re-check for 24 h.
|
|
228
255
|
writeCache({ ...cache, last_check: Date.now(), latest_known: manifest.version });
|
|
229
256
|
|