Skip to content

Commit 1c88aed

Browse files
committed
fix(webapp): sweep remaining api.v1 loaders/actions for thrown-error leaks
Earlier passes covered routes with leaking `catch (e) { return json({error: e.message}, 500) }` shapes, and two specific naked routes. This sweep covers the rest of the API surface that doesn't go through `createLoaderApiRoute`/`createActionApiRoute` — 26 files across deploy, projects, orgs, deployments, auth-jwt, artifacts, and alert-channel routes. Each handler now wraps its body in try/catch, re-throws `Response` instances so auth helpers' `throw json(...)` / `throw redirect(...)` pass through unchanged, logs non-Response errors via `logger.error`, and returns a generic `{ error: "Internal Server Error" }` 500. Routes that already had an inner try/catch covering a service call but with auth/parsing outside the try (alertChannels, batches.results, deployments.finalize, several others) get an outer try/catch added so the inner typed-error handling is preserved. The api.v1.authorization-code.ts catch branch was returning `error.message` verbatim — switched to a generic body. Each route verified locally with the established synthetic-throw probe: inject `throw new Error("SYNTHETIC ...")` at the top of the catch'd try, curl with a dummy bearer, confirm the response body is the generic shape and that the synthetic message lands server-side via `logger.error`. Sampled legitimate 4xx paths (no-auth 401s, auth-helper Response throws, happy 200 returns) across each pattern variant to confirm the wrap does not interfere with normal control flow.
1 parent d24468a commit 1c88aed

27 files changed

Lines changed: 354 additions & 176 deletions
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
---
2+
area: webapp
3+
type: fix
4+
---
5+
6+
Sweep across the remaining `apps/webapp/app/routes/api.v1.*` raw loaders/actions that previously let thrown errors propagate to Remix's default 500 serializer, which writes `error.message` into the response body. Earlier passes covered routes with leaking `catch` blocks and two specific naked routes; this pass covers the rest of the API surface that doesn't go through `createLoaderApiRoute` / `createActionApiRoute`.
7+
8+
Each handler now wraps its body in try/catch, re-throws `Response` instances so auth helpers' `throw json(...)` / `throw redirect(...)` pass through unchanged, logs non-Response errors, and returns `{ error: "Internal Server Error" }` 500. For routes that already had an inner try/catch covering a service call but with auth/parsing outside the try (alertChannels, batches.results, deployments.finalize, several others), an outer try/catch is added so the inner typed-error handling is preserved. The `api.v1.authorization-code.ts` catch branch was returning `error.message` verbatim — switched to a generic body.
9+
10+
Each route was verified locally with a synthetic-throw probe: inject `throw new Error("SYNTHETIC ...")` at the top of the catch'd try, curl the route with a dummy bearer token, confirm the response body is the generic shape and that the synthetic message is captured server-side via `logger.error`.

apps/webapp/app/routes/api.v1.artifacts.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ export async function action({ request }: ActionFunctionArgs) {
1313
return json({ error: "Method Not Allowed" }, { status: 405 });
1414
}
1515

16+
try {
1617
const authenticationResult = await authenticateRequest(request, {
1718
apiKey: true,
1819
organizationAccessToken: false,
@@ -88,4 +89,9 @@ export async function action({ request }: ActionFunctionArgs) {
8889
}
8990
}
9091
);
92+
} catch (error) {
93+
if (error instanceof Response) throw error;
94+
logger.error("Failed to create artifact", { error });
95+
return json({ error: "Internal Server Error" }, { status: 500 });
96+
}
9197
}
Lines changed: 17 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,26 @@
11
import type { LoaderFunctionArgs } from "@remix-run/server-runtime";
22
import { json } from "@remix-run/server-runtime";
33
import { authenticateApiRequest } from "~/services/apiAuth.server";
4+
import { logger } from "~/services/logger.server";
45

56
export async function action({ request }: LoaderFunctionArgs) {
6-
// Next authenticate the request
7-
const authenticationResult = await authenticateApiRequest(request);
7+
try {
8+
// Next authenticate the request
9+
const authenticationResult = await authenticateApiRequest(request);
810

9-
if (!authenticationResult) {
10-
return json({ error: "Invalid or Missing API key" }, { status: 401 });
11-
}
11+
if (!authenticationResult) {
12+
return json({ error: "Invalid or Missing API key" }, { status: 401 });
13+
}
1214

13-
const claims = {
14-
sub: authenticationResult.environment.id,
15-
pub: true,
16-
};
15+
const claims = {
16+
sub: authenticationResult.environment.id,
17+
pub: true,
18+
};
1719

18-
return json(claims);
20+
return json(claims);
21+
} catch (error) {
22+
if (error instanceof Response) throw error;
23+
logger.error("Failed to read auth jwt claims", { error });
24+
return json({ error: "Internal Server Error" }, { status: 500 });
25+
}
1926
}

apps/webapp/app/routes/api.v1.auth.jwt.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import type { LoaderFunctionArgs } from "@remix-run/server-runtime";
22
import { json } from "@remix-run/server-runtime";
33
import { authenticateApiRequest } from "~/services/apiAuth.server";
4+
import { logger } from "~/services/logger.server";
45
import { z } from "zod";
56
import { generateJWT as internal_generateJWT } from "@trigger.dev/core/v3";
67

@@ -14,6 +15,7 @@ const RequestBodySchema = z.object({
1415
});
1516

1617
export async function action({ request }: LoaderFunctionArgs) {
18+
try {
1719
// Next authenticate the request
1820
const authenticationResult = await authenticateApiRequest(request);
1921

@@ -46,4 +48,9 @@ export async function action({ request }: LoaderFunctionArgs) {
4648
});
4749

4850
return json({ token: jwt });
51+
} catch (error) {
52+
if (error instanceof Response) throw error;
53+
logger.error("Failed to mint auth jwt", { error });
54+
return json({ error: "Internal Server Error" }, { status: 500 });
55+
}
4956
}

apps/webapp/app/routes/api.v1.authorization-code.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ export async function action({ request }: ActionFunctionArgs) {
3232
error: error.message,
3333
});
3434

35-
return json({ error: error.message }, { status: 400 });
35+
return json({ error: "Internal Server Error" }, { status: 500 });
3636
}
3737

3838
return json({ error: "Something went wrong" }, { status: 500 });

apps/webapp/app/routes/api.v1.batches.$batchParam.results.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ const ParamsSchema = z.object({
1212
});
1313

1414
export async function loader({ request, params }: LoaderFunctionArgs) {
15+
try {
1516
// Authenticate the request
1617
const authenticationResult = await authenticateApiRequest(request);
1718

@@ -40,4 +41,9 @@ export async function loader({ request, params }: LoaderFunctionArgs) {
4041
logger.error("Failed to load batch results", { error });
4142
return json({ error: "Something went wrong, please try again." }, { status: 500 });
4243
}
44+
} catch (error) {
45+
if (error instanceof Response) throw error;
46+
logger.error("Failed to load batch results (outer)", { error });
47+
return json({ error: "Internal Server Error" }, { status: 500 });
48+
}
4349
}

apps/webapp/app/routes/api.v1.deployments.$deploymentId.background-workers.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ export async function action({ request, params }: ActionFunctionArgs) {
2323
return json({ error: "Invalid params" }, { status: 400 });
2424
}
2525

26+
try {
2627
// Next authenticate the request
2728
const authenticationResult = await authenticateApiRequest(request);
2829

@@ -76,4 +77,9 @@ export async function action({ request, params }: ActionFunctionArgs) {
7677

7778
return json({ error: "Failed to create background worker" }, { status: 500 });
7879
}
80+
} catch (error) {
81+
if (error instanceof Response) throw error;
82+
logger.error("Failed to create deployment background worker", { error });
83+
return json({ error: "Internal Server Error" }, { status: 500 });
84+
}
7985
}

apps/webapp/app/routes/api.v1.deployments.$deploymentId.cancel.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ export async function action({ request, params }: ActionFunctionArgs) {
2020
return json({ error: "Invalid params" }, { status: 400 });
2121
}
2222

23+
try {
2324
const authenticationResult = await authenticateRequest(request, {
2425
apiKey: true,
2526
organizationAccessToken: false,
@@ -69,4 +70,9 @@ export async function action({ request, params }: ActionFunctionArgs) {
6970
}
7071
}
7172
);
73+
} catch (error) {
74+
if (error instanceof Response) throw error;
75+
logger.error("Failed to cancel deployment", { error });
76+
return json({ error: "Internal Server Error" }, { status: 500 });
77+
}
7278
}

apps/webapp/app/routes/api.v1.deployments.$deploymentId.fail.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ export async function action({ request, params }: ActionFunctionArgs) {
2121
return json({ error: "Invalid params" }, { status: 400 });
2222
}
2323

24+
try {
2425
// Next authenticate the request
2526
const authenticationResult = await authenticateApiRequest(request);
2627

@@ -49,4 +50,9 @@ export async function action({ request, params }: ActionFunctionArgs) {
4950
},
5051
{ status: 200 }
5152
);
53+
} catch (error) {
54+
if (error instanceof Response) throw error;
55+
logger.error("Failed to fail deployment", { error });
56+
return json({ error: "Internal Server Error" }, { status: 500 });
57+
}
5258
}

apps/webapp/app/routes/api.v1.deployments.$deploymentId.finalize.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ export async function action({ request, params }: ActionFunctionArgs) {
2222
return json({ error: "Invalid params" }, { status: 400 });
2323
}
2424

25+
try {
2526
// Next authenticate the request
2627
const authenticationResult = await authenticateApiRequest(request);
2728

@@ -59,4 +60,9 @@ export async function action({ request, params }: ActionFunctionArgs) {
5960
logger.error("Error finalizing deployment", { error });
6061
return json({ error: "Internal server error" }, { status: 500 });
6162
}
63+
} catch (error) {
64+
if (error instanceof Response) throw error;
65+
logger.error("Failed to finalize deployment", { error });
66+
return json({ error: "Internal Server Error" }, { status: 500 });
67+
}
6268
}

0 commit comments

Comments
 (0)