mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-08-23 15:42:12 +03:00
fix(security): resolve open CodeQL alerts (#10188)
Alert 806 (js/insecure-randomness, open-sse/executors/tinycms.ts): the TinyCMS nonce is signed into x-secure-signature and reused as x-secure-nonce / x-session-id, so the Math.random() fallback made a signed request predictable and replayable. Use randomUUID() from node:crypto unconditionally. Alert 811 (js/double-escaping, chatgpt-web adapters/environment.ts): decodeXmlText() decoded & before " / ', so the bare & it produced was re-consumed and the text was unescaped twice (&quot; collapsed to "). These values become the trusted Codex sandbox cwd / workspace_roots, so the double-unescape silently rewrote the workspace boundary. Decode & last. Alerts 813/814 (js/incomplete-url-substring-sanitization, test files): replace the includes() URL checks with exact comparisons (new URL(url).hostname === ... and an explicit === over the recorded URL array). Both assertions get strictly tighter. Regression guards: tests/unit/tinycms-secure-nonce-randomness.test.ts and tests/unit/chatgpt-web-environment-double-unescape.test.ts, both failing before the fix and passing after. Co-authored-by: backryun <bakryun0718@proton.me>
This commit is contained in:
committed by
GitHub
parent
423b616503
commit
c19da4db46
@@ -17,10 +17,7 @@ import assert from "node:assert/strict";
|
||||
import { WEB_COOKIE_PROVIDERS } from "../../src/shared/constants/providers/web-cookie.ts";
|
||||
import { REGISTRY } from "../../open-sse/config/providers/index.ts";
|
||||
import { getExecutor, TinyCmsExecutor } from "../../open-sse/executors/index.ts";
|
||||
import {
|
||||
setupDomMocks,
|
||||
type DomMockRestore,
|
||||
} from "../../open-sse/executors/tinycmsSigner.ts";
|
||||
import { setupDomMocks, type DomMockRestore } from "../../open-sse/executors/tinycmsSigner.ts";
|
||||
|
||||
// tinycmsSigner.ts intentionally does NOT install its window/document/canvas
|
||||
// shims as a module-load side effect (see setupDomMocks() there) — doing so
|
||||
@@ -41,9 +38,10 @@ after(() => {
|
||||
// ── Catalog / WEB_COOKIE_PROVIDERS ────────────────────────────────────────────
|
||||
|
||||
test("tinycms-web is present in WEB_COOKIE_PROVIDERS", () => {
|
||||
const p = (WEB_COOKIE_PROVIDERS as Record<string, unknown>)[
|
||||
"tinycms-web"
|
||||
] as Record<string, unknown>;
|
||||
const p = (WEB_COOKIE_PROVIDERS as Record<string, unknown>)["tinycms-web"] as Record<
|
||||
string,
|
||||
unknown
|
||||
>;
|
||||
assert.ok(p, "WEB_COOKIE_PROVIDERS['tinycms-web'] must exist");
|
||||
assert.equal(p.id, "tinycms-web");
|
||||
assert.equal(p.alias, "tcw");
|
||||
@@ -51,9 +49,10 @@ test("tinycms-web is present in WEB_COOKIE_PROVIDERS", () => {
|
||||
});
|
||||
|
||||
test("tinycms-web WEB_COOKIE_PROVIDERS entry is marked as free-tier", () => {
|
||||
const p = (WEB_COOKIE_PROVIDERS as Record<string, unknown>)[
|
||||
"tinycms-web"
|
||||
] as Record<string, unknown>;
|
||||
const p = (WEB_COOKIE_PROVIDERS as Record<string, unknown>)["tinycms-web"] as Record<
|
||||
string,
|
||||
unknown
|
||||
>;
|
||||
assert.equal(p.hasFree, true);
|
||||
assert.ok(typeof p.freeNote === "string" && (p.freeNote as string).length > 0);
|
||||
assert.ok(typeof p.authHint === "string" && (p.authHint as string).length > 0);
|
||||
@@ -79,10 +78,7 @@ test("tinycms-web registry has all expected models", () => {
|
||||
|
||||
assert.ok(ids.includes("gpt-5-free"), "gpt-5-free must be registered");
|
||||
assert.ok(ids.includes("gpt-5.3-free"), "gpt-5.3-free must be registered");
|
||||
assert.ok(
|
||||
ids.includes("gpt-5.3-thinking-free"),
|
||||
"gpt-5.3-thinking-free must be registered"
|
||||
);
|
||||
assert.ok(ids.includes("gpt-5.3-thinking-free"), "gpt-5.3-thinking-free must be registered");
|
||||
assert.ok(ids.includes("deepseek-v4-flash"), "deepseek-v4-flash must be registered");
|
||||
assert.ok(ids.includes("claude-sonnet-5"), "claude-sonnet-5 must be registered");
|
||||
assert.ok(ids.includes("gemini-3.5-flash"), "gemini-3.5-flash must be registered");
|
||||
@@ -140,10 +136,7 @@ test("TinyCmsExecutor returns 401 when UUID is missing", async () => {
|
||||
assert.equal(result.response.status, 401);
|
||||
const body = await result.response.json();
|
||||
const errMsg = body?.error?.message || "";
|
||||
assert.ok(
|
||||
errMsg.includes("Invalid or missing device UUID"),
|
||||
"error must mention missing UUID"
|
||||
);
|
||||
assert.ok(errMsg.includes("Invalid or missing device UUID"), "error must mention missing UUID");
|
||||
// Hard Rule #12: must NOT leak stack traces
|
||||
assert.ok(!errMsg.includes("at /"), "error must not contain a stack trace path");
|
||||
});
|
||||
@@ -161,10 +154,7 @@ test("TinyCmsExecutor returns 401 when UUID does not start with 'R'", async () =
|
||||
assert.equal(result.response.status, 401);
|
||||
const body = await result.response.json();
|
||||
const errMsg = body?.error?.message || "";
|
||||
assert.ok(
|
||||
errMsg.includes("Invalid or missing device UUID"),
|
||||
"error must mention missing UUID"
|
||||
);
|
||||
assert.ok(errMsg.includes("Invalid or missing device UUID"), "error must mention missing UUID");
|
||||
assert.ok(!errMsg.includes("at /"), "error must not contain a stack trace path");
|
||||
});
|
||||
|
||||
@@ -172,7 +162,7 @@ test("TinyCmsExecutor returns the standard executor response envelope on success
|
||||
const originalFetch = globalThis.fetch;
|
||||
globalThis.fetch = async (input) => {
|
||||
const url = String(input);
|
||||
if (url.includes("api64.ipify.org")) {
|
||||
if (new URL(url).hostname === "api64.ipify.org") {
|
||||
return new Response(JSON.stringify({ ip: "127.0.0.1" }), {
|
||||
headers: { "Content-Type": "application/json" },
|
||||
});
|
||||
@@ -217,10 +207,7 @@ test("TinyCmsExecutor returns the standard executor response envelope on success
|
||||
|
||||
test("initTinyCmsWasm module exports expected functions", async () => {
|
||||
const signer = await import("../../open-sse/executors/tinycmsSigner.ts");
|
||||
assert.ok(
|
||||
typeof signer.initTinyCmsWasm === "function",
|
||||
"must export initTinyCmsWasm function"
|
||||
);
|
||||
assert.ok(typeof signer.initTinyCmsWasm === "function", "must export initTinyCmsWasm function");
|
||||
assert.ok(
|
||||
typeof signer.generateSecurePayload === "function",
|
||||
"must export generateSecurePayload function"
|
||||
@@ -254,10 +241,7 @@ test("TinyCmsExecutor sanitizes errors (no stack traces in error response)", asy
|
||||
assert.ok(result.response, "response must be present");
|
||||
const body = await result.response.json();
|
||||
const errMsg = body?.error?.message || "";
|
||||
assert.ok(
|
||||
errMsg.includes("Invalid or missing device UUID"),
|
||||
"error must mention missing UUID"
|
||||
);
|
||||
assert.ok(errMsg.includes("Invalid or missing device UUID"), "error must mention missing UUID");
|
||||
assert.ok(!errMsg.includes("at /"), "error must not contain a stack trace path (Hard Rule #12)");
|
||||
});
|
||||
|
||||
@@ -274,12 +258,6 @@ test("tinycms-web credential requirement is kind: token with app-config-uuid", a
|
||||
assert.equal(req.credentialName, "app-config-uuid");
|
||||
assert.equal(req.acceptsFullCookieHeader, false);
|
||||
assert.ok(Array.isArray(req.storageKeys), "must have storageKeys array");
|
||||
assert.ok(
|
||||
(req.storageKeys as string[]).includes("apiKey"),
|
||||
"apiKey must be in storageKeys"
|
||||
);
|
||||
assert.ok(
|
||||
(req.storageKeys as string[]).includes("uuid"),
|
||||
"uuid must be in storageKeys"
|
||||
);
|
||||
assert.ok((req.storageKeys as string[]).includes("apiKey"), "apiKey must be in storageKeys");
|
||||
assert.ok((req.storageKeys as string[]).includes("uuid"), "uuid must be in storageKeys");
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user