Skip to content

fix(api): scrub driver errors from the preview endpoint and answer 400, not 500 - #258

Merged
thiagoluga merged 2 commits into
masterfrom
fix/preview-scrub-driver-errors
Aug 3, 2026
Merged

fix(api): scrub driver errors from the preview endpoint and answer 400, not 500#258
thiagoluga merged 2 commits into
masterfrom
fix/preview-scrub-driver-errors

Conversation

@thiagoluga

Copy link
Copy Markdown
Owner

What

POST /reports/{name}/preview caught only ConfigurationException. It opens a live connection and runs SQL, so a bad filter value (Equals "abc" against an integer column) surfaced the raw SqlException/PostgresException/OracleExceptionnaming the host, port and database — as an unhandled 500. On a host running in Development, the developer exception page renders the full stack trace too.

Every sibling endpoint that touches a source already routes failures through SchemaProblem (logged server-side, generic 502 to the caller). This one now does the same — it was the only data-plane endpoint left unscrubbed after WP5.

A second bug the first fix would have masked

A code-first report's name is only checked for non-blank, so sales.daily is legal — while a config store validates its argument against the dynamic-name pattern and throws for it. ReportPreviewRunner probed that store before deciding the report was typed, so the ArgumentException escaped as another 500.

Adding only the catch-all would have converted it into a misleading 502 ("the source's database could not be read") for what is really "this report isn't filterable". A name no config store can hold is definitively not a dynamic report, so it is now recognised as such and the endpoint returns the clear 400 it always intended.

Verification

  • New Filters_on_a_typed_report_whose_name_the_config_store_cannot_hold_return_400.
  • The test initially passed for the wrong reasonTestApp registers no IReportConfigStore unless AddDynamicReports is called, so the runner short-circuited on configStore is null and never reached the name check. Hardened to register a real file-backed store; it now fails without the guard (502 instead of 400).
  • AspNetCore integration 149 passed, Core 299 passed; full solution builds clean.

Found by the API-layer bug hunt; remaining findings in docs/STATUS-AND-BACKLOG.md §6.

…0, not 500

POST /reports/{name}/preview caught only ConfigurationException. It opens a live
connection and runs SQL, so a bad filter value (say Equals "abc" against an
integer column) surfaced the raw SqlException/PostgresException/OracleException —
which names the host, port and database — as an unhandled 500, and on a host
running in Development the developer exception page renders the whole stack
trace. Every sibling endpoint that touches a source already routes failures
through SchemaProblem (logged server-side, generic to the caller); this one now
does too.

That alone would have turned a second bug into a misleading 502: a code-first
report's name is only checked for non-blank, so "sales.daily" is legal, while a
config store validates its argument against the dynamic-name pattern and throws
for it. ReportPreviewRunner probed the store before deciding the report was
typed, so that ArgumentException escaped as a 500. A name the store cannot hold
is definitively not a dynamic report, so it is now recognised as such and the
endpoint returns the clear 400 it always intended.

The regression test initially passed for the wrong reason - TestApp registers no
config store unless AddDynamicReports is called, so the runner short-circuited
before reaching the name check. Hardened to register a real file-backed store;
it now fails without the guard (502 instead of 400).
The Sonar gate flagged new_coverage 0%: the two new lines are the catch and its
SchemaProblem return, and nothing exercised them — the existing regression test
takes the name-guard path, which returns 400 before any driver call. So the
security fix itself was untested.

Add a filter translator that fails the way a provider does, with a message naming
the connection target, and assert the response is the scrubbed 502 and carries
neither the host, the database, nor the driver text. Verified to fail without the
catch (the exception escapes with the connection string intact).
@sonarqubecloud

sonarqubecloud Bot commented Aug 3, 2026

Copy link
Copy Markdown

@sonarqubecloud

sonarqubecloud Bot commented Aug 3, 2026

Copy link
Copy Markdown

@thiagoluga
thiagoluga merged commit 2fec2d2 into master Aug 3, 2026
5 checks passed
@thiagoluga
thiagoluga deleted the fix/preview-scrub-driver-errors branch August 3, 2026 22:30
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant