# LangChain4j: SQL injection via metadata filters in langchain4j-mariadb and langchain4j-pgvector

LangChain4j MariaDB and pgvector embedding stores concatenate metadata filter keys into SQL, letting crafted keys inject SQL into search and removeAll.

- Payout: Research
- Severity: high
- Program: LangChain4j
- Found by: v9d0g
- Disclosed: 2026-06-17
- CVE: CVE-2026-55405
- Write-up quality: 7/10
- Original: https://github.com/advisories/GHSA-2mfg-cc43-9pcj
- Library page: http://bugbountylibrary.com/r/ghsa%3AGHSA-2mfg-cc43-9pcj
- Tags: area:ai domain:ai impact:data-leak impact:dos impact:integrity kind:research platform:ghsa sev:high tech:java technique:source-review vuln:sqli

**Target:** Embedding store metadata filter mappers (pgvector, MariaDB)
**Root cause:** Metadata filter keys (and MariaDB string values) are string-concatenated into SQL literals/identifiers without escaping or validation in JSONFilterMapper, ColumnFilterMapper and MariaDbFilterMapper.formatValue().
**Steps:**
1. Pass a crafted metadata key into `EmbeddingSearchRequest.filter()`, e.g. `metadataKey("')::text IS NOT NULL OR pg_sleep(1) IS NOT NULL --")` for pgvector JSON mode.
2. Trigger `search()` (or `removeAll(Filter)`) on the store; the key breaks out of the `(metadata->>'<key>')::text` literal.
3. Observe the injected `pg_sleep(1)` delay to confirm blind injection.
4. In column mode (`COLUMN_PER_KEY`), use a key like `1=1 OR true --` which is used as a bare unquoted identifier.
5. For MariaDB, break out of the `'$.<key>'` JSON path literal, or use a string value ending in a backslash since only `'` was escaped.
**Exploitation:** An attacker who controls metadata filter keys (e.g. via LLM-generated filters) injects SQL into search/removeAll statements, enabling blind data exfiltration, sleep-based DoS, and deletion of arbitrary rows via removeAll.
**Insight:** Embedding-store filter mappers that build JSON-path or identifier SQL from user keys are injection sinks; test both JSON and COLUMN_PER_KEY modes, and note MariaDB backslash escaping gaps when only quotes are escaped.

## Proof of concept (copied exactly from the original report)

**pgvector JSON-mode key breakout with pg_sleep**

```
metadataKey("')::text IS NOT NULL OR pg_sleep(1) IS NOT NULL --")
```

**pgvector column-mode raw identifier injection**

```
`<key>::<type>`), so a key such as `1=1 OR true --`
injects directly.
```

