chore: harden production readiness gates

This commit is contained in:
Codex
2026-07-01 06:09:23 +08:00
parent f3f6028633
commit bb938cf6e2
13 changed files with 695 additions and 63 deletions

View File

@@ -102,6 +102,25 @@ function isLocalHost(hostname: string) {
return ['localhost', '127.0.0.1', '::1', '0.0.0.0'].includes(hostname);
}
function isHttpsProductionUrl(value: string) {
if (!value.trim()) return false;
try {
const url = new URL(value);
return url.protocol === 'https:' && !isLocalHost(url.hostname.toLowerCase());
} catch {
return false;
}
}
function isAllowedHost(value: string, allowedHosts: string[]) {
try {
const hostname = new URL(value).hostname.toLowerCase();
return allowedHosts.some(host => hostname === host || hostname.endsWith(`.${host}`));
} catch {
return false;
}
}
function isUnsafeSecret(value: string) {
const normalized = value.trim().toLowerCase();
return (
@@ -121,6 +140,8 @@ function validateProductionConfig(nextConfig: WorkerConfig) {
const failures: string[] = [];
if (nextConfig.storageDefaultProvider === 'local_dev') {
failures.push('STORAGE_DEFAULT_PROVIDER=local_dev is not allowed in production workers');
} else if (!['aliyun_oss', 'tencent_cos', 'supabase_storage'].includes(nextConfig.storageDefaultProvider)) {
failures.push('STORAGE_DEFAULT_PROVIDER must be aliyun_oss, tencent_cos or supabase_storage in production workers');
}
if (!nextConfig.storageDefaultBucket.trim()) {
failures.push('STORAGE_DEFAULT_BUCKET is required in production workers');
@@ -128,6 +149,9 @@ function validateProductionConfig(nextConfig: WorkerConfig) {
if (!nextConfig.storageRequireTenantPrefix) {
failures.push('STORAGE_REQUIRE_TENANT_PREFIX=false is not allowed in production workers');
}
if (nextConfig.storagePublicBaseUrl && !isHttpsProductionUrl(nextConfig.storagePublicBaseUrl)) {
failures.push('STORAGE_PUBLIC_BASE_URL must be a production HTTPS URL when configured');
}
if (nextConfig.assetSecurityScanFailOpen) {
failures.push('WORKER_ASSET_SECURITY_SCAN_FAIL_OPEN=true is not allowed in production workers');
}
@@ -158,12 +182,18 @@ function validateProductionConfig(nextConfig: WorkerConfig) {
}
}
if (nextConfig.storageDefaultProvider === 'aliyun_oss') {
if (nextConfig.aliyunOssInternal) {
failures.push('ALIYUN_OSS_INTERNAL=true is not allowed for user-facing production signed URLs');
}
if (!nextConfig.aliyunOssAccessKeyId || !nextConfig.aliyunOssAccessKeySecret) {
failures.push('ALIYUN_OSS_ACCESS_KEY_ID and ALIYUN_OSS_ACCESS_KEY_SECRET are required for aliyun_oss');
}
if (!nextConfig.aliyunOssRegion && !nextConfig.aliyunOssEndpoint) {
failures.push('ALIYUN_OSS_REGION or ALIYUN_OSS_ENDPOINT is required for aliyun_oss');
}
if (nextConfig.aliyunOssEndpoint && (!isHttpsProductionUrl(nextConfig.aliyunOssEndpoint) || !isAllowedHost(nextConfig.aliyunOssEndpoint, ['aliyuncs.com']))) {
failures.push('ALIYUN_OSS_ENDPOINT must be a production HTTPS aliyuncs.com endpoint');
}
}
if (nextConfig.storageDefaultProvider === 'tencent_cos') {
if (!nextConfig.tencentCosRegion || !nextConfig.tencentCosAppId || !nextConfig.tencentCosSecretId || !nextConfig.tencentCosSecretKey) {
@@ -174,6 +204,9 @@ function validateProductionConfig(nextConfig: WorkerConfig) {
if (!nextConfig.supabaseStorageUrl || !nextConfig.supabaseStorageServiceKey) {
failures.push('SUPABASE_STORAGE_URL and SUPABASE_STORAGE_SERVICE_KEY are required for supabase_storage');
}
if (nextConfig.supabaseStorageUrl && !isHttpsProductionUrl(nextConfig.supabaseStorageUrl)) {
failures.push('SUPABASE_STORAGE_URL must be a production HTTPS URL');
}
}
if (failures.length > 0) {