Skip to content

Commit 9ca6e01

Browse files
committed
Merge branch 'staging' into feat/org-improv-big
2 parents 5b817ee + c246f5c commit 9ca6e01

583 files changed

Lines changed: 33134 additions & 4706 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.

.claude/rules/global.md

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,5 +30,25 @@ const shortId = generateShortId()
3030
const tiny = generateShortId(8)
3131
```
3232

33+
## Common Utilities
34+
Use shared helpers from `@/lib/core/utils/helpers` instead of writing inline implementations:
35+
36+
- `sleep(ms)` — async delay. Never write `new Promise(resolve => setTimeout(resolve, ms))`
37+
- `toError(value)` — normalize unknown caught values to `Error`. Never write `e instanceof Error ? e : new Error(String(e))`
38+
- `toError(value).message` — get error message safely. Never write `e instanceof Error ? e.message : String(e)`
39+
40+
```typescript
41+
// ✗ Bad
42+
await new Promise(resolve => setTimeout(resolve, 1000))
43+
const msg = error instanceof Error ? error.message : String(error)
44+
const err = error instanceof Error ? error : new Error(String(error))
45+
46+
// ✓ Good
47+
import { sleep, toError } from '@/lib/core/utils/helpers'
48+
await sleep(1000)
49+
const msg = toError(error).message
50+
const err = toError(error)
51+
```
52+
3353
## Package Manager
3454
Use `bun` and `bunx`, not `npm` and `npx`.

.cursor/rules/global.mdc

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

40+
## Common Utilities
41+
Use shared helpers from `@/lib/core/utils/helpers` instead of writing inline implementations:
42+
43+
- `sleep(ms)` — async delay. Never write `new Promise(resolve => setTimeout(resolve, ms))`
44+
- `toError(value)` — normalize unknown caught values to `Error`. Never write `e instanceof Error ? e : new Error(String(e))`
45+
- `toError(value).message` — get error message safely. Never write `e instanceof Error ? e.message : String(e)`
46+
47+
```typescript
48+
// ✗ Bad
49+
await new Promise(resolve => setTimeout(resolve, 1000))
50+
const msg = error instanceof Error ? error.message : String(error)
51+
const err = error instanceof Error ? error : new Error(String(error))
52+
53+
// ✓ Good
54+
import { sleep, toError } from '@/lib/core/utils/helpers'
55+
await sleep(1000)
56+
const msg = toError(error).message
57+
const err = toError(error)
58+
```
59+
4060
## Package Manager
4161
Use `bun` and `bunx`, not `npm` and `npx`.

.cursor/rules/sim-sandbox.mdc

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
---
2+
description: Isolated-vm sandbox worker security policy. Hard rules for anything that lives in the worker child process that runs user code.
3+
globs: ["apps/sim/lib/execution/isolated-vm-worker.cjs", "apps/sim/lib/execution/isolated-vm.ts", "apps/sim/lib/execution/sandbox/**", "apps/sim/sandbox-tasks/**"]
4+
---
5+
6+
# Sim Sandbox — Worker Security Policy
7+
8+
The isolated-vm worker child process at
9+
`apps/sim/lib/execution/isolated-vm-worker.cjs` runs untrusted user code inside
10+
V8 isolates. The process itself is a trust boundary. Everything in this rule is
11+
about what must **never** live in that process.
12+
13+
## Hard rules
14+
15+
1. **No app credentials in the worker process**. The worker must not hold, load,
16+
or receive via IPC: database URLs, Redis URLs, AWS keys, Stripe keys,
17+
session-signing keys, encryption keys, OAuth client secrets, internal API
18+
secrets, or any LLM / email / search provider API keys. If you catch yourself
19+
`require`'ing `@/lib/auth`, `@sim/db`, `@/lib/uploads/core/storage-service`,
20+
or anything that imports `env` directly inside the worker, stop and use a
21+
host-side broker instead.
22+
23+
2. **Host-side brokers own all credentialed work**. The worker can only access
24+
resources through `ivm.Reference` / `ivm.Callback` bridges back to the host
25+
process. Today the only broker is `workspaceFileBroker`
26+
(`apps/sim/lib/execution/sandbox/brokers/workspace-file.ts`); adding a new
27+
one requires co-reviewing this file.
28+
29+
3. **Host-side brokers must scope every resource access to a single tenant**.
30+
The `SandboxBrokerContext` always carries `workspaceId`. Any new broker that
31+
accesses storage, DB, or an external API must use `ctx.workspaceId` to scope
32+
the lookup — never accept a raw path, key, or URL from isolate code without
33+
validation.
34+
35+
4. **Nothing that runs in the isolate is trusted, even if we wrote it**. The
36+
task `bootstrap` and `finalize` strings in `apps/sim/sandbox-tasks/` execute
37+
inside the isolate. They must treat `globalThis` as adversarial — no pulling
38+
values from it that might have been mutated by user code. The hardening
39+
script in `executeTask` undefines dangerous globals before user code runs.
40+
41+
## Why
42+
43+
A V8 JIT bug (Chrome ships these roughly monthly) gives an attacker a native
44+
code primitive inside the process that owns whatever that process can reach.
45+
If the worker only holds `isolated-vm` + a single narrow workspace-file broker,
46+
a V8 escape leaks one tenant's files. If the worker holds a Stripe key or a DB
47+
connection, a V8 escape leaks the service.
48+
49+
The original `doc-worker.cjs` vulnerability (CVE-class, 225 production secrets
50+
leaked via `/proc/1/environ`) was the forcing function for this architecture.
51+
Keep the blast radius small.
52+
53+
## Checklist for changes to `isolated-vm-worker.cjs`
54+
55+
Before landing any change that adds a new `require(...)` or `process.send(...)`
56+
payload or `ivm.Reference` wrapper in the worker:
57+
58+
- [ ] Does it load a credential, key, connection string, or secret? If yes,
59+
move it host-side and expose as a broker.
60+
- [ ] Does it import from `@/lib/auth`, `@sim/db`, `@/lib/uploads/core/*`,
61+
`@/lib/core/config/env`, or any module that reads `process.env` of the
62+
main app? If yes, same — move host-side.
63+
- [ ] Does it expose a resource that's workspace-scoped without taking a
64+
`workspaceId`? If yes, re-scope.
65+
- [ ] Did you update the broker limits (`IVM_MAX_BROKER_ARGS_JSON_CHARS`,
66+
`IVM_MAX_BROKER_RESULT_JSON_CHARS`, `IVM_MAX_BROKERS_PER_EXECUTION`) if
67+
the new broker can emit large payloads or fire frequently?
68+
69+
## What the worker *may* hold
70+
71+
- `isolated-vm` module
72+
- Node built-ins: `node:fs` (only for reading the checked-in bundle `.cjs`
73+
files) and `node:path`
74+
- The three prebuilt library bundles under
75+
`apps/sim/lib/execution/sandbox/bundles/*.cjs`
76+
- IPC message handlers for `execute`, `cancel`, `fetchResponse`,
77+
`brokerResponse`
78+
79+
The worker deliberately has **no host-side logger**. All errors and
80+
diagnostics flow through IPC back to the host, which has `@sim/logger`. Do
81+
not add `createLogger` or console-based logging to the worker — it would
82+
require pulling the main app's config / env, which is exactly what this
83+
rule is preventing.
84+
85+
Anything else is suspect.

CLAUDE.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ You are a professional software engineer. All code must follow best practices: a
88
- **Comments**: Use TSDoc for documentation. No `====` separators. No non-TSDoc comments
99
- **Styling**: Never update global styles. Keep all styling local to components
1010
- **ID Generation**: Never use `crypto.randomUUID()`, `nanoid`, or `uuid` package. Use `generateId()` (UUID v4) or `generateShortId()` (compact) from `@/lib/core/utils/uuid`
11+
- **Common Utilities**: Use shared helpers from `@/lib/core/utils/helpers` instead of inline implementations. `sleep(ms)` for delays, `toError(e)` to normalize caught values.
1112
- **Package Manager**: Use `bun` and `bunx`, not `npm` and `npx`
1213

1314
## Architecture

README.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -142,13 +142,15 @@ See the [environment variables reference](https://docs.sim.ai/self-hosting/envir
142142
- **Database**: PostgreSQL with [Drizzle ORM](https://orm.drizzle.team)
143143
- **Authentication**: [Better Auth](https://better-auth.com)
144144
- **UI**: [Shadcn](https://ui.shadcn.com/), [Tailwind CSS](https://tailwindcss.com)
145-
- **State Management**: [Zustand](https://zustand-demo.pmnd.rs/)
145+
- **Streaming Markdown**: [Streamdown](https://github.com/vercel/streamdown)
146+
- **State Management**: [Zustand](https://zustand-demo.pmnd.rs/), [TanStack Query](https://tanstack.com/query)
146147
- **Flow Editor**: [ReactFlow](https://reactflow.dev/)
147148
- **Docs**: [Fumadocs](https://fumadocs.vercel.app/)
148149
- **Monorepo**: [Turborepo](https://turborepo.org/)
149150
- **Realtime**: [Socket.io](https://socket.io/)
150151
- **Background Jobs**: [Trigger.dev](https://trigger.dev/)
151152
- **Remote Code Execution**: [E2B](https://www.e2b.dev/)
153+
- **Isolated Code Execution**: [isolated-vm](https://github.com/laverdet/isolated-vm)
152154

153155
## Contributing
154156

apps/docs/components/icons.tsx

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3602,6 +3602,29 @@ export function OpenRouterIcon(props: SVGProps<SVGSVGElement>) {
36023602
)
36033603
}
36043604

3605+
export function MondayIcon(props: SVGProps<SVGSVGElement>) {
3606+
return (
3607+
<svg
3608+
{...props}
3609+
viewBox='0 -50 256 256'
3610+
xmlns='http://www.w3.org/2000/svg'
3611+
preserveAspectRatio='xMidYMid'
3612+
>
3613+
<g>
3614+
<path
3615+
d='M31.8458633,153.488694 C20.3244423,153.513586 9.68073708,147.337265 3.98575204,137.321731 C-1.62714067,127.367831 -1.29055839,115.129325 4.86093879,105.498969 L62.2342919,15.4033556 C68.2125882,5.54538256 79.032489,-0.333585033 90.5563073,0.0146553508 C102.071737,0.290611552 112.546041,6.74705604 117.96667,16.9106216 C123.315033,27.0238906 122.646488,39.1914174 116.240607,48.6847625 L58.9037201,138.780375 C52.9943022,147.988884 42.7873202,153.537154 31.8458633,153.488694 L31.8458633,153.488694 Z'
3616+
fill='#F62B54'
3617+
/>
3618+
<path
3619+
d='M130.25575,153.488484 C118.683837,153.488484 108.035731,147.301291 102.444261,137.358197 C96.8438154,127.431292 97.1804475,115.223704 103.319447,105.620522 L160.583402,15.7315506 C166.47539,5.73210989 177.327374,-0.284878136 188.929728,0.0146553508 C200.598885,0.269918151 211.174058,6.7973526 216.522421,17.0078646 C221.834319,27.2183766 221.056375,39.4588356 214.456008,48.9278699 L157.204209,138.816842 C151.313487,147.985468 141.153618,153.5168 130.25575,153.488484 Z'
3620+
fill='#FFCC00'
3621+
/>
3622+
<ellipse fill='#00CA72' cx='226.465527' cy='125.324379' rx='29.5375538' ry='28.9176274' />
3623+
</g>
3624+
</svg>
3625+
)
3626+
}
3627+
36053628
export function MongoDBIcon(props: SVGProps<SVGSVGElement>) {
36063629
return (
36073630
<svg {...props} xmlns='http://www.w3.org/2000/svg' viewBox='0 0 128 128'>

apps/docs/components/ui/action-media.tsx

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
'use client'
22

3-
import { useState } from 'react'
3+
import { useRef, useState } from 'react'
44
import { cn, getAssetUrl } from '@/lib/utils'
55
import { Lightbox } from './lightbox'
66

@@ -50,18 +50,22 @@ export function ActionImage({ src, alt, enableLightbox = true }: ActionImageProp
5050
}
5151

5252
export function ActionVideo({ src, alt, enableLightbox = true }: ActionVideoProps) {
53+
const videoRef = useRef<HTMLVideoElement>(null)
54+
const startTimeRef = useRef(0)
5355
const [isLightboxOpen, setIsLightboxOpen] = useState(false)
5456
const resolvedSrc = getAssetUrl(src)
5557

5658
const handleClick = () => {
5759
if (enableLightbox) {
60+
startTimeRef.current = videoRef.current?.currentTime ?? 0
5861
setIsLightboxOpen(true)
5962
}
6063
}
6164

6265
return (
6366
<>
6467
<video
68+
ref={videoRef}
6569
src={resolvedSrc}
6670
autoPlay
6771
loop
@@ -80,6 +84,7 @@ export function ActionVideo({ src, alt, enableLightbox = true }: ActionVideoProp
8084
src={src}
8185
alt={alt}
8286
type='video'
87+
startTime={startTimeRef.current}
8388
/>
8489
)}
8590
</>

0 commit comments

Comments
 (0)