Replace codetabs with first-party proxy#25
Conversation
Co-authored-by: Siraj Chokshi <SirajChokshi@users.noreply.github.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
Co-authored-by: Siraj Chokshi <SirajChokshi@users.noreply.github.com>
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 2 potential issues.
Autofix Details
Bugbot Autofix prepared fixes for both issues found in the latest run.
- ✅ Fixed: GitLab path validation off-by-one allows branch-only URLs
- Changed the length check from
rawPathSegment + 2torawPathSegment + 3to correctly require a file segment after the branch in GitLab raw paths.
- Changed the length check from
- ✅ Fixed: Proxy follows redirects without validating redirect destination
- Replaced
redirect: 'follow'withredirect: 'manual'and added a loop that validates each redirect target againstisAllowedProxyTargetbefore following it, preventing SSRF via open redirects.
- Replaced
Or push these changes by commenting:
@cursor push 26bb8719f4
Preview (26bb8719f4)
diff --git a/src/routes/api/proxy/+server.ts b/src/routes/api/proxy/+server.ts
--- a/src/routes/api/proxy/+server.ts
+++ b/src/routes/api/proxy/+server.ts
@@ -4,6 +4,7 @@
getAllowedProxyOrigins,
getRequestOrigin,
isAllowedOrigin,
+ isAllowedProxyTarget,
MAX_PROXY_RESPONSE_BYTES,
parseProxyTarget,
ProxyRequestError,
@@ -50,15 +51,41 @@
}
let upstream: Response
+ let currentTarget = target
try {
- upstream = await fetch(target.toString(), {
- redirect: 'follow',
- headers: {
- Accept: request.headers.get('accept') ?? '*/*',
- },
- })
- } catch {
+ const maxRedirects = 5
+ for (let i = 0; ; i++) {
+ upstream = await fetch(currentTarget.toString(), {
+ redirect: 'manual',
+ headers: {
+ Accept: request.headers.get('accept') ?? '*/*',
+ },
+ })
+
+ if (upstream.status < 300 || upstream.status >= 400) break
+ if (i >= maxRedirects) throw error(502, 'Too many redirects')
+
+ const location = upstream.headers.get('location')
+ if (!location) break
+
+ let redirectTarget: URL
+ try {
+ redirectTarget = new URL(location, currentTarget)
+ } catch {
+ throw error(502, 'Invalid redirect location')
+ }
+
+ redirectTarget.hash = ''
+
+ if (!isAllowedProxyTarget(redirectTarget)) {
+ throw error(403, 'Redirect target is not allowed')
+ }
+
+ currentTarget = redirectTarget
+ }
+ } catch (err) {
+ if (err && typeof err === 'object' && 'status' in err) throw err
throw error(502, `Could not load ${target.toString()}`)
}
diff --git a/src/utils/proxy.ts b/src/utils/proxy.ts
--- a/src/utils/proxy.ts
+++ b/src/utils/proxy.ts
@@ -70,7 +70,7 @@
// /:owner/(optional subgroups...)/:repo/-/raw/:branch/:file...
if (rawPathSegment < 2) return false
- if (pathSegments.length <= rawPathSegment + 2) return false
+ if (pathSegments.length <= rawPathSegment + 3) return false
return true
}You can send follow-ups to the cloud agent here.
Co-authored-by: Siraj Chokshi <SirajChokshi@users.noreply.github.com>
Co-authored-by: Siraj Chokshi <SirajChokshi@users.noreply.github.com>
Co-authored-by: Siraj Chokshi <SirajChokshi@users.noreply.github.com>
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
Autofix Details
Bugbot Autofix prepared a fix for the issue found in the latest run.
- ✅ Fixed: Invalid configured origins silently fall back to permissive defaults
- Changed getAllowedProxyOrigins to return only valid parsed origins (possibly empty) whenever configuredOrigins is defined, reserving the permissive default fallback for when the env var is truly undefined.
Or push these changes by commenting:
@cursor push 22d9a116f6
Preview (22d9a116f6)
diff --git a/src/utils/proxy.ts b/src/utils/proxy.ts
--- a/src/utils/proxy.ts
+++ b/src/utils/proxy.ts
@@ -120,14 +120,14 @@
configuredOrigins: string | undefined,
requestOrigin: string,
): string[] {
- const allowedOrigins = configuredOrigins
- ?.split(',')
- .map((origin) => origin.trim())
- .filter(Boolean)
- .map((origin) => normalizeOrigin(origin))
- .filter((origin): origin is string => Boolean(origin))
+ if (configuredOrigins !== undefined) {
+ const allowedOrigins = configuredOrigins
+ .split(',')
+ .map((origin) => origin.trim())
+ .filter(Boolean)
+ .map((origin) => normalizeOrigin(origin))
+ .filter((origin): origin is string => Boolean(origin))
- if (allowedOrigins && allowedOrigins.length > 0) {
return [...new Set(allowedOrigins)]
}You can send follow-ups to the cloud agent here.
…ns are invalid (#29) When PROXY_ALLOWED_ORIGINS is set but all entries fail normalizeOrigin (e.g. missing protocol), the function now returns the empty valid set instead of silently falling back to DEFAULT_ALLOWED_ORIGINS which includes localhost. Only fall back to defaults when the env var is truly undefined. Co-authored-by: Cursor Agent <cursoragent@cursor.com>
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
Bugbot Autofix prepared a fix for the issue found in the latest run.
- ✅ Fixed: Proxy serves arbitrary HTML at app origin enabling XSS
- Added Content-Security-Policy: sandbox and Content-Disposition: attachment headers to proxy responses, preventing browsers from executing scripts or rendering HTML at the app's origin.
Or push these changes by commenting:
@cursor push 42265fb190
Preview (42265fb190)
diff --git a/src/routes/api/proxy/+server.ts b/src/routes/api/proxy/+server.ts
--- a/src/routes/api/proxy/+server.ts
+++ b/src/routes/api/proxy/+server.ts
@@ -81,6 +81,8 @@
upstream.headers.get('content-type') ?? 'text/plain; charset=utf-8',
)
headers.set('X-Content-Type-Options', 'nosniff')
+ headers.set('Content-Security-Policy', 'sandbox')
+ headers.set('Content-Disposition', 'attachment')
headers.set(
'Cache-Control',
upstream.headers.get('cache-control') ?? 'public, max-age=300',You can send follow-ups to the cloud agent here.
Reviewed by Cursor Bugbot for commit aa0bc52. Configure here.
Co-authored-by: Siraj Chokshi <SirajChokshi@users.noreply.github.com>
…https://github.com/SirajChokshi/static-preview into cursor/-bc-1df83f69-aca0-4717-9d91-b3109ce612ef-8fcd Co-authored-by: Siraj Chokshi <SirajChokshi@users.noreply.github.com>


Summary
/api/proxyserver route to fetch allowlisted GitHub/GitLab raw contentapi.codetabs.comto internal proxy endpoint.vercel/to.gitignoreloggerand ignore.vercel/**in ESLint/-/raw/<branch>URLs without a file segment (off-by-one fix)Security hardening
raw.githubusercontent.comand GitLab/-/raw/pathsPROXY_ALLOWED_ORIGINS, with sensible defaults)Verification evidence
/opt/cursor/artifacts/proxy_endpoint_checks.log/opt/cursor/artifacts/gitlab_raw_branch_validation.log/api/proxy:Notes