Problem
Every controller in app/controllers/ had try/catch blocks that returned the caught error verbatim:
return res.status(500).json({ message: "Error!", error: String(error) });
That error field surfaces raw internal text to the client:
"value too long for type character varying(255)" — leaks column names + length limits, useful for SQL-injection / schema enumeration
"connection to server at \"db\" (10.0.0.5) failed" — leaks internal hostnames and IPs
"duplicate key value violates unique constraint \"some_table_pkey\"" — leaks table + constraint names
- pg error codes that reveal which backend you're on, etc.
The global error-handler in app/middleware/error-handler.js (#22) was already written to NOT surface this detail:
5xx: always generic. The full error lands in the log; clients get a stable shape + a requestId to correlate.
…but the per-controller catches intercept the error before it can reach the global path, so the policy is silently bypassed across all 17 controllers — 137 occurrences total of the leak.
Proposed fix
Strip , error: String(error) from every 5xx return across the 17 controllers. The controllers already log the full error via log.error({ err: error }, …), so the operator-side signal is unchanged — only the client-facing body loses the leak.
One static-string variant in customercontroller.js (error: "Sequelize Op not available") is also removed since it violated the same policy.
Pin the policy with a source-level regression test (tests/unit/controller-error-shape.test.js) that does test.each() over every controller file and asserts the pattern doesn't reappear. Future copy-paste of the old shape will fail CI.
Proudly Made in Nebraska. Go Big Red! 🌽 https://xkcd.com/2347/
Problem
Every controller in
app/controllers/had try/catch blocks that returned the caught error verbatim:That
errorfield surfaces raw internal text to the client:"value too long for type character varying(255)"— leaks column names + length limits, useful for SQL-injection / schema enumeration"connection to server at \"db\" (10.0.0.5) failed"— leaks internal hostnames and IPs"duplicate key value violates unique constraint \"some_table_pkey\""— leaks table + constraint namesThe global error-handler in
app/middleware/error-handler.js(#22) was already written to NOT surface this detail:…but the per-controller catches intercept the error before it can reach the global path, so the policy is silently bypassed across all 17 controllers — 137 occurrences total of the leak.
Proposed fix
Strip
, error: String(error)from every 5xx return across the 17 controllers. The controllers already log the full error vialog.error({ err: error }, …), so the operator-side signal is unchanged — only the client-facing body loses the leak.One static-string variant in
customercontroller.js(error: "Sequelize Op not available") is also removed since it violated the same policy.Pin the policy with a source-level regression test (
tests/unit/controller-error-shape.test.js) that doestest.each()over every controller file and asserts the pattern doesn't reappear. Future copy-paste of the old shape will fail CI.Proudly Made in Nebraska. Go Big Red! 🌽 https://xkcd.com/2347/