补强静态发布安全边界

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

@@ -3,6 +3,10 @@ import type { HostApiContext } from '../context';
import { sendJson } from '../route-utils';
import { hasRendererCapability } from '../renderer-capability';
import { WORKS_SQUARE_CONFIG } from '../works-config';
import {
trustedWorksProjectPlayUrl,
trustedWorksReleasePreviewUrl,
} from '../works-play-url';
import { getValidWorksSquareAccessToken } from '../../services/works-square-session';
import { proxyAwareFetch } from '../../utils/proxy-fetch';
import type { WorksSubmissionBindingRecord } from '../../../shared/works-submission-binding';
@@ -80,46 +84,6 @@ function normalizedWorksBaseUrl(): URL {
return new URL(`${base}/`);
}
function isLoopbackHostname(hostname: string): boolean {
const normalized = hostname.toLowerCase().replace(/^\[|\]$/g, '');
return normalized === 'localhost'
|| normalized === '::1'
|| normalized === '0.0.0.0'
|| normalized.startsWith('127.');
}
function trustedLaunchUrl(value: string, worksBase: URL): string | null {
try {
const target = new URL(value, worksBase);
const absoluteUrl = target.toString();
if (
absoluteUrl.length > 1_024
|| worksBase.protocol !== 'https:'
|| target.protocol !== 'https:'
|| target.origin !== worksBase.origin
|| Boolean(target.username || target.password)
|| isLoopbackHostname(target.hostname)
) {
return null;
}
return absoluteUrl;
} catch {
return null;
}
}
function trustedReleasePreviewUrl(
value: string,
worksBase: URL,
releaseId: string,
): string | null {
const trusted = trustedLaunchUrl(value, worksBase);
if (!trusted) return null;
const target = new URL(trusted);
const expectedPrefix = `/previews/${encodeURIComponent(releaseId)}/`;
return target.pathname.startsWith(expectedPrefix) ? trusted : null;
}
async function requireManagedAccessToken(): Promise<string> {
const accessToken = await getValidWorksSquareAccessToken();
if (!accessToken) {
@@ -294,7 +258,11 @@ async function resolveDevicePreview(
const targetVersionMatches = remote.latestVersionId === deployment.version_id
&& remote.latestVersionName === deployment.version_name;
const launchUrl = remote.runtimeUrl
? trustedLaunchUrl(remote.runtimeUrl, normalizedWorksBaseUrl())
? trustedWorksProjectPlayUrl(
remote.runtimeUrl,
normalizedWorksBaseUrl(),
deployment.app_id,
)
: null;
if (!targetVersionMatches) {
@@ -334,7 +302,7 @@ async function resolveDevicePreview(
remote.latestReleaseId,
accessToken,
);
const previewUrl = trustedReleasePreviewUrl(
const previewUrl = trustedWorksReleasePreviewUrl(
rawPreviewUrl,
normalizedWorksBaseUrl(),
remote.latestReleaseId,