This is an automated email from the ASF dual-hosted git repository.

rubenada pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/calcite-avatica.git


The following commit(s) were added to refs/heads/main by this push:
     new ce9e84e1f [CALCITE-7674] Add Avatica threat model
ce9e84e1f is described below

commit ce9e84e1f3a8b1c5401c04b333001f071a7ace47
Author: Ruben Quesada Lopez <[email protected]>
AuthorDate: Tue Jul 28 09:00:22 2026 +0100

    [CALCITE-7674] Add Avatica threat model
---
 .ratignore                          |   3 +
 AGENTS.md                           |  10 +
 CLAUDE.md                           |   1 +
 SECURITY.md                         |  14 ++
 site/_data/docs.yml                 |   4 +
 site/_docs/security_threat_model.md | 428 ++++++++++++++++++++++++++++++++++++
 6 files changed, 460 insertions(+)

diff --git a/.ratignore b/.ratignore
index 42ec83056..59df039e1 100644
--- a/.ratignore
+++ b/.ratignore
@@ -2,6 +2,9 @@
 **/.gitignore
 .ratignore
 .idea/vcs.xml
+AGENTS.md
+CLAUDE.md
+SECURITY.md
 # Third-party license files
 src/main/config/licenses/
 #
diff --git a/AGENTS.md b/AGENTS.md
new file mode 100644
index 000000000..a5df0232b
--- /dev/null
+++ b/AGENTS.md
@@ -0,0 +1,10 @@
+# Agent guidance
+
+Avatica is a JDBC/ODBC wire-protocol layer: a server that fronts a
+local JDBC `DataSource` (typically Apache Calcite, but any JDBC driver
+is supported), and a client-side JDBC driver that speaks the Avatica
+wire protocol over HTTP or HTTPS.
+
+## Security
+
+See [SECURITY.md](./SECURITY.md) before reporting a vulnerability.
diff --git a/CLAUDE.md b/CLAUDE.md
new file mode 100644
index 000000000..43c994c2d
--- /dev/null
+++ b/CLAUDE.md
@@ -0,0 +1 @@
[email protected]
diff --git a/SECURITY.md b/SECURITY.md
new file mode 100644
index 000000000..de1909076
--- /dev/null
+++ b/SECURITY.md
@@ -0,0 +1,14 @@
+# Security Policy
+
+## Reporting a Vulnerability
+
+`apache/calcite-avatica` 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 e5d37ffb9..b921eba79 100644
--- a/site/_data/docs.yml
+++ b/site/_data/docs.yml
@@ -38,6 +38,10 @@
   - go_client_reference
   - go_howto
 
+- title: Security
+  docs:
+    - security_threat_model
+
 - title: Avatica Meta
   docs:
   - history
diff --git a/site/_docs/security_threat_model.md 
b/site/_docs/security_threat_model.md
new file mode 100644
index 000000000..565c107eb
--- /dev/null
+++ b/site/_docs/security_threat_model.md
@@ -0,0 +1,428 @@
+---
+layout: docs
+title: Security threat model
+sidebar_title: 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 %}
+-->
+
+Apache Avatica is a JDBC/ODBC wire-protocol layer: a server that fronts a
+local JDBC `DataSource` (typically Apache Calcite, but any JDBC driver
+is supported), and a client-side JDBC driver that speaks the Avatica
+wire protocol over HTTP or HTTPS. This threat model covers what an
+attacker who reaches an Avatica server over the wire, or who tricks an
+Avatica client into connecting to a server, can and cannot do.
+This model covers the Java server and the Java (JDBC) client in this
+repository. `apache/calcite-avatica-go`
+is a separate implementation that speaks the same wire protocol; it
+is not covered here and would need its own model.
+
+Avatica 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.
+
+Companion documents:
+
+* [Security features]({{ site.baseurl }}/docs/security.html) — how to
+  configure authentication, TLS, and impersonation. This document (threat
+  model) is normative; the features document is operational guidance.
+* Apache Calcite's [security threat
+  model](https://calcite.apache.org/docs/security_threat_model.html) —
+  Calcite's model covers only what happens inside the embedded engine
+  once a JDBC connection has been established. When Avatica is fronting
+  Calcite, the two documents divide responsibility: Avatica owns the
+  wire; Calcite owns the query engine.
+
+
+* TOC
+{:toc}
+
+## Attacker and trust boundary
+
+Avatica has two deployment shapes and therefore two attacker profiles:
+
+**Server-side attacker: an HTTP client** that can reach an Avatica
+server over the network.
+
+The attacker can:
+
+* open TCP connections to the server's listening port;
+* send arbitrary HTTP requests, including well-formed and malformed
+  Avatica protocol frames (JSON or Protobuf);
+* if the server has no authentication configured, be treated as
+  authenticated;
+* if the server has authentication configured, attempt to authenticate
+  and observe responses; a valid credential upgrades the attacker to
+  the *authenticated caller* profile.
+
+The attacker cannot:
+
+* change JVM system properties, `avatica-server` command-line
+  arguments, `HttpServer.Builder` configuration, or the classpath;
+* obtain valid authentication credentials without an out-of-band step
+  (that step is outside this model — see [Downstream
+  responsibilities](#downstream-responsibilities)).
+
+**Authenticated caller:** the profile above plus a valid credential.
+An authenticated caller can:
+
+* execute any SQL the underlying JDBC driver accepts, subject to the
+  underlying driver's own authorization model (Avatica has none of its
+  own — see [Downstream
+  responsibilities](#downstream-responsibilities));
+* set any Avatica connection property that is not restricted by the
+  server's configuration.
+
+**Client-side attacker: a hostile Avatica server**, or a network
+attacker between the client and a legitimate server.
+
+The attacker can:
+
+* return arbitrary bytes on the HTTP response body, including malformed
+  or crafted Avatica protocol frames;
+* if hostname verification or truststore validation is disabled or
+  weakened by the client's configuration, present any TLS certificate.
+
+The attacker cannot:
+
+* choose the client's JVM system properties, connection-string
+  properties, or classpath — those are operator- and application-level
+  decisions.
+
+## Assets
+
+* **The Avatica server host.** No code execution outside the query
+  path Avatica is configured to expose; no incidental file or network
+  access.
+* **The credentials Avatica handles.** BASIC and DIGEST passwords in
+  transit, Bearer tokens, Kerberos tickets, and the passwords for
+  configured keystores and truststores.
+* **The backend JDBC driver's data.** Avatica is a wire layer, not an
+  access-control layer; the driver's own authorization model governs
+  what data is reachable, but the *identity* Avatica hands to the
+  driver via impersonation must correctly reflect the authenticated
+  caller.
+* **The Avatica client's JVM.** No code execution triggered by
+  content on the wire; no incidental file or network access.
+
+## Inputs
+
+Everything the attacker controls resolves to one of P1–P5 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 |
+| --- | --- | --- | --- |
+| HTTP request bytes | wire | Jetty request parser → Avatica handler → wire 
deserialization (JSON via Jackson, or Protobuf) → `Service` dispatch | P1, P2 |
+| Authentication credentials | HTTP `Authorization` header, query string, 
SPNEGO negotiation | Avatica server configuration → Jetty security handlers | 
P5 |
+| `doAs` / impersonation identity | either the authenticated principal, or a 
configurable extractor from HTTP request (e.g. 
`HttpQueryStringParameterRemoteUserExtractor`) | `DoAsRemoteUserCallback` → 
backend JDBC driver | P5 (see [Impersonation](#impersonation)) |
+| SQL text and JDBC parameter values | HTTP body | Avatica `Service` → backend 
JDBC driver | Governed by the backend driver's own model; Avatica passes it 
through |
+| `TypedValue` payloads (parameter values, result rows) | HTTP body | 
`TypedValue.fromProto` / `fromJson` → `Object` in server or client JVM | P1 |
+| Class-naming client-side wire fields — e.g. `CursorFactory.className` | HTTP 
response body from server | `Class.forName(name)` on the client | Surprising vs 
unsurprising class loading (P1); see [Client-side surprising class 
loading](#client-side-surprising-class-loading) |
+| Client-side connection properties — `httpclient_factory`, `httpclient_impl`, 
`bearer_token_provider_class`, `lb_strategy`, `factory` | JDBC connection 
string, `Properties`, or a URL fragment the embedding application composes | 
client-side `AvaticaUtils.instantiatePlugin` and equivalents | Surprising vs 
unsurprising class loading (P1) |
+| TLS material — `truststore`, `keystore`, passwords, `hostname_verification` 
| JDBC connection string or `Properties` | client-side TLS setup | P5 |
+| Server-side configuration — port, `sslFactory`, `withImpersonation`, 
`withCustomAuthentication`, `HandlerFactory` | `HttpServer.Builder` in embedder 
code | Jetty server assembly | Operator trust (see [Downstream 
responsibilities](#downstream-responsibilities)) |
+
+## Security properties
+
+* **P1: no code execution.** Neither receiving a wire frame on either
+  end nor decoding it may execute code outside Avatica's protocol
+  semantics. This covers `Runtime.exec`, `ProcessBuilder`, and the
+  weaker primitive of loading an attacker-named class so that its
+  static initializer, constructor, or an accessed static field runs.
+* **P2: no incidental file access.** Neither receiving a wire frame nor
+  decoding it may read or create a file, except for the specific
+  configuration files an operator has explicitly named (keystore,
+  truststore, keytab, token file). An attacker-controlled path reaching
+  a file-read sink is a vulnerability.
+* **P3: no server-side request forgery.** Neither receiving a wire
+  frame nor decoding it may open an outbound network connection to an
+  attacker-chosen host. The Avatica server dials only the JDBC URL its
+  operator configured; the Avatica client dials only the URL its own
+  operator configured.
+* **P4: no impersonation escape.** The identity that Avatica hands to
+  the backend JDBC driver, via `DoAsRemoteUserCallback` or an
+  equivalent, must correspond to the authenticated caller — never to a
+  caller-chosen identity in a context where authentication was
+  supposed to establish it.
+* **P5: no wire secret disclosure.** Credentials on the wire — BASIC
+  passwords, Bearer tokens, Kerberos tickets, keystore and truststore
+  passwords, session cookies — must not be exposed to a passive
+  observer on the network under the configured TLS settings, and must
+  not be logged or echoed back to unauthenticated callers.
+
+## Always a vulnerability
+
+1. **Code execution** — on the server host, or in the client JVM —
+   that results from receiving a wire frame or from decoding it. 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** on either the server host or the
+   client JVM, driven by an attacker-controlled input on the wire or
+   in a connection property that a caller can set. Reading the
+   operator-configured keystore, truststore, keytab, or token file is
+   not incidental access.
+3. **Server-side request forgery** — the Avatica server or client
+   opens a network connection to an attacker-chosen host as a result
+   of a wire frame or connection property.
+4. **Impersonation escape.** A caller reaches the backend JDBC driver
+   under an identity that is not the identity authentication
+   established. Includes: unauthenticated `doAs` when authentication
+   is meant to be required; a `doAs` value the extractor accepts
+   without validation against the authenticated principal; a session
+   whose identity survives past its authentication.
+5. **Wire secret disclosure.** Credentials sent over the wire are
+   observable by a passive attacker under the deployment's declared TLS
+   settings; or are logged or echoed to a caller who did not
+   authenticate; or are stored on the server in a form other than
+   what the deployment declared. Falling back to plaintext HTTP for
+   an authentication scheme that requires TLS is included.
+6. **Authentication bypass.** A caller reaches the `Service` dispatch
+   under a configured authentication mode without supplying a valid
+   credential.
+7. **Denial of service by a single wire frame** — a single request
+   parseable within stated size limits should not be able to exhaust
+   server resources, except where [Denial of service](#denial-of-service)
+   records the bound as not yet landed. Combinatorial or unbounded resource
+   consumption from a single frame is a vulnerability (see also
+   [Denial of service](#denial-of-service) for known limitations).
+
+## Not a vulnerability
+
+* **An unauthenticated Avatica server exposed to an untrusted
+  network.** `AuthenticationType.NONE` is a valid configuration for
+  isolated or internally-networked deployments. Reachability of an
+  unauthenticated server from the public internet is an operator
+  decision (see [Downstream
+  responsibilities](#downstream-responsibilities)).
+* **An unencrypted Avatica server** — running Avatica without
+  `sslFactory` — exposing HTTP credentials to a network eavesdropper.
+  This is an operator decision. Avatica does not force TLS.
+* **Backend driver behavior.** Once Avatica has connected to the JDBC
+  URL its operator configured, the backend driver's SQL semantics,
+  its authorization model, its own configuration, its bugs and its
+  CVEs are that driver's concern — Avatica passes SQL through.
+* **The behavior of the `StandaloneServer` demo.** The
+  `standalone-server` module ships a runnable jar for local
+  development and testing. It has no authentication, no TLS, and no
+  impersonation by default. Its purpose is to demonstrate the
+  protocol; running it in production without additional configuration
+  is not supported and not covered by this model.
+* **Cross-tenant or cross-schema visibility handled by the backend
+  driver.** Avatica has no schema visibility model of its own; the
+  backend driver (typically Calcite) is responsible for scoping what
+  each authenticated caller may see. See Calcite's threat model for
+  its treatment of this concern.
+* **Anything requiring a change to server-side configuration** — port,
+  `sslFactory`, `HttpServer.Builder` options, command-line arguments,
+  system properties, or the classpath. Those are operator inputs, not
+  attacker inputs, by definition.
+* **`hostname_verification=NONE` or an accepted truststore that
+  trusts a hostile CA.** Both are documented client-side connection
+  properties. A client that configures itself into a weakened trust
+  posture accepts that posture. Not a client-side vulnerability.
+
+## Downstream responsibilities
+
+Several controls belong to the operator or the embedding application, not to
+Avatica itself. A finding that lands in one of these is not an Avatica
+vulnerability.
+
+* **Network perimeter.** Deciding who can reach Avatica's listening
+  port is the operator's job. Avatica has no built-in IP allowlist,
+  no rate limiting, and no DDoS protection.
+* **Choice of authentication mode.** `AuthenticationType.NONE` is
+  legal for isolated networks; production deployments on shared or
+  public networks should configure BASIC, DIGEST, SPNEGO, Bearer, or a
+  `CUSTOM` scheme. Avatica does not force a choice.
+* **Choice to enable TLS.** `HttpServer.Builder.withSSL(...)` opts
+  into HTTPS. Deploying without TLS on an untrusted network exposes
+  credentials by construction; Avatica does not require TLS.
+* **Choice of impersonation extractor.** Configuring
+  `HttpQueryStringParameterRemoteUserExtractor` or a custom
+  `RemoteUserExtractor` that trusts a caller-supplied `doAs` value
+  without authenticating it is an operator misconfiguration — see
+  [Impersonation](#impersonation) for the boundary.
+* **Backend driver selection.** Which JDBC URL Avatica fronts is the
+  operator's choice. The backend driver's own security posture is out
+  of this model.
+* **What credentials, keystores, keytabs, and token files exist on
+  disk.** The paths Avatica reads are named in the operator's
+  configuration; ensuring those files are readable only by the
+  Avatica process is a filesystem-permission concern for the
+  operator.
+* **Classpath.** The operator owns the classpath. `HttpClient`
+  factories, `BearerTokenProvider` classes, and load-balancing
+  strategies are loaded by name; which classes are present is the
+  operator's trust decision.
+* **The Avatica client's connection string.** A JDBC connection
+  string may name a `httpclient_factory` or a
+  `bearer_token_provider_class`. If the embedding application lets an
+  untrusted caller compose the connection string, that caller has the
+  same capability as the operator with respect to class loading.
+
+## Surprising vs unsurprising class loading
+
+Avatica loads a class named in a connection property, in a wire frame,
+or in a configuration file only to use it through a specific SPI:
+`AvaticaHttpClient`, `BearerTokenProvider`, `LBStrategy`, `Meta.Factory`,
+`ColumnMetaData.Rep` element type. The security boundary follows that
+contract, not a blanket trust of the classpath or of the wire.
+
+* **Unsurprising.** The class implements the SPI interface for the
+  position it was named in, and Avatica invokes it through that
+  interface.
+* **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.
+  Surprising class loading is always a vulnerability.
+
+**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.
+
+### Client-side surprising class loading
+
+The Avatica client reconstructs `Meta.CursorFactory` from a wire
+`Common.CursorFactory` proto whose `className` field is
+attacker-controllable if the server is hostile or the wire is
+tampered with. The current implementation of `CursorFactory.fromProto`
+in `Meta` calls `Class.forName(name)` — which initializes the class
+before any type check.
+
+This sink is tracked as a known open primitive under the P1 triage
+rule: a reachable class-loading sink that fires before an interface
+gate is a vulnerability on its own, whether or not a specific
+end-to-end exploit chain against a specific classpath has been
+demonstrated.
+
+The fix pattern is the same as elsewhere: load with `initialize=false`,
+gate on an allowlist of expected element types (`ColumnMetaData.Rep`
+values and their corresponding Java classes), and only then initialize.
+
+## Impersonation
+
+Avatica supports impersonation: an authenticated caller may execute
+SQL on the backend JDBC driver *as* another identity, using
+`DoAsRemoteUserCallback`. The security boundary here is subtle
+enough to spell out.
+
+The identity Avatica hands to the callback is produced by a
+`RemoteUserExtractor`. Three implementations ship with Avatica:
+
+* `HttpRequestRemoteUserExtractor` — returns
+  `HttpServletRequest.getRemoteUser()`, i.e. the identity Jetty's
+  security handler established through the configured authentication
+  mode. Safe by construction: only reflects a value the caller
+  authenticated to.
+* `HttpQueryStringParameterRemoteUserExtractor` — returns the value
+  of a caller-supplied query-string parameter. **This is safe only
+  when the caller is authenticated to some other identity** (e.g. a
+  Kerberos proxy identity that is separately authorized to impersonate
+  other users). Configuring this extractor on an
+  `AuthenticationType.NONE` server, or on any server where the
+  caller's own authenticated identity is not checked against
+  authorization to impersonate the extracted user, is an operator
+  misconfiguration that P4 rules out.
+* Any operator-supplied `RemoteUserExtractor`. Operator's
+  responsibility.
+
+The boundary: **who is allowed to impersonate whom** is an operator
+policy that Avatica does not enforce. A bug where Avatica accepts an
+impersonation request under a configured mode without applying the
+operator's declared policy is a P4 vulnerability. An operator who
+configures an extractor that accepts unauthenticated `doAs` values
+has misconfigured Avatica; that is not a P4 vulnerability but it *is*
+an item Avatica's documentation should call out sharply.
+
+## Denial of service
+
+A single request within stated size limits should not be able to
+exhaust the server. 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.
+
+* **Request size.** Jetty's `maxAllowedHeaderSize` configuration on
+  `HttpServer.Builder` bounds header size; the wire body has no
+  explicit Avatica-side cap and inherits Jetty's default. Very large
+  SQL bodies or `TypedValue` payloads can exhaust heap.
+* **Result-set streaming.** A backend query that returns a very
+  large result exhausts server heap unless the caller uses the
+  incremental fetch protocol correctly. Operator-tunable via fetch
+  size.
+* **Connection accumulation.** Long-lived Avatica connections keep
+  backend JDBC connections open. A caller that opens many
+  connections and never closes them exhausts backend resources.
+  Mitigation is an operator-side connection cap.
+
+Once request-size and connection caps exist, a single reasonably-sized
+request that exceeds them is a configuration choice, not a
+vulnerability.
+
+## Historical CVEs
+
+Avatica has published these CVEs relevant to this model, fixed in current 
releases;
+they are noted here as ground truth for the rule they establish.
+
+* **CVE-2022-36364** — an untrusted URL supplied to the Avatica
+  JDBC driver could load an arbitrary `httpclient_impl` class via
+  `Class.forName`, without an interface gate. The fix added the
+  `asSubclass(AvaticaHttpClient.class)` check that is now present in
+  `AvaticaHttpClientFactoryImpl`. Established the surprising-class-
+  loading rule for client-side connection properties.
+
+## Triage dispositions
+
+Every security report against Avatica resolves to exactly one of:
+
+* **Valid** — violates P1–P5, or matches an item in [Always a
+  vulnerability](#always-a-vulnerability). Gets a fix. A demonstrated
+  class-loading primitive qualifies on its own.
+* **Not a vulnerability (by design)** — matches an item in [Not a
+  vulnerability](#not-a-vulnerability): an unauthenticated or
+  un-encrypted deployment the operator chose, backend driver
+  behavior past the connection, cross-schema visibility that lives
+  in the backend driver, `StandaloneServer` demo behavior, or a
+  client that opted itself into a weakened trust posture. Close with
+  a pointer to this model.
+* **Out of model** — requires a capability the attacker does not
+  have (changing a JVM system property, server-side configuration,
+  the classpath), or lands in a layer this model assigns to the
+  operator (network perimeter, authentication choice, TLS choice,
+  filesystem permissions). Close; redirect to the operator or
+  embedder.
+* **Backend concern** — the report is really about the backend JDBC
+  driver Avatica is fronting. Close; redirect to that project's
+  security process. If the backend is Apache Calcite, redirect to
+  [Calcite's threat
+  model](https://calcite.apache.org/docs/security_threat_model.html).
+* **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.

Reply via email to