Why the HTTP QUERY Method Is a Bad Idea, and Accept-Query Is Why

Why the HTTP QUERY Method Is a Bad Idea, and Accept-Query Is Why

In June 2026 the IETF published RFC 10008 and gave HTTP a new method: QUERY. New HTTP methods are rare. Most engineers have not seen one added in about twenty years, so this is worth understanding before it shows up in your stack.

At first, when I saw this new method, I thought, "OK, we need to support it in our WAF." But after reading the RFC... I changed my mind...

Here is what QUERY does, in plain terms. It works like GET, but it can carry a request body. A client uses it to send a read-only query and get back the result. It is "safe", which means it does not change anything on the server, and "idempotent", which means sending it twice has the same effect as sending it once. Those two properties let caches store the response and let proxies retry the request, which they are not allowed to do with POST. So QUERY fills a real gap: a read request whose input is too big or too structured to fit in the URL.

Let me start with the good part, because the problem only makes sense once you see what QUERY gets right. Putting a large query in a URL hits length limits and encoding it's always a "problem" (browsers encode some chracters, http libs do it different, apps need to handle it, etc...). Putting it in a POST loses caching and safe retries, because POST is (usually) allowed to change data.

For twenty years the workaround was: "ok guys, let's put everything in a GET request with body", and that has never worked reliably... proxies, servers, and libraries treat a GET body in different ways, and some ignore it, strip it or block it (basically every WAF based on OWASP Coreruleset). QUERY gives you one clear method for this job. As a method, it is fine.

Here is what a QUERY exchange looks like, and the order matters. A client is meant to find out which query formats a resource accepts before it sends a query. The RFC does this with a HEAD request (an OPTIONS works too), and the server replies with the Accept-Query header:

HEAD /reports/sales HTTP/1.1
Host: api.example.com
HTTP/1.1 200 OK
Accept-Query: application/json, "application/sql", "application/jsonpath"
Vary: Accept-Query, Content-Type

Now the client knows its choices and picks one. The safe choice is a structured JSON query, sent with QUERY:

QUERY /reports/sales HTTP/1.1
Host: api.example.com
Content-Type: application/json
Accept: application/json

{
  "region": "EMEA",
  "period": { "from": "2026-01-01", "to": "2026-06-30" },
  "group_by": ["country", "product"],
  "limit": 100
}

Look at that Accept-Query line in the reply. Before the client has sent any query, the server has already said: you can send your query as JSON, or as SQL, or as JSONPath, and I will run whichever you pick. Why this is bad?

  • Accept-Query as a design problem: negotiating a query language instead of a data format.
  • application/sql: do you really want to accept a whole SQL query from a user and execute it on your application database? I don't think so, at least for the most part of the web application and API. In case you were wondering, prepared statements don't helps you here...
  • application/jsonpath: Also here, do you really want to accept something like $..book[?(@.author =~ /.*Tolkien/i)] in you web application? deep-dive DoS, ReDoS, and real-world CVEs (CVE-2026-45756, CVE-2026-1615, jsonpath-plus).
  • WAF blindness to any content type that is not JSON, form-urlencoded or XML(ish).

So, let me say that the problem is not the QUERY method IMO. It is the Accept-Query, and what the RFC give us as examples: letting a server offer, and a client choose, which query language to send in the body. HTTP already lets a client choose a format for the answer. Asking a server to accept a whole query language and run it is a different and far more dangerous thing, even though on the wire it looks the same. The rest of this post is about why the two languages the RFC uses in its own examples, application/sql and application/jsonpath, are the wrong things to offer over the network, and why the security tools you count on cannot inspect any of it.

What Accept-Query actually offers

Accept-Query is a response header, the one we just saw. A server uses it to list the query formats it accepts. The RFC's own example is Accept-Query: "application/jsonpath", application/sql;charset="UTF-8". It looks exactly like the normal Accept header, only for queries. The RFC does not pick a query language for you. It lets each resource decide, and asks only that the request carry a matching Content-Type. Its own examples use application/x-www-form-urlencoded, application/sql, application/xslt+xml, and application/jsonpath.

The Accept header asks for a format for the data the server sends back. That is safe, because the server owns the code that produces the data, and the result is just data. Accept-Query asks the server to take a query written by the client and run it. Now the input is not data, it is a small program. Choosing a format for the answer is not the same as choosing a language to send the server to run. Accept-Query makes the second one look as ordinary as the first.

application/sql

application/sql is a real, registered media type (RFC 6922, 2013). That registration says nothing about safety. It only gives a name to "this body is SQL text." It does not say whether running that text from a stranger is a good idea.

Every backend engineer's first reaction is right: an app that takes SQL from the client and runs it is a serious hole. The usual fix people mention is prepared statements, but prepared statements do not help at all here. A prepared statement keeps the query's structure, written by the server, separate from the values, which are sent as parameters. It assumes the server wrote the query. With QUERY and application/sql, the client sends the whole query. There is no server-written structure to put values into. The query itself is the untrusted input. There is nothing to prepare. The main defense against SQL injection simply does not apply.

So what would it take to accept application/sql safely? This is not impossible but it is what query engines already do: ClickHouse's HTTP interface, Trino, Datasette's read-only SQLite endpoint, and analytics APIs that accept SQL over POST today. For them, QUERY with application/sql is the correct next step, and they have spent real work on the sandbox. At the moment, if you have a WAF in front of you web application, there's no way to inspect a SQL syntax in body. It will simply triggers all of your SQL Injection rules, including libinjection.

So, why offering it on Accept-Query response header? It doesn't make any sense to me TBH.

If your web application needs a filter functionality, 90% of times you already know which kind of format your application requires for filtering (and most of the time is JSON or form-urlencoded).

application/jsonpath: a query language you let attackers run on your data

application/sql at least scares people. application/jsonpath looks harmless, which makes it worse. It is "just" picking fields out of JSON. It is not.

Think about what accepting a JSONPath body means: you take an expression written by the attacker and run it, on your server, against a JSON document you hold. JSONPath is like a small language, but it is expensive to run, and most of its features become DoS tools when the expression is hostile.

The recursive descent operator .., walks the whole document. $..* selects every node in it. On a large or deeply nested document that means visiting and collecting every node, and the query that does it is four characters long.

You can even use RegEx like $..book[?(@.author =~ /.*Tolkien/i)] and if the backend execute it with PCRE you can easily produce a catastrophic backtracking and ReDoS. This is not an old, fixed problem. A 2026 advisory, CVE-2026-45756, describes Symfony's JsonPath component running attacker-supplied regular expressions in match() and search() with no limits.

That sounds like a good DoS to me: the attacker sends a few bytes JSONpath syntax, and the server spends CPU and memory on the whole dataset.

Even the RFC 9535, which finally standardized JSONPath in February 2024, says this plainly in its security section: a bad query can "cause excessive time or memory consumption by selecting unbounded numbers of nodes or triggering deep recursion," and it tells implementations to "consider imposing reasonable limits on query complexity, recursion depth, and result set size." When a language's own standard tells you to add limits before you use it, that is the language warning you.

DoS is not even the worst outcome, only the most reliable one. JSONPath libraries have a long history of turning "evaluate this expression" into "run this code," because some of them built filters on top of the host language's eval. The jsonpath npm package has a critical code-injection advisory, CVE-2026-1615, for evaluating expressions through static-eval, which gives remote code execution in Node and XSS in the browser. jsonpath-plus shipped a fix for a remote-code-execution bug and then a second fix because the first was incomplete (CVE-2024-21534 and CVE-2025-1302).

WAF will protect you? Ehmmm, nope. At the moment there isn't any WAF (OSS or commercial) that can inspect, validate or sanitize JSONPath.

So? This means that QUERY should not be used?

None of this means you should avoid the QUERY method. It means you should avoid the part Accept-Query makes tempting.

Use QUERY with a structured query written as JSON that the server defines. The client sends fields, the server checks them and builds the real query itself. That keeps you inside normal content negotiation, a data format the server controls, and it keeps your WAF able to read the body as fields, which is the whole point. This covers almost every "complex read with a body" need, which is what QUERY was for.

If you really do need to accept a query language, and sometimes you do, treat the endpoint as remote code execution, because that is what it is. Run it with a locked-down user, put a real parser and allow-list in front, set hard limits on time, memory, recursion, and result size, authenticate and rate-limit per client, and do not expect a WAF or CDN to save you. Build the sandbox first, offer the format second. But if you prefer to sleep, don't do it.

Closing

The QUERY method is a good answer to an old problem. Accept-Query is a good-looking feature that answers a question no one should ask: which query language would you like to run today? Content negotiation earned our trust for choosing a format for data, where the server owns the code and the output is data. Extending it to query languages, where the client sends the code and the server runs it, quietly moves the most dangerous choice in the whole request into a header that reads like Accept. The RFC's own examples, SQL and JSONPath, are not corner cases to ignore. They are the spec showing you the trap and calling it a feature. Take the method. Skip the part where clients bring their own query language.

References