Skip to content

Commit 759707a

Browse files
waleedlatif1claude
andcommitted
fix(core): consolidate ID generation to prevent HTTP self-hosted crashes
crypto.randomUUID() requires a secure context (HTTPS) in browsers, causing white-screen crashes on self-hosted HTTP deployments. This replaces all direct usage of crypto.randomUUID(), nanoid, and the uuid package with a central utility that falls back to crypto.getRandomValues() which works in all contexts. - Add generateId(), generateShortId(), isValidUuid() in @/lib/core/utils/uuid - Replace crypto.randomUUID() imports across ~220 server + client files - Replace nanoid imports with generateShortId() - Replace uuid package validate with isValidUuid() - Remove nanoid dependency from apps/sim and packages/testing - Remove browser polyfill script from layout.tsx - Update test mocks to target @/lib/core/utils/uuid - Update CLAUDE.md, AGENTS.md, cursor rules, claude rules Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 235f074 commit 759707a

File tree

320 files changed

+1145
-889
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

320 files changed

+1145
-889
lines changed

.claude/rules/global.md

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,5 +9,26 @@ Use TSDoc for documentation. No `====` separators. No non-TSDoc comments.
99
## Styling
1010
Never update global styles. Keep all styling local to components.
1111

12+
## ID Generation
13+
Never use `crypto.randomUUID()`, `nanoid`, or the `uuid` package directly. Use the utilities from `@/lib/core/utils/uuid`:
14+
15+
- `generateId()` — UUID v4, use by default
16+
- `generateShortId(size?)` — short URL-safe ID (default 21 chars), for compact identifiers
17+
18+
Both use `crypto.getRandomValues()` under the hood and work in all contexts including non-secure (HTTP) browsers.
19+
20+
```typescript
21+
// ✗ Bad
22+
import { nanoid } from 'nanoid'
23+
import { v4 as uuidv4 } from 'uuid'
24+
const id = crypto.randomUUID()
25+
26+
// ✓ Good
27+
import { generateId, generateShortId } from '@/lib/core/utils/uuid'
28+
const uuid = generateId()
29+
const shortId = generateShortId()
30+
const tiny = generateShortId(8)
31+
```
32+
1233
## Package Manager
1334
Use `bun` and `bunx`, not `npm` and `npx`.

.cursor/rules/global.mdc

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,5 +16,26 @@ Use TSDoc for documentation. No `====` separators. No non-TSDoc comments.
1616
## Styling
1717
Never update global styles. Keep all styling local to components.
1818

19+
## ID Generation
20+
Never use `crypto.randomUUID()`, `nanoid`, or the `uuid` package directly. Use the utilities from `@/lib/core/utils/uuid`:
21+
22+
- `generateId()` — UUID v4, use by default
23+
- `generateShortId(size?)` — short URL-safe ID (default 21 chars), for compact identifiers
24+
25+
Both use `crypto.getRandomValues()` under the hood and work in all contexts including non-secure (HTTP) browsers.
26+
27+
```typescript
28+
// ✗ Bad
29+
import { nanoid } from 'nanoid'
30+
import { v4 as uuidv4 } from 'uuid'
31+
const id = crypto.randomUUID()
32+
33+
// ✓ Good
34+
import { generateId, generateShortId } from '@/lib/core/utils/uuid'
35+
const uuid = generateId()
36+
const shortId = generateShortId()
37+
const tiny = generateShortId(8)
38+
```
39+
1940
## Package Manager
2041
Use `bun` and `bunx`, not `npm` and `npx`.

AGENTS.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ You are a professional software engineer. All code must follow best practices: a
77
- **Logging**: Import `createLogger` from `@sim/logger`. Use `logger.info`, `logger.warn`, `logger.error` instead of `console.log`
88
- **Comments**: Use TSDoc for documentation. No `====` separators. No non-TSDoc comments
99
- **Styling**: Never update global styles. Keep all styling local to components
10+
- **ID Generation**: Never use `crypto.randomUUID()`, `nanoid`, or `uuid` package. Use `generateId()` (UUID v4) or `generateShortId()` (compact) from `@/lib/core/utils/uuid`
1011
- **Package Manager**: Use `bun` and `bunx`, not `npm` and `npx`
1112

1213
## Architecture

CLAUDE.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ You are a professional software engineer. All code must follow best practices: a
77
- **Logging**: Import `createLogger` from `@sim/logger`. Use `logger.info`, `logger.warn`, `logger.error` instead of `console.log`
88
- **Comments**: Use TSDoc for documentation. No `====` separators. No non-TSDoc comments
99
- **Styling**: Never update global styles. Keep all styling local to components
10+
- **ID Generation**: Never use `crypto.randomUUID()`, `nanoid`, or `uuid` package. Use `generateId()` (UUID v4) or `generateShortId()` (compact) from `@/lib/core/utils/uuid`
1011
- **Package Manager**: Use `bun` and `bunx`, not `npm` and `npx`
1112

1213
## Architecture

apps/sim/app/api/a2a/agents/route.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,11 @@ import { a2aAgent, workflow } from '@sim/db/schema'
99
import { createLogger } from '@sim/logger'
1010
import { and, eq, isNull, sql } from 'drizzle-orm'
1111
import { type NextRequest, NextResponse } from 'next/server'
12-
import { v4 as uuidv4 } from 'uuid'
1312
import { generateSkillsFromWorkflow } from '@/lib/a2a/agent-card'
1413
import { A2A_DEFAULT_CAPABILITIES } from '@/lib/a2a/constants'
1514
import { sanitizeAgentName } from '@/lib/a2a/utils'
1615
import { checkSessionOrInternalAuth } from '@/lib/auth/hybrid'
16+
import { generateId } from '@/lib/core/utils/uuid'
1717
import { captureServerEvent } from '@/lib/posthog/server'
1818
import { loadWorkflowFromNormalizedTables } from '@/lib/workflows/persistence/utils'
1919
import { hasValidStartBlockInState } from '@/lib/workflows/triggers/trigger-utils'
@@ -173,7 +173,7 @@ export async function POST(request: NextRequest) {
173173
skillTags
174174
)
175175

176-
const agentId = uuidv4()
176+
const agentId = generateId()
177177
const agentName = name || sanitizeAgentName(wf.name)
178178

179179
const [agent] = await db

apps/sim/app/api/a2a/serve/[agentId]/route.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ import { a2aAgent, a2aPushNotificationConfig, a2aTask, workflow } from '@sim/db/
44
import { createLogger } from '@sim/logger'
55
import { and, eq, isNull } from 'drizzle-orm'
66
import { type NextRequest, NextResponse } from 'next/server'
7-
import { v4 as uuidv4 } from 'uuid'
87
import { A2A_DEFAULT_TIMEOUT, A2A_MAX_HISTORY_LENGTH } from '@/lib/a2a/constants'
98
import { notifyTaskStateChange } from '@/lib/a2a/push-notifications'
109
import {
@@ -18,6 +17,7 @@ import { acquireLock, getRedisClient, releaseLock } from '@/lib/core/config/redi
1817
import { validateUrlWithDNS } from '@/lib/core/security/input-validation.server'
1918
import { SSE_HEADERS } from '@/lib/core/utils/sse'
2019
import { getBaseUrl } from '@/lib/core/utils/urls'
20+
import { generateId } from '@/lib/core/utils/uuid'
2121
import { markExecutionCancelled } from '@/lib/execution/cancellation'
2222
import { checkWorkspaceAccess } from '@/lib/workspaces/permissions/utils'
2323
import { getWorkspaceBilledAccountUserId } from '@/lib/workspaces/utils'
@@ -400,11 +400,11 @@ async function handleMessageSend(
400400

401401
const message = params.message
402402
const taskId = message.taskId || generateTaskId()
403-
const contextId = message.contextId || uuidv4()
403+
const contextId = message.contextId || generateId()
404404

405405
// Distributed lock to prevent concurrent task processing
406406
const lockKey = `a2a:task:${taskId}:lock`
407-
const lockValue = uuidv4()
407+
const lockValue = generateId()
408408
const acquired = await acquireLock(lockKey, lockValue, 60)
409409

410410
if (!acquired) {
@@ -628,12 +628,12 @@ async function handleMessageStream(
628628
}
629629

630630
const message = params.message
631-
const contextId = message.contextId || uuidv4()
631+
const contextId = message.contextId || generateId()
632632
const taskId = message.taskId || generateTaskId()
633633

634634
// Distributed lock to prevent concurrent task processing
635635
const lockKey = `a2a:task:${taskId}:lock`
636-
const lockValue = uuidv4()
636+
const lockValue = generateId()
637637
const acquired = await acquireLock(lockKey, lockValue, 300)
638638

639639
if (!acquired) {
@@ -1427,7 +1427,7 @@ async function handlePushNotificationSet(
14271427
.where(eq(a2aPushNotificationConfig.id, existingConfig.id))
14281428
} else {
14291429
await db.insert(a2aPushNotificationConfig).values({
1430-
id: uuidv4(),
1430+
id: generateId(),
14311431
taskId: params.id,
14321432
url: config.url,
14331433
token: config.token || null,

apps/sim/app/api/a2a/serve/[agentId]/utils.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import type { Artifact, Message, PushNotificationConfig, Task, TaskState } from '@a2a-js/sdk'
2-
import { v4 as uuidv4 } from 'uuid'
32
import { generateInternalToken } from '@/lib/auth/internal'
43
import { getInternalApiBaseUrl } from '@/lib/core/utils/urls'
4+
import { generateId } from '@/lib/core/utils/uuid'
55

66
/** A2A v0.3 JSON-RPC method names */
77
export const A2A_METHODS = {
@@ -85,7 +85,7 @@ export function isJSONRPCRequest(obj: unknown): obj is JSONRPCRequest {
8585
}
8686

8787
export function generateTaskId(): string {
88-
return uuidv4()
88+
return generateId()
8989
}
9090

9191
export function createTaskStatus(state: TaskState): { state: TaskState; timestamp: string } {

apps/sim/app/api/academy/certificates/route.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,14 @@ import { db } from '@sim/db'
22
import { academyCertificate, user } from '@sim/db/schema'
33
import { createLogger } from '@sim/logger'
44
import { and, eq } from 'drizzle-orm'
5-
import { nanoid } from 'nanoid'
65
import { type NextRequest, NextResponse } from 'next/server'
76
import { z } from 'zod'
87
import { getCourseById } from '@/lib/academy/content'
98
import type { CertificateMetadata } from '@/lib/academy/types'
109
import { getSession } from '@/lib/auth'
1110
import type { TokenBucketConfig } from '@/lib/core/rate-limiter'
1211
import { RateLimiter } from '@/lib/core/rate-limiter'
12+
import { generateShortId } from '@/lib/core/utils/uuid'
1313

1414
const logger = createLogger('AcademyCertificatesAPI')
1515

@@ -106,7 +106,7 @@ export async function POST(req: NextRequest) {
106106
const [certificate] = await db
107107
.insert(academyCertificate)
108108
.values({
109-
id: nanoid(),
109+
id: generateShortId(),
110110
userId: session.user.id,
111111
courseId,
112112
status: 'active',
@@ -211,5 +211,5 @@ export async function GET(req: NextRequest) {
211211
/** Generates a human-readable certificate number, e.g. SIM-2026-A3K9XZ2P */
212212
function generateCertificateNumber(): string {
213213
const year = new Date().getFullYear()
214-
return `SIM-${year}-${nanoid(8).toUpperCase()}`
214+
return `SIM-${year}-${generateShortId(8).toUpperCase()}`
215215
}

apps/sim/app/api/auth/shopify/authorize/route.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { type NextRequest, NextResponse } from 'next/server'
33
import { getSession } from '@/lib/auth'
44
import { env } from '@/lib/core/config/env'
55
import { getBaseUrl } from '@/lib/core/utils/urls'
6+
import { generateId } from '@/lib/core/utils/uuid'
67

78
const logger = createLogger('ShopifyAuthorize')
89

@@ -161,7 +162,7 @@ export async function GET(request: NextRequest) {
161162
const baseUrl = getBaseUrl()
162163
const redirectUri = `${baseUrl}/api/auth/oauth2/callback/shopify`
163164

164-
const state = crypto.randomUUID()
165+
const state = generateId()
165166

166167
const oauthUrl =
167168
`https://${cleanShop}/admin/oauth/authorize?` +

apps/sim/app/api/chat/[identifier]/otp/route.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { randomInt, randomUUID } from 'crypto'
1+
import { randomInt } from 'crypto'
22
import { db } from '@sim/db'
33
import { chat, verification } from '@sim/db/schema'
44
import { createLogger } from '@sim/logger'
@@ -10,6 +10,7 @@ import { getRedisClient } from '@/lib/core/config/redis'
1010
import { addCorsHeaders, isEmailAllowed } from '@/lib/core/security/deployment'
1111
import { getStorageMethod } from '@/lib/core/storage'
1212
import { generateRequestId } from '@/lib/core/utils/request'
13+
import { generateId } from '@/lib/core/utils/uuid'
1314
import { sendEmail } from '@/lib/messaging/email/mailer'
1415
import { setChatAuthCookie } from '@/app/api/chat/utils'
1516
import { createErrorResponse, createSuccessResponse } from '@/app/api/workflows/utils'
@@ -61,7 +62,7 @@ async function storeOTP(email: string, chatId: string, otp: string): Promise<voi
6162
await db.transaction(async (tx) => {
6263
await tx.delete(verification).where(eq(verification.identifier, identifier))
6364
await tx.insert(verification).values({
64-
id: randomUUID(),
65+
id: generateId(),
6566
identifier,
6667
value,
6768
expiresAt,

0 commit comments

Comments
 (0)