This is an automated email from the ASF dual-hosted git repository. rubenada pushed a commit to branch site in repository https://gitbox.apache.org/repos/asf/calcite.git
commit fedd4b77727e2203749b4626f9f4334c45475cba Author: Vladimir Sitnikov <[email protected]> AuthorDate: Tue Jul 21 15:48:29 2026 +0300 [CALCITE-7605] Add threat model Document what Calcite treats as a security vulnerability: the attacker model, the properties it provides and disclaims (P1-P4), the surprising-vs-unsurprising class-loading rule, and denial of service as a hardening goal. Add SECURITY.md with the private-reporting channel. This is an alternative draft for the same Jira as #5020, reorganizing the same normative content into a structure that is easier to read and to maintain per-CVE. Co-Authored-By: Claude Opus 4.8 <[email protected]> --- .ratignore | 1 + SECURITY.md | 14 +++ site/_data/docs.yml | 4 + site/_docs/security_threat_model.md | 202 ++++++++++++++++++++++++++++++++++++ 4 files changed, 221 insertions(+) diff --git a/.ratignore b/.ratignore index 71048d405c..93e7e93e7a 100644 --- a/.ratignore +++ b/.ratignore @@ -13,6 +13,7 @@ **/data.txt **/data2.txt .idea/vcs.xml +SECURITY.md example/csv/src/test/resources/smoke_test.sql # TODO: remove when pom.xml files are removed diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 0000000000..4d14a5b793 --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,14 @@ +# Security Policy + +## Reporting a Vulnerability + +`apache/calcite` follows the [Apache Software Foundation security process](https://www.apache.org/security/). +Please report suspected vulnerabilities privately to `[email protected]`; do not open public +GitHub issues or pull requests for security reports. + +## Threat Model + +What the project treats as in scope and out of scope, the security +properties it provides and disclaims, the adversary model, and how +findings are triaged are documented in the +[threat model](site/_docs/security_threat_model.md). diff --git a/site/_data/docs.yml b/site/_data/docs.yml index 5a6a714914..fc1bac1f61 100644 --- a/site/_data/docs.yml +++ b/site/_data/docs.yml @@ -44,6 +44,10 @@ - model - howto +- title: Security + docs: + - security_threat_model + - title: Meta docs: - history diff --git a/site/_docs/security_threat_model.md b/site/_docs/security_threat_model.md new file mode 100644 index 0000000000..5f5deb7b9a --- /dev/null +++ b/site/_docs/security_threat_model.md @@ -0,0 +1,202 @@ +--- +layout: docs +title: Security threat model +permalink: /docs/security_threat_model.html +--- +<!-- +{% comment %} +Licensed to the Apache Software Foundation (ASF) under one or more +contributor license agreements. See the NOTICE file distributed with +this work for additional information regarding copyright ownership. +The ASF licenses this file to you under the Apache License, Version 2.0 +(the "License"); you may not use this file except in compliance with +the License. You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +{% endcomment %} +--> + +Calcite is an embedded library: it runs inside a host application's JVM and +exposes no network port of its own. This threat model covers what an attacker +who reaches that embedded engine over a JDBC connection can and cannot do. + +Calcite treats the behaviors below as security vulnerabilities, so that +reporters and committers triage them the same way. A report that +contradicts this model is a feature request or a documentation gap, not a +vulnerability. + +**Status:** draft for PMC discussion; not yet ratified. + +* TOC +{:toc} + +## Attacker and trust boundary + +One attacker profile: a *query author* who reaches Calcite over a JDBC +connection. + +The attacker can: + +* set any connection property to any value: `model`, `parserFactory`, + `schemaFactory`, `fun`, `typeSystem`, `dataSource`, `jdbcUrl`, and the + rest; +* execute any SQL, including DDL. + +The attacker cannot: + +* change JVM system properties; +* change the classpath (add or replace classes or JARs). + +Out of scope by definition: the configuration and behavior of a +third-party driver or service that a [model]({{ site.baseurl }}/docs/model.html) +points at. If a model references h2, h2's own settings and behavior are +h2's concern, not Calcite's. + +Because Calcite listens on no socket of its own, network transport, TLS, and +authentication belong to the host application, not to this model. A host that +lets an untrusted principal set connection properties has handed that principal +the attacker's full capability above. + +## Assets + +* the host running Calcite: no code execution, and no file access beyond + what an adapter is configured to perform; +* the internal network reachable from that host: no attacker-directed + outbound requests. + +## Security properties + +* **P1: no code execution.** Neither connecting nor running SQL may + execute code outside Calcite's query-processing semantics. This covers + `Runtime.exec` and `ProcessBuilder`, and the weaker primitive of loading + an attacker-named class so that its static initializer, constructor, or + an accessed static field runs. Exception: the os-adapter. +* **P2: no incidental file access.** Neither connecting nor running + SQL may read or create a file, except where a file-oriented adapter or + table function reads the local path it was explicitly configured with. The + carve-out covers local filesystem paths only; a file adapter that fetches a + URL (`http://`, `https://`) is making a network request and falls under P3. +* **P3: no server-side request forgery.** Neither connecting nor + running SQL may open a network connection to an attacker-chosen host. +* **P4: no escape from the configured schemas.** Neither connecting nor + running SQL may read data outside the schemas the connection exposes. A query + that reaches another schema, a file, or a catalog that the connection's root + schema does not make visible is a vulnerability. + +## Always a vulnerability + +1. **Code execution** that results from connecting or running SQL, except + through the os-adapter. The bar is the primitive, not a full chain: a + reachable sink that loads an attacker-named class qualifies, because + class loading runs the static initializer before any type check. +2. **Arbitrary file read or write** that no explicitly-configured file + adapter was asked to perform. +3. **Server-side request forgery**, forcing Calcite to connect to a + host the attacker chooses (internal services, cloud metadata endpoints, + port scans). +4. **Reading beyond the configured schemas**, reaching another schema, + a file, or a catalog that the connection's root schema does not make + visible. + +## Not a vulnerability + +* The os-adapter running OS commands. It exists to do that, and an operator + must add it on purpose. +* A file, CSV, or JSON adapter reading the local path it was configured + with. Opt-in, by the same reasoning as the os-adapter. +* Anything that needs a changed system property or classpath. Both are + outside the attacker's reach by assumption. +* The behavior of a third-party driver once Calcite has connected to the + endpoint it was configured with. +* Cross-tenant reads that follow from the embedder exposing more than one + principal's schemas on a single connection. Calcite has no authentication or + authorization; scoping each connection's root schema to what its principal may + see is the embedder's job. P4 applies where the user submits only SQL; a user + who also sets connection properties configures their own schema visibility. + +## Surprising vs unsurprising class loading + +Calcite loads a class named in a connection property or in SQL only to use it +through a specific SPI: a schema factory, table factory, function, operator, +data source, or driver. The security boundary follows that contract, not a +blanket trust of the classpath or of SQL. + +* **Unsurprising.** The class implements the SPI interface for the position it + was named in, and Calcite invokes it through that interface. This is working + as designed, even when the class is otherwise dangerous. The operator who + placed the class on the classpath, and the author of the class, own that + contract. +* **Surprising.** Naming a class runs the class's own code (a static + initializer, constructor, method, or static-field read) even though it + does not implement the SPI for that position. `java.lang.Runtime`, + `org.springframework.boot.SpringApplication`, and `javax.naming.InitialContext` + are surprising in a `SchemaFactory` slot. Surprising class loading is always a + vulnerability. + +This boundary lets three goals hold at once: + +* untrusted classes may sit on the classpath; a dangerous class that does + not implement a Calcite SPI is never instantiated by name, so the operator + need not audit every class; +* SQL may be arbitrary; it can name SPI classes, but only SPI + implementations run, and only through their SPI; +* a reasonable-looking query cannot trigger an unexpected process launch, file + read, or network call, because the interface gate rejects the classes that + would cause one. + +The same rule governs any path that reconstructs objects or loads classes from +attacker-controlled input, including JSON plan deserialization. + +**Mechanism.** Load with `Class.forName(name, false, loader)`, check +`pluginClass.isAssignableFrom(clazz)`, and only then initialize and instantiate. +A class that fails the check never runs its static initializer. + +**Standard-Java SPIs.** The gate is tightest when the SPI is a Calcite interface +(`SchemaFactory`, `TableFactory`, `Function`, `SqlOperator`): only a class +written to be a Calcite plugin passes. Two positions name a standard Java +interface instead, `dataSource` (`javax.sql.DataSource`) and `jdbcDriver` +(`java.sql.Driver`), which many unrelated libraries implement. The +interface gate still blocks the surprising case, since `java.lang.Runtime` +implements neither, so naming a `DataSource` or `Driver` implementation is the +documented feature, not a vulnerability. An allowlist of permitted +implementations is optional hardening for these positions, not a security +boundary. The host a driver then connects to is governed by P3, independently of +which class is named. + +### Triage rule for class-loading sinks + +A reachable sink that loads an attacker-named class is a vulnerability on +its own. A reporter need not demonstrate end-to-end remote code execution +on a specific classpath: the demonstrated primitive (a static +initializer, a constructor, or a static-field read on an attacker-named +class) is enough to require a fix. The fix loads with +`initialize=false`, gates on the expected interface (or an allowlist), and +then instantiates. + +## Denial of service + +A single query should not be able to exhaust the host. This is in scope as a +hardening goal. The controls are not all in place yet, so treat the gaps below +as known limitations rather than per-report vulnerabilities until the controls +land. + +* **Planning.** A crafted query can drive the planner into a combinatorial + blow-up. The fix is a set of bounds: a planning deadline, a cap on rule + firings, and a cap on the number of explored alternatives. Calcite already + carries a `CancelFlag` in the planner context, so a deadline can build on it; + the firing and size caps are new. +* **Execution.** Catastrophic regex backtracking in `LIKE`, `SIMILAR TO`, or + `RLIKE`, or an unbounded join, exhausts resources at run time. Planning bounds + do not help here; the mitigation is a match-time limit or a backtracking-free + regex engine. +* **Parsing.** Deeply nested expressions can overflow the parser stack. The + mitigation is a nesting-depth limit. + +Once the planning bounds exist, a single reasonably-sized query that exceeds +them is a configuration choice, not a vulnerability.
