fix(security): supabase rpc path validation, ssh stream byte cap, storage quota coverage#4605
Conversation
…rage quota coverage
|
The latest updates on your projects. Learn more about Vercel for GitHub. |
PR SummaryHigh Risk Overview Multipart uploads now enforce Workflow execution result logging adds a pre-check that an Also adds a workspace access guard to Reviewed by Cursor Bugbot for commit 1f7c652. Configure here. |
Greptile SummaryThis PR applies five targeted security hardening measures across the Sim application: Supabase RPC path traversal prevention, SSH stream byte-cap enforcement, storage quota coverage broadening, cross-tenant execution log write isolation, and workspace membership gating on env-var decryption.
Confidence Score: 5/5Safe to merge — all five security fixes are well-scoped, properly tested, and no regressions are introduced in the changed paths. Each fix is narrow in scope and verified by new unit tests. The cross-tenant log guard adds a pre-check SELECT and tightens every WHERE clause with workflowId; the static markExecutionAsFailed signature change is completed across all call sites. The workspace membership guard in getPersonalAndWorkspaceEnv correctly propagates errors through the existing cache layer. The storage quota lift and SSH stream cap are straightforward additions with no observable side effects on exempt contexts or normal-sized reads. No files require special attention. Important Files Changed
Reviews (5): Last reviewed commit: "fix(logging): scope completeWithCancella..." | Re-trigger Greptile |
…-var workspace membership guard
Closes two cross-tenant vulnerabilities:
1. Workflow log cross-tenant write (route.ts + logging-session.ts):
- Route: SELECT before creating LoggingSession to verify executionId belongs
to the claimed workflowId; reject with 404 if owned by a different workflow.
- LoggingSession: add workflow_id to all UPDATE/SELECT WHERE clauses
(raw SQL marker queries, flushAccumulatedCost, loadExistingCost) so
writes are a no-op if executionId was somehow injected.
2. Env-var workspace membership guard (environment/utils.ts):
- getPersonalAndWorkspaceEnv now calls checkWorkspaceAccess when workspaceId
is provided; throws if the userId is not a member, preventing any future
caller from reading another workspace's decrypted secrets without
explicit membership verification at the call site.
|
@greptile |
|
@cursor review |
…ad workflowId through HITL callers
…site route; scope markExecutionAsFailed by workflowId
…ct biome .next glob
|
@greptile |
|
@cursor review |
The previous fix only checked userId equality for personal credentials and workspace membership (via getUserEntityPermissions) for workspace credentials. authorizeCredentialUse additionally enforces credentialMember access for workspace-scoped credentials, matching the standard pattern used by all other tool selector routes.
Making workflowId optional left a footgun — future callers could silently omit it and the WHERE clause would degrade to executionId-only, losing the cross-tenant scoping guarantee. All callers already supply workflowId, so making it required (with string | undefined for the middle params to keep call sites unchanged) closes the gap without touching any caller.
…x, and workflowId scoping - log/route.test.ts: verifies cross-tenant executionId guard returns 404 when the execution belongs to a different workflow, and passes for same workflow or fresh executions - multipart/route.test.ts: verifies fileSize:0 no longer bypasses quota check and that the logs context is rejected at the endpoint level - logging-session.test.ts: verifies markExecutionAsFailed scopes by both executionId and workflowId, and that the instance method forwards workflowId
|
@greptile |
|
@cursor review |
…ads by workflowId Both SELECT queries that check execution status before writing a terminal result were only filtering on executionId. Adds workflowId to the WHERE clause so all seven reads and writes in LoggingSession consistently scope by (workflowId, executionId).
|
@greptile |
|
@cursor review |
There was a problem hiding this comment.
✅ Bugbot reviewed your changes and found no new issues!
Comment @cursor review or bugbot run to trigger another review on this PR
Reviewed by Cursor Bugbot for commit 1f7c652. Configure here.
Summary
Four security fixes across storage, SSH, and execution isolation:
1. Supabase vector_search path traversal
functionNamewas interpolated directly into the RPC URL without validation. AddedvalidateDatabaseIdentifier(same guard used by all other Supabase tools) andencodeURIComponentas defense-in-depth. Prevents prompt-injected path traversal to Supabase admin endpoints via the RPC prefix.2. SSH read-file-content stream byte cap
The route checked file size via SFTP stat before reading, but a hostile server could lie about the stat and stream more bytes than declared. Added a running
totalBytescounter in the streamdatahandler that destroys the stream and rejects immediately on overflow. The stat pre-check is kept as an early-exit fast path.3. Storage quota bypass
checkStorageQuotaonly ran forworkspaceandmothershipcontexts. User-driven uploads viaknowledge-base,chat,copilot, andexecutioncontexts bypassed billing limits entirely. Moved the quota check before the context branching so it applies to all non-exempt contexts. Exempt contexts (profile-pictures,workspace-logos,og-images) extracted into a sharedQUOTA_EXEMPT_STORAGE_CONTEXTSconstant inlib/uploads/shared/types.ts.4. Workflow execution log cross-tenant write (
route.ts+logging-session.ts)An attacker who owns any workflow could call
POST /api/workflows/[their-id]/logwith a victim'sexecutionIdin the body. The route verified workflow ownership but not that theexecutionIdbelonged to it — allowing the attacker to overwrite another user's execution log.LoggingSession, SELECT to verify theexecutionId(if already in DB) belongs to the claimed workflow. Returns 404 on mismatch.AND workflow_id = ?to all UPDATE/SELECT WHERE clauses inLoggingSession— raw SQL marker persistence queries and Drizzle queries influshAccumulatedCost/loadExistingCost— so an injectedexecutionIdresults in a no-op rather than a cross-tenant write.5. Env-var workspace membership guard (
environment/utils.ts)getPersonalAndWorkspaceEnvcould be called with anyuserId/workspaceIdpair and would return decrypted workspace secrets without verifying membership. The primary call sites already did the check, but the function had no internal guard.checkWorkspaceAccessat the top ofgetPersonalAndWorkspaceEnvwhenworkspaceIdis provided. Throws if the user lacks access, preventing any code path from inadvertently exfiltrating another workspace's secrets.Type of Change
Test plan
knowledge-base/copilot/executionuploads hit quota limitsexecutionIdgetPersonalAndWorkspaceEnvthrows for non-member workspaceChecklist