# Apache Doris — Threat Model > **Status: v1.0 — accepted (technical content). Pending wave-4 process > items.** Wave-1/2/3/4 maintainer interviews completed 2026-05-14 > (Doris committer morningman). All technical `(inferred)` tags from > v0.1 have been resolved or consciously deferred. Amended 2026-07-29 > with wave 5 (M19, FE `enable_all_http_auth` / HTTP auth posture). This document is the **security contract** for Apache Doris: what the project assumes, what it guarantees given those assumptions, what it explicitly leaves to the operator, and how a vulnerability triager should classify any inbound report. --- ## 4.1 Header - **Project**: Apache Doris (https://doris.apache.org) - **Model version binding**: written against `master` at commit `1d1846591f7`, 2026-05-14. Per M15 (single-living-doc policy), the `model-version` field at the top of this file is bumped per minor release; vulnerability reports against project version *N* are triaged against the model as it stood at *N* (read the file at the matching git tag). - **Reporting cross-reference**: per M1, security findings should be reported to **`security@apache.org`** (ASF security team will route to Doris). A short `SECURITY.md` at the repo root links to this document as canonical scope (M16 (A)). Findings that fall under §4.3 / §4.9 / §4.11a will be closed with a citation to this document. - **Status**: v1.0 — technical model accepted. The four wave-4 (M15–M18) meta/process answers are recorded below; the root `SECURITY.md` coexistence artifact is complete, while model-version field policy text remains follow-up work. - **Provenance legend**: - *(documented)* — stated in Doris' own README, code comments, `conf/*.conf`, or user docs - *(maintainer, Qn)* / *(maintainer, Mn)* — answered by a Doris committer in interviews on 2026-05-14. `Q1`–`Q8` are wave-1/2 questions; `M1`–`M18` are wave-3/4 questions - *(inferred)* — producer's working hypothesis, not yet ratified. **None remain in v1.0.** - **Draft confidence**: *2 documented / 88 maintainer / 0 inferred*. Up from v0.1 (2 / 45 / 37); all 14 wave-3 and 4 wave-4 questions resolved on 2026-05-14. **One-paragraph project description.** Apache Doris is an MPP analytical (OLAP) database. Clients submit SQL over the MySQL wire protocol or Arrow Flight; queries are parsed and planned by the **FE** (Frontend, Java) and executed by the **BE** (Backend, C++) against locally managed columnar storage and/or external lakehouse catalogs (Hive, Iceberg, Hudi, Paimon, JDBC, S3/HDFS/Azure). Doris ships in two deployment shapes: classic on-prem (FE+BE+Broker), and the cloud-native **`cloud/`** variant (storage-compute disaggregated, shared Meta Service, K8s-native, multi-tenant). Both are in-model; content that differs is marked `[on-prem]` / `[cloud]`. --- ## 4.2 Scope and intended use **Primary intended use** — In-cluster MPP execution of analytical SQL, where the cluster is operated by the same organization that controls its network perimeter *(maintainer, Q2)*. **Deployment shapes in scope** *(maintainer, Q2)*: - **(A) On-prem / single-tenant** — FE+BE+Broker processes inside a corporate network or private VPC. Cluster-internal network is *implicitly trusted* by operator-provided isolation. **Default shape.** - **(B) Cloud variant** — `cloud/` directory; storage-compute disaggregated, K8s-native. **Tenancy model**: Meta Service is shared across tenants; **per-tenant isolation enforced inside Meta Service is a security claim of the project** *(maintainer, M2)*. Cross-tenant data leak / privilege escalation through Meta Service is `VALID`, not `OUT-OF-MODEL`. **Deployment shape explicitly OUT of scope** *(maintainer, Q2)*: - Direct internet exposure of any Doris-listened port. Collapses §4.4 trust model. **Caller roles**: | Role | Trust level | In §4.7? | |---|---|---| | Anonymous network attacker on client-facing ports (MySQL 9030, HTTP 8030, FE Arrow Flight 8070, **BE Arrow Flight 8050**) | Untrusted | **Yes — primary pre-auth adversary** | | Authenticated SQL user with limited RBAC privileges | Untrusted within RBAC scope | **Yes — primary post-auth adversary** | | Authenticated user holding `CREATE CATALOG` (sub-admin) | Untrusted within RBAC; can attach external URL endpoints | **Yes** *(maintainer, M13)* — narrow SSRF actor; see §4.9 | | Authenticated user in tenant T₁ trying to reach tenant T₂ data `[cloud]` | Untrusted across tenant boundary | **Yes** *(maintainer, M2)* — cross-tenant adversary | | `SUPER` / `ADMIN_PRIV` / database owner / operator-level user | Trusted *(maintainer, M3)* | No | | Cluster-internal RPC peer (FE↔BE, BE↔BE, FE↔Follower, FE↔Broker, FE↔MetaService) | Trusted by network isolation *(maintainer, Q1)* | No | | External catalog / storage system (Hive Metastore, Iceberg, JDBC source, S3, HDFS, Azure Blob) | Trusted by admin connection *(maintainer, Q8)* | No | **Component-family table.** Distinct threat profiles. `Surface` lists ports / inputs each family exposes; `In model?` ties to §4.3. | # | Family | Path | Surface | In model? | |---|---|---|---|---| | 1 | **FE core** (Java) | `fe/fe-core/` | MySQL 9030, HTTP 8030, FE Arrow Flight 8070 (client); RPC 9020, Edit-log 9010 (internal) | **Yes** | | 2 | **BE core** (C++) | `be/src/` | **BE Arrow Flight 8050 (client-facing, M7)**; BRPC 8060, Webserver 8040, Heartbeat 9050, BE↔BE 9060 (internal) | **Yes** | | 3 | **Cloud variant** | `cloud/src/` | Meta Service (shared, multi-tenant), Recycler, Resource Manager | **Yes** *(maintainer, Q2, M2)* | | 4 | **FE auth providers** | `fe/fe-authentication/` | Pluggable: native, LDAP | **Yes** | | 5 | **FE connectors** (catalogs) | `fe/fe-connector/{iceberg,hudi,hms,jdbc,paimon,trino,maxcompute,es}` | Outbound to external systems; in-process JAR loading | **Yes** (memory safety only; data trusted per §4.6) | | 6 | **BE Java extensions** | `fe/be-java-extensions/` | In-process JVM in BE | **Yes** (memory safety; UDF code trusted per §4.6) | | 7 | **HDFS / FS broker client** | `fe/fe-filesystem/fe-filesystem-broker/` | Thrift RPC to external broker (cluster-internal); the in-tree `apache_hdfs_broker` daemon has been removed | **Yes** (internal trust per §4.4) | | 8 | Web UI | `ui/` + `webroot/` | Served via FE 8030 (auth gated) | **Yes** | | 9 | Vendored MySQL source | `mysql/mysql-{9.4.0,9.5.0}/` | None (reference only, not built or shipped) | **No** *(maintainer, Q4)* | | 10 | Sample / dev / CI | `samples/`, `docker/`, `pytest/`, `regression-test/`, `jdbc-version-test/`, `task_executor_simulator/`, `hooks/`, `build-support/` | None at runtime | **No** *(maintainer, Q4)* | | 11 | All FE plugins | `fe_plugins/` (`auditdemo`, `auditloader`, `sparksql-converter`, `trino-converter`) | FE plugin SPI | **No** *(maintainer, Q4, M4)* — `auditloader` included; users opting in take ownership | | 12 | Client SDKs / extensions / CDC client | `extension/`, `cdc_client` (Doris SDKs now maintained out-of-tree in a separate `doris-sdk` repo) | Client-side libraries | **No** *(maintainer, Q4)* — separately versioned | --- ## 4.3 Out of scope (explicit non-goals) **Use cases not supported.** 1. **Direct internet exposure of any port** *(maintainer, Q2)*. Operators must place Doris behind a network perimeter (VPC, firewall, K8s `NetworkPolicy`, equivalent). 2. **Cluster-internal-network adversary** *(maintainer, Q1)*. The trust boundary sits at the client-facing ports (§4.4). An attacker reaching BE BRPC 8060, BE Webserver 8040, FE Edit-log 9010, FE RPC 9020, BE Heartbeat 9050, BE↔BE 9060, or the FS broker is presumed to have already compromised the operator's network. 3. **DoS via pathological SQL or query plans** *(maintainer, Q5)*. A single authenticated SQL user submitting an unbounded-resource query is **not** a Doris bug. Operators must constrain users via the canonical knob set *(maintainer, M5)*: **`exec_mem_limit`** (per-query memory cap), **Workload Group** (`CREATE WORKLOAD GROUP ...` — recommended production posture for memory/CPU/ concurrency caps per user/group), and **`max_connections` / `max_connection_per_user`** (FE config, prevent connection exhaustion). 4. **Side-channel / timing-based information disclosure** *(maintainer, Q5)*. 5. **Adversary-controlled external catalog data** *(maintainer, Q8)*. Bytes returned from admin-connected Iceberg/Hive/Hudi/Paimon/ JDBC/S3 catalogs are trusted. Crashes from crafted Parquet/ORC/ Avro/JSON files are `OUT-OF-MODEL: trusted-input`. 6. **`SUPER`-privileged adversary** *(maintainer, M3)*. `SUPER` (and `ADMIN_PRIV` / equivalent) holders are trusted by definition. RCE achievable only after acquiring `SUPER` — UDF install, JDBC driver attach, FE plugin registration, `ADMIN SET CONFIG` — `OUT-OF-MODEL: adversary-not-in-scope`. 7. **Compromise of an external system Doris connects to**. Downstream effects on Doris are out of model per (5). 8. **Transport-layer confidentiality on default config** *(maintainer, Q7)*. TLS off by default IS the supported production posture; "credentials sniffable in default config" is `BY-DESIGN: property-disclaimed` (§4.9). 9. **Default ship of pre-auth login lockout** *(maintainer, M11)*. Doris ships `numFailedLogin = 0` and `passwordLockSeconds = 0` — the *mechanism* exists but is opt-in per user via `CREATE USER ... FAILED_LOGIN_ATTEMPTS N PASSWORD_LOCK_TIME T`. "I brute-forced an account in default config" is `BY-DESIGN: property-disclaimed` plus an §4.10 obligation. 10. **Byzantine cluster peers** *(maintainer, M6)*. BDB-JE FE replication and tablet replication assume honest peers. 11. **Co-tenant escape at the K8s / OS level** *(maintainer, M2 by extension)*. M2 commits to per-tenant isolation enforced *inside the Meta Service*; OS / K8s / hypervisor isolation between co-tenant pods is the cloud operator's job, not Doris'. **Code shipped but not threat-modeled.** Family rows 9–12 (vendored MySQL, samples/dev/CI, all FE plugins including `auditloader`, SDK/extension/CDC) per §4.2. Reports landing there are `OUT-OF-MODEL: unsupported-component`. --- ## 4.4 Trust boundaries and data flow **Three concentric trust zones**, with a **tenant boundary inside Meta Service for cloud** *(maintainer, Q1, Q8, M2)*: ``` ┌──────────────────────────────────────────────────────────────────────┐ │ Zone-3 EXTERNAL (Hive, Iceberg, Hudi, Paimon, JDBC, S3, │ │ HDFS, Azure) │ │ ── trusted-by-admin-connection ── │ │ ┌──────────────────────────────────────────────────────────────┐ │ │ │ Zone-2 CLUSTER-INTERNAL (FE↔FE, FE↔BE, BE↔BE, FE↔Broker, │ │ │ │ FE↔MetaService [cloud]) │ │ │ │ ── trusted-by-network-isolation ── │ │ │ │ ┌─────────────────────────────────────────────────┐ │ │ │ │ │ Zone-1 CLUSTER-CORE PROCESS │ │ │ │ │ │ FE JVM, BE C++ process, │ │ │ │ │ │ Cloud Meta Service ←── enforces tenant │ │ │ │ │ │ boundary T1│T2│T3 │ │ │ │ │ └─────────────────────────────────────────────────┘ │ │ │ └──────────────────────────────────────────────────────────────┘ │ │ │ │ ▲ THE BOUNDARY ▲ │ │ │ │ Zone-0 CLIENT (untrusted MySQL/HTTP/Arrow-Flight clients) │ │ ── BE Arrow Flight 8050 lives here too (M7) ── │ └──────────────────────────────────────────────────────────────────────┘ ``` **The single load-bearing trust transition is Zone-0 → Zone-1 at the client-facing ports.** All other transitions assume the source is trusted. **The cloud Meta Service additionally claims a *tenant boundary* inside Zone-1** — see §4.8 (NEW property). **Per-port reachability precondition.** A finding in code reachable *only* from Zone-2 or Zone-3 inputs is OUT-OF-MODEL by §4.3 (2) or (5). Triage applies this test before anything else. | Port | Protocol | Zone | Reachability precondition | |---|---|---|---| | FE 9030 | MySQL wire | 0→1 | bytes attacker-controlled before / during auth, or SQL post-auth | | FE 8030 | HTTP / REST | 0→1 | request bytes attacker-controlled | | FE 8070 | Arrow Flight (FE) | 0→1 | handshake / auth bytes attacker-controlled | | **BE 8050** | **Arrow Flight (BE, client-facing)** | **0→1** *(maintainer, M7)* | **handshake bytes / result-stream consumption attacker-controlled** | | FE 9020 | Thrift RPC (FE↔BE) | 2 | **none — out of model** | | FE 9010 | BDB JE edit log (FE↔Follower) | 2 | **none — out of model** | | BE 8060 | BRPC | 2 | **none — out of model** | | BE 8040 | HTTP webserver / metrics | 2 | **none — out of model** | | BE 9050 | Thrift heartbeat (FE→BE) | 2 | **none — out of model** | | BE 9060 | Thrift fragment exec (BE↔BE) | 2 | **none — out of model** | | Broker | Thrift | 2 | **none — out of model** | | Cloud Meta Service ↔ FE/BE | gRPC | 2 (network) + tenant-boundary (data) | a finding inside Meta Service is in-model if it crosses the per-tenant boundary | **Data flow from Zone-3 (external catalogs) into Zone-1.** Per §4.3 (5) and §4.6, all bytes from external systems are admin-trusted. They flow into BE format readers (Parquet, ORC, Avro, JSON, CSV) and FE catalog metadata (HMS tables, Iceberg manifests). Crafted-byte crashes are OUT-OF-MODEL. --- ## 4.5 Assumptions about the environment **Supported toolchain / platform** *(maintainer, M8)*: - **OS**: Linux x86_64 and Linux aarch64 (both first-class). - **FE runtime**: JDK 17. - **BE toolchain**: GCC 11+ libstdc++ (or equivalent conformant C++ toolchain matching the official docker build image). - Anything else (different JDK, non-conformant C++ toolchain) is `OUT-OF-MODEL: non-default-build`. Operational assumptions: - **Allocator**: BE defaults to jemalloc. - **Concurrency**: BE assumes a conformant C++ memory model with atomic intrinsics; FE assumes the JMM. Neither is signal-safe. - **Filesystem**: BE assumes ownership of `storage_root_path` directories; concurrent external mutation is undefined. - **Network**: cluster-internal network is treated as a security boundary (operator's responsibility) *(maintainer, Q1)*. - **Time**: FE replica clock skew tolerated within Raft / BDB-JE bounds; clock-rollback is not an attacker capability. **Negative claims (what Doris does NOT do to its host)** *(maintainer, M9 — code-verified)*: - **BE process spawns subprocesses only in these cases**: Python UDF execution (`fork()`), Python venv creation (`system()`), Broker shell utilities (`popen()`), and CDC client (`fork()`). No other arbitrary subprocess execution. - **FE process does NOT install custom signal handlers** beyond standard JVM behavior. Relies on JVM defaults. - **BE/FE consume the following environment variables at runtime**: `DORIS_HOME`, `LOG_DIR`, `PID_DIR`, `HADOOP_CONF_DIR`, `HADOOP_USER_NAME`, `JAVA_HOME`, `JAVA_OPTS`, `LIBHDFS_OPTS`, `TZDIR`, `DORIS_LOG_TO_STDERR`, and AWS credential providers (`AWS_ROLE_ARN`, `AWS_WEB_IDENTITY_TOKEN_FILE`, `AWS_CONTAINER_CREDENTIALS_*`). **No password-via-env pattern** — database passwords are never read from environment. --- ## 4.5a Build-time and configuration variants | Knob | Default | Maintainer stance | Effect on model | |---|---|---|---| | `enable_ssl` (FE MySQL/HTTP) | **off** | (Q7) Off **IS** supported production posture | §4.9 disclaims wire confidentiality unconditionally | | `enable_ssl` (Arrow Flight) | **off** | Same as above | Same | | `enable_java_udf` (FE) | **on** *(maintainer, M10)* | Intentional | §4.10 (4) "treat SUPER as RCE" is load-bearing in default | | `enable_python_udf` (FE) | **on** *(maintainer, M10)* | Intentional. Asymmetric with BE | FE allows registration; BE requires `enable_python_udf_support` to actually execute | | `enable_python_udf_support` (BE) | **off** *(maintainer, M10)* | Intentional. Operator must opt in to actually run Python UDFs | Default deployment cannot execute Python UDFs even if FE accepts them | | `numFailedLogin` (per-user, `CREATE USER ... FAILED_LOGIN_ATTEMPTS N`) | **0 / DISABLED** *(maintainer, M11)* | (A) Off IS supported production posture; operator must enable per user | §4.10 (NEW) requires per-user enable for any account on a network-reachable client port | | `passwordLockSeconds` (per-user, `... PASSWORD_LOCK_TIME T`) | **0 / DISABLED** *(maintainer, M11)* | Same | Same | | `enable_all_http_auth` (FE, HTTP 8030) | **on**, **not runtime-mutable** *(maintainer, M19)* | On **IS** the supported production posture. Turning it off requires editing `fe.conf` and restarting — deliberately not an `ADMIN SET` command, so disabling authentication is a recorded on-disk decision. A migration aid for clusters upgrading from a release where it defaulted off, not a supported steady state | On: §4.8 (11) (**authentication**) applies unconditionally and a bypass is `VALID`; authorization is the separate, narrower §4.8 (12) with recorded gaps. Off: knob flipped toward the less-secure side → `OUT-OF-MODEL: non-default-build`. Note the effective value may come from `fe_custom.conf`, not `fe.conf` | | `enable_all_http_auth` (BE, webserver 8040) | **off**, **not runtime-mutable** *(maintainer, M19)* | Unchanged in this release — only the FE default was flipped. BE 8040 is a Zone-2 port (§4.4) that operators are already required to keep off end-user networks, so the compatibility cost of flipping it was judged to outweigh the gain. Changing it requires editing `be.conf` and restarting (`DEFINE_Bool`, not `DEFINE_mBool`) | Off: BE 8040 handlers declared with the `NONE` privilege type answer without credentials → `BY-DESIGN: property-disclaimed` (§4.9), not `VALID` | | `auth_type` | native | LDAP / Kerberos / OIDC are non-default backends | Out of this row's scope (handled in family row 4) | | Cluster shape: `on-prem` vs `cloud/` | on-prem | Both shapes supported *(maintainer, Q2)* | Cloud adds Meta Service component (family row 3); cloud has additional tenant-boundary claim per §4.8 | A vulnerability report of "I sniffed plaintext credentials on port 9030 in default config" is closed `BY-DESIGN: property-disclaimed` per Q7 → §4.9 / §4.10. A vulnerability report of "I brute-forced account `analytics_user` in default config (no `FAILED_LOGIN_ATTEMPTS` set)" is closed the same way per M11 → §4.9 / §4.10. **`enable_all_http_auth` now defaults on for FE** *(maintainer, M19)*. Prior releases shipped it off on both sides, so part of the FE HTTP surface — the metadata, statistics and admin REST endpoints, and the privilege check on part of the FE admin surface — answered without checking credentials or privileges. That default was a compatibility concession, not a security stance, and it has been flipped **on FE 8030 only**. The consequence for the model: unauthenticated access to those FE endpoints is no longer disclaimed; it is a violation of §4.8 (11) and reports of it are `VALID`. **Two carve-outs**: (a) FE `/metrics` on 8030 is public by design and the flag does not gate it — see §4.9; (b) **BE 8040 is unchanged** — its default stays off, and its `NONE`-privilege handlers remain disclaimed under §4.9. Do not assume the two ports behave alike. **Upgrade consequence, and what an operator turning it back off is buying.** A cluster upgrading from a release where the flag defaulted off may have callers that poll those FE endpoints anonymously — monitoring agents, health probes, ops scripts, and any tooling driving the FE `/api/**` REST surface. They start receiving 401 on upgrade. Metrics scrapers pointed at FE `/metrics` or at BE 8040 are **not** affected, since neither is gated by the FE flag. The supported fix is to give those callers credentials (§4.10 (12)). An operator who instead sets `enable_all_http_auth = false` has flipped a §4.5a knob toward the less-secure side and is back to the pre-flip exposure: findings that require the flag off are then closed `OUT-OF-MODEL: non-default-build`, and the operator owns the risk. **Where the effective value comes from.** FE reads `fe.conf` and then `${custom_config_dir}/fe_custom.conf`, and values in the latter overwrite the former. In releases where this flag was mutable, `ADMIN SET FRONTEND CONFIG (...) PROPERTIES("persist" = "true")` wrote to `fe_custom.conf`; making the flag non-mutable in this release neither removes nor migrates such a value. An upgraded cluster can therefore have no `false` entry anywhere in `fe.conf` and still start with authentication off. "`fe.conf` says true" is **not** sufficient evidence that a cluster is running with the flag on — read the effective value back (`/api/show_config`, `ADMIN SHOW FRONTEND CONFIG`) and check `fe_custom.conf`. Operators upgrading should delete any stale entry from `fe_custom.conf` to pick up the new default; see §4.10. **Security-testing baseline.** Security scans, penetration tests, fuzzing campaigns, and automated review agents MUST run against a cluster whose **effective** `enable_all_http_auth` is `true` on **FE** — i.e. **do not turn it off** for the test run, and confirm it is on (per the paragraph above, not by reading `fe.conf` alone) before reporting FE HTTP-surface coverage. On BE it defaults off; a test run may turn it on to probe BE 8040, but findings that require it to be on there are hardening observations, not defects. A run performed with the flag off does not constitute coverage of the HTTP surface: every finding it produces is `OUT-OF-MODEL: non-default-build`, and the findings that actually matter — endpoints that skip authentication or skip the caller's privilege check *with the flag on* — go unfound. --- ## 4.6 Assumptions about inputs **Per-endpoint trust table.** | Endpoint | Message / parameter | Trust | Caller / operator must enforce | |---|---|---|---| | FE MySQL 9030 | handshake bytes (pre-auth) | **untrusted** *(maintainer, Q6)* | nothing — Doris memory-safe by §4.8 (1) | | FE MySQL 9030 | username / auth response | **untrusted** *(maintainer, Q6)* | server-side: brute-force resistance is *not* default; operator must `CREATE USER ... FAILED_LOGIN_ATTEMPTS` per §4.10 | | FE MySQL 9030 | SQL text (post-auth) | **untrusted** *(maintainer, Q5)* | nothing — parser/planner memory-safe by §4.8 (2) | | FE MySQL 9030 | SQL semantic content (table / column / privilege requested) | **untrusted, RBAC-enforced** *(maintainer, Q5)* | nothing — RBAC enforced by §4.8 (4) | | FE MySQL 9030 | `iceberg.rest.uri` and similar URLs in `CREATE EXTERNAL CATALOG` | **post-auth, attacker-controllable** *(maintainer, M13)* | operator: only grant `CREATE CATALOG` privilege to admins; otherwise SSRF surface (§4.9) | | FE HTTP 8030 | request bytes (pre-auth) | **untrusted** | memory safety | | FE HTTP 8030 | request body (post-auth) | **untrusted within RBAC** | RBAC | | FE HTTP 8030 | `/api/show_proc`, admin REST surface | **post-auth, privileged** | RBAC; admin-only endpoints must check. The privilege check on part of this surface is gated on `enable_all_http_auth` *(maintainer, M19)*, **on** by default (§4.5a) — operator: do not turn it off | | FE HTTP 8030 | metadata / statistic / import REST endpoints | **post-auth** *(maintainer, M19)* | nothing in default config — authenticated by §4.8 (11). Callers must present credentials | | **FE HTTP 8030 `/metrics`** | **anonymous by design — NOT covered by `enable_all_http_auth`** *(code-verified, M19)* | `MetricsAction` is deliberately public and is not routed through the FE auth path; the flag does not change it. Operator: keep 8030 off untrusted networks if FE metrics are sensitive | | **FE HTTP 8030 `/api/health`** | **anonymous by design — NOT covered by `enable_all_http_auth`** *(code-verified, M19)* | `HealthAction` is deliberately public and the flag does not gate it. It discloses liveness plus total/online backend counts. Excluded from §4.8 (11); see §4.11a before filing it as a bypass | | **FE HTTP 8030 `/api/get_small_file`, `/api/bootstrap`** | **cluster token (+ cluster id) instead of a user password** *(code-verified, M19)* | these are the FE endpoints that BE, the CDC client and a joining FE call with only the cluster token; the token is verified and is the credential, so `enable_all_http_auth` does not additionally demand a password on the token-bearing path. A leaked cluster token is a Zone-2 compromise (§4.5) | operator: treat the cluster token as a secret; do not expose 8030 to untrusted networks | | **FE HTTP 8030 `/api/streaming/commit_offset`, `/api/streaming/report_task_failure`** | **cluster token only — no user credential is accepted at all** *(code-verified, M19)* | `StreamingJobAction.checkAuth` requires a `token` header and verifies it against the cluster token; there is no password path. Same disposition as the row above: token absent or invalid → `VALID`; no user password while a valid token is presented → by design | | **FE HTTP 8030 `/api/{db}/{table}/_stream_load`** | **user password, or the cluster token on the token-bearing path** *(code-verified, M19)* | the token branch is a credential in the same sense as the rows above; the password branch is covered by §4.8 (11) | | FE Arrow Flight 8070 | handshake | **untrusted** | memory safety | | FE Arrow Flight 8070 | result-stream consumption | mostly post-auth | RBAC | | **BE Arrow Flight 8050** | **handshake bytes (pre-auth)** *(maintainer, M7)* | **untrusted** | memory safety | | **BE Arrow Flight 8050** | **result-stream consumption (post-auth)** | **untrusted within RBAC** | RBAC; results must respect querying user's grants | | FE 9020 (RPC) | all parameters | **trusted (Zone-2)** *(maintainer, Q1)* | operator: network isolation | | FE 9010 (edit log) | all parameters | **trusted (Zone-2)** | operator: network isolation | | BE 8060 (BRPC) | all parameters | **trusted (Zone-2)** *(maintainer, Q1)* | operator: network isolation | | BE 8040 (webserver) | all requests | **trusted (Zone-2)** | operator: do not expose to authenticated end-users (§4.11). `enable_all_http_auth` still defaults **off** here — only the FE default was flipped *(maintainer, M19)* — so handlers declared with the `NONE` privilege type answer without credentials in default config. Network isolation is the control | | BE 9050 (heartbeat) | FE→BE control msgs | **trusted (Zone-2)** | operator: network isolation | | BE 9060 (BE↔BE) | fragment exec, data transfer | **trusted (Zone-2)** | operator: network isolation | | Broker (Thrift) | all parameters | **trusted (Zone-2)** | operator: network isolation | | **Cloud Meta Service ↔ tenants** | **per-tenant requests** *(maintainer, M2)* | **untrusted across the tenant boundary** | Meta Service must enforce; cross-tenant leak/escalation is `VALID` | | External catalog metadata (HMS, Iceberg manifests, JDBC driver responses) | Zone-3 admin-trusted *(maintainer, Q8)* | admin: do not connect untrusted catalogs | | External data files (Parquet/ORC/Avro/JSON/CSV from S3/HDFS/Azure) | **Zone-3 admin-trusted** *(maintainer, Q8)* | admin: do not point Doris at untrusted file systems | | UDF code (Java JAR / C++ `.so` / Python script) | **trusted (`SUPER`-installed)** *(maintainer, Q3, M3)* | admin: only install code you wrote / audited | | JDBC driver JAR (loaded via JDBC catalog) | **trusted (`SUPER`-attached)** *(maintainer, Q3, M3)* | admin: only attach catalogs whose drivers you trust | **Size / shape / rate.** Doris does not commit to bounded resource behavior on adversarial post-auth SQL *(maintainer, Q5)*. Operator must use the §4.10 (3) knob set. --- ## 4.7 Adversary model In-scope adversaries: 1. **Anonymous network attacker on a client-facing port** (MySQL 9030, HTTP 8030, FE Arrow Flight 8070, **BE Arrow Flight 8050**). Capabilities: send arbitrary bytes, complete or abandon TLS handshake (when enabled), submit credential guesses without default lockout. Goals modeled: crash FE/BE; achieve RCE pre-auth; brute-force credentials (operator's job to enable per-user lockout per §4.10); enumerate users / cluster topology. 2. **Authenticated SQL user with restricted RBAC privileges**. Capabilities: any SQL the protocol accepts, bounded only by the operator's workload-group config. Goals modeled: privilege escalation; reading objects outside grant; cross-user data leak via shared state; SQL execution layer escape (RCE); corrupting other users' data. 3. **Authenticated user with `CREATE CATALOG` privilege but no `SUPER`** *(maintainer, M13)*. Narrow SSRF actor: can attach an Iceberg REST catalog whose `iceberg.rest.uri` is an attacker-supplied URL, causing FE to issue HTTP requests to internal hosts. Mitigation is operator-side per §4.10. 4. **Authenticated user in tenant T₁ trying to reach tenant T₂ data** `[cloud only]` *(maintainer, M2)*. Cross-tenant adversary: any leak / escalation across the Meta-Service-enforced tenant boundary is `VALID`. Out-of-scope adversaries: | Adversary | Reason | Disposition | |---|---|---| | Cluster-internal network attacker | (Q1) Network isolation is operator's job | `OUT-OF-MODEL: adversary-not-in-scope` | | Side-channel observer (timing, cache, branch-predictor) | (Q5) Not in model | `OUT-OF-MODEL: adversary-not-in-scope` | | `SUPER` / `ADMIN_PRIV` user behaving maliciously | (M3) admin trusted by definition | `OUT-OF-MODEL: adversary-not-in-scope` | | Co-tenant escape at K8s / OS level `[cloud]` | Meta Service tenant boundary IS in model (§4.8); host/K8s isolation is the cloud operator's job | `OUT-OF-MODEL: adversary-not-in-scope` for OS-level; `VALID` for Meta-Service-level | | Eavesdropper on the wire when TLS off | (Q7) Default disclaims wire confidentiality | `BY-DESIGN: property-disclaimed` (§4.9) | | Brute-force attacker against an account without `FAILED_LOGIN_ATTEMPTS` set | (M11) Default disclaims; operator's job | `BY-DESIGN: property-disclaimed` (§4.9) | | Compromised external catalog (Hive/Iceberg/JDBC/S3 source) | (Q8) admin-trusted | `OUT-OF-MODEL: trusted-input` | | Operator misconfiguration (e.g., exposing 8060 to public internet) | (Q2) Not a supported deployment | `OUT-OF-MODEL: adversary-not-in-scope` | | Byzantine FE follower / BE replica | (M6) honest peers assumed | `OUT-OF-MODEL: adversary-not-in-scope` | --- ## 4.8 Security properties the project provides For each: property + condition, violation symptom, severity tier, provenance. 1. **Memory safety on untrusted MySQL handshake / auth bytes (FE, pre-auth)** *(maintainer, Q6)*. *Violation symptom*: FE JVM crash, OOB, info disclosure. *Severity*: **security-critical**. 2. **Memory safety on untrusted SQL text (FE, post-auth)** *(maintainer, Q5)*. *Violation symptom*: FE crash, OOB, parser RCE. *Severity*: **security-critical** when reachable from a non-`SUPER` user. 3. **Memory safety on untrusted HTTP request bytes (FE, pre- and post-auth)** *(maintainer, derived from Q5/Q6)*. *Severity*: **security-critical**. 4. **Memory safety on untrusted BE Arrow Flight bytes (BE, pre- and post-auth)** *(maintainer, M7)*. *Violation symptom*: BE crash, OOB, parser RCE in Arrow Flight handler. *Severity*: **security-critical**. 5. **RBAC enforcement: an authenticated user cannot read, modify, or discover existence of objects outside their grant scope** *(maintainer, Q5)*. *Condition*: RBAC configured. *Violation symptom*: privilege escalation; reading rows / tables / databases / metadata not granted. *Severity*: **security-critical**. *Excluded*: side-channel inference per §4.3 (4). 6. **Authentication of MySQL / HTTP / FE Arrow Flight / BE Arrow Flight clients (when credentials are presented)** *(documented; configurable backends: native, LDAP, Kerberos, OIDC)*. *Violation symptom*: wrong credential authenticates; session hijack. *Severity*: **security-critical**. 7. **Per-tenant isolation enforced inside the Cloud Meta Service** *(maintainer, M2)* `[cloud only]`. *Condition*: cluster runs the `cloud/` variant; tenant T₁ requests must not see tenant T₂ metadata or data. *Violation symptom*: cross-tenant metadata read, cross-tenant data read, cross-tenant privilege escalation. *Severity*: **security-critical**. 8. **Cluster metadata replication safety under non-Byzantine FE peers** *(maintainer, M6)*. *Violation symptom*: FE replica metadata divergence, lost ACK'd writes, BDB JE log fork. *Severity*: **correctness-critical**; CVE only if reachable from a Zone-0 actor. 9. **Tablet replication consistency under non-Byzantine BE peers and the documented replication protocol** *(maintainer, M6)*. *Severity*: **correctness-critical**. 10. **Per-user pre-auth lockout *mechanism* (PASSWORD_LOCK_TIME) is available and works when configured** *(maintainer, M11)*. *Condition*: operator has run `CREATE USER ... FAILED_LOGIN_ATTEMPTS N PASSWORD_LOCK_TIME T` for the account in question. *Violation symptom*: configured lockout fails to take effect; counter resets unexpectedly; wraparound. *Severity*: **security-critical** when the configured behavior is broken (NOT when default is unconfigured — see §4.9). 11. **HTTP *authentication* on the FE HTTP surface** *(maintainer, M19)*. *Condition*: default config — `enable_all_http_auth` ships **on** for FE (8030) (§4.5a). Scope: - **FE 8030 — every endpoint that routes through the FE auth path** (the `/api/**` and `/rest/v2/**` REST actions), which must establish a caller identity — valid user credentials, or one of the credential forms listed under *Excluded* below — before returning data or performing an action. **Read the property title literally: this is a claim about authentication, not a blanket claim about authorization.** The default flip made the FE HTTP surface demand a credential; it did **not** introduce a centralized privilege check. Authorization on FE HTTP is per-handler, and its coverage is uneven. Handlers that do run a privilege check (for example `checkAdminAuth`, or a per-object `PrivPredicate.SHOW` filter) are covered by property (12) below; the ones that do not are listed as known gaps there. Do not cite this property as evidence that a given FE endpoint enforces the caller's privileges — check the handler. **BE 8040 is not in this property.** Its `enable_all_http_auth` default is unchanged (off), so its `NONE`-privilege handlers stay disclaimed under §4.9 and are governed by Zone-2 network isolation, not by this property. *Violation symptom*: an in-scope endpoint answers a request that carries no credential of any accepted form, or accepts invalid credentials. *Severity*: **security-critical**. Reports of this shape are `VALID`. *Excluded from this property* — this list is meant to be exhaustive; a path that belongs here and is missing is a defect in this document, not a finding: - (a) **FE `/metrics`**, deliberately anonymous, not gated by the flag — see §4.9. - (b) **FE `/api/health`** (`HealthAction`), deliberately anonymous and not gated by the flag either. It returns liveness plus total/online backend counts. A report that `/api/health` answers an unauthenticated request is `BY-DESIGN: property-disclaimed`, not a violation. - (c) Clusters where the operator has set `enable_all_http_auth = false` — a §4.5a knob flipped toward the less-secure side, closed `OUT-OF-MODEL: non-default-build`. Note that the effective value can come from `fe_custom.conf`, which is read after and overwrites `fe.conf`; see §4.5a. - (d) The **cluster-token authenticated** endpoints, where the cluster token *is* the credential — authenticated, just not by user password: FE `/api/get_small_file`; FE `/api/bootstrap` when `cluster_id`+`token` are presented; FE `/api/streaming/commit_offset` and `/api/streaming/report_task_failure`, which accept **only** a `token` header and no user credential at all; the token branch of `/api/{db}/{table}/_stream_load`; and the BE handlers that accept an auth token. A report that one of these serves a request without a user password, while presenting a valid cluster token, is `BY-DESIGN: property-disclaimed`. A report that one of them accepts an *invalid* or absent token is `VALID` — **with one recorded exception**: `/api/bootstrap` validates the pair only on the ready path, so while the FE is not ready any two non-empty strings are accepted and the caller learns "not ready" and nothing else. That branch is a known, deliberate asymmetry (§4.14), not a bypass of this property. 12. **Per-handler authorization on the FE HTTP surface, where the handler implements it** *(maintainer, M19)*. *Condition*: property (11) holds and the endpoint in question runs a privilege check. *Violation symptom*: a handler that performs a privilege check returns data, or performs an action, that the authenticated caller has no privilege for — for example a metadata listing that leaks objects the caller has no `SHOW` privilege on, or an admin action reachable without `ADMIN_PRIV`. *Severity*: **security-critical**. Reports of this shape are `VALID`. **Known gaps — password-only handlers, not yet covered by this property.** These authenticate but perform no SQL-equivalent authorization. They are recorded here so that a report against them is triaged as a *known, accepted gap* rather than silently treated as covered; closing them is tracked in §4.14. **This list is maintained by inspection and has been wrong before — treat it as the current best inventory, not as a proof of completeness. A password-only handler that is missing from it is a defect in this document, and the finding against the handler still stands.** - `/api/backends` (`BackendsAction`) — any valid account can enumerate backend host/port/liveness. Intentional: the Flink/Spark connectors call it with an ordinary load account to discover backends before a stream load. - `POST /rest/v2/api/storage_policy` (`AddStoragePolicyAction`) — any valid account can journal a global storage policy, whereas the SQL equivalent (`CREATE POLICY`) requires global `ADMIN_PRIV`. This is a **privilege gap, not a design choice**. - `GET /rest/v2/api/es_catalog/get_mapping` and `POST /rest/v2/api/es_catalog/search` (`ESCatalogAction`) — any valid account can issue raw mapping/search requests through the catalog's server-side credentials with no `SHOW`/`SELECT` check. - `POST /rest/v2/api/import/file_review` (`ImportAction`) — any valid account can make FE list and read a caller-supplied external location with no `LOAD` or `ADMIN` check (outbound request from FE). - `GET /rest/v2/api/cluster_overview` (`StatisticAction`) — any valid account reads cluster-wide `dbCount` / `tblCount` / `beCount` / `feCount` / `diskOccupancy` / `remainDisk`. - `POST /api/query_schema/{ns}/{db}` (`StmtExecutionAction`) — any valid account can dump the `CREATE TABLE` DDL of arbitrary tables by naming them in the submitted SQL. The mechanism is worth recording because it is not obvious: the handler plans the statement at `ExplainLevel.ANALYZED_PLAN`, and `NereidsPlanner.planWithoutLock` returns at that level *before* running the rewriter — while `CheckPrivileges` is a **rewrite** rule (`Rewriter`, `RuleType.CHECK_PRIVILEGES`). So the usual "Nereids checks privileges" assumption does not hold on this path. Unchanged by the default flip: this handler always required a credential and never checked privileges. Until these carry a privilege check, a report of "authenticated low-privilege user performed X here" against one of them is `BY-DESIGN: property-disclaimed` **with this section cited**, and should be filed against the §4.14 follow-up rather than closed as noise. **Resource properties** — *threshold*: **NONE**. Doris explicitly makes **no** quantitative or categorical resource guarantee on a post-auth single-user query *(maintainer, Q5)*. Operator must use the §4.10 (3) knob set. --- ## 4.9 Security properties the project does *not* provide State plainly: - **No transport-layer confidentiality by default.** TLS off is the supported production posture *(maintainer, Q7)*. Enable TLS on client ports if your network is not acceptably trusted, or layer mTLS / VPN externally. - **No default pre-auth brute-force defense** *(maintainer, M11)*. Doris ships with `numFailedLogin = 0` and `passwordLockSeconds = 0` — accounts have no lockout unless the operator enables it via `CREATE USER ... FAILED_LOGIN_ATTEMPTS N PASSWORD_LOCK_TIME T`. See §4.10 for the obligation. - **No authentication on FE `/metrics`** *(code-verified, M19)*. `MetricsAction` on FE 8030 is public by design and is **not** gated by `enable_all_http_auth`; turning the flag on does not change it. Anyone who can reach 8030 can read FE metrics — cluster topology hints, query and load counters, JVM state. Operator: if FE metrics are sensitive, restrict 8030 at the network layer (§4.10 (1)). - **No authentication on the BE 8040 handlers declared with the `NONE` privilege type** *(maintainer, M19)*. BE ships `enable_all_http_auth = false`; only the FE default was flipped. BE metrics, health, and some debug / metadata endpoints answer without credentials. BE 8040 is a Zone-2 port (§4.4) and §4.3 (2) already requires it to be unreachable from end users; network isolation is the control. An operator who wants it closed sets `enable_all_http_auth = true` in `be.conf` and restarts. - **No HTTP API authentication once the operator sets `enable_all_http_auth = false` on FE** *(maintainer, M19)*. The FE flag ships **on** and FE HTTP authn/authz is a stated property in default config (§4.8 (11)). Turning it off restores the pre-flip behavior — the metadata, statistics and import REST endpoints, and the privilege check on part of the FE admin REST surface, stop checking credentials. Doris makes no security claim about a cluster running that way; see §4.5a and §4.10 (12). - **No defense against query-DoS or query-OOM by an authenticated user** *(maintainer, Q5)*. Operator must use the §4.10 (3) knob set. - **No defense against malicious data files in connected external catalogs** *(maintainer, Q8)*. - **No sandboxing of UDF code** *(maintainer, Q3)*. Java/C++/Python UDFs run with full BE process privileges; granting `EXECUTE` to non-admin = granting whatever the UDF can do. Note: Java UDF default-on (`enable_java_udf=true`) per M10. - **No defense against side-channel / timing attacks** *(maintainer, Q5)*. - **No Byzantine-fault tolerance** *(maintainer, M6)*. - **No defense against any actor inside the cluster network** *(maintainer, Q1)*. - **No defense against a `SUPER`-privileged insider** *(maintainer, M3)*. - **No mutual authentication on internal RPC.** FE↔BE Thrift, BE↔BE BRPC, FE↔Follower, FE↔Broker accept any peer that can connect at the network layer. - **No reverse-proxy auth header support** *(code-verified, M14)*. FE HTTP auth (`BaseController.java`) only honors `Authorization` header (Basic/Bearer) or `PALO_SESSION_ID` cookie; Doris does NOT trust `X-Forwarded-User` / `X-User-Authenticated` / similar upstream-proxy headers. Setting up such a pattern via reverse proxy is unsupported. **False-friend properties — features that look security-relevant but are not:** - **`md5()` / `sha1()` SQL functions are NOT cryptographic primitives.** Checksum / compatibility functions; not safe for password hashing, MAC construction, or collision-resistance. - **`password()` SQL function (MySQL-compat) is NOT a KDF.** Exists for wire compatibility; do not use for app-side credential storage. - **`AES_ENCRYPT` / `AES_DECRYPT` / `DES_ENCRYPT` / `DES_DECRYPT` SQL functions are NOT a managed-key envelope** *(maintainer, M12)*. Key handling is the application's job; ECB-mode and IV-reuse pitfalls are not mitigated; DES is broken — provided for compatibility only. - **`current_user()` and audit log entries are NOT tamper-evident records** *(maintainer, M12)*. Operational logs, not legal evidence; queryable by anyone with BE filesystem access. - **The web UI (port 8030) is NOT a security boundary against an authenticated user.** Anything the UI lets a user click, the user could already do via SQL. - **LDAP integration's local cache is NOT a backstop for LDAP compromise.** If your LDAP server is owned, Doris auth is owned on the next refresh. - **Workload Group is NOT a security isolation boundary** *(maintainer, M12)*. It is resource isolation / QoS only. A query in workload group A operating on data accessible to workload group B is NOT a "security isolation" violation. - **Resource Tag (BE node tag) is NOT a tenant isolation boundary** *(maintainer, M12)*. It is a tablet scheduling hint, not "this user can only access this BE group" enforcement. **Well-known attack classes left to the caller / operator:** - **SQL injection** in applications building queries by string concatenation — application's job. - **Credential leakage in BI tools** that store the Doris password in plaintext config — operator's job. - **Decompression / deserialization bombs** in Parquet/ORC/Avro files — admin must trust the catalog source (§4.3 (5)). - **HTTP server-side request forgery (SSRF) via Iceberg REST catalog URL** *(maintainer, M13)* — `CREATE EXTERNAL CATALOG ... PROPERTIES("iceberg.rest.uri" = "http://...")` does not validate or block localhost / private IPs. Operator must restrict `CREATE CATALOG` privilege to admins (§4.10, §4.11). - **`COPY INTO` / `OUTFILE` exfiltration** — write privileges to external locations are admin-trusted; granting `LOAD_PRIV` / `EXPORT_PRIV` to a low-priv user opens an exfiltration channel (§4.11). - **Account brute-force in default config** — see §4.9 (no default lockout) and §4.10 (operator must enable per user). --- ## 4.10 Downstream responsibilities The operator MUST: 1. **Place every Doris-listened port behind a network perimeter such that only client-facing ports (MySQL 9030, HTTP 8030, FE Arrow Flight 8070, BE Arrow Flight 8050) are reachable from authorized clients.** Internal ports (§4.4) must be reachable only by other Doris processes in the same cluster. Failure collapses §4.4. 2. **Enable TLS** on client-facing ports if the network is not acceptably trusted *(maintainer, Q7)*. 3. **Configure resource caps per the canonical knob set** *(maintainer, M5)* before granting access to any user not fully trusted: **`exec_mem_limit`** (per-query memory), **Workload Group** via `CREATE WORKLOAD GROUP ...` (recommended; per-user/ group memory + CPU + concurrency caps), and **`max_connections` / `max_connection_per_user`** (FE config; prevent connection exhaustion). 4. **Treat `SUPER` (and equivalent) as equivalent to local code execution on every BE in the cluster** *(maintainer, Q3, M3)*. Anyone who can install a UDF, attach a JDBC catalog, or set arbitrary `ADMIN SET CONFIG` values can run arbitrary code on the BE process. Note: Java UDF is default-on per M10 — `SUPER` discipline is load-bearing in default config. 5. **Audit every external catalog** before connecting it *(maintainer, Q8)*. 6. **Audit every JDBC driver JAR** before attaching a JDBC catalog that uses it. 7. **Deploy on a Linux host the operator controls.** 8. **For cloud deployments**, rely on the Meta Service's per-tenant isolation (§4.8 (7)); additionally enforce K8s `NetworkPolicy` / namespace isolation and node-level isolation for OS-layer defense *(maintainer, M2)*. 9. **Rotate credentials** on a schedule appropriate for the data; do not embed `root` credentials in BI tools or app configs. 10. **Enable per-user pre-auth lockout** for any account reachable from a network-adjacent attacker *(maintainer, M11)*: `CREATE USER ... FAILED_LOGIN_ATTEMPTS N PASSWORD_LOCK_TIME T`. Doris does NOT ship a default lockout. A reasonable starting value: `FAILED_LOGIN_ATTEMPTS 5 PASSWORD_LOCK_TIME 300` (operator's judgment). 11. **Restrict `CREATE CATALOG` privilege** to administrators *(maintainer, M13)*. Granting it to a low-privilege user lets them issue HTTP requests from FE to attacker-chosen URLs (SSRF) via Iceberg REST catalog. If you must grant it more broadly, apply network egress controls at the FE host level. 12. **Keep `enable_all_http_auth` on, and migrate HTTP callers onto credentials** *(maintainer, M19)*. It ships on; do not turn it off. When upgrading from a release where it defaulted off, the migration the operator owes: (a) enumerate everything that calls the FE 8030 `/api/**` and `/rest/v2/**` surface — monitoring and alerting agents, load tooling, health probes, Kubernetes liveness/readiness checks, internal ops scripts. FE `/metrics` and BE 8040 are not affected; scrapers pointed only at those need no change; (b) give each of them credentials (HTTP Basic) and a Doris user with only the privileges it needs; (c) verify in staging before upgrading production — anything still polling anonymously will start getting 401; (d) **check `fe_custom.conf`, not only `fe.conf`.** It is read after `fe.conf` and overwrites it, and an earlier release where this flag was mutable may have persisted `enable_all_http_auth=false` into it via `ADMIN SET FRONTEND CONFIG ... persist=true`. That value survives the upgrade, so the new default silently does not take effect. Delete the stale entry, then confirm the effective value with `ADMIN SHOW FRONTEND CONFIG`. The flag is **not runtime-mutable**: turning it off means editing `fe.conf` and restarting the node. That is deliberate (M19) — disabling authentication should be a recorded, on-disk decision, not a runtime command, so the running posture is always auditable from the config files. "The config files" is plural on purpose: audit `fe.conf` **and** `fe_custom.conf`. It also means an upgrade surprise cannot be papered over with `ADMIN SET FRONTENDS CONFIG`; if you must fall back, do it in the config file and treat it as a temporary migration aid, not a resting state — it re-opens the §4.9 exposure and puts the cluster outside §4.8 (11). --- ## 4.11 Known misuse patterns - **Exposing BE webserver (8040) directly to authenticated end users.** Default no auth; intra-cluster admin/metrics traffic only. - **Exposing BE BRPC (8060) outside the cluster network.** Bypasses every Zone-0 access control. - **Exposing BE Arrow Flight 8050 without auth configured.** It IS client-facing (M7) — auth must be configured if reachable. - **Granting `EXECUTE` on a UDF to a non-admin user** without understanding UDF runs with full BE privileges. - **Connecting a JDBC catalog whose driver does eval-on-connection-string.** - **Granting `CREATE CATALOG` to non-admin users** *(maintainer, M13)*. Opens an SSRF vector via Iceberg REST URL. - **Using `md5()` / `sha1()` / `password()` SQL functions for app credential hashing.** See §4.9 false-friends. - **Granting `LOAD_PRIV` / `EXPORT_PRIV` to low-privilege users.** Exfiltration channel. - **Treating Workload Group or Resource Tag as security isolation** *(maintainer, M12)*. They are not. - **Running `cloud/` deployments without K8s `NetworkPolicy` / namespace isolation at the host layer.** Meta Service enforces the *tenant data* boundary (§4.8 (7)), but K8s/host-level isolation is still the operator's job. - **Leaving accounts on a network-adjacent client port without `FAILED_LOGIN_ATTEMPTS`** *(maintainer, M11)*. Brute-forceable with no server-side lockout. - **Turning FE `enable_all_http_auth` off to unbreak an anonymous monitoring agent or ops script after an upgrade, and leaving it off** *(maintainer, M19)*. It is a migration aid. The fix is to give the caller credentials; see §4.10 (12). - **Performing a security assessment of the FE HTTP surface with `enable_all_http_auth` turned off** *(maintainer, M19)*. Every finding it produces is `OUT-OF-MODEL: non-default-build`, and the real bypasses — endpoints that skip authn/authz with the flag on — go unfound. See the security-testing baseline in §4.5a. --- ## 4.11a Known non-findings (recurring false positives) Patterns scanners / fuzzers / AI analyzers / human reviewers repeatedly flag that are NOT bugs given this model. **Internal primary; cite externally only when closing a specific report** *(maintainer, M18)*. - **"BE BRPC port 8060 has no authentication."** — `OUT-OF-MODEL: adversary-not-in-scope` per §4.4. - **"BE webserver port 8040 has no authentication."** — Same. - **"BE↔BE port 9060 / heartbeat 9050 / FE Edit-log 9010 / FE RPC 9020 — no mutual TLS."** — Same. - **"FS broker accepts unauthenticated Thrift calls."** — Same. - **"MySQL credentials transmitted in plaintext on port 9030 in default config."** — `BY-DESIGN: property-disclaimed` per §4.9 (Q7). - **"Cluster traffic between FE and BE is unencrypted."** — Same. - **"Brute-force possible against `analytics_user` in default config (no `FAILED_LOGIN_ATTEMPTS` set)."** — `BY-DESIGN: property-disclaimed` per §4.9 (M11); operator's obligation per §4.10 (10). - **"`SELECT * FROM huge_table CROSS JOIN huge_table` causes BE OOM."** — `BY-DESIGN: property-disclaimed` per §4.9 (Q5); operator's obligation per §4.10 (3). - **"`md5()` / `sha1()` are cryptographically broken."** — `BY-DESIGN: property-disclaimed` per §4.9 false-friends. - **"`DES_ENCRYPT` is broken."** — Same. - **"Java UDF runs without sandbox; can call `Runtime.exec()`."** — `OUT-OF-MODEL: trusted-input` per §4.6 (M3). - **"JDBC catalog driver runs arbitrary code on connection."** — Same. - **"Reading a crafted Parquet from S3 crashes BE."** — `OUT-OF-MODEL: trusted-input` per §4.6 (Q8). - **"ES connector calls `use_untrusted_ssl()`."** — Configurable per-catalog; admin's choice. Tag the connection, not the call. `OUT-OF-MODEL: trusted-input`. - **"FE follower can be tricked by a malicious peer to corrupt the edit log."** — `OUT-OF-MODEL: adversary-not-in-scope` per §4.7 (M6). - **"`mysql/mysql-9.x.x/` has an XYZ issue."** — `OUT-OF-MODEL: unsupported-component` per §4.2 row 9. - **"`samples/` / `pytest/` / `regression-test/` / `task_executor_simulator/` has an XYZ issue."** — Per §4.2 row 10. - **"`fe_plugins/*` (including `auditloader`) has an XYZ issue."** — `OUT-OF-MODEL: unsupported-component` per §4.2 row 11 (M4). - **"`extension/` / `cdc_client/` has an XYZ issue."** — Per §4.2 row 12. - **"`ADMIN SET CONFIG ...` allows arbitrary file path / arbitrary command."** — Requires `ADMIN_PRIV`; admin trusted by §4.7 (M3). `OUT-OF-MODEL: adversary-not-in-scope`. - **"Workload group / resource tag does not isolate cross-user data."** — `BY-DESIGN: property-disclaimed` per §4.9 false-friends (M12). - **"FE `/metrics` (port 8030) is readable without credentials."** — `BY-DESIGN: property-disclaimed` per §4.9 (M19). Public by design; not gated by `enable_all_http_auth`. - **"FE `/api/health` (port 8030) answers without credentials and discloses backend counts."** — `BY-DESIGN: property-disclaimed` per §4.8 (11) exclusion (b) (M19). `HealthAction` is deliberately public and the flag does not gate it; the response is liveness plus total/online backend counts. Excluded from property (11) explicitly, so this is not a bypass. - **"FE `/api/streaming/commit_offset` (or `/api/streaming/report_task_failure`) accepts a request with no user credential."** — `BY-DESIGN: property-disclaimed` per §4.8 (11) exclusion (d) (M19). These take a cluster-token `token` header and nothing else; the token is the credential. A report that they accept an *absent or invalid* token is `VALID`. - **"An authenticated low-privilege user could create a storage policy / query Elasticsearch / make FE fetch an external URL over HTTP."** — `BY-DESIGN: property-disclaimed` per §4.8 (12) known gaps (M19), and file against the §4.14 follow-up. These handlers authenticate but do not authorize; that is a recorded open gap, not a violation of a property the project currently claims. - **"BE 8040 endpoint X answers an unauthenticated request."** — `BY-DESIGN: property-disclaimed` per §4.9 (M19). The BE default is unchanged (off) and BE 8040 is a Zone-2 port; network isolation is the control. An operator may set `enable_all_http_auth = true` in `be.conf` to close it, but the default is not a defect. - **"FE 8030 endpoint X returns metadata to an unauthenticated caller"** *(maintainer, M19)* — **not a non-finding by default.** The FE default is on, so this is `VALID` per §4.8 (11) unless the reporter had turned the flag off, in which case it is `OUT-OF-MODEL: non-default-build` per §4.5a. **Always confirm which config was tested before closing** — ask for the *effective* `enable_all_http_auth` value on the tested cluster, i.e. the output of `ADMIN SHOW FRONTEND CONFIG` or `/api/show_config`, plus both `fe.conf` and `fe_custom.conf`. The flag is not runtime-mutable, but `fe_custom.conf` is read after `fe.conf` and overwrites it, so `fe.conf` alone is **not** authoritative (§4.5a). --- ## 4.12 Conditions that would change this model Per M17, model is updated **only** when a §4.12 trigger fires (no periodic review). Triggers: - TLS default flips to **on** for any client port (Q7). - Internal RPC gains mutual authentication or per-call auth (Q1 inverted). - A new client-facing port is added (Zone-0 expands). - `numFailedLogin` / `passwordLockSeconds` defaults change to non-zero (M11): §4.8 (5)/(10) re-stated as default property, §4.9 disclaimer drops, §4.11a entry drops. - `enable_java_udf` default flips off (M10): §4.10 (4) becomes conditional on a §4.5a non-default knob. - `enable_all_http_auth` FE default flips back to **off** (M19 inverted, e.g. if the upgrade breakage forces a revert): §4.8 (11) reverts to a conditional property, §4.9 re-states the unauthenticated-HTTP disclaimer unconditionally, §4.10 (12) becomes "turn it on" rather than "keep it on", and the §4.11a entry inverts back to a default non-finding. *(This trigger fired once already, in the on direction, on 2026-07-29 — see §4.14 wave 5.)* - `enable_all_http_auth` BE default flips **on** (M19 extended to BE 8040): §4.8 (11) extends to every `HttpHandlerWithAuth` handler, the §4.9 BE disclaimer and the §4.11a BE non-finding both drop. - Iceberg REST URL gains validation / localhost-blocking (M13): SSRF moves from §4.9 attack-class to §4.8 property; §4.11 misuse drops. - A new catalog connector is added that touches data formats not previously parsed by BE (§4.6 Zone-3 surface grows). - An entry in §4.2 rows 9–12 is promoted to "core supported". - Cloud Meta Service tenant model changes (M2 inverted): if per-customer Meta Services replace the shared Meta Service, §4.8 (7) becomes a stricter property and §4.7 cross-tenant adversary moves to OUT-OF-MODEL. - A vulnerability report routes to `MODEL-GAP` (§4.13). The correct response is to revise this model — add a §4.8 / §4.9 entry — not to make an ad-hoc call on the report. --- ## 4.13 Triage dispositions Closed set. Cite the section. | Disposition | When | Licensed by | |---|---|---| | `VALID` | Violates a §4.8 property via an in-scope §4.7 actor and §4.6 input. | §4.8, §4.6, §4.7 | | `VALID-HARDENING` | No §4.8 property violated, but the API enables a §4.11 misuse cleanly enough to warrant a hardening change. | §4.11 | | `OUT-OF-MODEL: trusted-input` | Requires attacker control of a §4.6 input marked trusted. | §4.6 | | `OUT-OF-MODEL: adversary-not-in-scope` | Requires a §4.7 capability that is excluded. | §4.7 | | `OUT-OF-MODEL: unsupported-component` | Lands in §4.2 rows 9–12. | §4.3 | | `OUT-OF-MODEL: non-default-build` | Only manifests when a §4.5a knob is flipped from its default toward the less-secure side. | §4.5a | | `BY-DESIGN: property-disclaimed` | Concerns a property §4.9 explicitly disclaims. | §4.9 | | `KNOWN-NON-FINDING` | Matches a §4.11a entry verbatim or closely. | §4.11a | | `MODEL-GAP` | Cannot be cleanly routed to any of the above. **Triggers §4.12 — model gets revised before the report is closed.** | (§4.12) | --- ## 4.14 Open questions **Wave 3 — RESOLVED 2026-05-14.** All 14 technical questions (M1–M14) answered by maintainer; promotions applied throughout the body. Summary table: | ID | Topic | Outcome | |---|---|---| | M1 | Disclosure channel | `security@apache.org` (ASF) + short `SECURITY.md` linking back to this doc | | M2 | Cloud tenancy | **Shared Meta Service; per-tenant isolation IS a security claim** (§4.8 new property, §4.7 new actor) | | M3 | SUPER admin | Out-of-scope adversary by definition | | M4 | `auditloader` | Stays out-of-model demo (row 11) | | M5 | Resource knobs | `exec_mem_limit` + Workload Group + `max_connections{,_per_user}` | | M6 | Byzantine peers | Honest peers assumed; out-of-scope adversary | | M7 | BE Arrow Flight 8050 | **Client-facing — Zone-0** (was Zone-2 in v0.1) | | M8 | Toolchain | JDK 17 (FE), GCC 11+ libstdc++ (BE), Linux x86_64 + aarch64 | | M9 | Negative claims | Subprocess: Python UDF + Python venv + Broker shell + CDC client; no FE custom signal handlers; env vars enumerated; **no password-via-env** | | M10 | UDF defaults | `enable_java_udf=true` (FE), `enable_python_udf=true` (FE), `enable_python_udf_support=false` (BE); intentional asymmetric | | M11 | Login lockout | **No default lockout** — operator's responsibility per §4.10 (10) | | M12 | False friends | DES_*, Workload Group, Resource Tag, Audit Log added | | M13 | SSRF | **Real surface** — Iceberg REST URL + non-admin `CREATE CATALOG` | | M14 | Reverse-proxy auth | Code-verified NOT supported; no `X-Forwarded-User` trust | **Wave 4 — RESOLVED 2026-05-14.** Process / meta: | ID | Topic | Outcome | |---|---|---| | M15 | Versioning policy | Single living doc + `model-version` field at top, bumped per minor release | | M16 | SECURITY.md coexistence | Short `SECURITY.md` at repo root linking back to this doc as canonical scope | | M17 | Revision cadence | Trigger-driven only (§4.12 events); no periodic review | | M18 | §4.11a publication | Internal primary; cite externally only when closing a specific report | **Wave 5 — RESOLVED 2026-07-29.** HTTP surface: | ID | Topic | Outcome | |---|---|---| | M19 | `enable_all_http_auth` (FE) | **FE default flipped to `true` on 2026-07-29, and the flag is not runtime-mutable** — it can only be changed on disk (`fe.conf`, or `fe_custom.conf` which overwrites it) with a restart, so the running posture is auditable from disk and cannot be silently dropped at runtime — but auditing it means reading **both** files, since a pre-flip release could have persisted `false` into `fe_custom.conf` (§4.5a). FE HTTP **authentication** is now a default-config property (§4.8 (11)); unauthenticated access to the FE 8030 `/api/**` and `/rest/v2/**` surface is `VALID`, not disclaimed. **Authorization is a separate, narrower property** (§4.8 (12)): the flip did not add a centralized privilege check, and `AddStoragePolicyAction`, `ESCatalogAction` and `ImportAction` remain password-only — a recorded open gap tracked in §4.14, not a claim this release makes. **Carve-outs**: FE `/metrics` and FE `/api/health` stay public by design and are not gated by the flag (§4.9, §4.8 (11) (b), code-verified); the cluster-token endpoints (including `/api/streaming/*`) authenticate by token, not password (§4.8 (11) (d)); and the **BE default is unchanged (off)** — BE 8040 stays governed by Zone-2 network isolation and its `NONE`-privilege handlers stay disclaimed (§4.9, §4.11a). Extending the flip to BE is tracked as a §4.12 trigger. Turning the FE flag off is a migration aid for upgrades (§4.10 (12)), lands findings in `OUT-OF-MODEL: non-default-build` (§4.5a), and is a misuse pattern as a resting state (§4.11) | **Open follow-up items (not blocking v1.0 acceptance):** - Add `model-version` field to top of this doc per M15. Currently bound to commit `1d1846591f7` / pre-3.x release. Update when cutting next release. - Consider opening upstream issues per M10 (UDF default-off proposal), M11 (default lockout proposal), M13 (Iceberg URL validation). Each is a §4.12 trigger if accepted. - Per M19, the FE `enable_all_http_auth` default flip is a **breaking change for anonymous callers of the FE `/api/**` and `/rest/v2/**` surface** (health probes, ops scripts, management tooling) upgrading from an earlier release. Release notes and the upgrade guide must carry it, with the `enable_all_http_auth = false` fallback (in `fe.conf`, requiring a restart — the flag is not runtime-mutable) documented as temporary per §4.10 (12). - Per M19, whether to extend the flip to BE 8040 is **open**. The BE default stays off in this change; the compatibility cost there is higher (metrics scrapers point at BE 8040) and BE 8040 is already required to be off end-user networks by §4.3 (2). Tracked as a §4.12 trigger. - Per M19, **the password-only FE handlers listed under §4.8 (12) are an open gap**: `AddStoragePolicyAction`, `ESCatalogAction`, `ImportAction`, `StatisticAction` and `StmtExecutionAction`'s `query_schema` authenticate but perform no SQL-equivalent authorization, so any valid account can respectively journal a global storage policy, query Elasticsearch through the catalog's server-side credentials, make FE read a caller-supplied external location, read cluster-wide capacity statistics, and dump arbitrary table DDL. The default flip does not close these — it only means the caller must now hold *some* account. Adding the privilege checks (with negative low-privilege regression tests) is deliberately out of scope for the flip itself and is tracked here. Until then they are triaged per §4.8 (12), not as noise. The `query_schema` one deserves priority: it is a genuine read of another tenant's schema, and the reason it slips through — planning stops at `ANALYZED_PLAN`, before the rewrite-phase `CheckPrivileges` rule — may well apply to other callers of the planner that ask for an analyzed-only plan. That is worth a sweep, not just a point fix. - Per M19, **the cloud overdue-warehouse fence on FE HTTP is opt-in per handler, not centralized.** `BaseController.checkWithCookie`'s `checkAuth` flag gates two unrelated things at once: the global `ADMIN_OR_NODE` requirement and, in cloud mode, the overdue check. Handlers that pass `false` to do their own narrower authorization therefore also lose the fence, and must call `checkInstanceOverdueIfCloud` explicitly — the metadata controllers (`MetaInfoAction`, `MetaInfoActionV2`) now do. This is deliberately not folded into `checkWithCookie`: `/api/query` also passes `false` but hands the statement to a real JDBC session that enforces the overdue state itself and reports it as `COMMON_ERROR`; moving that rejection up would silently change the endpoint's response code to `UNAUTHORIZED`. Anyone "simplifying" this back into `checkWithCookie` will break that contract — the overloaded flag is the underlying wart, and unpicking it properly is a follow-up. - Per M19, **`/api/bootstrap` accepts the cluster-id/token pair unverified while the FE is not ready.** The pair is only validated on the ready path, so any caller supplying two non-empty strings can learn that a node is not ready; nothing else is disclosed on that branch. Validating earlier would mean comparing against `getClusterId()`/`getToken()` before the cluster is established and would turn "not ready" into "invalid cluster id" for a legitimately joining FE, so it was left alone here. If the not-ready branch ever grows a richer response, this must be revisited first. - Per M19, the upgrade guide must also tell operators to check `fe_custom.conf`, not just `fe.conf`: a persisted `enable_all_http_auth=false` written by an earlier mutable release survives the upgrade and silently keeps authentication off (§4.5a). --- ## 4.15 Optional: machine-readable companion To be generated as `docs/threat-model.yaml` for automated / AI triage, encoding: - Per-port trust zone (from §4.4) - Per-endpoint parameter trust (from §4.6) - Component-family in/out-of-scope (from §4.2 / §4.3) - §4.5a knobs with default + maintainer stance - §4.8 property → severity-tier + violation-symptom - §4.9 disclaimed properties + false friends - §4.11a known-non-findings (suppression-list shape; per M18 kept internal-primary) - §4.13 disposition labels Not yet produced in v1.0. Optional follow-up. --- ## Self-check (skill §8) - [x] Every section is substantive or marked N/A. - [x] No bullet would be more at home in a code review. - [x] No bullet restates README — README has no security content. - [x] Every claim carries `(documented)` / `(maintainer)` / `(inferred)`. **Zero `(inferred)` remaining.** No hedge-tag variants. - [x] Header reports a draft-confidence count. - [x] All `(inferred)` resolved → no §4.14 wave-3 open items remain. - [x] Component families with distinct trust profiles modeled separately (FE / BE / cloud / brokers / catalogs / UDF code paths). - [x] §4.5a enumerates security-relevant knobs; both insecure-default cases (TLS per Q7, lockout per M11) explicitly resolved. - [x] §4.9 and §4.10 substantive; §4.9 names false-friends and well-known attack classes (now including SSRF per M13). - [x] §4.6 contains a per-parameter / per-endpoint trust table; BE Arrow Flight 8050 added per M7. - [x] §4.8 properties carry violation symptom + severity tier. - [x] Resource thresholds: §4.8 says **"NONE — no commitment"** per Q5. That is the threshold. - [x] §4.11a populated; M18 publication policy stated inline. - [x] §4.13 enumerates dispositions citing the section that licenses each. - [x] A reader can answer "what threats has Doris taken responsibility for, and which are left to me?". - [x] A triager can route an arbitrary finding to exactly one §4.13 disposition. - [x] Document length: ~7 pages (within recommended 3–8). v0.1's §4.14 wave-3 collapsed into a 14-row summary table. **v1.0 status**: ACCEPTED for technical content; root `SECURITY.md` coexistence artifact complete per M16.