Files
john b1577a16e9
Memind CI / Test, build, and release guards (push) Failing after 12m3s
fix: harden release gate and page delivery
2026-07-26 14:32:01 +08:00

103 lines
3.1 KiB
JavaScript

import fs from 'node:fs/promises';
import net from 'node:net';
import path from 'node:path';
const SCANNED_PUBLIC_EXTENSIONS = new Set(['.css', '.html']);
const ALLOWED_TKMIND_HOSTS = new Set([
'asr.tkmind.cn',
'go.tkmind.cn',
'm.tkmind.cn',
'plaza.tkmind.cn',
'rybbit.tkmind.cn',
]);
function isPrivateIpv4(hostname) {
const parts = hostname.split('.').map(Number);
if (parts.length !== 4 || parts.some((part) => !Number.isInteger(part))) return false;
return parts[0] === 10
|| parts[0] === 127
|| (parts[0] === 169 && parts[1] === 254)
|| (parts[0] === 172 && parts[1] >= 16 && parts[1] <= 31)
|| (parts[0] === 192 && parts[1] === 168);
}
export function classifyPublicUrl(rawUrl) {
let url;
try {
url = new URL(rawUrl);
} catch {
return 'invalid_absolute_url';
}
const hostname = url.hostname.toLowerCase();
if (
hostname === 'localhost'
|| hostname.endsWith('.localhost')
|| hostname === '::1'
|| isPrivateIpv4(hostname)
) {
return 'loopback_or_private_host';
}
if (hostname.endsWith('.tkmind.cn') && !ALLOWED_TKMIND_HOSTS.has(hostname)) {
return 'unknown_tkmind_public_host';
}
return null;
}
function extractPublicDocumentUrls(source, extension) {
const urls = [];
if (extension === '.html') {
const attributePattern = /\b(?:action|content|href|poster|src)\s*=\s*["'](https?:\/\/[^"'<> \t\r\n]+)["']/gi;
for (const match of source.matchAll(attributePattern)) urls.push(match[1]);
}
if (extension === '.css') {
const cssPattern = /url\(\s*["']?(https?:\/\/[^"') \t\r\n]+)["']?\s*\)/gi;
for (const match of source.matchAll(cssPattern)) urls.push(match[1]);
}
return urls;
}
async function walkPublicDocuments(root, current = root) {
const entries = await fs.readdir(current, { withFileTypes: true });
const files = [];
for (const entry of entries) {
const absolute = path.join(current, entry.name);
if (entry.isDirectory()) files.push(...await walkPublicDocuments(root, absolute));
else if (entry.isFile() && SCANNED_PUBLIC_EXTENSIONS.has(path.extname(entry.name).toLowerCase())) {
files.push(absolute);
}
}
return files;
}
export async function inspectRuntimePublicUrls(runtimeRoot) {
const distRoot = path.join(runtimeRoot, 'dist');
const findings = [];
for (const file of await walkPublicDocuments(distRoot)) {
const extension = path.extname(file).toLowerCase();
const source = await fs.readFile(file, 'utf8');
for (const rawUrl of extractPublicDocumentUrls(source, extension)) {
const reason = classifyPublicUrl(rawUrl);
if (reason) {
findings.push({
file: path.relative(runtimeRoot, file).split(path.sep).join('/'),
url: rawUrl,
reason,
});
}
}
}
const entryScript = await fs.readFile(
path.join(runtimeRoot, 'scripts', 'run-memind-portal-prod.sh'),
'utf8',
);
if (!/H5_PUBLIC_BASE_URL[^\n]*https:\/\/m\.tkmind\.cn/.test(entryScript)) {
findings.push({
file: 'scripts/run-memind-portal-prod.sh',
url: null,
reason: 'missing_canonical_h5_public_base',
});
}
return findings;
}