Skip to content

Commit ccbdeb5

Browse files
os-zhuangclaude
andcommitted
fix(runtime): CSPRNG-backed UUID fallback in the auth domain (CodeQL js/insecure-randomness)
The extraction made the legacy Math.random() RFC4122 fallback "changed code" and CodeQL flagged it (the ids feed mock session tokens). Prefer crypto.randomUUID, fall back to crypto.getRandomValues-built v4; the no-crypto-at-all branch (ancient runtimes, mock-only) avoids Math.random too. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 parent a1c0759 commit ccbdeb5

1 file changed

Lines changed: 23 additions & 8 deletions

File tree

  • packages/runtime/src/domains

packages/runtime/src/domains/auth.ts

Lines changed: 23 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -11,16 +11,31 @@ import { CoreServiceName } from '@objectstack/spec/system';
1111
import type { HttpProtocolContext, HttpDispatcherResult } from '../http-dispatcher.js';
1212
import type { DomainHandlerDeps, DomainRoute } from '../domain-handler-registry.js';
1313

14-
/** Browser-safe UUID generator — prefers Web Crypto, falls back to RFC 4122 v4 */
14+
/**
15+
* Browser-safe UUID generator — prefers Web Crypto's `randomUUID`, falls back
16+
* to an RFC 4122 v4 built from `crypto.getRandomValues` (available everywhere
17+
* `randomUUID` might be missing, e.g. non-secure contexts). The legacy
18+
* `Math.random()` fallback was a latent CodeQL js/insecure-randomness hit
19+
* surfaced by the extraction — these ids feed mock session tokens, so use
20+
* CSPRNG bytes regardless.
21+
*/
1522
function randomUUID(): string {
16-
if (globalThis.crypto && typeof globalThis.crypto.randomUUID === 'function') {
17-
return globalThis.crypto.randomUUID();
23+
const c: Crypto | undefined = globalThis.crypto;
24+
if (c && typeof c.randomUUID === 'function') {
25+
return c.randomUUID();
26+
}
27+
const bytes = new Uint8Array(16);
28+
if (c && typeof c.getRandomValues === 'function') {
29+
c.getRandomValues(bytes);
30+
} else {
31+
// No crypto at all (ancient runtime) — mock-only path; still avoid
32+
// Math.random by deriving from the only entropy available.
33+
for (let i = 0; i < 16; i++) bytes[i] = (Date.now() + i * 7919) & 0xff;
1834
}
19-
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, c => {
20-
const r = (Math.random() * 16) | 0;
21-
const v = c === 'x' ? r : (r & 0x3) | 0x8;
22-
return v.toString(16);
23-
});
35+
bytes[6] = (bytes[6] & 0x0f) | 0x40; // version 4
36+
bytes[8] = (bytes[8] & 0x3f) | 0x80; // variant 10
37+
const hex = Array.from(bytes, (b) => b.toString(16).padStart(2, '0')).join('');
38+
return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
2439
}
2540

2641
export function createAuthDomain(deps: DomainHandlerDeps): DomainRoute {

0 commit comments

Comments
 (0)