feat(project): added project support v1#7
Conversation
Greptile SummaryThis PR introduces project/org support by adding two new DB tables (
Confidence Score: 3/5Not safe to merge as-is — the onboarding handler trusts an arbitrary user ID from the client to create org records, breaking tenant isolation. The onboarding server function uses a client-supplied userId to look up and insert org rows without verifying it against the authenticated session. An authenticated user can substitute any other user's ID and claim ownership of their org slot. This is an active authorization flaw on a write path, not a theoretical edge case. src/lib/server/onboarding.ts is the primary file that needs attention — specifically how userId is sourced and how ctx.data is forwarded wholesale to the scrawn backend.
|
| Filename | Overview |
|---|---|
| src/lib/server/onboarding.ts | New file encapsulating onboarding logic; reads MASTER_API_KEY server-side (fixing the previous review's concern), but accepts userId from the client and uses it without session verification — a horizontal privilege escalation risk. |
| src/db/schema.ts | Adds org and project tables with proper cascade-delete foreign keys; project references org via lazy callback correctly. |
| src/lib/server/core.ts | New shared helpers (validator, apiGet, apiPost, apiDelete) extracted from the monolithic scrawn-server.ts; no logic changes, straightforward refactor. |
| src/lib/scrawn-server.ts | Large file split into focused modules under src/lib/server/; now only re-exports from those modules. No behavioral change. |
| src/routes/onboarding.tsx | Adds a new Project Name step (step 0, shifting all existing steps by one); userId sourced from session client-side and sent to the server function, which is where the trust boundary issue originates. |
| drizzle.config.ts | dotenv.config() now loads from .env instead of .env.local, which may break local dev workflows that store DATABASE_URL only in .env.local. |
Reviews (2): Last reviewed commit: "fix(project): fixed issues by greptile" | Re-trigger Greptile
| let userOrg = await db.query.org.findFirst({ | ||
| where: eq(org.userId, ctx.data.userId), | ||
| }) | ||
|
|
||
| if (!userOrg) { | ||
| const newOrgId = randomUUID() | ||
| const [newOrg] = await db | ||
| .insert(org) | ||
| .values({ | ||
| orgId: newOrgId, | ||
| userId: ctx.data.userId, | ||
| }) | ||
| .returning() | ||
| userOrg = newOrg |
There was a problem hiding this comment.
Horizontal privilege escalation via client-supplied
userId
The server function blindly trusts ctx.data.userId to look up and create orgs. Since submitOnboarding is a POST server function whose input is fully client-controlled, any authenticated user can supply a different user's ID and onboard on their behalf — creating an org row tied to an arbitrary userId they do not own. The userId should instead be derived server-side from the authenticated session (e.g., via BetterAuth's server-side session API) and never accepted as client input for authorization decisions.
No description provided.