补强静态发布安全边界

This commit is contained in:
2026-08-10 18:27:53 +08:00
parent 4df047708b
commit 8dd99c1855
15 changed files with 560 additions and 85 deletions

View File

@@ -0,0 +1,62 @@
const MAX_WORKS_PLAY_URL_LENGTH = 1_024;
function isLoopbackHostname(hostname: string): boolean {
const normalized = hostname.toLowerCase().replace(/^\[|\]$/g, '');
return normalized === 'localhost'
|| normalized.endsWith('.localhost')
|| normalized === '::'
|| normalized === '::1'
|| normalized.startsWith('::ffff:127.')
|| normalized.startsWith('::ffff:7f00:')
|| normalized === '0.0.0.0'
|| normalized.startsWith('127.');
}
function trustedWorksHttpsUrl(value: string, worksBase: URL): URL | null {
try {
const target = new URL(value, worksBase);
if (
target.toString().length > MAX_WORKS_PLAY_URL_LENGTH
|| worksBase.protocol !== 'https:'
|| target.protocol !== 'https:'
|| target.origin !== worksBase.origin
|| Boolean(target.username || target.password)
|| isLoopbackHostname(target.hostname)
) {
return null;
}
return target;
} catch {
return null;
}
}
export function trustedWorksProjectPlayUrl(
value: string,
worksBase: URL,
appId: string,
): string | null {
const normalizedAppId = appId.trim();
if (!normalizedAppId) return null;
const target = trustedWorksHttpsUrl(value, worksBase);
if (
!target
|| target.pathname !== `/apps/${encodeURIComponent(normalizedAppId)}/`
|| target.search
|| target.hash
) {
return null;
}
return target.toString();
}
export function trustedWorksReleasePreviewUrl(
value: string,
worksBase: URL,
releaseId: string,
): string | null {
const target = trustedWorksHttpsUrl(value, worksBase);
if (!target) return null;
const expectedPrefix = `/previews/${encodeURIComponent(releaseId)}/`;
return target.pathname.startsWith(expectedPrefix) ? target.toString() : null;
}