ANT-2026-KC1Y51NF · openmeterio/openmeter
sql-injection medium
Severity Claude high · Security research firm high · Maintainer medium
Discovered by Claude Mythos Preview
Anthropic's analysis, sealed at approval. Disclosure to the maintainer was performed by Anvil Security.
ANT-2026-KC1Y51NF: SQL injection in ClickHouse JSONPath validation
The meter create/update HTTP handlers validate user-supplied valueProperty and groupBy JSONPaths by building SELECT JSON_VALUE('{}', '<path>') with fmt.Sprintf and executing it against ClickHouse. The value is passed through sqlbuilder.Escape, which only doubles $ and does not escape single quotes, so an attacker can break out of the string literal. This validation runs on the raw request body before Go-side input validation, and the OpenAPI schema imposes no pattern restriction on valueProperty. Because the query runs against the shared om_events table without tenant scoping, an authenticated tenant can inject arbitrary ClickHouse SQL and read every other tenant's usage events.
Target
Project: openmeterio/openmeter
Location: openmeter/streaming/clickhouse/utils_query.go:15
Discovery: static analysis — not yet dynamically reproduced
Technical Details
sqlbuilder.Escape (huandu/go-sqlbuilder) only replaces $ with $$ for placeholder protection and performs no SQL string-literal escaping, so single quotes in the JSONPath terminate the literal and inject SQL. A correct escaper, escapeJSONPathLiteral (meter_query.go:484-509), exists and is used everywhere else but was omitted at this call site.
Reproduction
- Send POST /api/v1/meters with body {"slug":"m","aggregation":"SUM","eventType":"e","valueProperty":"$.a'), throwIf((SELECT count() FROM openmeter.om_events WHERE namespace='victim-ns' AND subject='target')>0) --"}
- Server builds SELECT JSON_VALUE('{}', '$.a'), throwIf(...) --') and executes it via ClickHouse.Exec
- Observe HTTP 200 vs 500 as a boolean oracle, or use sleep() for time-based blind extraction, or url() for out-of-band exfiltration
[No reproducer or sanitizer output attached — request from security-cvd@anthropic.com if needed.]
Suggested Fix
Pass user-supplied JSONPath strings to ClickHouse as bound parameters, or escape them with a ClickHouse-aware string-literal escaper (e.g. the existing escapeJSONPathLiteral) before interpolation; never let them influence query structure.
Acknowledgement
This vulnerability was discovered by Claude, Anthropic's AI assistant, and triaged by the Anthropic security team in collaboration with Anthropic Research. Please direct questions to security-cvd@anthropic.com and reference ANT-2026-KC1Y51NF.
Reference: ANT-2026-KC1Y51NF
Anthropic CVD Policy: https://www.anthropic.com/coordinated-vulnerability-disclosure
Triage and disclosure were performed by Anvil Security.
- Verdict
- true positive
- Severity
- high
Dates from discovery through public reveal.
- 2026-04-21 Reported to tracker
- 2026-05-19 Sent to maintainer
- 2026-05-19 Patch released
- 2026-05-19 Maintainer acknowledged
- 2026-08-17 Publicly revealed
SHA-3-512 hash:
551ef9de796cb83e2d8a460f860441abb3fd6c1559e97ca04e279fbc62694ab4688a3dc7a995d86d6eebd4b93a1b94d22afac15a2d797c4094befcd2cd1c8f5b
Committed 2026-05-19 14:41 PT
Revealed 2026-08-17 10:47 PT
Verify (download preimage.json)
Show preimage JSON
{
"ant_id": "ANT-2026-KC1Y51NF",
"bug_class": "sql_injection",
"claude_severity": "high",
"commit_sha": null,
"created_at": "2026-04-21T16:56:09+00:00",
"description": "The meter create/update HTTP handlers validate user-supplied `valueProperty` and `groupBy` JSONPaths by building `SELECT JSON_VALUE('{}', '<path>')` with `fmt.Sprintf` and executing it against ClickHouse. The value is passed through `sqlbuilder.Escape`, which only doubles `$` and does not escape single quotes, so an attacker can break out of the string literal. This validation runs on the raw request body before Go-side input validation, and the OpenAPI schema imposes no pattern restriction on `valueProperty`. Because the query runs against the shared `om_events` table without tenant scoping, an authenticated tenant can inject arbitrary ClickHouse SQL and read every other tenant's usage events.",
"discovered_at": "2026-04-19T00:00:00+00:00",
"location": "openmeter/streaming/clickhouse/utils_query.go:15",
"poc_sha256": null,
"preimage_version": 1,
"project": "openmeterio/openmeter",
"reproduction": [
"Send POST /api/v1/meters with body {\"slug\":\"m\",\"aggregation\":\"SUM\",\"eventType\":\"e\",\"valueProperty\":\"$.a'), throwIf((SELECT count() FROM openmeter.om_events WHERE namespace='victim-ns' AND subject='target')>0) --\"}",
"Server builds SELECT JSON_VALUE('{}', '$.a'), throwIf(...) --') and executes it via ClickHouse.Exec",
"Observe HTTP 200 vs 500 as a boolean oracle, or use sleep() for time-based blind extraction, or url() for out-of-band exfiltration"
],
"technical_details": "`sqlbuilder.Escape` (huandu/go-sqlbuilder) only replaces `$` with `$$` for placeholder protection and performs no SQL string-literal escaping, so single quotes in the JSONPath terminate the literal and inject SQL. A correct escaper, `escapeJSONPathLiteral` (meter_query.go:484-509), exists and is used everywhere else but was omitted at this call site.",
"title": "SQL injection in ClickHouse JSONPath validation",
"vendor_severity": "high"
}