Skip to content

Commit 0a12b09

Browse files
lac5qcursoragent
andauthored
fix: allow token-based agent onboarding routes through proxy auth (#18)
* fix: allow token-based agent onboarding routes through proxy auth The onboarding bootstrap flow serves /api/onboarding/script and /api/onboarding/register to agents using signed invite tokens, not human JWTs. The app proxy was requiring authentication for all API routes except an explicit allowlist, which returned 401 before the onboarding handlers could verify the invite token. Add script (GET) and register (POST) to route-local auth passthrough so invite creation stays operator-protected while agent bootstrap works. Co-authored-by: Luis Calderon <luis@calderon.com> * chore: cover onboarding routes in route-auth boundary check Co-authored-by: Luis Calderon <luis@calderon.com> * chore: refresh proxy trust-boundary checklist hash Co-authored-by: Luis Calderon <luis@calderon.com> --------- Co-authored-by: Cursor Agent <cursoragent@cursor.com>
1 parent 8786aba commit 0a12b09

5 files changed

Lines changed: 38 additions & 1 deletion

File tree

apps/memroos/src/__tests__/proxy.test.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,26 @@ describe("proxy", () => {
7878
expect(await response.json()).toEqual({ error: "authentication required" });
7979
});
8080

81+
it("lets agent onboarding bootstrap routes handle their own signed-token authorization", async () => {
82+
const scriptResponse = await proxy(
83+
new NextRequest("http://localhost:3002/api/onboarding/script?token=signed-token", {
84+
method: "GET",
85+
headers: { host: "localhost:3002" },
86+
})
87+
);
88+
const registerResponse = await proxy(
89+
new NextRequest("http://localhost:3002/api/onboarding/register", {
90+
method: "POST",
91+
headers: { host: "localhost:3002", "content-type": "application/json" },
92+
})
93+
);
94+
95+
expect(scriptResponse.status).toBe(200);
96+
expect(await scriptResponse.text()).toBe("");
97+
expect(registerResponse.status).toBe(200);
98+
expect(await registerResponse.text()).toBe("");
99+
});
100+
81101
it("rejects expired or malformed JWT credentials on protected API routes", async () => {
82102
const expired = await expiredAccessToken("reviewer-expired", "reviewer");
83103
const expiredResponse = await proxy(

apps/memroos/src/proxy.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,9 @@ const ADMIN_ROUTES: Array<{ method?: string; pattern: RegExp }> = [
7575
];
7676

7777
const ROUTE_LOCAL_AUTH_API_ROUTES: Array<{ method?: string; pattern: RegExp }> = [
78+
// Agent onboarding bootstrap uses signed invite tokens, not human JWTs.
79+
{ method: "GET", pattern: /^\/api\/onboarding\/script$/ },
80+
{ method: "POST", pattern: /^\/api\/onboarding\/register$/ },
7881
{ pattern: /^\/api\/chatgpt\/actions\// },
7982
{ pattern: /^\/api\/agent-context\// },
8083
{ method: "POST", pattern: /^\/api\/agent-memory\/capture$/ },

docs/next-trust-boundary-upgrade.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ the defense-in-depth layer for privileged APIs.
77
## Reviewed Baseline
88

99
- Reviewed Next.js dependency: `^16.2.7`
10-
- Reviewed proxy sha256: `8ec45a8fa941800764109978f3255ad4425736916e2a6ec9fc72854095286f9b`
10+
- Reviewed proxy sha256: `2805d91afc06b92f5d457ba350ef83c60399a97276e83f93cf5485622c50e40d`
1111

1212
## Required Before Changing Next Or Proxy
1313

scripts/check-route-auth-boundary.mjs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,14 @@ const repoRoot = path.resolve(scriptDir, "..");
88
const proxyPath = path.join(repoRoot, "apps/memroos/src/proxy.ts");
99

1010
const routeLocalAuthCoverage = [
11+
{
12+
pattern: "/^\\/api\\/onboarding\\/script$/",
13+
files: [["apps/memroos/src/app/api/onboarding/script/route.ts", ["verifyAgentOnboardingToken("]]],
14+
},
15+
{
16+
pattern: "/^\\/api\\/onboarding\\/register$/",
17+
files: [["apps/memroos/src/app/api/onboarding/register/route.ts", ["verifyAgentOnboardingToken("]]],
18+
},
1119
{
1220
pattern: "/^\\/api\\/chatgpt\\/actions\\//",
1321
files: [

scripts/check-route-auth-boundary.test.mjs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ import {
88

99
const PROXY_FIXTURE = `
1010
const ROUTE_LOCAL_AUTH_API_ROUTES: Array<{ method?: string; pattern: RegExp }> = [
11+
{ method: "GET", pattern: /^\\/api\\/onboarding\\/script$/ },
12+
{ method: "POST", pattern: /^\\/api\\/onboarding\\/register$/ },
1113
{ pattern: /^\\/api\\/chatgpt\\/actions\\// },
1214
{ pattern: /^\\/api\\/agent-context\\// },
1315
{ method: "POST", pattern: /^\\/api\\/agent-memory\\/capture$/ },
@@ -32,6 +34,8 @@ const AUTH_MARKERS = {
3234

3335
function goodFiles() {
3436
return new Map([
37+
["apps/memroos/src/app/api/onboarding/script/route.ts", "verifyAgentOnboardingToken("],
38+
["apps/memroos/src/app/api/onboarding/register/route.ts", "verifyAgentOnboardingToken("],
3539
["apps/memroos/src/app/api/chatgpt/actions/search/route.ts", AUTH_MARKERS.chatgpt],
3640
["apps/memroos/src/app/api/chatgpt/actions/fetch/route.ts", AUTH_MARKERS.chatgpt],
3741
["apps/memroos/src/app/api/chatgpt/actions/save/route.ts", AUTH_MARKERS.chatgpt],
@@ -74,6 +78,8 @@ function goodFiles() {
7478
describe("route auth boundary checker", () => {
7579
it("parses proxy route-local auth patterns", () => {
7680
assert.deepEqual(parseRouteLocalAuthPatterns(PROXY_FIXTURE), [
81+
"/^\\/api\\/onboarding\\/script$/",
82+
"/^\\/api\\/onboarding\\/register$/",
7783
"/^\\/api\\/chatgpt\\/actions\\//",
7884
"/^\\/api\\/agent-context\\//",
7985
"/^\\/api\\/agent-memory\\/capture$/",

0 commit comments

Comments
 (0)