diff options
| author | Adam <[email protected]> | 2026-02-11 19:05:15 -0600 |
|---|---|---|
| committer | GitHub <[email protected]> | 2026-02-12 01:05:15 +0000 |
| commit | 81ca2df6ad57085b895caafc386e4ac4ab9098a6 (patch) | |
| tree | 3a78f5125aff9ebf84e98275a72f5fdd13d930ff /packages/app/src/utils | |
| parent | aea68c386a4f64cf718c3eeee9dffec8409ee6b0 (diff) | |
| download | opencode-81ca2df6ad57085b895caafc386e4ac4ab9098a6.tar.gz opencode-81ca2df6ad57085b895caafc386e4ac4ab9098a6.zip | |
fix(app): guard randomUUID in insecure browser contexts (#13237)
Co-authored-by: Selim <[email protected]>
Diffstat (limited to 'packages/app/src/utils')
| -rw-r--r-- | packages/app/src/utils/perf.ts | 8 | ||||
| -rw-r--r-- | packages/app/src/utils/uuid.test.ts | 78 | ||||
| -rw-r--r-- | packages/app/src/utils/uuid.ts | 12 |
3 files changed, 94 insertions, 4 deletions
diff --git a/packages/app/src/utils/perf.ts b/packages/app/src/utils/perf.ts index 0ecbc33ff..105d02a82 100644 --- a/packages/app/src/utils/perf.ts +++ b/packages/app/src/utils/perf.ts @@ -1,3 +1,5 @@ +import { uuid } from "@/utils/uuid" + type Nav = { id: string dir?: string @@ -16,8 +18,6 @@ const key = (dir: string | undefined, to: string) => `${dir ?? ""}:${to}` const now = () => performance.now() -const uid = () => crypto.randomUUID?.() ?? Math.random().toString(16).slice(2) - const navs = new Map<string, Nav>() const pending = new Map<string, string>() const active = new Map<string, string>() @@ -94,7 +94,7 @@ function ensure(id: string, data: Omit<Nav, "marks" | "logged" | "timer">) { export function navStart(input: { dir?: string; from?: string; to: string; trigger?: string }) { if (!dev) return - const id = uid() + const id = uuid() const start = now() const nav = ensure(id, { ...input, id, start }) nav.marks["navigate:start"] = start @@ -109,7 +109,7 @@ export function navParams(input: { dir?: string; from?: string; to: string }) { const k = key(input.dir, input.to) const pendingId = pending.get(k) if (pendingId) pending.delete(k) - const id = pendingId ?? uid() + const id = pendingId ?? uuid() const start = now() const nav = ensure(id, { ...input, id, start, trigger: pendingId ? "key" : "route" }) diff --git a/packages/app/src/utils/uuid.test.ts b/packages/app/src/utils/uuid.test.ts new file mode 100644 index 000000000..e6b4e2824 --- /dev/null +++ b/packages/app/src/utils/uuid.test.ts @@ -0,0 +1,78 @@ +import { afterEach, describe, expect, test } from "bun:test" +import { uuid } from "./uuid" + +const cryptoDescriptor = Object.getOwnPropertyDescriptor(globalThis, "crypto") +const secureDescriptor = Object.getOwnPropertyDescriptor(globalThis, "isSecureContext") +const randomDescriptor = Object.getOwnPropertyDescriptor(Math, "random") + +const setCrypto = (value: Partial<Crypto>) => { + Object.defineProperty(globalThis, "crypto", { + configurable: true, + value: value as Crypto, + }) +} + +const setSecure = (value: boolean) => { + Object.defineProperty(globalThis, "isSecureContext", { + configurable: true, + value, + }) +} + +const setRandom = (value: () => number) => { + Object.defineProperty(Math, "random", { + configurable: true, + value, + }) +} + +afterEach(() => { + if (cryptoDescriptor) { + Object.defineProperty(globalThis, "crypto", cryptoDescriptor) + } + + if (secureDescriptor) { + Object.defineProperty(globalThis, "isSecureContext", secureDescriptor) + } + + if (!secureDescriptor) { + delete (globalThis as { isSecureContext?: boolean }).isSecureContext + } + + if (randomDescriptor) { + Object.defineProperty(Math, "random", randomDescriptor) + } +}) + +describe("uuid", () => { + test("uses randomUUID in secure contexts", () => { + setCrypto({ randomUUID: () => "00000000-0000-0000-0000-000000000000" }) + setSecure(true) + expect(uuid()).toBe("00000000-0000-0000-0000-000000000000") + }) + + test("falls back in insecure contexts", () => { + setCrypto({ randomUUID: () => "00000000-0000-0000-0000-000000000000" }) + setSecure(false) + setRandom(() => 0.5) + expect(uuid()).toBe("8") + }) + + test("falls back when randomUUID throws", () => { + setCrypto({ + randomUUID: () => { + throw new DOMException("Failed", "OperationError") + }, + }) + setSecure(true) + setRandom(() => 0.5) + expect(uuid()).toBe("8") + }) + + test("falls back when randomUUID is unavailable", () => { + setCrypto({}) + setSecure(true) + setRandom(() => 0.5) + expect(uuid()).toBe("8") + }) +}) diff --git a/packages/app/src/utils/uuid.ts b/packages/app/src/utils/uuid.ts new file mode 100644 index 000000000..7b964068c --- /dev/null +++ b/packages/app/src/utils/uuid.ts @@ -0,0 +1,12 @@ +const fallback = () => Math.random().toString(16).slice(2) + +export function uuid() { + const c = globalThis.crypto + if (!c || typeof c.randomUUID !== "function") return fallback() + if (typeof globalThis.isSecureContext === "boolean" && !globalThis.isSecureContext) return fallback() + try { + return c.randomUUID() + } catch { + return fallback() + } +} |
