Repository navigation
Fix CodeRouter trusted tenant exchange #9607
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
4528ac0
1bfae4b
0e8fa1c
b5a88c2
935611b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,59 @@ | ||
| import { resolveSubrouterRequestContext } from "../../../../services/subrouter/requestContext"; | ||
| import { subrouterErrorResponse } from "../../../../services/subrouter/routeHelpers"; | ||
| import { env } from "../../../env"; | ||
|
|
||
| export const runtime = "nodejs"; | ||
| export const dynamic = "force-dynamic"; | ||
|
|
||
| export async function POST(request: Request): Promise<Response> { | ||
| const resolved = await resolveSubrouterRequestContext(request, { | ||
| permission: "use-or-manage", | ||
| allowCookie: false, | ||
| }); | ||
| if (!resolved.ok) return resolved.response; | ||
|
|
||
| try { | ||
| const controlToken = env.SUBROUTER_STACK_TENANT_DELETE_TOKEN?.trim(); | ||
| const hostedUrl = env.SUBROUTER_HOSTED_URL?.trim().replace( | ||
| /\/+$/, | ||
| "", | ||
| ); | ||
| if (!controlToken || !hostedUrl) { | ||
| return Response.json( | ||
| { error: "service_unavailable" }, | ||
| { status: 503 }, | ||
| ); | ||
| } | ||
| const capabilities = [ | ||
| ...(resolved.value.team.manageAccounts ? ["manage_accounts"] : []), | ||
| ...(resolved.value.team.use ? ["use"] : []), | ||
| ]; | ||
| const upstream = await fetch(`${hostedUrl}/_subrouter/auth/stack`, { | ||
| method: "POST", | ||
| headers: { | ||
| authorization: `Bearer ${resolved.value.accessToken}`, | ||
| "content-type": "application/json", | ||
| "x-subrouter-stack-control-token": controlToken, | ||
| }, | ||
| body: JSON.stringify({ | ||
| capabilities, | ||
| teamId: resolved.value.team.teamId, | ||
| teamName: resolved.value.team.teamName, | ||
| }), | ||
| cache: "no-store", | ||
| }); | ||
| const body = await upstream.text(); | ||
|
Comment on lines
+31
to
+45
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win 🧩 Analysis chain🏁 Script executed: #!/bin/bash
set -euo pipefail
echo "== locate target and related files =="
fd -a 'route\.ts$' . | rg 'web/app/api/subrouter/exchange-v2/route\.ts|subrouter|exchange' || true
echo "== target excerpt =="
if [ -f web/app/api/subrouter/exchange-v2/route.ts ]; then
wc -l web/app/api/subrouter/exchange-v2/route.ts
cat -n web/app/api/subrouter/exchange-v2/route.ts
fi
echo "== reference resolveSubrouterRequestContext usages =="
rg -n "resolveSubrouterRequestContext|subrouter.*auth/stack|_subrouter/auth/stack|setTimeout|AbortSignal|with.*abort|cancellation|deadline" web/app web/lib packages apps . --glob '!node_modules' --glob '!dist' --glob '!build' | head -200Repository: manaflow-ai/cmux Length of output: 22236 🏁 Script executed: #!/bin/bash
set -euo pipefail
python3 - <<'PY'
from pathlib import Path
p = Path('web/app/api/subrouter/exchange-v2/route.ts')
text = p.read_text() if p.exists() else ''
checks = {
'contains_fetch_call': 'fetch(`${hostedUrl}/_subrouter/auth/stack`' in text,
'contains_upstream_text': 'const body = await upstream.text()' in text,
'contains_no_abort_signal': '"signal"' not in text or text.find('"signal"') > text.find('fetch(') and 'AbortSignal' not in text,
'contains_abort_controller': 'AbortController' in text,
'imports_resolveSubrouterRequestContext': 'resolveSubrouterRequestContext' in text,
}
for k, v in checks.items():
print(f'{k}={v}')
PYRepository: manaflow-ai/cmux Length of output: 316 🏁 Script executed: #!/bin/bash
set -euo pipefail
echo "== locate subrouter requestContext files =="
fd -a 'requestContext\.(ts|tsx|js|jsx)$' web | sort
echo "== inspect requestContext =="
for f in $(fd -a 'requestContext\.(ts|tsx|js|jsx)$' web | sort); do
echo "--- $f ---"
wc -l "$f"
cat -n "$f"
done
echo "== inspect tenant-exchange route =="
f="web/app/api/subrouter/tenant-exchange/route.ts"
if [ -f "$f" ]; then
wc -l "$f"
cat -n "$f"
fi
echo "== inspect subrouter route tests under web =="
fd -a -e ts -e tsx 'subrouter.*test|test.*subrouter' web | sort
for f in $(fd -a -e ts -e tsx 'subrouter.*test|test.*subrouter' web | sort); do
echo "--- $f ---"
wc -l "$f"
sed -n '1,240p' "$f"
doneRepository: manaflow-ai/cmux Length of output: 43186 Bound the hosted Stack exchange request with a cancellation-aware deadline.
🤖 Prompt for AI Agents |
||
| if (!upstream.ok) { | ||
| return new Response(body, { | ||
| status: upstream.status, | ||
| headers: { "content-type": "text/plain; charset=utf-8" }, | ||
| }); | ||
|
Comment on lines
+43
to
+50
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🔒 Security & Privacy | 🟠 Major | ⚡ Quick win 🧩 Analysis chain🏁 Script executed: #!/bin/bash
set -euo pipefail
echo "== candidate file =="
wc -l web/app/api/subrouter/tenant-exchange/route.ts
cat -n web/app/api/subrouter/tenant-exchange/route.ts
echo "== route helpers and cache header helpers =="
rg -n "Cache-Control|cache-control|jsonResponse|serviceUnavailableResponse|unauthorized" web/services/subrouter -S
cat -n web/services/subrouter/routeHelpers.ts | sed -n '1,240p'
echo "== subrouter auth helpers for cache headers =="
rg -n "Cache-Control|cache-control|unauthorized|forbidden|serviceUnavailable" web/services/subrouter web/app/api/subrouter -SRepository: manaflow-ai/cmux Length of output: 13353 Sensitive Data Exposure (CWE-525): Use of Web Browser Cache Containing Sensitive Information Reachability: External Reachability pathSet
🤖 Prompt for AI Agents |
||
| } | ||
| const tenant: unknown = JSON.parse(body); | ||
| return Response.json(tenant, { | ||
| headers: { "cache-control": "no-store" }, | ||
| }); | ||
| } catch (error) { | ||
| return subrouterErrorResponse(error); | ||
| } | ||
| } | ||
|
Comment on lines
+8
to
+59
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win Add behavior-level regression coverage before the implementation commit. This PR fixes a CLI login regression. Add a failing test that verifies the CLI receives the v2 URL and that the v2 exchange request includes the scoped capabilities and control header. Put that test in a test-only commit before the implementation commit. As per coding guidelines, “Regression fixes should use two commits: the first adds only the failing test, and the second adds the fix.” 🤖 Prompt for AI AgentsSource: Coding guidelines |
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🩺 Stability & Availability | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
Repository: manaflow-ai/cmux
Length of output: 29951
🌐 Web query:
next.js NextRequest context deadline authorization header💡 Result:
In Next.js, NextRequest extends the standard Web Request API, meaning it adheres to standard request handling patterns rather than having a built-in context or deadline object [1][2]. Handling Authorization Headers To access an Authorization header in Next.js middleware, use the standard Headers API via request.headers.get('authorization') [3]. Because Next.js middleware operates in the Edge runtime, you must use this standard Web API rather than legacy Node.js request properties [4][3]. To pass authorization or other context downstream (e.g., to API routes or Server Components), you must explicitly set them in the outgoing request headers using NextResponse.next [5][6]: // middleware.ts example import { NextResponse, NextRequest } from 'next/server'; export function middleware(request: NextRequest) { const requestHeaders = new Headers(request.headers); // Set context/headers for downstream routes requestHeaders.set('x-user-id', 'extracted-user-id'); return NextResponse.next({ request: { headers: requestHeaders, }, }); } Context and Deadlines Next.js does not provide a native "context" object or request "deadline" (timeout) mechanism within the NextRequest API [1][2]. 1. Context: There is no built-in mechanism to pass state between middleware and the application other than by modifying request headers [7]. Developers often use headers to pass verified user identities or other context metadata downstream [7][4]. 2. Deadlines: Next.js does not expose a request-level deadline property. If you need to enforce timeouts (e.g., for database queries or API calls performed within middleware), you must implement them manually using standard JavaScript approaches like AbortController [7]. Best Practices Avoid setting large headers, as this can lead to 431 Request Header Fields Too Large errors [5]. Additionally, verify authentication inside sensitive functions (like Server Functions) rather than relying solely on middleware-based authorization, as middleware can be bypassed if routes are not correctly matched [8].
Citations:
🏁 Script executed:
Repository: manaflow-ai/cmux
Length of output: 17024
Bound the hosted tenant exchange request with a cancellation-aware signal.
resolveSubrouterRequestContext()returns a value, so the stack authorization deadline has ended before this route makes the externalfetch. The call also has nosignal, so a stalled hosted service can hold route capacity. Use anAbortSignalwith a reasonable timeout for this hosted request, and add test coverage for the timeout response path.🤖 Prompt for AI Agents