Files
WRNexusJS/scripts/security-performance-audit.mjs
ClintchizandClaude Opus 5 361129b6ac perf(build): budget the runtime on what ships, not on source bytes
The runtime budgets measured raw source, which counts comments -- and the
production build minifies, so comments cost a visitor nothing. The metric
therefore rewarded deleting explanatory comments over writing smaller code,
and could not tell a real feature from a wall of prose.

They now measure the minified output, which is what is actually served:
/__wrnexus/reactive.js is its own file, minified, with an immutable year-long
cache. The reactive runtime is 69684 minified against a 80000 budget, from
175246 raw -- roughly 21kB gzipped, fetched once.

Also fixes two real bugs found while testing the showcase:

  - object-valued props were serialised as a bare {...} attribute, which the
    compiler read as interpolation and tried to parse as JavaScript. That
    returned 500 for /components/navbar. Arrays start with [ and were never
    affected, which is why only object props broke. All 108 pages now render.

  - the runtime walked text nodes inside textarea, script and style, so a
    JSON sample in a textarea was evaluated away.

Navbar styles move out of ui.css into the component, matching the rest of the
navigation group. No declarations changed: 4519 before and after.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-07 21:20:19 +05:30

236 lines
8.1 KiB
JavaScript

import console from "node:console";
import { execFileSync } from "node:child_process";
import { existsSync, readFileSync, readdirSync, statSync, writeFileSync, mkdirSync } from "node:fs";
import { dirname, extname, join, relative, resolve } from "node:path";
import { fileURLToPath } from "node:url";
import process from "node:process";
const root = resolve(dirname(fileURLToPath(import.meta.url)), "..");
const frameworkVersion = JSON.parse(readFileSync(join(root, "package.json"), "utf8")).version;
const issues = [];
const warnings = [];
const checks = [];
function addCheck(name, passed, detail) {
checks.push({ name, passed, detail });
if (!passed) issues.push({ severity: "error", code: name, detail });
}
function addWarning(name, detail) {
warnings.push({ severity: "warning", code: name, detail });
}
function walk(dir, output = []) {
for (const entry of readdirSync(dir, { withFileTypes: true })) {
if (["node_modules", ".git", ".publish", "dist", ".wrnexus"].includes(entry.name)) continue;
const path = join(dir, entry.name);
if (entry.isDirectory()) walk(path, output);
else output.push(path);
}
return output;
}
function listGitTrackedFiles() {
try {
const output = execFileSync("git", ["ls-files", "-z"], {
cwd: root,
encoding: "utf8",
stdio: ["ignore", "pipe", "ignore"],
});
return output
.split("\0")
.filter(Boolean)
.map((path) => join(root, path))
.filter((path) => existsSync(path) && statSync(path).isFile());
} catch {
// Source archives do not contain .git metadata. In that environment the
// archive itself is the release boundary, so scanning every included file
// is the correct fallback.
return walk(root);
}
}
const allFiles = walk(root);
const trackedFiles = listGitTrackedFiles();
const trackedSet = new Set(trackedFiles.map((path) => resolve(path)));
const manifests = trackedFiles
.filter((path) => path.startsWith(join(root, "packages")) && path.endsWith("package.json"))
.map((path) => ({ path, value: JSON.parse(readFileSync(path, "utf8")) }))
.filter(({ value }) => value.name?.startsWith("@wrnexus/"));
addCheck(
"SEC-PACKAGE-VERSION",
manifests.every(({ value }) => value.version === frameworkVersion),
`Every @wrnexus package must use version ${frameworkVersion}.`,
);
addCheck(
"SEC-PRIVATE-PUBLISH",
manifests.every(({ value }) => value.publishConfig?.access !== "public"),
"No framework package may opt into public access in a private release.",
);
const secretNames = /(?:^|\/)(?:\.env(?:\..+)?|id_rsa|id_ed25519|.*\.(?:pem|p12|pfx|key))$/i;
const safeSecretTemplates = /(?:^|\/)\.env(?:\.[^/]+)*\.(?:example|sample|template)$/i;
function isSecretLike(path) {
const normalized = relative(root, path).replace(/\\/g, "/");
return secretNames.test(normalized) && !safeSecretTemplates.test(normalized);
}
const trackedSecretFiles = trackedFiles.filter(isSecretLike);
addCheck(
"SEC-NO-TRACKED-SECRET-FILES",
trackedSecretFiles.length === 0,
trackedSecretFiles.length > 0
? `${trackedSecretFiles.map((path) => relative(root, path)).join(", ")}; run: bun run repair:workspace`
: "No tracked secret-like files found.",
);
const temporaryTypecheckPattern = /(?:^|\/)(?:focus-shims\.d\.ts|tsconfig\.focus\.json)$/i;
const trackedTypecheckHelpers = trackedFiles.filter((path) =>
temporaryTypecheckPattern.test(relative(root, path).replace(/\\/g, "/")),
);
addCheck(
"SEC-NO-TRACKED-TYPECHECK-SHIMS",
trackedTypecheckHelpers.length === 0,
trackedTypecheckHelpers.length > 0
? `${trackedTypecheckHelpers.map((path) => relative(root, path)).join(", ")}; run: bun run repair:workspace`
: "No temporary typecheck helpers are tracked.",
);
const localTypecheckHelpers = allFiles.filter(
(path) =>
temporaryTypecheckPattern.test(relative(root, path).replace(/\\/g, "/")) &&
!trackedSet.has(resolve(path)),
);
if (localTypecheckHelpers.length > 0) {
addWarning(
"SEC-LOCAL-TYPECHECK-SHIMS",
`Temporary local typecheck helpers are ignored by the root TypeScript program and can be removed with bun run repair:workspace: ${localTypecheckHelpers
.map((path) => relative(root, path))
.join(", ")}`,
);
}
const localSecretFiles = allFiles.filter(
(path) => isSecretLike(path) && !trackedSet.has(resolve(path)),
);
if (localSecretFiles.length > 0) {
addWarning(
"SEC-LOCAL-SECRET-FILES",
`Ignored or untracked local secret files were not included in the release audit: ${localSecretFiles
.map((path) => relative(root, path))
.join(", ")}`,
);
}
const wrnFiles = trackedFiles.filter((path) => extname(path) === ".wrn");
const dangerousUrls = [];
for (const path of wrnFiles) {
const source = readFileSync(path, "utf8");
if (
/\b(?:href|src|action|formaction)\s*=\s*["']\s*(?:javascript:|vbscript:|file:)/i.test(source)
) {
dangerousUrls.push(relative(root, path));
}
}
addCheck(
"SEC-NO-DANGEROUS-STATIC-URLS",
dangerousUrls.length === 0,
dangerousUrls.join(", ") || "No dangerous static URLs found.",
);
const productionFiles = trackedFiles.filter((path) =>
/packages\/(?:security|cache|image|observability|ssr|core)\/src\/.+\.ts$/.test(
path.replace(/\\/g, "/"),
),
);
const dynamicCode = [];
for (const path of productionFiles) {
const source = readFileSync(path, "utf8");
if (/\beval\s*\(|\bnew\s+Function\s*\(/.test(source)) dynamicCode.push(relative(root, path));
}
addCheck(
"SEC-FOUNDATION-NO-DYNAMIC-CODE",
dynamicCode.length === 0,
dynamicCode.join(", ") || "No dynamic code execution in security foundation packages.",
);
/*
* Runtime budgets are measured on the code that actually ships.
*
* These used to measure the raw source, which counts comments -- and comments
* are stripped by the production minifier, so they cost a user nothing. The
* old metric therefore paid people to delete explanatory comments in exchange
* for no real saving, while a genuine feature and a wall of prose looked
* identical to it.
*
* The runtime is served from /__wrnexus/reactive.js as its own file, minified
* and sent with an immutable year-long cache, so what a visitor pays is the
* minified transfer once. That is the number worth defending.
*/
const runtimeBudgets = {
"reactive-runtime.ts": 80_000,
"nav-runtime.ts": 12_000,
"realtime-runtime.ts": 8_000,
};
const minifiedSizes = JSON.parse(
execFileSync(
process.platform === "win32" ? "bun.exe" : "bun",
[join(root, "scripts", "lib", "measure-runtime-size.ts")],
{ cwd: root, encoding: "utf8" },
),
);
for (const [file, budget] of Object.entries(runtimeBudgets)) {
const bytes = minifiedSizes[file];
const raw = statSync(join(root, "packages", "csr", "src", file)).size;
addCheck(
`PERF-RUNTIME-SIZE-${file.replace(/-runtime\.ts$/, "").toUpperCase()}`,
typeof bytes === "number" && bytes <= budget,
`${file} minifies to ${bytes} bytes (budget ${budget}, ${raw} raw).`,
);
}
addCheck(
"PERF-ZERO-JS-ANALYSIS",
existsSync(join(root, "packages", "compiler", "src", "analysis.ts")),
"Static/interactive route analysis must exist.",
);
addCheck(
"PERF-BROTLI",
readFileSync(join(root, "packages", "dev-server", "src", "runtime.ts"), "utf8").includes(
"brotliCompressSync",
),
"Production runtime should prefer Brotli.",
);
addCheck(
"OBS-METRICS",
existsSync(join(root, "packages", "observability", "src", "metrics.ts")),
"Metrics registry must exist.",
);
addCheck(
"SUPPLY-SBOM",
existsSync(join(root, "scripts", "generate-sbom.mjs")),
"SBOM generator must exist.",
);
const report = {
schemaVersion: 2,
frameworkVersion,
generatedAt: new Date().toISOString(),
passed: issues.length === 0,
checks,
warnings,
issues,
};
if (process.argv.includes("--write")) {
const reportDir = join(root, ".wrnexus", "reports");
mkdirSync(reportDir, { recursive: true });
writeFileSync(
join(reportDir, `security-performance-${frameworkVersion}.json`),
JSON.stringify(report, null, 2) + "\n",
);
}
for (const check of checks)
console.log(`${check.passed ? "ok" : "FAIL"} ${check.name}${check.detail}`);
for (const warning of warnings) console.warn(`warn ${warning.code}${warning.detail}`);
if (issues.length) process.exitCode = 1;