The Legacy API message lookup endpoints parsed the request body as JSON and
passed the `id` parameter straight through to the message database. A JSON
object supplied for `id` arrived as a Ruby Hash and was used as a raw set of
SQL `WHERE` conditions. `hash_to_sql` interpolated each Hash key directly
inside backtick identifier quoting while escaping only the value, so a key
containing a backtick could break out of the identifier and inject arbitrary
SQL into the SELECT (blind, time-based) against the message database.
Fixes:
- Escape all identifiers (columns, tables, database names) through a new
`escape_identifier` helper that wraps in backticks and doubles embedded
backticks. Applied across hash_to_sql, select, insert, insert_multi,
update and delete so no caller can inject via an identifier.
- Validate the Legacy API `id` parameter at the controller boundary: reject
any non-scalar value before it reaches the database and coerce it to an
integer. Internal Hash-based lookups (e.g. tracking middleware) are
unaffected.
Adds regression tests at the unit (hash_to_sql / escape_identifier) and
request (legacy messages/deliveries) levels.
url_with_return_to only checked that return_to started with a forward
slash, which also allowed protocol-relative values like //host and
/\host. Rails 7.1 already refuses to follow those via redirect_to, so
the user just saw a 500. Reject the same shapes in the helper instead
so we fall back to the default URL cleanly.
Adds a sessions request spec covering the rejected shapes plus the
happy-path relative redirect.
The app-wide CSP already blocks inline script execution, but the HTML
preview iframe for a stored email was same-origin and un-sandboxed, and
the html_raw response had no per-action hardening. Add a sandbox on the
iframe and tighten the CSP on html_raw to script-src 'none' with
nosniff and no-referrer so the preview has defence in depth against a
future CSP bypass or regression.
Relates to GHSA-f6g9-8555-cw28.
This commit also adds some of tests for the Domain model. It was during the writing of these tests that the DNS resolution refactoring requirement became apparent.
* Update mysql2 query call to cast booleans
* Treat messages:held field as boolean
* Treat messages:inspected field as boolean
* Treat messages:spam field as boolean
* Treat messages:threat field as boolean
* Treat messages:bounce field as boolean
* Treat messages:received_with_ssl field as boolean
* Treat deliveries:sent_with_ssl field as boolean