Skip to content

Commit 7ca44f0

Browse files
committed
chore(utils): migrate to shared random/ID utilities and add enforcement linting
1 parent 0dc1611 commit 7ca44f0

79 files changed

Lines changed: 555 additions & 187 deletions

File tree

Some content is hidden

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

.cursor/rules/global.mdc

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,18 @@ const shortId = generateShortId()
3737
const tiny = generateShortId(8)
3838
```
3939

40+
## Randomness
41+
Never use `Math.random()`, `crypto.randomBytes()`, or one-off random helpers. Use `@sim/utils/random`:
42+
43+
- `generateRandomBytes(size)` — secure bytes
44+
- `generateRandomHex(byteLength)` — lowercase hex for salts, object keys, and trace/span IDs
45+
- `generateRandomString(size?, alphabet?)` — URL-safe or custom-alphabet suffixes
46+
- `randomFloat()` — sampling and jitter in `[0, 1)`
47+
- `randomInt(maxExclusive)` — unbiased integer ranges
48+
- `randomItem(items)` — unbiased array selection
49+
50+
Use these for IDs, suffixes, MIME boundaries, storage keys, salts, tokens, retry jitter, telemetry sampling, and random UI palette/name selection. Do not add a `Math.random()` fallback; `crypto.getRandomValues()` is the standard CSPRNG primitive and works in insecure browser contexts. Only standalone published packages that cannot depend on `@sim/utils` may use Web Crypto directly, with a comment explaining why.
51+
4052
## Common Utilities
4153
Use shared helpers from `@sim/utils` instead of writing inline implementations:
4254

0

Whitespace-only changes.

AGENTS.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ You are a professional software engineer. All code must follow best practices: a
99
- **Comments**: Use TSDoc for documentation. No `====` separators. No non-TSDoc comments
1010
- **Styling**: Never update global styles. Keep all styling local to components
1111
- **ID Generation**: Never use `crypto.randomUUID()`, `nanoid`, or `uuid` package. Use `generateId()` (UUID v4) or `generateShortId()` (compact) from `@sim/utils/id`
12+
- **Randomness**: Never use `Math.random()`, `crypto.randomBytes()`, or ad-hoc random helpers. Use `@sim/utils/random`: `generateRandomBytes`, `generateRandomHex`, `generateRandomString`, `randomFloat`, `randomInt`, or `randomItem`. Use `randomInt` / `randomItem` for unbiased selection, jitter, and sampling; use hex/string helpers for suffixes, object keys, boundaries, salts, and tokens. Do not add a `Math.random()` fallback — the shared util uses `crypto.getRandomValues()`, which is the standard CSPRNG primitive that also works in non-secure browser contexts. Only standalone published packages that cannot depend on `@sim/utils` may use Web Crypto directly, with a comment explaining why.
1213
- **Package Manager**: Use `bun` and `bunx`, not `npm` and `npx`
1314

1415
## Architecture

CLAUDE.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ You are a professional software engineer. All code must follow best practices: a
1010
- **Comments**: Use TSDoc for documentation. No `====` separators. No non-TSDoc comments
1111
- **Styling**: Never update global styles. Keep all styling local to components
1212
- **ID Generation**: Never use `crypto.randomUUID()`, `nanoid`, or `uuid` package. Use `generateId()` (UUID v4) or `generateShortId()` (compact) from `@sim/utils/id`
13+
- **Randomness**: Never use `Math.random()`, `crypto.randomBytes()`, or ad-hoc random helpers. Use `@sim/utils/random`: `generateRandomBytes`, `generateRandomHex`, `generateRandomString`, `randomFloat`, `randomInt`, or `randomItem`. Use `randomInt` / `randomItem` for unbiased selection, jitter, and sampling; use hex/string helpers for suffixes, object keys, boundaries, salts, and tokens. Do not add a `Math.random()` fallback — the shared util uses `crypto.getRandomValues()`, which is the standard CSPRNG primitive that also works in non-secure browser contexts. Only standalone published packages that cannot depend on `@sim/utils` may use Web Crypto directly, with a comment explaining why.
1314
- **Common Utilities**: Use shared helpers from `@sim/utils` instead of inline implementations. `sleep(ms)` from `@sim/utils/helpers` for delays, `toError(e)` from `@sim/utils/errors` to normalize caught values.
1415
- **Package Manager**: Use `bun` and `bunx`, not `npm` and `npx`
1516

apps/realtime/src/database/operations.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import {
1313
VARIABLE_OPERATIONS,
1414
WORKFLOW_OPERATIONS,
1515
} from '@sim/realtime-protocol/constants'
16+
import { randomFloat } from '@sim/utils/random'
1617
import { getActiveWorkflowContext } from '@sim/workflow-authz'
1718
import { loadWorkflowFromNormalizedTablesRaw } from '@sim/workflow-persistence/load'
1819
import { mergeSubBlockValues } from '@sim/workflow-persistence/subblocks'
@@ -204,7 +205,7 @@ export async function persistWorkflowOperation(workflowId: string, operation: an
204205
throw new Error(`Workflow ${workflowId} is archived or unavailable`)
205206
}
206207

207-
if (op === BLOCK_OPERATIONS.UPDATE_POSITION && Math.random() < 0.01) {
208+
if (op === BLOCK_OPERATIONS.UPDATE_POSITION && randomFloat() < 0.01) {
208209
logger.debug('Socket DB operation sample:', {
209210
operation: op,
210211
target,

apps/realtime/src/index.test.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
*/
66
import { createServer, request as httpRequest } from 'http'
77
import { createMockLogger } from '@sim/testing'
8+
import { randomInt } from '@sim/utils/random'
89
import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from 'vitest'
910
import { createSocketIOServer } from '@/config/socket'
1011
import { MemoryRoomManager } from '@/rooms'
@@ -95,7 +96,7 @@ describe('Socket Server Index Integration', () => {
9596
})
9697

9798
beforeEach(async () => {
98-
PORT = 3333 + Math.floor(Math.random() * 1000)
99+
PORT = 3333 + randomInt(1000)
99100

100101
httpServer = createServer()
101102

@@ -120,7 +121,7 @@ describe('Socket Server Index Integration', () => {
120121
httpServer.on('error', (err: any) => {
121122
clearTimeout(timeout)
122123
if (err.code === 'EADDRINUSE') {
123-
PORT = 3333 + Math.floor(Math.random() * 1000)
124+
PORT = 3333 + randomInt(1000)
124125
httpServer.close(() => {
125126
httpServer.listen(PORT, '0.0.0.0', () => {
126127
resolve()

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -213,6 +213,7 @@ describe('Chat OTP API Route', () => {
213213

214214
vi.stubGlobal('crypto', {
215215
...crypto,
216+
getRandomValues: crypto.getRandomValues.bind(crypto),
216217
randomUUID: vi.fn().mockReturnValue('test-uuid-1234'),
217218
})
218219

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1-
import { randomInt } from 'crypto'
21
import { db } from '@sim/db'
32
import { chat, verification } from '@sim/db/schema'
43
import { createLogger } from '@sim/logger'
54
import { generateId } from '@sim/utils/id'
5+
import { randomInt } from '@sim/utils/random'
66
import { and, eq, gt, isNull } from 'drizzle-orm'
77
import type { NextRequest } from 'next/server'
88
import { renderOTPEmail } from '@/components/emails'
@@ -36,7 +36,7 @@ const OTP_EMAIL_RATE_LIMIT: TokenBucketConfig = {
3636
}
3737

3838
function generateOTP(): string {
39-
return randomInt(100000, 1000000).toString()
39+
return (randomInt(900000) + 100000).toString()
4040
}
4141

4242
const OTP_EXPIRY = 15 * 60 // 15 minutes

apps/sim/app/api/files/parse/route.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { createHash } from 'crypto'
33
import fsPromises, { readFile } from 'fs/promises'
44
import path from 'path'
55
import { createLogger } from '@sim/logger'
6+
import { generateRandomString, LOWERCASE_ALPHANUMERIC_ALPHABET } from '@sim/utils/random'
67
import binaryExtensionsList from 'binary-extensions'
78
import { type NextRequest, NextResponse } from 'next/server'
89
import { fileParseContract } from '@/lib/api/contracts/storage-transfer'
@@ -552,7 +553,7 @@ async function handleCloudFile(
552553
// If file is already from execution context, create UserFile reference without re-uploading
553554
if (context === 'execution') {
554555
userFile = {
555-
id: `file_${Date.now()}_${Math.random().toString(36).substring(2, 9)}`,
556+
id: `file_${Date.now()}_${generateRandomString(7, LOWERCASE_ALPHANUMERIC_ALPHABET)}`,
556557
name: filename,
557558
url: normalizedFilePath,
558559
size: fileBuffer.length,

apps/sim/app/api/tools/file/manage/route.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { createLogger } from '@sim/logger'
2+
import { generateRandomString, LOWERCASE_ALPHANUMERIC_ALPHABET } from '@sim/utils/random'
23
import { type NextRequest, NextResponse } from 'next/server'
34
import { fileManageContract } from '@/lib/api/contracts/tools/file'
45
import { parseRequest } from '@/lib/api/server'
@@ -314,7 +315,7 @@ export const POST = withRouteHandler(async (request: NextRequest) => {
314315
}
315316

316317
const lockKey = `file-append:${workspaceId}:${existing.id}`
317-
const lockValue = `${Date.now()}-${Math.random().toString(36).slice(2)}`
318+
const lockValue = `${Date.now()}-${generateRandomString(10, LOWERCASE_ALPHANUMERIC_ALPHABET)}`
318319
const acquired = await acquireLock(lockKey, lockValue, 30)
319320
if (!acquired) {
320321
return NextResponse.json(

0 commit comments

Comments
 (0)