GitHub Security Advisory: GHSA-pwqg-q8pg-pp6r
Package: github.com/daptin/daptin (GO)
Severity: HIGH (CVSS 0.0)
CWE: CWE-89: Improper Neutralization of Special Elements used in an SQL Command ('SQL Injection')
Vulnerable versions: <= 0.11.4
Patched in: 0.11.5

Summary: Daptin fuzzy search injects unvalidated column name into raw SQL

Details:
## Summary

`processFuzzySearch` in `server/resource/resource_findallpaginated.go:1484` splits the user-supplied `column` parameter by comma and interpolates each segment directly into `goqu.L(fmt.Sprintf("LOWER(%s) LIKE ?", prefix+col))` raw SQL with no column whitelist check. The entry point is `GET /api/<entity>` with `operator=fuzzy` (or `fuzzy_any`, `fuzzy_all`). Any authenticated user — including one who self-registered with no admin involvement — can read the entire database.

---

## Details

At `resource_findallpaginated.go:1761`, when the operator is `fuzzy`, `fuzzy_any`, or `fuzzy_all`, execution routes to `processFuzzySearch` (line 1763) before `processQueryFilter` (line 1780). `processQueryFilter` is the only path that calls `GetColumnByName` (line 1351), which validates column names against the table schema. The fuzzy branch never reaches that check.

Inside `processFuzzySearch` (line 1484), `filterQuery.ColumnName` is split by comma. After `strings.TrimSpace` (line 1486), each segment is routed to a DB-driver-specific function. The injectable sink reached depends on the driver and the `fuzzy_options.fallback_mode` field.

**SQLite** (`processFuzzySearchSQLite`, lines 1632–1676) uses `goqu.L` in all code paths — no `fallback_mode` required:
- `goqu.L(fmt.Sprintf("LOWER(%s) LIKE ?", prefix+col), ...)` — line 1650/1657

**PostgreSQL, MySQL, MSSQL** default to `goqu.Ex` (identifier-quoted, not injectable). The `goqu.L` sink is only reached when the attacker supplies a specific `fuzzy_options.fallback_mode` value in the HTTP `query` JSON:

- PostgreSQL `word_boundary` mode (line 1540): `goqu.L(fmt.Sprintf("%s ~* ?", prefix+col), ...)`
- MySQL `soundex` mode (line 1598): `goqu.L(fmt.Sprintf("SOUNDEX(%s) = SOUNDEX(?)", prefix+col), ...)`
- MSSQL `soundex` mode (line 1694): `goqu.L(fmt.Sprintf("DIFFERENCE(%s, ?) >= 3", prefix+col), ...)`

`fuzzy_options` is deserialized from the HTTP request at line 243 (`json.Unmarshal([]byte(query[0]), &queries)`) — it is fully attacker-controlled.

`goqu.L` emits its first argument as a raw SQL literal. The column position uses `%s` string formatting, not a bound parameter.

`prefix` is fixed at line 351 as `dbResource.model.GetName() + "."` — for `/api/world` this is `"world."`. Against SQLite, an attacker-supplied column value of `reference_id) OR 1=1 OR LOWER(world.reference_id` expands in the WHERE clause to `LOWER(world.reference_id) OR 1=1 OR LOWER(world.reference_id) LIKE ?`. Against PostgreSQL (where `reference_id` is stored as `bytea`), the `~*` regex operator requires a text-type column; the attack targets a `varchar` column instead (e.g., `table_name`) with an adapted injection template.

**Relation to GHSA-rw2c-8rfq-gwfv**: That patch modified `resource_aggregate.go` to fix `/aggregate/:typename`. This vulnerability is in `resource_findallpaginated.go` on the `/api/<entity>` fuzzy path — different file, different endpoint, different operator. The existing patch does not cover this path.

**T