fix(files): authorize presigned upload contexts per type - #6175
Conversation
The batch presign endpoint validated its type param against the shared seven-value upload enum while only authorizing knowledge-base, so any authenticated user could mint an S3 presigned PUT into workspace-logos, profile-pictures, execution, mothership, chat and copilot prefixes — objects that are then served unauthenticated from the app origin with a one-year public cache. The single presign endpoint had the same gap for chat, which has no authorization predicate and no client. - Batch presign now accepts only knowledge-base, and always requires a workspaceId the caller has write/admin on before anything is minted - Single presign drops chat from its accepted contexts; every remaining context has a per-context predicate - Both allowlists live in the contract module so the enforced enum and the documented one cannot drift apart again
|
The latest updates on your projects. Learn more about Vercel for GitHub. |
PR SummaryHigh Risk Overview
Tests add full coverage for the batch route (401, rejected contexts, missing params, 403, happy path, fallback) and a regression that Reviewed by Cursor Bugbot for commit 999b80d. Configure here. |
Greptile SummaryThe PR closes unauthorized presigned-upload paths by defining endpoint-specific upload-context schemas and requiring workspace write access for batch knowledge-base uploads.
Confidence Score: 5/5The PR appears safe to merge with no actionable regressions identified in the changed upload authorization paths. The endpoint-specific allowlists match all repository callers, workspace authorization occurs before batch URL issuance or fallback, and the removed profile-picture session check remains enforced by the route-level authentication guard.
|
| Filename | Overview |
|---|---|
| apps/sim/app/api/files/presigned/batch/route.ts | Restricts batch uploads to knowledge-base files, requires workspace write authorization, and consistently records ownership for generated keys. |
| apps/sim/app/api/files/presigned/route.ts | Uses the narrowed single-upload context schema so chat is rejected while each remaining context follows its existing authorization and validation path. |
| apps/sim/lib/api/contracts/storage-transfer.ts | Introduces endpoint-specific upload-context enums and reuses the single-upload enum in the documented query contract. |
| apps/sim/app/api/files/presigned/batch/route.test.ts | Adds comprehensive regression tests covering authentication, authorization, context and file validation, storage fallback, and ownership metadata. |
| apps/sim/app/api/files/presigned/route.test.ts | Replaces obsolete chat success cases and verifies that the unauthorizable chat context cannot mint a URL. |
Sequence Diagram
sequenceDiagram
participant Client
participant Route as Presigned Upload Route
participant Auth as Session / Workspace Auth
participant Storage as Object Storage
participant Metadata as File Metadata
Client->>Route: POST upload request with context
Route->>Auth: Validate session
Auth-->>Route: Authenticated user
Route->>Route: Validate endpoint-specific context
alt Batch knowledge-base upload
Route->>Auth: Check workspace write/admin access
Auth-->>Route: Authorized
end
Route->>Storage: Mint presigned upload URL
Storage-->>Route: URL and object key
opt Knowledge-base upload
Route->>Metadata: Record workspace ownership
end
Route-->>Client: Presigned URL and file information
Reviews (1): Last reviewed commit: "fix(files): authorize presigned upload c..." | Re-trigger Greptile
Summary
POST /api/files/presigned/batchvalidated itstypeparam against the shared 7-value upload enum but only authorizedknowledge-base, so any authenticated user could mint an S3 presigned PUT intoworkspace-logos/,profile-pictures/,execution/,mothership/,chat/andcopilot/— objects then served unauthenticated from the app origin with a one-year public cacheknowledge-baseonly (its sole client and its sole authorizable context) and always requires aworkspaceIdthe caller has write/admin on before any URL is mintedPOST /api/files/presignedhad the same class of gap forchat: no authorization predicate, no content-type restriction, no client. Dropped it — every remaining context there has a per-context predicate (copilotself-scoped + type-restricted,profile-picturesself-scoped + image-only,mothership/executionworkspace write,workspace-logosworkspace admin,knowledge-baseworkspace write)lib/api/contracts/storage-transfer.tsso the enum that is enforced and the one that is documented can't drift apart again — that drift was the root causeType of Change
Testing
app/api/files/presigned/batch/route.test.ts: 401 unauthenticated, 400 for each of the six refused contexts with no URL minted, 400 missingworkspaceId, 403 for read/no access, authz enforced ahead of the local-storage fallback, file-type rejection before minting, and the authorized happy pathtype=chatis rejected without mintingapp/api/files+lib/uploads: 449 tests passing;tsc -p apps/simclean;bun run check:api-validationpassedNot included
Per-user upload quota / billing attribution for presign issuance. With authorization restored, only workspace members with write access can mint these URLs, so metering is a separate billing feature rather than part of this fix.
Checklist