- Move the read-only OpenAPI compat surface from /api/v2/assets to /mjr/am/compat/assets so it never squats ComfyUI core's /api namespace; the legacy alias is opt-in via MJR_AM_LEGACY_API_V2_ASSETS=1. - Allow Python 3.14 (requires-python <3.15) and add 3.14 to CI and tox. - Stop wrapping api.queuePrompt: progress tracking now resolves the running prompt from the core Jobs API (getJobDetail), then /queue and /history, driven by websocket events. Remove wrapHostQueuePrompt. - Add opt-in Majoor.Integration.HideNativeAssetsTab setting that hides ComfyUI's native Assets sidebar tab through the official getSidebarTabs/unregisterSidebarTab API and restores it when disabled. - Fix all 913 type errors in tsconfig.test.json and type-check tests in CI; drop a duplicated `stubs` key in asset_card_inner test; make addToastHistory's type argument optional for the object form. - Update audit report, changelog, API reference and env var docs.
62 lines
2.1 KiB
TypeScript
62 lines
2.1 KiB
TypeScript
import { beforeEach, describe, expect, it, vi } from "vitest";
|
|
import { getRuntimeState, setRuntimeStatePatch } from "../app/runtimeState.js";
|
|
|
|
describe("runtimeState prototype-pollution resistance", () => {
|
|
beforeEach(() => {
|
|
const state = getRuntimeState();
|
|
for (const key of Object.keys(state)) {
|
|
delete state[key];
|
|
}
|
|
Object.assign(state, {
|
|
api: null,
|
|
assetsDeletedHandler: null,
|
|
enrichmentActive: false,
|
|
enrichmentQueueLength: 0,
|
|
});
|
|
});
|
|
|
|
it("blocks __proto__ key from being set via patch", () => {
|
|
const proto = Object.getPrototypeOf({});
|
|
const patch = JSON.parse('{"__proto__": {"polluted": true}}');
|
|
setRuntimeStatePatch(patch);
|
|
|
|
expect(({} as any).polluted).toBeUndefined();
|
|
expect(Object.getPrototypeOf({})).toBe(proto);
|
|
});
|
|
|
|
it("blocks constructor key from being set via patch", () => {
|
|
setRuntimeStatePatch({ constructor: "evil" });
|
|
|
|
const state = getRuntimeState();
|
|
expect(Object.hasOwn(state, "constructor")).toBe(false);
|
|
});
|
|
|
|
it("blocks prototype key from being set via patch", () => {
|
|
setRuntimeStatePatch({ prototype: { polluted: true } });
|
|
|
|
const state = getRuntimeState();
|
|
expect(state.prototype).toBeUndefined();
|
|
});
|
|
|
|
it("applies safe keys normally", () => {
|
|
setRuntimeStatePatch({ enrichmentActive: true, enrichmentQueueLength: 5 });
|
|
|
|
const state = getRuntimeState();
|
|
expect(state.enrichmentActive).toBe(true);
|
|
expect(state.enrichmentQueueLength).toBe(5);
|
|
});
|
|
|
|
it("handles null/undefined patch gracefully", () => {
|
|
expect(() => setRuntimeStatePatch(null)).not.toThrow();
|
|
expect(() => setRuntimeStatePatch(undefined)).not.toThrow();
|
|
expect(() => setRuntimeStatePatch()).not.toThrow();
|
|
});
|
|
|
|
it("uses Symbol key so state is not enumerable on globalThis", () => {
|
|
getRuntimeState();
|
|
const keys = Object.keys(globalThis);
|
|
const hasPlainKey = keys.some((k) => k.includes("runtime_state") || k.includes("majoor"));
|
|
expect(hasPlainKey).toBe(false);
|
|
});
|
|
});
|