You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@calcite.apache.org by vl...@apache.org on 2026/07/25 15:17:19 UTC
(calcite) branch main updated (8447eda2f2 -> 97d4b030f2)
This is an automated email from the ASF dual-hosted git repository.
vlsi pushed a change to branch main
in repository https://gitbox.apache.org/repos/asf/calcite.git
from 8447eda2f2 [CALCITE-7505] RelToSqlConverter fails to alias outer relation for correlated sub-queries in Filter
new 2e87f1d90a [CALCITE-7605] Add threat model
new 97d4b030f2 [CALCITE-7605] Threat model: add Inputs, Downstream responsibilities, Triage dispositions
The 2 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails. The revisions
listed as "add" were already present in the repository and have only
been added to this reference.
Summary of changes:
.ratignore | 1 +
SECURITY.md | 14 ++
site/_data/docs.yml | 4 +
site/_docs/security_threat_model.md | 267 ++++++++++++++++++++++++++++++++++++
4 files changed, 286 insertions(+)
create mode 100644 SECURITY.md
create mode 100644 site/_docs/security_threat_model.md
(calcite) 01/02: [CALCITE-7605] Add threat model
Posted by vl...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
vlsi pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/calcite.git
commit 2e87f1d90a7581bf2d565acd6fdcbb174c90bc7b
Author: Vladimir Sitnikov <si...@gmail.com>
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 <no...@anthropic.com>
---
.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 `security@apache.org`; 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.
(calcite) 02/02: [CALCITE-7605] Threat model: add Inputs, Downstream responsibilities, Triage dispositions
Posted by vl...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
vlsi pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/calcite.git
commit 97d4b030f29b6427491542be9e5ba89b7d33245e
Author: Vladimir Sitnikov <si...@gmail.com>
AuthorDate: Tue Jul 21 18:22:04 2026 +0300
[CALCITE-7605] Threat model: add Inputs, Downstream responsibilities, Triage dispositions
Fold in the three additive sections proposed in vlsi/calcite#3, corrected
against the code:
- Inputs: map every attacker-controlled input to its governing rule. Add the
metaTableFactory/metaColumnFactory PLUGIN properties, place operator under the
RelJson row (SqlOperator loads only via RelJson), and note that fun selects
built-in libraries rather than loading a class.
- Downstream responsibilities: collect the host/operator duties in one place and
drop the now-duplicated transport paragraph from the attacker section.
- Triage dispositions: a closed outcome set a human or agent can route against.
- Not a vulnerability: state that pushed-down SQL is by design, while a pushdown
bug that reads beyond the configured schemas is P4.
Also remove the draft-status line ahead of merge.
Co-authored-by: Jarek Potiuk <ja...@potiuk.com>
Co-Authored-By: Claude Opus 4.8 <no...@anthropic.com>
---
site/_docs/security_threat_model.md | 79 +++++++++++++++++++++++++++++++++----
1 file changed, 72 insertions(+), 7 deletions(-)
diff --git a/site/_docs/security_threat_model.md b/site/_docs/security_threat_model.md
index 5f5deb7b9a..d9936197d4 100644
--- a/site/_docs/security_threat_model.md
+++ b/site/_docs/security_threat_model.md
@@ -31,8 +31,6 @@
contradicts this model is a feature request or a documentation gap, not a
vulnerability.
-**Status:** draft for PMC discussion; not yet ratified.
-
* TOC
{:toc}
@@ -58,11 +56,6 @@ ## Attacker and trust boundary
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
@@ -70,6 +63,23 @@ ## Assets
* the internal network reachable from that host: no attacker-directed
outbound requests.
+## Inputs
+
+Everything the attacker controls resolves to one of P1–P4 or to an explicit
+carve-out below. A report that reaches a sink not covered here is a model gap
+(see [Triage dispositions](#triage-dispositions)).
+
+| Input | How it is supplied | What it feeds | Governing rule |
+| --- | --- | --- | --- |
+| SQL text, including DDL | any statement on the connection | parser → validator → planner → generated code | P1–P4; parser nesting depth is a DoS surface (see [Denial of service](#denial-of-service)) |
+| Class-naming connection properties — `schemaFactory`, `parserFactory`, `typeSystem`, `metaTableFactory`, `metaColumnFactory` | connection property or `model` | a class loaded through a Calcite SPI | [Surprising vs unsurprising class loading](#surprising-vs-unsurprising-class-loading) (P1) |
+| `tableFactory` and function classes | `model` | a class loaded through a Calcite table or function SPI | Surprising vs unsurprising class loading (P1) |
+| `dataSource`, `jdbcDriver` | connection property or `model` | a class loaded through a standard-Java SPI (`javax.sql.DataSource`, `java.sql.Driver`) | Surprising vs unsurprising class loading (P1); the host it then dials is P3 |
+| `fun` | connection property | selects built-in function libraries by name | no class loading; ordinary SQL semantics under P1–P4 |
+| `model` — inline JSON, a `file:` path, or a URL | connection property | schema/table factories and adapter operands | P1 (factories via SPI), P2 (local-file operands), P3 (a URL model, or a URL-fetching adapter) |
+| A serialized RelNode plan (`RelJson`) — types and operators | any path that reconstructs a plan from attacker input | type and operator class resolution | Surprising vs unsurprising class loading (P1) |
+| Adapter operands — e.g. a file/CSV/JSON path, or the os-adapter | `model` or SQL | the adapter's configured resource | P2 for a configured local path (opt-in ⇒ not a vulnerability); the os-adapter is opt-in (not a vulnerability) |
+
## Security properties
* **P1: no code execution.** Neither connecting nor running SQL may
@@ -114,12 +124,39 @@ ## Not a vulnerability
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.
+* SQL that Calcite pushes down to a configured backend. Calcite generates the
+ text and sends it to the endpoint the operator configured, and the query
+ author can already reach that endpoint's data through the visible schemas. A
+ pushdown bug that reads beyond the configured schemas is P4 and a
+ vulnerability; the generated SQL reaching the configured backend is not.
* 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.
+## Downstream responsibilities
+
+Calcite is embedded, so several controls belong to the host or the operator,
+not to the library. A finding that lands in one of these is not a Calcite
+vulnerability.
+
+* **Transport and identity.** Calcite opens no socket. TLS, the network
+ perimeter, authentication, and authorization live in the host. A host that
+ lets an untrusted principal set connection properties hands that principal the
+ full capability in [Attacker and trust boundary](#attacker-and-trust-boundary).
+* **Schema scoping.** Scope each connection's root schema to what its principal
+ may see; Calcite has no authentication or authorization. Cross-tenant reads
+ across schemas exposed on one connection are the embedder's to prevent (see
+ [Not a vulnerability](#not-a-vulnerability)).
+* **Adapter selection.** Add the os-adapter and the file, CSV, or JSON adapters
+ only where the query author is trusted to reach what they expose.
+* **Classpath.** The operator owns the classpath. Calcite gates class loading by
+ SPI; which classes are present is the operator's trust decision.
+* **What a `model` points at.** A third-party driver or service a `model`
+ references is configured and patched by the operator; its behavior past the
+ connection boundary is out of this model.
+
## Surprising vs unsurprising class loading
Calcite loads a class named in a connection property or in SQL only to use it
@@ -200,3 +237,31 @@ ## Denial of service
Once the planning bounds exist, a single reasonably-sized query that exceeds
them is a configuration choice, not a vulnerability.
+
+## Triage dispositions
+
+Every security report against Calcite resolves to exactly one of:
+
+* **Valid** — violates P1–P4, or matches an item in
+ [Always a vulnerability](#always-a-vulnerability). Gets a fix. A demonstrated
+ class-loading primitive qualifies on its own (see
+ [Triage rule for class-loading sinks](#triage-rule-for-class-loading-sinks));
+ it need not be chained to end-to-end RCE.
+* **Not a vulnerability (by design)** — matches an item in
+ [Not a vulnerability](#not-a-vulnerability): the os-adapter, an opt-in
+ file/CSV/JSON adapter reading its configured path, third-party driver or
+ pushed-down SQL behavior past the connection, or cross-tenant reads that
+ follow from the embedder's schema exposure. Close with a pointer to this
+ model.
+* **Out of model** — requires a capability the attacker does not have (changing
+ a JVM system property or the classpath), or lands in a layer this model
+ assigns to the host (network transport, TLS, authentication, authorization).
+ Close; redirect to the operator or embedder.
+* **Known limitation** — a [Denial of service](#denial-of-service) gap whose
+ control has not landed yet. Tracked as hardening, not a per-report
+ vulnerability, until the bound exists.
+* **Duplicate** — the same sink or root cause is already tracked in an open
+ Jira. Link and close.
+* **Model gap** — plausible, but this model does not clearly place it in or out.
+ Escalate to the PMC to decide, then update this document with the ruling so
+ the next report of its kind is no longer a gap.