Conversation
[dev] [Marfuen] mariano/chore-improve-api-middleware-hardening
|
The latest updates on your projects. Learn more about Vercel for GitHub. 2 Skipped Deployments
|
PR SummaryHigh Risk Overview API keys move to strict scoping. New API keys must include at least one valid scope (no more empty-scope “full access” keys), and Input/content hardening. Attachment uploads now validate file content (magic bytes for common binary types + script/HTML pattern scanning for text) before storing, Browserbase URL inputs add SSRF-safe URL validation, markdown rendering adds Written by Cursor Bugbot for commit 990d767. This will update automatically on new commits. Configure here. |
…r auth * fix(app): sanitize HTML in markdown renderer to prevent stored XSS Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix(api): deny legacy API keys with empty scopes and add SSRF protection - API keys with empty scopes now throw ForbiddenException instead of granting full access - Added URL safety validator to Browserbase endpoints blocking private IPs and metadata Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix(api): prevent policy publishing when approval is pending Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix(api): add magic-byte file content validation to prevent MIME spoofing Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix(api): keep legacy API keys working with warning log for migration tracking Legacy keys (empty scopes) continue to have full access but now log a warning on every use for migration tracking. This avoids breaking 188 existing production keys while giving visibility into which need migration. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix(api): enforce scopes on new API keys and set April 20 deprecation for legacy keys - New API keys must have at least one scope (no more empty-scope keys) - Frontend "Full Access" preset now sends all scopes explicitly - Legacy keys with empty scopes continue working until April 20, 2026 - After April 20, legacy keys will be blocked with a clear error message Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * feat(app): show scope details popover on API key badge click Clicking the scope badge on an API key now shows a popover with all permissions grouped by resource. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix(app): remove unsupported className on PopoverContent Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix(api): exclude better-auth internal 'ac' resource from API key scopes Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix(app): add missing labels for app, trust, pentest, training, portal scopes Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix(api): use generic error message for file validation to avoid leaking detection logic Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix(api): use generic error messages to avoid leaking security implementation details Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix(api): add Origin header validation middleware for CSRF protection CORS only blocks fetch-based CSRF (JSON content-type triggers preflight). HTML form submissions with application/x-www-form-urlencoded bypass CORS entirely since they're "simple requests" that don't trigger preflight. This middleware validates the Origin header on all state-changing requests (POST/PUT/PATCH/DELETE) and rejects requests from untrusted origins. Requests without an Origin header (API keys, service tokens, webhooks) are allowed through — they're authenticated by other means. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix(api): prevent task approval workflow bypass via direct status change Block two bypass vectors in the generic PATCH /tasks/:id endpoint: 1. Cannot change status when task is in_review (must use approve/reject) 2. Cannot set status to done when an approver is assigned (must submit for review first) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix(app): fix nested button hydration error in scope popover trigger PopoverTrigger with asChild renders its own button element, so the inner element must not be a button. Changed to span with role=button. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix(app): add security headers to prevent clickjacking and other attacks Adds X-Frame-Options: DENY, CSP frame-ancestors: none, X-Content-Type-Options, and Referrer-Policy headers to all frontend responses. This prevents the app from being embedded in iframes on attacker-controlled pages (clickjacking). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix(api): address bugbot review findings - SSRF: block IPv4-mapped IPv6 addresses (::ffff:169.254.169.254) - SSRF: fix [::1] bracket handling in URL hostname check - File validation: skip text pattern scan for binary files that passed magic bytes - File validation: use specific event handler names instead of broad on\w+ regex - API keys: fix create button enabled before scopes load - API keys: remove dead code in scope validation - API keys: restore ac resource (used by roles controller), add "Roles" label Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix(app): remove unsupported asChild prop from PopoverTrigger Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix(api): enforce approval workflow in bulk task status update endpoint The bulk PATCH /v1/tasks/bulk/status endpoint bypassed the approval workflow guards added to the single-task update. Now excludes tasks that are in_review and tasks with an approver when setting status to done. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix(api): add cookie prefix for staging to prevent collision with production Staging cookies now use 'staging' prefix (e.g. __Secure-staging.session_token) instead of the default 'better-auth' prefix. This prevents cookie conflicts when developers visit both staging and production on *.trycomp.ai. Production is unchanged — keeps the default 'better-auth' prefix. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * revert: remove cookie prefix change — breaks hardcoded cookie name references The proxy.ts middleware checks for cookies by hardcoded name (better-auth.session_token). Changing the prefix breaks auth flow. The staging/production cookie collision is a pre-existing issue caused by .trycomp.ai parent domain cookies overlapping with .staging.trycomp.ai — not caused by our security changes. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix(api): add staging cookie prefix with proxy.ts support Staging API uses 'staging' cookie prefix to avoid collision with production cookies on .trycomp.ai parent domain. Updated proxy.ts to check for both prefixes. Production and localhost unchanged. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix(api): add environment-specific cookie prefixes to prevent collisions Local dev uses 'local' prefix, staging uses 'staging', production keeps default 'better-auth' (unchanged). Proxy.ts checks all prefixes to determine authentication state. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Mariano Fuentes <marfuen98@gmail.com> Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
| 'image/png': [Buffer.from([0x89, 0x50, 0x4e, 0x47])], | ||
| 'image/jpeg': [Buffer.from([0xff, 0xd8, 0xff])], | ||
| 'image/gif': [Buffer.from('GIF87a'), Buffer.from('GIF89a')], | ||
| 'image/webp': [Buffer.from('RIFF')], |
There was a problem hiding this comment.
Weak WebP magic bytes allow content scan bypass
Medium Severity
The image/webp magic byte check only verifies the RIFF prefix (4 bytes), which matches any RIFF-based container (WAV, AVI, etc.). A valid WebP signature requires checking bytes 8–11 for WEBP as well. Because matching a binary MIME type skips the dangerous-content text scan entirely, a crafted RIFF file declared as image/webp containing <script> tags in its payload would bypass both the magic-byte and text-pattern checks.
Additional Locations (1)
|
🎉 This PR is included in version 3.10.3 🎉 The release is available on GitHub release Your semantic-release bot 📦🚀 |


This is an automated pull request to release the candidate branch into production, which will trigger a deployment.
It was created by the [Production PR] action.