71 lines
2.8 KiB
TypeScript
71 lines
2.8 KiB
TypeScript
import { describe, expect, it } from "vitest";
|
|
|
|
import {
|
|
PLUGIN_SETTING_ARRAY_MAX_LENGTH,
|
|
PLUGIN_SETTING_KEY_MAX_LENGTH,
|
|
PLUGIN_SETTING_OBJECT_MAX_KEYS,
|
|
PLUGIN_SETTING_VALUE_MAX_BYTES,
|
|
isPluginSettingKeyFormatSafe,
|
|
isPluginSettingKeyReserved,
|
|
isPluginSettingKeySecretLike,
|
|
isPluginSettingValue,
|
|
isPluginSettingValueShapeSafe,
|
|
isPluginSettingValueWithinSize,
|
|
isSafePluginSettingKey,
|
|
} from "./pluginSettingsValidation";
|
|
|
|
describe("plugin settings validation", () => {
|
|
it("keeps frame and persistence setting keys on the same safe-key contract", () => {
|
|
expect(isSafePluginSettingKey("theme.accent:color-1")).toBe(true);
|
|
expect(isPluginSettingKeyFormatSafe("k".repeat(PLUGIN_SETTING_KEY_MAX_LENGTH))).toBe(true);
|
|
expect(isPluginSettingKeyFormatSafe("k".repeat(PLUGIN_SETTING_KEY_MAX_LENGTH + 1))).toBe(false);
|
|
|
|
for (const key of [
|
|
"../theme",
|
|
"__proto__",
|
|
"constructor",
|
|
"prototype",
|
|
"apiKey",
|
|
"access-token",
|
|
"password",
|
|
"secret.value",
|
|
"authorization",
|
|
"credentials",
|
|
"auth.header",
|
|
"bearer",
|
|
"cookie",
|
|
"sessionId",
|
|
"private_key",
|
|
"clientSecret",
|
|
"webhookUrl",
|
|
]) {
|
|
expect(isSafePluginSettingKey(key)).toBe(false);
|
|
}
|
|
expect(isPluginSettingKeyReserved("__proto__")).toBe(true);
|
|
expect(isPluginSettingKeySecretLike("refresh-token")).toBe(true);
|
|
expect(isPluginSettingKeySecretLike("authorization")).toBe(true);
|
|
expect(isPluginSettingKeySecretLike("clientSecret")).toBe(true);
|
|
});
|
|
|
|
it("rejects values that cannot cross the iframe-to-persistence boundary safely", () => {
|
|
expect(isPluginSettingValue({ theme: "dark", flags: [true, false], count: 2 })).toBe(true);
|
|
expect(isPluginSettingValue(Number.NaN)).toBe(false);
|
|
expect(isPluginSettingValue(Array.from({ length: PLUGIN_SETTING_ARRAY_MAX_LENGTH + 1 }, () => true))).toBe(false);
|
|
expect(isPluginSettingValue(Object.fromEntries(Array.from({ length: PLUGIN_SETTING_OBJECT_MAX_KEYS + 1 }, (_, index) => [`key${index}`, true])))).toBe(
|
|
false
|
|
);
|
|
expect(isPluginSettingValue(JSON.parse('{"theme":"dark","__proto__":{"polluted":true}}'))).toBe(false);
|
|
expect(isPluginSettingValue({ theme: { accessToken: "must-not-persist" } })).toBe(false);
|
|
expect(isPluginSettingValue({ request: { authorization: "Bearer must-not-persist" } })).toBe(false);
|
|
expect(isPluginSettingValue({ endpoint: { webhookUrl: "https://hooks.example.test/secret" } })).toBe(false);
|
|
});
|
|
|
|
it("separates JSON shape errors from serialized size errors for server error mapping", () => {
|
|
const oversized = "x".repeat(PLUGIN_SETTING_VALUE_MAX_BYTES);
|
|
|
|
expect(isPluginSettingValueShapeSafe(oversized)).toBe(true);
|
|
expect(isPluginSettingValueWithinSize(oversized)).toBe(false);
|
|
expect(isPluginSettingValue(oversized)).toBe(false);
|
|
});
|
|
});
|