[GH-ISSUE #2492] [Security] Missing Authentication on File Upload and S3 Presigned URL Generation Endpoints #695

Open
opened 2026-02-26 18:48:05 +03:00 by kerem · 2 comments
Owner

Originally created by @q1uf3ng on GitHub (Feb 13, 2026).
Original GitHub issue: https://github.com/documenso/documenso/issues/2492

Summary

The Documenso file upload API (/api/files/upload-pdf) and S3 presigned URL generation endpoint (/api/files/presigned-post-url) are mounted without any authentication middleware. Any unauthenticated attacker can upload arbitrary PDF files to the server's storage and request S3 presigned upload URLs, leading to storage exhaustion (DoS) and potential S3 bucket pollution.

Affected Component

  • Files:
    • apps/remix/server/router.ts, line 104
    • apps/remix/server/api/files/files.ts, lines 32-69
  • Endpoints:
    • POST /api/files/upload-pdf
    • POST /api/files/presigned-post-url
  • Affected versions: Latest (current main branch)

Severity

CVSS 3.1: 7.5 (High) AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:H/A:L

Vulnerability Details

Route Mounting (router.ts, line 104)

```typescript
// Files route. - NO AUTH MIDDLEWARE!
app.route('/api/files', filesRoute);
```

No authentication middleware is applied to the `/api/files` route. While other routes like `/api/ai/*` have specific middleware, and auth-protected routes use session checks, the files route is completely open.

Unauthenticated Upload (files.ts, lines 32-56)

```typescript
.post('/upload-pdf', sValidator('form', ZUploadPdfRequestSchema), async (c) => {
try {
const { file } = c.req.valid('form');
if (!file) {
return c.json({ error: 'No file provided' }, 400);
}
const MAX_FILE_SIZE = APP_DOCUMENT_UPLOAD_SIZE_LIMIT * 1024 * 1024;
if (file.size > MAX_FILE_SIZE) {
return c.json({ error: 'File too large' }, 400);
}
// Uploads file to storage AND creates database record
const result = await putNormalizedPdfFileServerSide(file);
return c.json(result);
} catch (error) {
console.error('Upload failed:', error);
return c.json({ error: 'Upload failed' }, 500);
}
})
```

No session check, no authentication verification. The `putNormalizedPdfFileServerSide` function stores the file and creates a `DocumentData` record in the database.

Unauthenticated Presigned URL (files.ts, lines 57-69)

```typescript
.post('/presigned-post-url', sValidator('json', ZGetPresignedPostUrlRequestSchema), async (c) => {
const { fileName, contentType } = c.req.valid('json');
try {
const { key, url } = await getPresignPostUrl(fileName, contentType);
return c.json({ key, url } satisfies TGetPresignedPostUrlResponse);
} catch (err) {
console.error(err);
throw new AppError(AppErrorCode.UNKNOWN_ERROR);
}
})
```

No session check. Any attacker can request S3 presigned upload URLs with arbitrary filenames and content types.

Contrast with Authenticated Endpoints

Other endpoints in the same file (line 70+) properly check for authentication:

```typescript
// Line 78 - This endpoint DOES check session
const session = await getOptionalSession(c);
let userId = session.user?.id;
```

Proof of Concept

```bash

Upload arbitrary PDF to server - no authentication needed

curl -X POST https://target.documenso.com/api/files/upload-pdf
-F "file=@malicious.pdf"

Request S3 presigned URL - no authentication needed

curl -X POST https://target.documenso.com/api/files/presigned-post-url
-H "Content-Type: application/json"
-d '{"fileName": "exploit.pdf", "contentType": "application/pdf"}'
```

Impact

  1. Storage Exhaustion (DoS): An attacker can upload unlimited PDF files without authentication, exhausting server storage or S3 bucket storage.
  2. S3 Bucket Pollution: Presigned URL generation allows arbitrary content to be uploaded to the S3 bucket.
  3. Database Pollution: Each upload creates a `DocumentData` record in the database.
  4. Cost Amplification: For cloud-hosted instances, unlimited file uploads directly increase S3 storage costs.

Remediation

Add authentication middleware to the files route:

```typescript
// Option 1: Add auth middleware to the entire route
app.use('/api/files/*', requireAuth);
app.route('/api/files', filesRoute);

// Option 2: Add session check to individual endpoints
.post('/upload-pdf', sValidator('form', ZUploadPdfRequestSchema), async (c) => {
const session = await getOptionalSession(c);
if (!session?.user) {
return c.json({ error: 'Unauthorized' }, 401);
}
// ... rest of handler
})
```

Originally created by @q1uf3ng on GitHub (Feb 13, 2026). Original GitHub issue: https://github.com/documenso/documenso/issues/2492 ## Summary The Documenso file upload API (`/api/files/upload-pdf`) and S3 presigned URL generation endpoint (`/api/files/presigned-post-url`) are mounted without any authentication middleware. Any unauthenticated attacker can upload arbitrary PDF files to the server's storage and request S3 presigned upload URLs, leading to storage exhaustion (DoS) and potential S3 bucket pollution. ## Affected Component - **Files**: - `apps/remix/server/router.ts`, line 104 - `apps/remix/server/api/files/files.ts`, lines 32-69 - **Endpoints**: - `POST /api/files/upload-pdf` - `POST /api/files/presigned-post-url` - **Affected versions**: Latest (current main branch) ## Severity **CVSS 3.1: 7.5 (High)** AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:H/A:L ## Vulnerability Details ### Route Mounting (router.ts, line 104) \`\`\`typescript // Files route. - NO AUTH MIDDLEWARE! app.route('/api/files', filesRoute); \`\`\` No authentication middleware is applied to the \`/api/files\` route. While other routes like \`/api/ai/*\` have specific middleware, and auth-protected routes use session checks, the files route is completely open. ### Unauthenticated Upload (files.ts, lines 32-56) \`\`\`typescript .post('/upload-pdf', sValidator('form', ZUploadPdfRequestSchema), async (c) => { try { const { file } = c.req.valid('form'); if (!file) { return c.json({ error: 'No file provided' }, 400); } const MAX_FILE_SIZE = APP_DOCUMENT_UPLOAD_SIZE_LIMIT * 1024 * 1024; if (file.size > MAX_FILE_SIZE) { return c.json({ error: 'File too large' }, 400); } // Uploads file to storage AND creates database record const result = await putNormalizedPdfFileServerSide(file); return c.json(result); } catch (error) { console.error('Upload failed:', error); return c.json({ error: 'Upload failed' }, 500); } }) \`\`\` No session check, no authentication verification. The \`putNormalizedPdfFileServerSide\` function stores the file and creates a \`DocumentData\` record in the database. ### Unauthenticated Presigned URL (files.ts, lines 57-69) \`\`\`typescript .post('/presigned-post-url', sValidator('json', ZGetPresignedPostUrlRequestSchema), async (c) => { const { fileName, contentType } = c.req.valid('json'); try { const { key, url } = await getPresignPostUrl(fileName, contentType); return c.json({ key, url } satisfies TGetPresignedPostUrlResponse); } catch (err) { console.error(err); throw new AppError(AppErrorCode.UNKNOWN_ERROR); } }) \`\`\` No session check. Any attacker can request S3 presigned upload URLs with arbitrary filenames and content types. ### Contrast with Authenticated Endpoints Other endpoints in the same file (line 70+) properly check for authentication: \`\`\`typescript // Line 78 - This endpoint DOES check session const session = await getOptionalSession(c); let userId = session.user?.id; \`\`\` ## Proof of Concept \`\`\`bash # Upload arbitrary PDF to server - no authentication needed curl -X POST https://target.documenso.com/api/files/upload-pdf \ -F "file=@malicious.pdf" # Request S3 presigned URL - no authentication needed curl -X POST https://target.documenso.com/api/files/presigned-post-url \ -H "Content-Type: application/json" \ -d '{"fileName": "exploit.pdf", "contentType": "application/pdf"}' \`\`\` ## Impact 1. **Storage Exhaustion (DoS)**: An attacker can upload unlimited PDF files without authentication, exhausting server storage or S3 bucket storage. 2. **S3 Bucket Pollution**: Presigned URL generation allows arbitrary content to be uploaded to the S3 bucket. 3. **Database Pollution**: Each upload creates a \`DocumentData\` record in the database. 4. **Cost Amplification**: For cloud-hosted instances, unlimited file uploads directly increase S3 storage costs. ## Remediation Add authentication middleware to the files route: \`\`\`typescript // Option 1: Add auth middleware to the entire route app.use('/api/files/*', requireAuth); app.route('/api/files', filesRoute); // Option 2: Add session check to individual endpoints .post('/upload-pdf', sValidator('form', ZUploadPdfRequestSchema), async (c) => { const session = await getOptionalSession(c); if (!session?.user) { return c.json({ error: 'Unauthorized' }, 401); } // ... rest of handler }) \`\`\`
Author
Owner

@github-actions[bot] commented on GitHub (Feb 13, 2026):

Thank you for opening your first issue and for being a part of the open signing revolution!

One of our team members will review it and get back to you as soon as it possible 💚

Meanwhile, please feel free to hop into our community in Discord

<!-- gh-comment-id:3895782231 --> @github-actions[bot] commented on GitHub (Feb 13, 2026): Thank you for opening your first issue and for being a part of the open signing revolution! <br /> One of our team members will review it and get back to you as soon as it possible 💚 <br /> Meanwhile, please feel free to hop into our community in [Discord](https://documen.so/discord)
Author
Owner

@wang2-lat commented on GitHub (Feb 21, 2026):

建议在 apps/remix/server/router.ts 的第 104 行前加上认证中间件(如 requireAuth 或 JWT 验证),同时对上传接口增加速率限制和文件大小校验,防止未授权访问和存储滥用。

<!-- gh-comment-id:3939040545 --> @wang2-lat commented on GitHub (Feb 21, 2026): 建议在 `apps/remix/server/router.ts` 的第 104 行前加上认证中间件(如 `requireAuth` 或 JWT 验证),同时对上传接口增加速率限制和文件大小校验,防止未授权访问和存储滥用。
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
starred/documenso#695
No description provided.