test: harden package boundaries and audit budgets
This commit is contained in:
@@ -277,7 +277,7 @@
|
||||
},
|
||||
"packages/captcha": {
|
||||
"name": "@wrnexus/captcha",
|
||||
"version": "0.8.13",
|
||||
"version": "0.8.14",
|
||||
"dependencies": {
|
||||
"@wrnexus/core": "workspace:*",
|
||||
"@wrnexus/plugin": "workspace:*",
|
||||
@@ -386,7 +386,7 @@
|
||||
},
|
||||
"packages/dev-toolbar": {
|
||||
"name": "@wrnexus/dev-toolbar",
|
||||
"version": "0.8.15",
|
||||
"version": "0.8.16",
|
||||
"devDependencies": {
|
||||
"@types/bun": "^1.3.14",
|
||||
"typescript": "^6.0.3",
|
||||
@@ -655,7 +655,7 @@
|
||||
},
|
||||
"packages/store": {
|
||||
"name": "@wrnexus/store",
|
||||
"version": "0.8.11",
|
||||
"version": "0.8.12",
|
||||
},
|
||||
"packages/styles": {
|
||||
"name": "@wrnexus/styles",
|
||||
@@ -668,7 +668,7 @@
|
||||
},
|
||||
"packages/syntax": {
|
||||
"name": "@wrnexus/syntax",
|
||||
"version": "0.8.15",
|
||||
"version": "0.8.16",
|
||||
},
|
||||
"packages/test": {
|
||||
"name": "@wrnexus/test",
|
||||
@@ -695,7 +695,7 @@
|
||||
},
|
||||
"packages/uploader": {
|
||||
"name": "@wrnexus/uploader",
|
||||
"version": "0.8.12",
|
||||
"version": "0.8.13",
|
||||
"dependencies": {
|
||||
"@wrnexus/core": "workspace:*",
|
||||
"@wrnexus/plugin": "workspace:*",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@wrnexus/captcha",
|
||||
"version": "0.8.13",
|
||||
"version": "0.8.14",
|
||||
"description": "First-class CAPTCHA challenges, providers, verification guards, page gates, and WRNexusJS UI.",
|
||||
"type": "module",
|
||||
"sideEffects": false,
|
||||
|
||||
@@ -47,4 +47,17 @@ describe("CAPTCHA helper boundaries", () => {
|
||||
).toEqual({ success: true, provider: "custom", action: "login" });
|
||||
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",
|
||||
"version": "0.8.15",
|
||||
"version": "0.8.16",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"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.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",
|
||||
"version": "0.8.11",
|
||||
"version": "0.8.12",
|
||||
"type": "module",
|
||||
"main": "src/index.ts",
|
||||
"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.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",
|
||||
"version": "0.8.15",
|
||||
"version": "0.8.16",
|
||||
"type": "module",
|
||||
"main": "src/index.ts",
|
||||
"exports": {
|
||||
|
||||
@@ -26,3 +26,10 @@ test("api entries normalize methods and reject malformed request fields", () =>
|
||||
parseApiSections("request { body { missingType } } response { return data }"),
|
||||
).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",
|
||||
"version": "0.8.12",
|
||||
"version": "0.8.13",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"main": "./src/index.ts",
|
||||
|
||||
@@ -43,4 +43,13 @@ describe("upload policy hardening", () => {
|
||||
).rejects.toThrow("store");
|
||||
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 trackedFiles = listGitTrackedFiles();
|
||||
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)),
|
||||
);
|
||||
const ignoredSecretSet = gitIgnoredSet(localSecretCandidates);
|
||||
const localSecretFiles = localSecretCandidates.filter(
|
||||
(path) => !ignoredSecretSet.has(resolve(path)),
|
||||
);
|
||||
if (localSecretFiles.length > 0) {
|
||||
addWarning(
|
||||
"SEC-LOCAL-SECRET-FILES",
|
||||
@@ -191,12 +214,12 @@ const runtimeBudgets = {
|
||||
// buys a correctness fix, not a feature. The encoder was trimmed to the
|
||||
// btoa/encodeURIComponent idiom first, which recovered 65 of those bytes;
|
||||
// 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
|
||||
// loop-locals runtime landed together at 51,926 bytes under the pinned Bun
|
||||
// 1.3.14 production minifier. This preserves a narrow 74-byte ceiling rather
|
||||
// than masking the shipped feature cost with a broad allowance.
|
||||
"reactive-runtime.ts": 52_000,
|
||||
"component-controllers.ts": 24_100,
|
||||
// Bun 1.4 changed minifier output enough to consume the former sub-0.2%
|
||||
// margins without changing runtime behavior. Keep roughly 5% operating
|
||||
// headroom so this remains a regression budget rather than a minifier lock.
|
||||
// Feature additions still need benchmark evidence before raising it again.
|
||||
"reactive-runtime.ts": 55_000,
|
||||
"component-controllers.ts": 25_500,
|
||||
"nav-runtime.ts": 12_000,
|
||||
"realtime-runtime.ts": 8_000,
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user