mirror of
https://github.com/QwenLM/qwen-code.git
synced 2026-05-19 07:54:38 +00:00
fix(installer): tighten verifier base-url + clarify test helper
Three small refinements from the second review pass: - normalizeHttpsBaseUrl rejects everything except https, since real release URLs are always HTTPS. Accepting http previously would let an operator silently target a stale or attacker-controlled mirror. - Drop EXPECTED_RELEASE_ASSET_NAMES from the public exports; it was only used internally for the verification log line. - Rename the test helper standaloneChecksumContent to placeholderChecksumContent and document that the hashes in its output are placeholders — the remote verifier does not download archives or compare hashes, it only validates that SHA256SUMS lists the expected names and that each archive URL is reachable. The non-https rejection test now also covers `http://` in addition to the existing `file://` case.
This commit is contained in:
parent
4100b8e239
commit
362bf588f8
2 changed files with 25 additions and 11 deletions
|
|
@ -789,7 +789,7 @@ describe('standalone release packaging', () => {
|
|||
it('verifies release asset URLs from SHA256SUMS', async () => {
|
||||
const { EXPECTED_STANDALONE_ARCHIVE_NAMES, verifyReleaseBaseUrl } =
|
||||
await import(installationReleaseVerificationScriptUrl);
|
||||
const checksumContent = standaloneChecksumContent(
|
||||
const checksumContent = placeholderChecksumContent(
|
||||
EXPECTED_STANDALONE_ARCHIVE_NAMES,
|
||||
);
|
||||
const fetchedUrls = [];
|
||||
|
|
@ -827,7 +827,7 @@ describe('standalone release packaging', () => {
|
|||
it('falls back to ranged GET when remote HEAD is unavailable', async () => {
|
||||
const { EXPECTED_STANDALONE_ARCHIVE_NAMES, verifyReleaseBaseUrl } =
|
||||
await import(installationReleaseVerificationScriptUrl);
|
||||
const checksumContent = standaloneChecksumContent(
|
||||
const checksumContent = placeholderChecksumContent(
|
||||
EXPECTED_STANDALONE_ARCHIVE_NAMES,
|
||||
);
|
||||
const observedMethods = [];
|
||||
|
|
@ -856,7 +856,7 @@ describe('standalone release packaging', () => {
|
|||
it('rejects a release base URL with no archives reachable', async () => {
|
||||
const { EXPECTED_STANDALONE_ARCHIVE_NAMES, verifyReleaseBaseUrl } =
|
||||
await import(installationReleaseVerificationScriptUrl);
|
||||
const checksumContent = standaloneChecksumContent(
|
||||
const checksumContent = placeholderChecksumContent(
|
||||
EXPECTED_STANDALONE_ARCHIVE_NAMES,
|
||||
);
|
||||
|
||||
|
|
@ -872,14 +872,22 @@ describe('standalone release packaging', () => {
|
|||
).rejects.toThrow(/Release asset URL is not available/);
|
||||
});
|
||||
|
||||
it('rejects a release base URL that is not http(s)', async () => {
|
||||
it('rejects a release base URL that is not https', async () => {
|
||||
const { verifyReleaseBaseUrl } = await import(
|
||||
installationReleaseVerificationScriptUrl
|
||||
);
|
||||
|
||||
// file:// must be rejected as a URL the verifier cannot reach safely.
|
||||
await expect(verifyReleaseBaseUrl('file:///tmp/release/')).rejects.toThrow(
|
||||
/--base-url must use http or https/,
|
||||
/--base-url must use https/,
|
||||
);
|
||||
|
||||
// Plain http must also be rejected even though it is technically a valid
|
||||
// URL — release URLs are always HTTPS, and accepting http would let an
|
||||
// operator silently target a stale or attacker-controlled mirror.
|
||||
await expect(
|
||||
verifyReleaseBaseUrl('http://example.com/release/'),
|
||||
).rejects.toThrow(/--base-url must use https/);
|
||||
});
|
||||
|
||||
it('rejects a runtime archive without a Node executable', () => {
|
||||
|
|
@ -2091,7 +2099,11 @@ function writeStandaloneReleaseChecksums(outDir, archiveNames) {
|
|||
writeFileSync(path.join(outDir, 'SHA256SUMS'), `${lines.join('\n')}\n`);
|
||||
}
|
||||
|
||||
function standaloneChecksumContent(archiveNames) {
|
||||
// Generates a SHA256SUMS-formatted string for the given archive names. The
|
||||
// hash values are placeholders — the remote verifier (verifyReleaseBaseUrl)
|
||||
// only checks that SHA256SUMS lists the expected entries and that each
|
||||
// archive URL is reachable; it does not download archives or compare hashes.
|
||||
function placeholderChecksumContent(archiveNames) {
|
||||
return `${archiveNames
|
||||
.map(
|
||||
(assetName) =>
|
||||
|
|
|
|||
|
|
@ -112,7 +112,7 @@ async function verifyReleaseDirectory(dir) {
|
|||
|
||||
async function verifyReleaseBaseUrl(baseUrl, options = {}) {
|
||||
const { fetchImpl = fetch } = options;
|
||||
const normalizedBaseUrl = normalizeHttpBaseUrl(baseUrl);
|
||||
const normalizedBaseUrl = normalizeHttpsBaseUrl(baseUrl);
|
||||
const checksumUrl = new URL('SHA256SUMS', normalizedBaseUrl).toString();
|
||||
const checksums = parseSha256Sums(await fetchText(checksumUrl, fetchImpl));
|
||||
assertExpectedChecksumEntries(checksums);
|
||||
|
|
@ -188,15 +188,18 @@ async function fetchText(url, fetchImpl) {
|
|||
return response.text();
|
||||
}
|
||||
|
||||
function normalizeHttpBaseUrl(baseUrl) {
|
||||
function normalizeHttpsBaseUrl(baseUrl) {
|
||||
let parsed;
|
||||
try {
|
||||
parsed = new URL(baseUrl);
|
||||
} catch {
|
||||
fail(`--base-url must be a valid URL: ${baseUrl}`);
|
||||
}
|
||||
if (parsed.protocol !== 'https:' && parsed.protocol !== 'http:') {
|
||||
fail(`--base-url must use http or https: ${baseUrl}`);
|
||||
// Real release URLs are always HTTPS. Tests use injected fetchImpl, so
|
||||
// they don't need a real protocol. Rejecting non-https early prevents an
|
||||
// operator from accidentally pointing the verifier at a plain-http mirror.
|
||||
if (parsed.protocol !== 'https:') {
|
||||
fail(`--base-url must use https: ${baseUrl}`);
|
||||
}
|
||||
if (!parsed.pathname.endsWith('/')) {
|
||||
parsed.pathname = `${parsed.pathname}/`;
|
||||
|
|
@ -205,7 +208,6 @@ function normalizeHttpBaseUrl(baseUrl) {
|
|||
}
|
||||
|
||||
export {
|
||||
EXPECTED_RELEASE_ASSET_NAMES,
|
||||
EXPECTED_STANDALONE_ARCHIVE_NAMES,
|
||||
verifyReleaseBaseUrl,
|
||||
verifyReleaseDirectory,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue