fix(anime): pin linux-x64 bridge checksum and fetch release by tag

- Add PINNED_BUNDLE_SHA256 entry for linux-x64-bundle.zip so Linux no longer refuses to start with "No pinned checksum"
- Fetch the bridge release by its pinned tag (releases/tags/<tag>) instead of listing newest releases, so an upstream publish can't swap in an unverified asset
- Update docs and changelog to describe the pin/tag behavior
This commit is contained in:
2026-07-31 23:41:46 -07:00
parent 3c19228b35
commit 7254db66ad
5 changed files with 104 additions and 34 deletions
+60 -18
View File
@@ -4,6 +4,7 @@ import { mkdtemp, mkdir, writeFile } from 'node:fs/promises';
import { tmpdir } from 'node:os';
import path from 'node:path';
import {
bundleReleaseUrl,
findBundleBinaries,
PINNED_BUNDLE_SHA256,
PINNED_BUNDLE_TAG,
@@ -26,34 +27,71 @@ test('resolveBundleAssetName returns null for unpublished combinations', () => {
assert.equal(resolveBundleAssetName('freebsd', 'x64'), null);
});
test('selectBundleAsset skips releases without a matching asset', () => {
const releases = [
// The iOS runtime release carries no desktop bundle.
{ tag_name: 'ios-runtime-v7', assets: [{ name: 'MExtensionServer-ios.jar' }] },
const PINNED_RELEASE = {
tag_name: PINNED_BUNDLE_TAG,
assets: [
{
tag_name: 'v1.0.6.0',
assets: [
{
name: 'macOS-arm64-bundle.zip',
browser_download_url: 'https://example.test/macOS-arm64-bundle.zip',
size: 133_058_560,
},
],
name: 'macOS-arm64-bundle.zip',
browser_download_url: 'https://example.test/macOS-arm64-bundle.zip',
size: 133_058_560,
},
];
],
};
const asset = selectBundleAsset(releases, 'macOS-arm64-bundle.zip');
assert.equal(asset?.tagName, 'v1.0.6.0');
test('selectBundleAsset reads the by-tag endpoint payload', () => {
const asset = selectBundleAsset(PINNED_RELEASE, 'macOS-arm64-bundle.zip');
assert.equal(asset?.tagName, PINNED_BUNDLE_TAG);
assert.equal(asset?.downloadUrl, 'https://example.test/macOS-arm64-bundle.zip');
assert.equal(asset?.sizeBytes, 133_058_560);
});
test('selectBundleAsset skips releases without a matching asset', () => {
const releases = [
// The iOS runtime release carries no desktop bundle.
{ tag_name: 'ios-runtime-v7', assets: [{ name: 'MExtensionServer-ios.jar' }] },
PINNED_RELEASE,
];
const asset = selectBundleAsset(releases, 'macOS-arm64-bundle.zip');
assert.equal(asset?.tagName, PINNED_BUNDLE_TAG);
});
test('selectBundleAsset ignores releases newer than the pin', () => {
const releases = [
{
tag_name: 'v9.9.9.9',
assets: [
{
name: 'macOS-arm64-bundle.zip',
browser_download_url: 'https://example.test/unpinned.zip',
size: 1,
},
],
},
PINNED_RELEASE,
];
const asset = selectBundleAsset(releases, 'macOS-arm64-bundle.zip');
assert.equal(asset?.tagName, PINNED_BUNDLE_TAG);
assert.equal(asset?.downloadUrl, 'https://example.test/macOS-arm64-bundle.zip');
});
test('selectBundleAsset returns null when nothing matches', () => {
assert.equal(selectBundleAsset([{ tag_name: 'v1', assets: [] }], 'linux-x64-bundle.zip'), null);
assert.equal(
selectBundleAsset([{ tag_name: PINNED_BUNDLE_TAG, assets: [] }], 'linux-x64-bundle.zip'),
null,
);
assert.equal(selectBundleAsset([], 'linux-x64-bundle.zip'), null);
assert.equal(selectBundleAsset({ message: 'rate limited' }, 'linux-x64-bundle.zip'), null);
});
test('bundleReleaseUrl targets the pinned tag', () => {
assert.equal(
bundleReleaseUrl(),
`https://api.github.com/repos/1Selxo/M-Extension-Server/releases/tags/${PINNED_BUNDLE_TAG}`,
);
});
test('findBundleBinaries locates the nested jre and jar', async () => {
const root = await mkdtemp(path.join(tmpdir(), 'subminer-bundle-'));
await mkdir(path.join(root, 'jre', 'jre', 'bin'), { recursive: true });
@@ -103,15 +141,19 @@ test('verifyPinnedBundle accepts a matching hash and rejects a mismatch', () =>
});
test('verifyPinnedBundle refuses an asset that has no pin', () => {
const result = verifyPinnedBundle('linux-x64-bundle.zip', new Uint8Array([1, 2, 3]));
const result = verifyPinnedBundle('windows-x64-bundle.zip', new Uint8Array([1, 2, 3]));
assert.equal(result.ok, false);
assert.match((result as { reason: string }).reason, /No pinned checksum/);
});
test('the pinned tag and macOS arm64 hash are the verified release', () => {
test('the pinned tag and hashes are the verified release', () => {
assert.equal(PINNED_BUNDLE_TAG, 'v1.0.6.0');
assert.equal(
PINNED_BUNDLE_SHA256['macOS-arm64-bundle.zip'],
'5f4fb03abfe88bc46ddf5f4d8221156ee2d66b9cbad7c4bc3ade4baf3a4266e6',
);
assert.equal(
PINNED_BUNDLE_SHA256['linux-x64-bundle.zip'],
'c2b869d3905b06a308517fec0b44f70ff76f7212230c60710bba39a7025c3a69',
);
});
+26 -7
View File
@@ -8,8 +8,15 @@ import path from 'node:path';
* required.
*/
export const BUNDLE_RELEASES_URL =
'https://api.github.com/repos/1Selxo/M-Extension-Server/releases?page=1&per_page=10';
const BUNDLE_REPO_API = 'https://api.github.com/repos/1Selxo/M-Extension-Server';
/**
* Fetch the pinned release by tag rather than listing releases: upstream ships
* several a week, so a paged list would scroll the pinned tag off page one.
*/
export function bundleReleaseUrl(tagName: string = PINNED_BUNDLE_TAG): string {
return `${BUNDLE_REPO_API}/releases/tags/${encodeURIComponent(tagName)}`;
}
/**
* The bridge release this integration was verified against.
@@ -24,6 +31,7 @@ export const PINNED_BUNDLE_TAG = 'v1.0.6.0';
/** SHA-256 of each pinned asset, keyed by release asset name. */
export const PINNED_BUNDLE_SHA256: Readonly<Record<string, string>> = {
'macOS-arm64-bundle.zip': '5f4fb03abfe88bc46ddf5f4d8221156ee2d66b9cbad7c4bc3ade4baf3a4266e6',
'linux-x64-bundle.zip': 'c2b869d3905b06a308517fec0b44f70ff76f7212230c60710bba39a7025c3a69',
};
/**
@@ -130,12 +138,23 @@ interface GithubRelease {
}
/**
* Pick the newest release carrying an asset for this platform. Releases are
* returned newest-first, and some (the iOS runtime) carry no desktop bundle.
* Pick the asset for this platform from the pinned release. Selecting "newest"
* instead would download a release whose checksum we never computed, so every
* upstream publish would break the install with a mismatch.
*/
export function selectBundleAsset(releases: unknown, assetName: string): BundleAsset | null {
if (!Array.isArray(releases)) return null;
for (const release of releases as GithubRelease[]) {
export function selectBundleAsset(
releases: unknown,
assetName: string,
tagName: string = PINNED_BUNDLE_TAG,
): BundleAsset | null {
// Accepts either a single release (the by-tag endpoint) or a list.
const candidates = Array.isArray(releases)
? releases
: releases && typeof releases === 'object'
? [releases]
: [];
for (const release of candidates as GithubRelease[]) {
if (release.tag_name !== tagName) continue;
const asset = release.assets?.find((candidate) => candidate.name === assetName);
if (asset?.browser_download_url && release.tag_name) {
return {