Summary
The API's global error handler returns the raw error message and full stack trace to the client on unhandled (generic 500) errors. This leaks internal implementation details to any caller, including unauthenticated ones. The API is publicly exposed and actively scanned by bots, so this is reachable in practice.
Location
apps/api/src/middleware/error-handler.ts — the generic fallback branch:
// Handle generic errors
return res
.status(500)
.json({ message: "Internal server error: " + error.message, code: "INTERNAL_ERROR", details: error.stack });
error.message and error.stack are sent in the response body.
Evidence
An unauthenticated POST /api/argo/<non-uuid>/webhook triggers an unhandled Postgres error and returns:
{
"message": "Internal server error: invalid input syntax for type uuid: \"...\"",
"code": "INTERNAL_ERROR",
"details": "error: invalid input syntax for type uuid: \"...\"\n at /app/node_modules/pg-pool/index.js:45:11\n at ...\n at async file:///app/node_modules/drizzle-orm/node-postgres/session.js:83:22\n at async handleWebhookRequest (file:///app/apps/api/dist/src/routes/argoworkflow/index.js:20:19)"
}
This exposes internal file paths, dependency layout (drizzle/pg-pool), handler locations, and raw SQL error text.
Impact
Information disclosure (CWE-209: error message containing sensitive information). Leaked stack traces and DB error strings help an attacker map the system, dependencies, and schema. Only the generic-500 fallback is affected; the ApiError and validation branches are fine.
Proposed fix
Return a generic body on 500 and keep the detail server-side only (the handler already logs the full error via logger.error):
return res.status(500).json({ message: "Internal server error", code: "INTERNAL_ERROR" });
Optionally include a correlation/request id in the response to aid log lookup without leaking internals.
Notes
- Found while wiring up API observability/monitoring.
- Low effort, contained change to a single middleware.
Summary
The API's global error handler returns the raw error message and full stack trace to the client on unhandled (generic 500) errors. This leaks internal implementation details to any caller, including unauthenticated ones. The API is publicly exposed and actively scanned by bots, so this is reachable in practice.
Location
apps/api/src/middleware/error-handler.ts— the generic fallback branch:error.messageanderror.stackare sent in the response body.Evidence
An unauthenticated
POST /api/argo/<non-uuid>/webhooktriggers an unhandled Postgres error and returns:{ "message": "Internal server error: invalid input syntax for type uuid: \"...\"", "code": "INTERNAL_ERROR", "details": "error: invalid input syntax for type uuid: \"...\"\n at /app/node_modules/pg-pool/index.js:45:11\n at ...\n at async file:///app/node_modules/drizzle-orm/node-postgres/session.js:83:22\n at async handleWebhookRequest (file:///app/apps/api/dist/src/routes/argoworkflow/index.js:20:19)" }This exposes internal file paths, dependency layout (drizzle/pg-pool), handler locations, and raw SQL error text.
Impact
Information disclosure (CWE-209: error message containing sensitive information). Leaked stack traces and DB error strings help an attacker map the system, dependencies, and schema. Only the generic-500 fallback is affected; the
ApiErrorand validation branches are fine.Proposed fix
Return a generic body on 500 and keep the detail server-side only (the handler already logs the full error via
logger.error):Optionally include a correlation/request id in the response to aid log lookup without leaking internals.
Notes