Skip to content

Commit 4042b99

Browse files
committed
cleanup: accessibility, emcn design tokens, react best practices across workspace UI
- Add sr-only ModalDescription to dialogs/modals for accessibility - Replace hardcoded colors and z-indices with design token CSS variables - Apply emcn design review fixes across tables, knowledge, logs, settings, workflows
1 parent 803ad19 commit 4042b99

483 files changed

Lines changed: 10903 additions & 138 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.

.deepsec/.gitignore

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
node_modules/
2+
.env*.local
3+
4+
# Scan output — regenerated by `deepsec scan` / `process`. INFO.md
5+
# and SETUP.md (manually edited) stay tracked.
6+
data/*/files/
7+
data/*/runs/
8+
data/*/reports/
9+
data/*/project.json

.deepsec/AGENTS.md

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
# Agent setup
2+
3+
This is a deepsec scanning workspace. Each registered project has its
4+
own setup prompt at `data/<id>/SETUP.md` — open the relevant one when
5+
asked to set a project up.
6+
7+
## Common tasks
8+
9+
- **Set up a project for scanning**: read `data/<id>/SETUP.md` and
10+
follow it (read `node_modules/deepsec/SKILL.md`, then fill
11+
`data/<id>/INFO.md` from the target codebase).
12+
- **Add a new project**: run `deepsec init-project <root>` — it
13+
scaffolds `data/<id>/` and prints/writes the setup prompt for the
14+
new project.
15+
- **Write a custom matcher** (only after a real true-positive shows you
16+
a pattern worth keeping): read
17+
`node_modules/deepsec/dist/docs/writing-matchers.md`.
18+
19+
## Reference
20+
21+
The deepsec skill is at `node_modules/deepsec/SKILL.md` (after
22+
`pnpm install`). The full docs ship at
23+
`node_modules/deepsec/dist/docs/`.

.deepsec/README.md

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
# deepsec
2+
3+
This directory holds the [deepsec](https://www.npmjs.com/package/deepsec)
4+
config for the parent repo. Checked into git so teammates inherit
5+
project context (auth shape, threat model, custom matchers); generated
6+
scan output is gitignored.
7+
8+
Currently configured project: `sim` (target: `..`).
9+
10+
## Setup
11+
12+
1. `pnpm install` — installs deepsec.
13+
2. Add an AI Gateway / Anthropic / OpenAI token to `.env.local`. If
14+
you already have `claude` or `codex` CLI logged in on this
15+
machine, you can skip the token for non-sandbox runs (`process` /
16+
`revalidate` / `triage`); deepsec auto-detects and reuses the
17+
subscription. See
18+
`node_modules/deepsec/dist/docs/vercel-setup.md` after install.
19+
3. Open the parent repo in your coding agent (Claude Code, Cursor, …)
20+
and have it follow `data/sim/SETUP.md` to fill in
21+
`data/sim/INFO.md`.
22+
23+
## Daily commands
24+
25+
```bash
26+
pnpm deepsec scan
27+
pnpm deepsec process --concurrency 5
28+
pnpm deepsec revalidate --concurrency 5 # cuts FP rate
29+
pnpm deepsec export --format md-dir --out ./findings
30+
```
31+
32+
`--project-id` is auto-resolved while there's only one project in
33+
`deepsec.config.ts`. Once you've added a second project, pass
34+
`--project-id sim` (or whichever id you want) explicitly.
35+
36+
`scan` is free (regex only). `process` is the AI stage (≈$0.30/file
37+
on Opus by default). Run state goes to `data/sim/`.
38+
39+
## Adding another project
40+
41+
To scan another codebase from this same `.deepsec/`:
42+
43+
```bash
44+
pnpm deepsec init-project ../some-other-package # path relative to .deepsec/
45+
```
46+
47+
Appends an entry to `deepsec.config.ts` and writes
48+
`data/<id>/{INFO.md,SETUP.md,project.json}`. Open the new SETUP.md
49+
in your agent to fill in INFO.md.
50+
51+
## Layout
52+
53+
```
54+
deepsec.config.ts Project list (one entry per scanned repo)
55+
data/sim/
56+
INFO.md Repo context — checked into git, hand-curated
57+
SETUP.md Agent setup prompt — checked in, deletable
58+
project.json Generated (gitignored)
59+
files/ One JSON per scanned source file (gitignored)
60+
runs/ Run metadata (gitignored)
61+
reports/ Generated markdown reports (gitignored)
62+
AGENTS.md Pointer for coding agents
63+
.env.local Tokens (gitignored)
64+
```
65+
66+
## Docs
67+
68+
After `pnpm install`:
69+
70+
- Skill: `node_modules/deepsec/SKILL.md`
71+
- Full docs: `node_modules/deepsec/dist/docs/{getting-started,configuration,models,writing-matchers,plugins,architecture,data-layout,vercel-setup,faq}.md`
72+
73+
Or browse on
74+
[GitHub](https://github.com/vercel/deepsec/tree/main/docs).

.deepsec/data/sim/INFO.md

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
# sim
2+
3+
## What this codebase does
4+
5+
Sim is a Next.js 15 (App Router) TypeScript monorepo — the `apps/sim` package is the main web app. It lets teams build, deploy, and manage AI agents visually. Users connect 1,000+ external integrations (OAuth, API keys) and run AI workflows against real services. The backend exposes ~735 API routes, a server-side workflow executor, and a public REST API (`/api/v1/**`) protected by user-managed API keys. The database is PostgreSQL via PlanetScale (Drizzle ORM). Auth is `better-auth`.
6+
7+
## Auth shape
8+
9+
- `getSession()` — cookie-session lookup (server side). Primary guard for web UI routes.
10+
- `checkHybridAuth(request)` — resolves one of: session cookie, `X-API-Key` header, or internal JWT (`Authorization: Bearer <token>`). Used on most internal API routes.
11+
- `checkSessionOrInternalAuth(request)` — session + internal JWT only; explicitly rejects API keys. Used for routes that must not be externally reachable.
12+
- `checkInternalAuth(request)` — internal JWT only (executor-to-server). Rejects both session and API key.
13+
- `verifyCronAuth(request)``Authorization: Bearer $CRON_SECRET` for cron endpoints.
14+
- `authorizeCredentialUse(request, { credentialId, workflowId? })` — workspace credential + `credentialMember` membership check.
15+
- `getUserEntityPermissions(userId, 'workspace', workspaceId)` — returns permission level or `null` (no access). Used for workspace RBAC.
16+
- `withRouteHandler(handler)` — mandatory wrapper around every export; provides request-ID context and error logging. Routes missing this wrapper skip error observability.
17+
- `authenticateV1Request(request)` from `/api/v1/auth` — public API key gate used by the v1 middleware (`checkRateLimit` + rate limiting).
18+
19+
## Threat model
20+
21+
An attacker would most want: (1) to run arbitrary workflows under another user's identity or with another user's OAuth credentials, since workflows can exfiltrate data from connected services; (2) to inject tool/block definitions or custom tool code that executes server-side; (3) to escalate within an organization (workspace RBAC bypass); (4) to abuse the public `/api/v1` endpoints at scale without triggering rate limits.
22+
23+
## Project-specific patterns to flag
24+
25+
- **Auth check ordering in routes**: Several routes call `parseRequest` (body parsing) *before* auth — if the auth check is later, untrusted input is parsed before the caller is authenticated.
26+
- **Internal JWT fallback**: `checkHybridAuth` prefers internal JWT before session. A valid internal JWT bypasses session auth entirely — flag any route that accepts internal JWT but shouldn't (e.g., user-facing mutation endpoints).
27+
- **Custom tool execution**: `/api/tools/custom` and related endpoints accept user-defined code/config; ensure these go through `checkSessionOrInternalAuth` and not API keys.
28+
- **`workflowId` used as implicit auth**: Several routes resolve `workspaceId` from a caller-supplied `workflowId` without confirming the caller owns the workflow — look for `db.select(...).from(workflow).where(eq(workflow.id, workflowId))` without a subsequent `userId` check.
29+
- **Cron routes**: `verifyCronAuth` returns `null` if `CRON_SECRET` is unset and logs a warning but still returns 401 — confirm every cron route calls this and checks the return.
30+
31+
## Known false-positives
32+
33+
- `apps/sim/app/api/auth/[...all]/route.ts` — better-auth handler; intentionally public, handles its own auth.
34+
- `apps/sim/app/api/health/route.ts` — public health-check endpoint, no auth expected.
35+
- `apps/sim/app/api/webhooks/[id]/route.ts` — inbound webhook receiver; intentionally unauthenticated (validated by HMAC/provider signature inside the handler).
36+
- `apps/sim/app/api/v1/**` — external public API; auth goes through `authenticateV1Request` + v1 middleware, not `getSession`.
37+
- `apps/sim/lib/auth/auth.ts` — Stripe webhook handler embedded inside better-auth; raw `request.json()` is expected here.

.deepsec/data/sim/SETUP.md

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
# Agent setup for `sim`
2+
3+
This is a deepsec scanning workspace. Project `sim` was just registered
4+
(target: `..`). Setup is incomplete — `data/sim/INFO.md`
5+
still has placeholder sections.
6+
7+
## What to do
8+
9+
1. **Read the deepsec skill.** After `pnpm install`, the file is at
10+
`node_modules/deepsec/SKILL.md`. It maps every doc topic to a file
11+
under `node_modules/deepsec/dist/docs/`. Read `getting-started.md`,
12+
`configuration.md`, and `writing-matchers.md` (skim the rest).
13+
14+
2. **Fill in `data/sim/INFO.md`.** It's auto-injected into the AI
15+
prompt for every batch — keep it short and selective.
16+
17+
**Length budget: 50–100 lines total.** Verbose context dilutes
18+
signal in the scanner's prompt window. The goal is "what would a
19+
reviewer miss if they didn't read this?", not exhaustive enumeration.
20+
21+
**Per-section rubric**:
22+
- Pick 3–5 representative items per section. **Don't list every
23+
file, helper, or callsite** — pick the patterns.
24+
- Name primitives by their public name (e.g. `withAuthentication`,
25+
`auth.can()`, `isTeamAdmin`). **No line numbers.** Don't enumerate
26+
more than 5 paths in any list.
27+
- Skip generic CWE categories — built-in matchers already cover
28+
"SSRF", "SQL injection", "XSS". Cover what's *project-specific*:
29+
internal auth helpers, custom middleware names, fork-specific
30+
stubs, intended-public endpoints.
31+
- One short paragraph or 3–5 short bullets per section. Not both.
32+
33+
Source material (read in this order, stop when you have enough):
34+
- `../README.md`
35+
- any `AGENTS.md` / `CLAUDE.md` in `..`
36+
- `../package.json` (or `go.mod`, `pyproject.toml`, etc.)
37+
- 5–10 representative code files (entry points, auth helpers) — not
38+
a full code tour.
39+
40+
3. **(Optional) Add custom matchers** for repo-specific patterns the
41+
built-in matchers won't catch. Read
42+
`node_modules/deepsec/dist/docs/writing-matchers.md` first; the
43+
workflow there starts from a confirmed finding and grows the matcher
44+
from it. Don't add matchers speculatively — wait for a real TP.
45+
46+
## When you're done
47+
48+
The user will run:
49+
50+
```bash
51+
pnpm deepsec scan --project-id sim
52+
pnpm deepsec process --project-id sim
53+
```
54+
55+
You can delete this file once setup is complete.

.deepsec/data/sim/tech.json

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"tags": ["bun", "github-actions", "node"],
3+
"sentinels": ["bun.lock", "package.json"],
4+
"detectedAt": "2026-05-10T18:53:22.646Z",
5+
"rootPath": "/Users/waleed/SimRegistry/sim"
6+
}

.deepsec/deepsec.config.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import { defineConfig } from 'deepsec/config'
2+
3+
export default defineConfig({
4+
projects: [
5+
{ id: 'sim', root: '..' },
6+
// <deepsec:projects-insert-above>
7+
],
8+
})
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
# [BUG] configurePolling trusts caller-supplied credentialId without ownership check
2+
3+
**File:** [`apps/sim/lib/webhooks/providers/gmail.ts`](https://github.com/simstudioai/sim/blob/fix/react-doc/blob/fix/apps/sim/lib/webhooks/providers/gmail.ts#L28-L87) (lines 28, 29, 30, 31, 32, 33, 34, 35, 36, 56, 58, 87)
4+
**Project:** sim
5+
**Severity:** BUG • **Confidence:** medium • **Slug:** `cross-tenant-id`
6+
7+
## Owners
8+
9+
**Suggested assignee:** `walif6@gmail.com` _(via last-committer)_
10+
11+
## Finding
12+
13+
configurePolling reads `credentialId` from `providerConfig` (L30) and uses `resolveOAuthAccountId` + a direct `account` table lookup (L44-47) to derive `effectiveUserId` from the credential owner — then stores that userId on the webhook record (L87). There is no check that the user creating/owning the webhook actually owns the credential. If the upstream webhook-creation API doesn't independently enforce that the caller owns `providerConfig.credentialId`, a workspace member (or attacker with workspace write access) could create a Gmail webhook that polls another user's Gmail using that user's stored credentials, with the workflow run attributed to (and billed/audited against) the credential owner. The polling job (`refreshAccessTokenIfNeeded` at L58-62) then mints access tokens against the credential owner's account. This file alone cannot determine if upstream validation exists, but configurePolling should defense-in-depth verify ownership rather than trust the user-controlled providerConfig.credentialId.
14+
15+
## Recommendation
16+
17+
In configurePolling, additionally verify that the credential identified by `credentialId` is owned by (or shared with) the webhook's owning workspace/user before deriving effectiveUserId from it. The webhook record already provides workspaceId via the caller chain — pass it in via PollingConfigContext and assert that the resolved credential row's workspaceId/userId is accessible to the webhook owner. Confirm the upstream webhook-creation route (`/api/webhooks` POST) explicitly validates credential ownership via `authorizeCredentialUse`.
18+
19+
## Recent committers (`git log`)
20+
21+
- Waleed <walif6@gmail.com> (2026-04-06)
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
# [BUG] sql.raw() interpolates Date.toISOString() into JSONB value — currently safe but brittle
2+
3+
**File:** [`apps/sim/lib/workflows/executor/human-in-the-loop-manager.ts`](https://github.com/simstudioai/sim/blob/fix/react-doc/blob/fix/apps/sim/lib/workflows/executor/human-in-the-loop-manager.ts#L1336) (lines 1336)
4+
**Project:** sim
5+
**Severity:** BUG • **Confidence:** high • **Slug:** `js-sql-raw`
6+
7+
## Owners
8+
9+
**Suggested assignee:** `vikhyathvikku@gmail.com` _(via last-committer)_
10+
11+
## Finding
12+
13+
Line 1336 builds the `resumedAt` JSON value with `'"${sql.raw(now.toISOString())}"'::jsonb` inside a `drizzle-orm` `sql` tagged template. `sql.raw(...)` bypasses parameter binding and splices the string directly into the SQL fragment. Here `now` is locally constructed as `const now = new Date()` (line 1321) and `Date.prototype.toISOString()` is spec-guaranteed to return a fixed `YYYY-MM-DDTHH:mm:ss.sssZ` form with no SQL metacharacters, so this is not exploitable in its current form. However, the pattern is fragile: any future refactor that derives `now` from external input (a header, a body field, a record from another query) would turn this into a JSONB/SQL injection. The fix is purely defensive — the same value is trivially parameterizable.
14+
15+
## Recommendation
16+
17+
Replace `'"${sql.raw(now.toISOString())}"'::jsonb` with a parameterized form: build the JSON string in JS (`const resumedAtJson = JSON.stringify(now.toISOString())`) and interpolate it as a normal parameter — e.g., `ARRAY[${contextId}, 'resumedAt'], ${resumedAtJson}::jsonb`. This removes the only `sql.raw` in the file and makes the SQL future-proof against accidental injection if the time source ever becomes external.
18+
19+
## Recent committers (`git log`)
20+
21+
- Vikhyath Mondreti <vikhyathvikku@gmail.com> (2026-05-05)
22+
- Theodore Li <theo@sim.ai> (2026-05-05)
23+
- Waleed <walif6@gmail.com> (2026-05-02)
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
# [BUG] User-controlled custom tool property names assigned without dangerous-key filtering
2+
3+
**File:** [`apps/sim/tools/utils.ts`](https://github.com/simstudioai/sim/blob/fix/react-doc/blob/fix/apps/sim/tools/utils.ts#L200-L217) (lines 200, 217)
4+
**Project:** sim
5+
**Severity:** BUG • **Confidence:** low • **Slug:** `object-injection`
6+
7+
## Owners
8+
9+
**Suggested assignee:** `vikhyathvikku@gmail.com` _(via last-committer)_
10+
11+
## Finding
12+
13+
In `createParamSchema` (lines 193-222), the function iterates `Object.entries(customTool.schema.function.parameters.properties)` and assigns each `key` to a fresh result object via `params[key] = paramConfig`. The custom tool schema is validated by `customToolSchemaSchema` in `lib/api/contracts/tools/custom.ts`, which uses `z.record(z.string(), z.unknown())` for `properties` — this does NOT filter prototype-pollution keys (`__proto__`, `constructor`, `prototype`).
14+
15+
If a user creates a custom tool with `properties.__proto__: {...}`, the bracket assignment `params['__proto__'] = paramConfig` triggers the prototype setter and rewires `params`'s prototype chain. The pollution is local to that object (no global Object.prototype impact), but it can cause confusing/incorrect property lookups when downstream code does `tool.params.someKey` and the key isn't an own property — JS will then walk the planted prototype.
16+
17+
Similarly, `applyDynamicSchemaForWorkflow` in `params.ts` (line 642-648) uses `propertySchema.properties[field.name] = …` with `field.name` from workflow definitions. While more constrained, the pattern is the same.
18+
19+
No concrete exploitation path was identified, but this is fragile and an obvious bug class. `safeAssign` already exists in `tools/safe-assign.ts` and should be used here.
20+
21+
## Recommendation
22+
23+
Reject or strip dangerous keys (`__proto__`, `constructor`, `prototype`) when constructing `params`. Either tighten `customToolSchemaSchema` to refuse such keys via `z.record(z.string().refine(k => !['__proto__','constructor','prototype'].includes(k)), z.unknown())`, or use `safeAssign` from `@/tools/safe-assign` when building the schema record. The same fix should apply anywhere `Object.create(null)` or `safeAssign` could replace direct bracket assignment over user-controlled keys.
24+
25+
## Recent committers (`git log`)
26+
27+
- Vikhyath Mondreti <vikhyathvikku@gmail.com> (2026-05-06)
28+
- Waleed <walif6@gmail.com> (2026-04-06)

0 commit comments

Comments
 (0)