Security threat model
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.
- Attacker and trust boundary
- Assets
- Inputs
- Security properties
- Always a vulnerability
- Not a vulnerability
- Downstream responsibilities
- Surprising vs unsurprising class loading
- Denial of service
- Triage dispositions
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 points at. If a model references h2, h2’s own settings and behavior are h2’s concern, not Calcite’s.
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.
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).
| 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) |
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 (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
execute code outside Calcite’s query-processing semantics. This covers
Runtime.execandProcessBuilder, 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
- 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.
- Arbitrary file read or write that no explicitly-configured file adapter was asked to perform.
- Server-side request forgery, forcing Calcite to connect to a host the attacker chooses (internal services, cloud metadata endpoints, port scans).
- 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.
- 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.
- 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).
- 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
modelpoints at. A third-party driver or service amodelreferences 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 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, andjavax.naming.InitialContextare surprising in aSchemaFactoryslot. 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
CancelFlagin 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, orRLIKE, 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.
Triage dispositions
Every security report against Calcite resolves to exactly one of:
- Valid — violates P1–P4, or matches an item in Always a vulnerability. Gets a fix. A demonstrated class-loading primitive qualifies on its own (see 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: 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 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.