补强静态发布安全边界
This commit is contained in:
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user