From 4dd8696774fc815f805120dd9b25fd584cd47e9e Mon Sep 17 00:00:00 2001 From: isonimus <19539979+Isonimus@users.noreply.github.com> Date: Wed, 10 Jun 2026 16:27:06 +0200 Subject: [PATCH 1/2] Clear CodeQL alerts: insecure randomness + unanchored regexes - session id: drop the Math.random() v4-UUID fallback (js/insecure-randomness). Prefer crypto.randomUUID, then crypto.getRandomValues (secure, near-universal support); only a non-random monotonic id remains for the rare runtime with no Web Crypto at all. The session id is UX telemetry, not a security token. - e2e: anchor the jsonplaceholder waitForResponse regexes (^https://jsonplaceholder.typicode.com/) so they cannot match the host as a substring elsewhere in a URL (js/regex/missing-regexp-anchor x3). --- e2e/tests/next.spec.ts | 2 +- e2e/tests/react.spec.ts | 2 +- e2e/tests/vue.spec.ts | 2 +- packages/core/src/session.ts | 28 +++++++++++++++++++++------- 4 files changed, 24 insertions(+), 10 deletions(-) diff --git a/e2e/tests/next.spec.ts b/e2e/tests/next.spec.ts index 628b948..076fa46 100644 --- a/e2e/tests/next.spec.ts +++ b/e2e/tests/next.spec.ts @@ -108,7 +108,7 @@ test.describe('next-app-router — network spans', () => { await page.goto('/'); await Promise.all([ - page.waitForResponse(/jsonplaceholder\.typicode\.com/), + page.waitForResponse(/^https:\/\/jsonplaceholder\.typicode\.com\//), page.click('[data-blindspot-label="fetch-task-data"]'), ]); await flushSpans(page); diff --git a/e2e/tests/react.spec.ts b/e2e/tests/react.spec.ts index d1c46bb..797bd32 100644 --- a/e2e/tests/react.spec.ts +++ b/e2e/tests/react.spec.ts @@ -108,7 +108,7 @@ test.describe('react-basic — network spans', () => { // Click "Fetch task data" and wait for the mocked response. await Promise.all([ - page.waitForResponse(/jsonplaceholder\.typicode\.com/), + page.waitForResponse(/^https:\/\/jsonplaceholder\.typicode\.com\//), page.click('button.btn-primary'), ]); await flushSpans(page); diff --git a/e2e/tests/vue.spec.ts b/e2e/tests/vue.spec.ts index 338fc73..95d056f 100644 --- a/e2e/tests/vue.spec.ts +++ b/e2e/tests/vue.spec.ts @@ -103,7 +103,7 @@ test.describe('vue-basic — network spans', () => { // Click "Fetch task data" (Vue has a data-blindspot-label on this button). await Promise.all([ - page.waitForResponse(/jsonplaceholder\.typicode\.com/), + page.waitForResponse(/^https:\/\/jsonplaceholder\.typicode\.com\//), page.click('[data-blindspot-label="fetch-task-data"]'), ]); await flushSpans(page); diff --git a/packages/core/src/session.ts b/packages/core/src/session.ts index 6b0f615..614e85d 100644 --- a/packages/core/src/session.ts +++ b/packages/core/src/session.ts @@ -1,16 +1,30 @@ const SESSION_KEY = 'blindspot.session_id'; function generateId(): string { - if (typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function') { - return crypto.randomUUID(); + if (typeof crypto !== 'undefined') { + if (typeof crypto.randomUUID === 'function') { + return crypto.randomUUID(); + } + // Fallback for environments without crypto.randomUUID: build a v4 UUID from + // crypto.getRandomValues, which is cryptographically secure and has far + // wider support than randomUUID. Math.random is intentionally avoided. + if (typeof crypto.getRandomValues === 'function') { + const bytes = crypto.getRandomValues(new Uint8Array(16)); + bytes[6] = (bytes[6] & 0x0f) | 0x40; // version 4 + bytes[8] = (bytes[8] & 0x3f) | 0x80; // variant 10 + const hex = Array.from(bytes, (b) => b.toString(16).padStart(2, '0')).join(''); + return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`; + } } - // Fallback for environments without crypto.randomUUID - return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => { - const r = (Math.random() * 16) | 0; - return (c === 'x' ? r : (r & 0x3) | 0x8).toString(16); - }); + // Last resort for the rare runtime with no Web Crypto at all. This id is a + // UX-telemetry session marker, not a security token, so a non-random + // monotonic fallback is acceptable here. + _fallbackCounter += 1; + return `bs-${Date.now().toString(36)}-${_fallbackCounter.toString(36)}`; } +let _fallbackCounter = 0; + export function getOrCreateSessionId(): string { try { const existing = sessionStorage.getItem(SESSION_KEY); From 7403d7e92c9c8f2ba5ebeab6e6f707ccfab7da19 Mon Sep 17 00:00:00 2001 From: isonimus <19539979+Isonimus@users.noreply.github.com> Date: Wed, 10 Jun 2026 16:34:43 +0200 Subject: [PATCH 2/2] Add changeset: secure session id (patch @tindalabs/blindspot-core) --- .changeset/secure-session-id.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/secure-session-id.md diff --git a/.changeset/secure-session-id.md b/.changeset/secure-session-id.md new file mode 100644 index 0000000..7bd4d5b --- /dev/null +++ b/.changeset/secure-session-id.md @@ -0,0 +1,5 @@ +--- +"@tindalabs/blindspot-core": patch +--- + +Session id generation no longer falls back to `Math.random()`. It now prefers `crypto.randomUUID`, then a `crypto.getRandomValues`-based v4 UUID, leaving only a non-random monotonic id (`bs--`) as a last resort for runtimes with no Web Crypto at all. Resolves CodeQL `js/insecure-randomness`. The session id is a UX-telemetry marker rather than a security token, but there's no reason to keep weak randomness in the SDK.