security(web): sanitize runtime map config

This commit is contained in:
lingniu
2026-07-16 03:15:25 +08:00
parent 4be4aaf265
commit 01150844df
7 changed files with 48 additions and 4 deletions

View File

@@ -0,0 +1,12 @@
import { copyFileSync } from 'node:fs';
import { resolve } from 'node:path';
const source = resolve(process.cwd(), 'public/app-config.example.js');
const destination = resolve(process.cwd(), 'dist/app-config.js');
// Vite copies public/app-config.js verbatim. That file is intentionally ignored
// because developers may keep local AMap credentials in it. Never let those
// machine-local values enter a production archive; the API renders the real
// runtime configuration at /app-config.js on the server.
copyFileSync(source, destination);

View File

@@ -1,10 +1,12 @@
import { readdirSync, statSync } from 'node:fs';
import { readFileSync, readdirSync, statSync } from 'node:fs';
import { resolve } from 'node:path';
const assetsDirectory = resolve(process.cwd(), 'dist/assets');
const assets = readdirSync(assetsDirectory);
const excelAssets = assets.filter((name) => /^exceljs(?:\.min)?-[\w-]+\.js$/.test(name));
const mileageWorkers = assets.filter((name) => /^mileageExport\.worker-[\w-]+\.js$/.test(name));
const runtimeConfig = readFileSync(resolve(process.cwd(), 'dist/app-config.js'), 'utf8');
const safeRuntimeTemplate = readFileSync(resolve(process.cwd(), 'public/app-config.example.js'), 'utf8');
if (excelAssets.length !== 1) {
throw new Error(`production build must contain exactly one ExcelJS asset, found ${excelAssets.length}: ${excelAssets.join(', ') || 'none'}`);
@@ -12,7 +14,14 @@ if (excelAssets.length !== 1) {
if (mileageWorkers.length !== 1) {
throw new Error(`production build must contain exactly one mileage export worker, found ${mileageWorkers.length}: ${mileageWorkers.join(', ') || 'none'}`);
}
if (runtimeConfig !== safeRuntimeTemplate) {
throw new Error('production dist/app-config.js must match the credential-free runtime template');
}
const configuredSecurityCode = runtimeConfig.match(/["']?amapSecurityJsCode["']?\s*:\s*(["'])(.*?)\1/s)?.[2].trim();
if (configuredSecurityCode) {
throw new Error('production dist/app-config.js must not contain an AMap security code');
}
const excelBytes = statSync(resolve(assetsDirectory, excelAssets[0])).size;
const workerBytes = statSync(resolve(assetsDirectory, mileageWorkers[0])).size;
process.stdout.write(`web_build_gate=ok exceljs_assets=1 exceljs_bytes=${excelBytes} mileage_workers=1 worker_bytes=${workerBytes}\n`);
process.stdout.write(`web_build_gate=ok exceljs_assets=1 exceljs_bytes=${excelBytes} mileage_workers=1 worker_bytes=${workerBytes} runtime_config_sanitized=1\n`);