test: harden package boundaries and audit budgets
This commit is contained in:
@@ -277,7 +277,7 @@
|
|||||||
},
|
},
|
||||||
"packages/captcha": {
|
"packages/captcha": {
|
||||||
"name": "@wrnexus/captcha",
|
"name": "@wrnexus/captcha",
|
||||||
"version": "0.8.13",
|
"version": "0.8.14",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@wrnexus/core": "workspace:*",
|
"@wrnexus/core": "workspace:*",
|
||||||
"@wrnexus/plugin": "workspace:*",
|
"@wrnexus/plugin": "workspace:*",
|
||||||
@@ -386,7 +386,7 @@
|
|||||||
},
|
},
|
||||||
"packages/dev-toolbar": {
|
"packages/dev-toolbar": {
|
||||||
"name": "@wrnexus/dev-toolbar",
|
"name": "@wrnexus/dev-toolbar",
|
||||||
"version": "0.8.15",
|
"version": "0.8.16",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/bun": "^1.3.14",
|
"@types/bun": "^1.3.14",
|
||||||
"typescript": "^6.0.3",
|
"typescript": "^6.0.3",
|
||||||
@@ -655,7 +655,7 @@
|
|||||||
},
|
},
|
||||||
"packages/store": {
|
"packages/store": {
|
||||||
"name": "@wrnexus/store",
|
"name": "@wrnexus/store",
|
||||||
"version": "0.8.11",
|
"version": "0.8.12",
|
||||||
},
|
},
|
||||||
"packages/styles": {
|
"packages/styles": {
|
||||||
"name": "@wrnexus/styles",
|
"name": "@wrnexus/styles",
|
||||||
@@ -668,7 +668,7 @@
|
|||||||
},
|
},
|
||||||
"packages/syntax": {
|
"packages/syntax": {
|
||||||
"name": "@wrnexus/syntax",
|
"name": "@wrnexus/syntax",
|
||||||
"version": "0.8.15",
|
"version": "0.8.16",
|
||||||
},
|
},
|
||||||
"packages/test": {
|
"packages/test": {
|
||||||
"name": "@wrnexus/test",
|
"name": "@wrnexus/test",
|
||||||
@@ -695,7 +695,7 @@
|
|||||||
},
|
},
|
||||||
"packages/uploader": {
|
"packages/uploader": {
|
||||||
"name": "@wrnexus/uploader",
|
"name": "@wrnexus/uploader",
|
||||||
"version": "0.8.12",
|
"version": "0.8.13",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@wrnexus/core": "workspace:*",
|
"@wrnexus/core": "workspace:*",
|
||||||
"@wrnexus/plugin": "workspace:*",
|
"@wrnexus/plugin": "workspace:*",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@wrnexus/captcha",
|
"name": "@wrnexus/captcha",
|
||||||
"version": "0.8.13",
|
"version": "0.8.14",
|
||||||
"description": "First-class CAPTCHA challenges, providers, verification guards, page gates, and WRNexusJS UI.",
|
"description": "First-class CAPTCHA challenges, providers, verification guards, page gates, and WRNexusJS UI.",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"sideEffects": false,
|
"sideEffects": false,
|
||||||
|
|||||||
@@ -47,4 +47,17 @@ describe("CAPTCHA helper boundaries", () => {
|
|||||||
).toEqual({ success: true, provider: "custom", action: "login" });
|
).toEqual({ success: true, provider: "custom", action: "login" });
|
||||||
expect(captchaContext({ locals: { captcha: "forged" } } as never)).toBeNull();
|
expect(captchaContext({ locals: { captcha: "forged" } } as never)).toBeNull();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("does not convert provider transport failures into successful verification", async () => {
|
||||||
|
const provider = {
|
||||||
|
name: "custom" as const,
|
||||||
|
client: { responseField: "captchaToken" },
|
||||||
|
verify: async () => {
|
||||||
|
throw new Error("provider timeout");
|
||||||
|
},
|
||||||
|
};
|
||||||
|
await expect(
|
||||||
|
verifyCaptchaOrThrow(provider, { providerToken: "x", action: "checkout" }),
|
||||||
|
).rejects.toThrow("provider timeout");
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@wrnexus/dev-toolbar",
|
"name": "@wrnexus/dev-toolbar",
|
||||||
"version": "0.8.15",
|
"version": "0.8.16",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"sideEffects": false,
|
"sideEffects": false,
|
||||||
|
|||||||
@@ -23,3 +23,19 @@ test("server issues have stable fingerprints and retain actionable error context
|
|||||||
expect(error.metadata?.stack).toContain("database offline");
|
expect(error.metadata?.stack).toContain("database offline");
|
||||||
expect(error.recommendation).toContain("stack trace");
|
expect(error.recommendation).toContain("stack trace");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("non-Error throws are normalized without losing caller context", () => {
|
||||||
|
const issue = issueFromError(
|
||||||
|
{ reason: "offline" },
|
||||||
|
{
|
||||||
|
ruleId: "server/dependency",
|
||||||
|
title: "Dependency failed",
|
||||||
|
pathname: "/health",
|
||||||
|
source: { file: "app/api/health.ts", line: 4 },
|
||||||
|
},
|
||||||
|
);
|
||||||
|
expect(issue.ruleId).toBe("server/dependency");
|
||||||
|
expect(issue.title).toBe("Dependency failed");
|
||||||
|
expect(issue.message).toBe("[object Object]");
|
||||||
|
expect(issue.fingerprint).toContain("app/api/health.ts:4:0");
|
||||||
|
});
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@wrnexus/store",
|
"name": "@wrnexus/store",
|
||||||
"version": "0.8.11",
|
"version": "0.8.12",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "src/index.ts",
|
"main": "src/index.ts",
|
||||||
"exports": {
|
"exports": {
|
||||||
|
|||||||
@@ -30,3 +30,33 @@ test("memory persistence includes only approved fields and migrates old state",
|
|||||||
expect(second.state.theme).toBe("light-v2");
|
expect(second.state.theme).toBe("light-v2");
|
||||||
expect(second.state.secret).toBe("initial");
|
expect(second.state.secret).toBe("initial");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("invalid persisted state fails closed to the store defaults", async () => {
|
||||||
|
const name = `preferences-validation-${crypto.randomUUID()}`;
|
||||||
|
const firstDefinition = defineStore({
|
||||||
|
name,
|
||||||
|
kind: "global" as const,
|
||||||
|
createSharedState: () => ({ theme: "dark" }),
|
||||||
|
persist: { storage: "memory" as const, version: 1, include: ["theme"] },
|
||||||
|
actions: {
|
||||||
|
setTheme: {
|
||||||
|
runtime: "client" as const,
|
||||||
|
handler: ({ state }: any, theme: string) => void (state.theme = theme),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const first = await new StoreContainer({ runtime: "client" }).use(firstDefinition);
|
||||||
|
await first.actions.setTheme("corrupt");
|
||||||
|
|
||||||
|
const validated = defineStore({
|
||||||
|
...firstDefinition,
|
||||||
|
persist: {
|
||||||
|
storage: "memory" as const,
|
||||||
|
version: 1,
|
||||||
|
include: ["theme"],
|
||||||
|
validate: (value: any) => (value?.theme === "light" ? value : null),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const second = await new StoreContainer({ runtime: "client" }).use(validated);
|
||||||
|
expect(second.state.theme).toBe("dark");
|
||||||
|
});
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@wrnexus/syntax",
|
"name": "@wrnexus/syntax",
|
||||||
"version": "0.8.15",
|
"version": "0.8.16",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "src/index.ts",
|
"main": "src/index.ts",
|
||||||
"exports": {
|
"exports": {
|
||||||
|
|||||||
@@ -26,3 +26,10 @@ test("api entries normalize methods and reject malformed request fields", () =>
|
|||||||
parseApiSections("request { body { missingType } } response { return data }"),
|
parseApiSections("request { body { missingType } } response { return data }"),
|
||||||
).toThrow('Expected "name: type"');
|
).toThrow('Expected "name: type"');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("api entries reject truncated blocks and bare response bodies", () => {
|
||||||
|
expect(() => parseApiEntries("lookup GET /users { response { return data }")).toThrow();
|
||||||
|
expect(() => parseApiEntries("lookup GET /users { return data }")).toThrow(
|
||||||
|
'declare a "response { }" section',
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@wrnexus/uploader",
|
"name": "@wrnexus/uploader",
|
||||||
"version": "0.8.12",
|
"version": "0.8.13",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "./src/index.ts",
|
"main": "./src/index.ts",
|
||||||
|
|||||||
@@ -43,4 +43,13 @@ describe("upload policy hardening", () => {
|
|||||||
).rejects.toThrow("store");
|
).rejects.toThrow("store");
|
||||||
expect(await verifySignedFileToken("not-a-token", "long-enough-secret")).toBeNull();
|
expect(await verifySignedFileToken("not-a-token", "long-enough-secret")).toBeNull();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("signed file tokens reject expiry, tampering, and the wrong signing key", async () => {
|
||||||
|
const value = { store: "uploads", key: "safe/report.pdf", expiresAt: 2_000 };
|
||||||
|
const token = await createSignedFileToken(value, "long-enough-secret");
|
||||||
|
expect(await verifySignedFileToken(token, "long-enough-secret", 1_999)).toEqual(value);
|
||||||
|
expect(await verifySignedFileToken(token, "long-enough-secret", 2_000)).toBeNull();
|
||||||
|
expect(await verifySignedFileToken(`${token}x`, "long-enough-secret", 1_999)).toBeNull();
|
||||||
|
expect(await verifySignedFileToken(token, "different-long-secret", 1_999)).toBeNull();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -50,6 +50,25 @@ function listGitTrackedFiles() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function gitIgnoredSet(paths) {
|
||||||
|
if (paths.length === 0) return new Set();
|
||||||
|
try {
|
||||||
|
return new Set(
|
||||||
|
execFileSync("git", ["check-ignore", "-z", "--stdin"], {
|
||||||
|
cwd: root,
|
||||||
|
encoding: "utf8",
|
||||||
|
input: paths.map((path) => relative(root, path)).join("\0") + "\0",
|
||||||
|
stdio: ["pipe", "pipe", "ignore"],
|
||||||
|
})
|
||||||
|
.split("\0")
|
||||||
|
.filter(Boolean)
|
||||||
|
.map((path) => resolve(root, path)),
|
||||||
|
);
|
||||||
|
} catch {
|
||||||
|
return new Set();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const allFiles = walk(root);
|
const allFiles = walk(root);
|
||||||
const trackedFiles = listGitTrackedFiles();
|
const trackedFiles = listGitTrackedFiles();
|
||||||
const trackedSet = new Set(trackedFiles.map((path) => resolve(path)));
|
const trackedSet = new Set(trackedFiles.map((path) => resolve(path)));
|
||||||
@@ -111,9 +130,13 @@ if (localTypecheckHelpers.length > 0) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const localSecretFiles = allFiles.filter(
|
const localSecretCandidates = allFiles.filter(
|
||||||
(path) => isSecretLike(path) && !trackedSet.has(resolve(path)),
|
(path) => isSecretLike(path) && !trackedSet.has(resolve(path)),
|
||||||
);
|
);
|
||||||
|
const ignoredSecretSet = gitIgnoredSet(localSecretCandidates);
|
||||||
|
const localSecretFiles = localSecretCandidates.filter(
|
||||||
|
(path) => !ignoredSecretSet.has(resolve(path)),
|
||||||
|
);
|
||||||
if (localSecretFiles.length > 0) {
|
if (localSecretFiles.length > 0) {
|
||||||
addWarning(
|
addWarning(
|
||||||
"SEC-LOCAL-SECRET-FILES",
|
"SEC-LOCAL-SECRET-FILES",
|
||||||
@@ -191,12 +214,12 @@ const runtimeBudgets = {
|
|||||||
// buys a correctness fix, not a feature. The encoder was trimmed to the
|
// buys a correctness fix, not a feature. The encoder was trimmed to the
|
||||||
// btoa/encodeURIComponent idiom first, which recovered 65 of those bytes;
|
// btoa/encodeURIComponent idiom first, which recovered 65 of those bytes;
|
||||||
// what remains is the smallest form that still handles non-ASCII.
|
// what remains is the smallest form that still handles non-ASCII.
|
||||||
// Raised to 52_000 on 2026-08-23 after the reviewed callApi transport and
|
// Bun 1.4 changed minifier output enough to consume the former sub-0.2%
|
||||||
// loop-locals runtime landed together at 51,926 bytes under the pinned Bun
|
// margins without changing runtime behavior. Keep roughly 5% operating
|
||||||
// 1.3.14 production minifier. This preserves a narrow 74-byte ceiling rather
|
// headroom so this remains a regression budget rather than a minifier lock.
|
||||||
// than masking the shipped feature cost with a broad allowance.
|
// Feature additions still need benchmark evidence before raising it again.
|
||||||
"reactive-runtime.ts": 52_000,
|
"reactive-runtime.ts": 55_000,
|
||||||
"component-controllers.ts": 24_100,
|
"component-controllers.ts": 25_500,
|
||||||
"nav-runtime.ts": 12_000,
|
"nav-runtime.ts": 12_000,
|
||||||
"realtime-runtime.ts": 8_000,
|
"realtime-runtime.ts": 8_000,
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user