Skip to content

[comp] Production Deploy#2344

Merged
Marfuen merged 4 commits intoreleasefrom
main
Mar 19, 2026
Merged

[comp] Production Deploy#2344
Marfuen merged 4 commits intoreleasefrom
main

Conversation

@github-actions
Copy link
Contributor

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.

github-actions bot and others added 2 commits March 19, 2026 17:38
[dev] [Marfuen] mariano/chore-improve-api-middleware-hardening
@vercel
Copy link

vercel bot commented Mar 19, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

2 Skipped Deployments
Project Deployment Actions Updated (UTC)
app (staging) Skipped Skipped Mar 19, 2026 9:18pm
portal (staging) Skipped Skipped Mar 19, 2026 9:18pm

Request Review

@cursor
Copy link

cursor bot commented Mar 19, 2026

PR Summary

High Risk
Touches security-critical surfaces (CORS/origin enforcement, auth cookie prefixes, API-key authorization rules) and could block legitimate clients if origins/scopes/cookies are misconfigured. Also changes task/policy approval constraints that may alter existing workflows.

Overview
Tightens multiple security controls across API + app. The API now enforces an explicit CORS allowlist via getTrustedOrigins(), adds an Origin-validation middleware to block state-changing browser requests from untrusted origins, and prevents auth cookie collisions by using environment-specific better-auth cookiePrefix values (with corresponding client cookie detection updates).

API keys move to strict scoping. New API keys must include at least one valid scope (no more empty-scope “full access” keys), and PermissionGuard begins deprecating legacy empty-scope keys by allowing them only until 2026-04-20 before rejecting them; the app UI is updated to always send explicit scopes and to show per-scope details.

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 rehype-sanitize, and policy/task services add guards to prevent bypassing approval workflows (blocking direct publish/status changes in certain pending-review states).

Written by Cursor Bugbot for commit 990d767. This will update automatically on new commits. Configure here.

@vercel vercel bot temporarily deployed to staging – portal March 19, 2026 20:06 Inactive
@vercel vercel bot temporarily deployed to staging – app March 19, 2026 20:06 Inactive
…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>
Copy link

@cursor cursor bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

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')],
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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)
Fix in Cursor Fix in Web

@Marfuen Marfuen merged commit 41559cd into release Mar 19, 2026
12 checks passed
@claudfuen
Copy link
Contributor

🎉 This PR is included in version 3.10.3 🎉

The release is available on GitHub release

Your semantic-release bot 📦🚀

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants