fix(security): harden auth, SSRF, injection, and CORS across API routes#3792
fix(security): harden auth, SSRF, injection, and CORS across API routes#3792waleedlatif1 merged 25 commits intostagingfrom
Conversation
…ile serve The /api/files/serve endpoint trusted a user-supplied `context` query parameter to skip authentication. An attacker could append `?context=profile-pictures` to any file URL and download files without auth. Now the public access gate checks the key prefix instead of the query param, and `og-images/` is added to `inferContextFromKey`. Co-Authored-By: Claude Opus 4.6 <[email protected]>
Prevents accidental heredoc termination if script content contains the delimiter string on its own line. Co-Authored-By: Claude Opus 4.6 <[email protected]>
Use escapeShellArg() with single quotes for the workingDirectory parameter, consistent with all other SSH routes (execute-script, create-directory, delete-file, move-rename). Co-Authored-By: Claude Opus 4.6 <[email protected]>
…tokens) - Add brute-force protection to OTP verification with attempt tracking (CWE-307) - Replace Math.random() with crypto.randomInt() for OTP generation (CWE-338) - Replace unsigned Base64 auth tokens with HMAC-SHA256 signed tokens (CWE-327) - Use shared isEmailAllowed utility in OTP route instead of inline duplicate - Simplify Redis OTP update to single KEEPTTL call Co-Authored-By: Claude Opus 4.6 <[email protected]>
Add DNS-based SSRF validation for MCP server URLs, secure OIDC discovery with IP-pinned fetch, strengthen OTP/chat/form input validation, sanitize 1Password vault parameters, and tighten deployment security checks. Co-Authored-By: Claude Opus 4.6 <[email protected]>
|
The latest updates on your projects. Learn more about Vercel for GitHub. |
PR SummaryHigh Risk Overview Prevents file-serve auth bypass by removing trust in user-supplied Adds OTP brute-force protection for chat email auth by storing OTPs as Strengthens deployed chat/form auth cookies by switching to HMAC-signed tokens ( Expands SSRF protections: OIDC discovery fetch now does DNS validation and IP-pinned fetch; MCP server create/update/test and service env-var resolution now perform DNS-based SSRF checks with explicit error mapping. Mitigates command injection in SSH tool routes by escaping Written by Cursor Bugbot for commit 4790853. Configure here. |
|
@greptile |
|
@cursor review |
…ted path The `?context` query param was still being passed to `handleCloudProxy` in the authenticated code path, allowing any logged-in user to spoof context as `profile-pictures` and bypass ownership checks in `verifyFileAccess`. Now always use `inferContextFromKey` from the server-controlled key prefix.
Greptile SummaryThis PR applies a broad set of hardening fixes across authentication, SSRF, SSH injection, CORS, and OTP brute-force protection. The changes are well-scoped and address real attack vectors:
Previous review comments (TOCTOU, Redis Confidence Score: 5/5Safe to merge — all raised security concerns from previous review rounds are resolved, and no new regressions or vulnerabilities were found. All eight stated attack vectors are correctly addressed. Previously flagged issues (TOCTOU race, Redis KEEPTTL compatibility, legacy OTP format backward-compat, DNS vs SSRF error distinction, unreachable fetch fallback) are all resolved. The single remaining note is a minor P2 style suggestion to parallelise four sequential DNS lookups during SSO registration — it does not affect correctness or security. No files require special attention. apps/sim/app/api/auth/sso/register/route.ts has a minor sequential DNS lookup pattern worth optimising but it is not blocking. Important Files Changed
Sequence DiagramsequenceDiagram
participant Client
participant MCPRoute as MCP Route (POST/PATCH)
participant DomainCheck as validateMcpDomain
participant SsrfCheck as validateMcpServerSsrf
participant DNS as DNS Resolver
participant Service as McpService.resolveConfigEnvVars
participant DB as Database
Client->>MCPRoute: POST /api/mcp/servers {url}
MCPRoute->>DomainCheck: validateMcpDomain(url)
alt Domain not allowed
DomainCheck-->>MCPRoute: throw McpDomainNotAllowedError
MCPRoute-->>Client: 403 Domain not allowed
end
MCPRoute->>SsrfCheck: validateMcpServerSsrf(url) [pre-resolution]
SsrfCheck->>DNS: dns.lookup(hostname)
alt DNS failure
DNS-->>SsrfCheck: error
SsrfCheck-->>MCPRoute: throw McpDnsResolutionError
MCPRoute-->>Client: 502 DNS resolution failed
else Resolves to private IP
SsrfCheck-->>MCPRoute: throw McpSsrfError
MCPRoute-->>Client: 403 SSRF blocked
end
MCPRoute->>DB: INSERT server
MCPRoute-->>Client: 201 Created
Note over Service,DNS: At tool-execution time (defense-in-depth)
Service->>DNS: dns.lookup(resolvedUrl)
alt DNS rebinding detected
DNS-->>Service: private IP
Service-->>Service: throw McpSsrfError (fail-closed)
end
Reviews (7): Last reviewed commit: "fix: format long lines in chat/form test..." | Re-trigger Greptile |
Add guard for OTP values without colon separator (pre-deploy format) to avoid misparse that would lock out users with in-flight OTPs. Co-Authored-By: Claude Opus 4.6 <[email protected]>
DNS lookup failures now throw McpDnsResolutionError (502) instead of McpSsrfError (403), so transient DNS hiccups surface as retryable upstream errors rather than confusing permission rejections. Co-Authored-By: Claude Opus 4.6 <[email protected]>
|
@greptile |
|
@cursor review |
Redis path: use Lua script for atomic read-increment-conditional-delete. DB path: use optimistic locking (UPDATE WHERE value = currentValue) with re-read fallback on conflict. Prevents concurrent wrong guesses from each counting as a single attempt. Co-Authored-By: Claude Opus 4.6 <[email protected]>
|
@greptile |
|
@cursor review |
Reject OTPs that have already reached max failed attempts before comparing the code, closing a race window where a correct guess could bypass brute-force protection. Co-Authored-By: Claude Opus 4.6 <[email protected]>
- Replace Redis KEEPTTL with TTL+SET EX for Redis <6.0 compatibility - Add retry loop to DB optimistic lock path so concurrent OTP attempts are actually counted instead of silently dropped - Remove unreachable fallback fetch in 1Password Connect; make validateConnectServerUrl return non-nullable string Co-Authored-By: Claude Opus 4.6 <[email protected]>
|
@greptile |
|
@cursor review |
When the Redis key is deleted/expired between getOTP and incrementOTPAttempts, the Lua script returns nil. Handle this as 'locked' instead of silently treating it as 'incremented'. Co-Authored-By: Claude Opus 4.6 <[email protected]>
…ution - Treat Redis Lua nil return (expired/deleted key) as 'locked' instead of silently treating it as a successful increment - Add validateMcpServerSsrf to MCP service resolveConfigEnvVars so env-var URLs are SSRF-validated after resolution at execution time Co-Authored-By: Claude Opus 4.6 <[email protected]>
Replace urlValidation.resolvedIP! with proper type narrowing by adding !urlValidation.resolvedIP to the guard clause, so TypeScript can infer the string type without a fragile assertion. Co-Authored-By: Claude Opus 4.6 <[email protected]>
Include a SHA-256 hash of the encrypted password in the HMAC-signed token payload. Changing the deployment password now immediately invalidates all existing auth cookies, restoring the pre-HMAC behavior. Co-Authored-By: Claude Opus 4.6 <[email protected]>
…n-null assertion - Include SHA-256 hash of encryptedPassword in HMAC token payload so changing a deployment's password immediately invalidates all sessions - Pass encryptedPassword through setChatAuthCookie/setFormAuthCookie and validateAuthToken at all call sites - Replace non-null assertion on resolvedIP with proper narrowing guard Co-Authored-By: Claude Opus 4.6 <[email protected]>
|
@cursor review |
|
@greptile |
Tests now expect the encryptedPassword arg passed to validateAuthToken and setDeploymentAuthCookie after the password-binding change. Co-Authored-By: Claude Opus 4.6 <[email protected]>
Co-Authored-By: Claude Opus 4.6 <[email protected]>
|
@greptile |
|
@cursor review |
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.
Select chat.password in PUT handler DB query and pass it to setChatAuthCookie so OTP-issued tokens include the correct password slot for subsequent validation. Co-Authored-By: Claude Opus 4.6 <[email protected]>

Summary
?contextquery param with key-prefix check so attackers can't skip auth by appending?context=profile-picturesto any file URL. Addedog-images/toinferContextFromKeyandverifyFileAccess.INCR/ DB fallback) that invalidates the OTP after 5 failed attempts and returns 429. Clears attempts on new OTP generation.BETTER_AUTH_SECRET+timingSafeEqualvalidation. RemovedencryptedPasswordfrom token generation.Access-Control-Allow-Credentials: truefrom chat/form CORS headers. Cookies areSameSite=Laxand only used within same-origin iframe contexts, so credentials header was unnecessary.validateMcpServerSsrf) before and after env var resolution in MCP server create/test-connection routes and service layer.workingDirectoryin execute-command route.validateUrlWithDNS+secureFetchWithPinnedIPto SSO register route for OIDC discovery URL fetches.Type of Change
Testing
Checklist