[Audit L-01] Public indexer API returns raw database error strings on 500
## Source
Internal full-stack audit [`audits/audit_full_1777967937.md`](https://gitlab.com/PlasticDigits/yieldomega/-/blob/main/audits/audit_full_1777967937.md) (2026-05-05), finding **L-01** (Low). Commit reviewed: `3b3bd0c6c501f9cf908667d304146b7b63302b87`.
## Problem
Many public indexer API handlers return HTTP 500 with:
```json
{ "error": "<sqlx / DB error to_string()>" }
```
Raw database error strings can expose table names, column names, SQL fragments, constraint names, migration drift hints, or infrastructure details. Browser clients do not need this detail; it increases reconnaissance value of the public API.
## Relevant code
Pattern repeated across handlers (example from `timecurve_buys`):
```339:350:indexer/src/api.rs
let total: i64 = match sqlx::query_scalar::<_, i64>("SELECT COUNT(*)::bigint FROM idx_timecurve_buy")
.fetch_one(&state.pool)
.await
{
Ok(n) => n,
Err(e) => {
return (
StatusCode::INTERNAL_SERVER_ERROR,
Json(json!({ "error": e.to_string() })),
)
.into_response();
}
};
```
The same `e.to_string()` pattern appears on many routes in `indexer/src/api.rs`.
## Recommended fix
- Return a **generic** public JSON body for 500 responses, e.g. `{ "error": "internal server error" }` (stable string for clients).
- Log the full error with `tracing::warn!` / `tracing::error!` server-side; include a **request id** or trace correlation id when available.
- Add a small helper (e.g. `internal_error_response(context: &str, err: impl std::error::Error)`) so handlers cannot drift back to raw echoes.
- Add an API/unit test that a forced query failure does **not** echo raw SQL or table names in the response body.
## Acceptance criteria
- No production JSON 500 response includes raw `sqlx` error text, table names, or constraint names in the `error` field.
- Detailed failures remain fully logged server-side.
- One centralized helper (or macro) used consistently across `api.rs` error branches for this class of failure.
- Automated test asserts sanitized body on simulated DB failure.
## Verification checklist
- [ ] Trigger a DB error on a test route — response body is generic; logs contain the real error.
- [ ] Grep `api.rs` for `e.to_string()` in JSON error responses — eliminated or confined to non-public paths.
- [ ] `cargo test` passes including new API error-redaction test.
issue