Skip to content

Commit 990d767

Browse files
github-actions[bot]Marfuenclaude
authored
chore: scope cookies to local, staging and default for prod for better 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>
1 parent 6c6d569 commit 990d767

2 files changed

Lines changed: 15 additions & 7 deletions

File tree

apps/api/src/auth/auth.server.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,14 @@ export const auth = betterAuth({
150150
database: {
151151
generateId: false,
152152
},
153+
// Prevent cookie collisions between environments.
154+
// Production keeps the default 'better-auth' prefix (unchanged).
155+
...(cookieDomain === '.staging.trycomp.ai' && {
156+
cookiePrefix: 'staging',
157+
}),
158+
...(!cookieDomain && {
159+
cookiePrefix: 'local',
160+
}),
153161
...(cookieDomain && {
154162
crossSubDomainCookies: {
155163
enabled: true,

apps/app/src/proxy.ts

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -27,13 +27,13 @@ export async function proxy(request: NextRequest) {
2727
}
2828
}
2929

30-
const secureCookieName = '__Secure-better-auth.session_token';
31-
const fallbackCookieName = 'better-auth.session_token';
32-
33-
let sessionToken = request.cookies.get(secureCookieName)?.value;
34-
if (!sessionToken) {
35-
sessionToken = request.cookies.get(fallbackCookieName)?.value;
36-
}
30+
// Check for session cookies across all environment prefixes
31+
const sessionToken =
32+
request.cookies.get('__Secure-better-auth.session_token')?.value ||
33+
request.cookies.get('better-auth.session_token')?.value ||
34+
request.cookies.get('__Secure-staging.session_token')?.value ||
35+
request.cookies.get('staging.session_token')?.value ||
36+
request.cookies.get('local.session_token')?.value;
3737
const hasToken = Boolean(sessionToken);
3838
const nextUrl = request.nextUrl;
3939
const requestHeaders = new Headers(request.headers);

0 commit comments

Comments
 (0)