From b8e73abdfa771ee995ea62ea393475a314c03bc0 Mon Sep 17 00:00:00 2001 From: Jarek Potiuk Date: Fri, 5 Jun 2026 03:47:38 +0200 Subject: [PATCH 1/4] Add THREAT_MODEL.md + SECURITY.md and wire AGENTS.md for security-model discoverability Adds a draft threat model (ASF Security team v0, for the PMC to own and refine), a SECURITY.md pointing to it, and a Security section in AGENTS.md so the AGENTS.md -> SECURITY.md -> THREAT_MODEL.md discoverability chain resolves. Documentation only; no code or behaviour changes. Assisted-by: Claude Code:claude-opus-4-8 --- AGENTS.md | 8 +- SECURITY.md | 28 ++++ THREAT_MODEL.md | 348 ++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 383 insertions(+), 1 deletion(-) create mode 100644 SECURITY.md create mode 100644 THREAT_MODEL.md diff --git a/AGENTS.md b/AGENTS.md index 4cd7d9b5f87..186da57b900 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -241,4 +241,10 @@ If AGENTS.md does not clearly cover a situation: 3. Surface the question to human maintainers (for example, by leaving a comment, or drafting a minimal PR that asks for guidance). This file is intended to help tools act like a careful, well‑informed contributor. When in doubt, defer to human -judgment and the canonical project documentation. \ No newline at end of file +judgment and the canonical project documentation. + +## Security + +For Apache TinkerPop's threat model — trust boundaries, in-scope / out-of-scope, the security properties +the project does and does not provide, and known non-findings — see [SECURITY.md](SECURITY.md), which +points to [THREAT_MODEL.md](THREAT_MODEL.md). Consult it before triaging or reporting security issues. diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 00000000000..825da471588 --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,28 @@ + + +# Security Policy + +Apache TinkerPop's threat model — the assumptions, trust boundaries, what is in and out of scope, the +security properties the project does and does not provide, and known non-findings — is documented in +[THREAT_MODEL.md](THREAT_MODEL.md). Please read it before reporting a security issue. + +## Reporting a Vulnerability + +Please report security vulnerabilities privately following the +[ASF security process](https://www.apache.org/security/) — email +[security@apache.org](mailto:security@apache.org). Do not open public GitHub issues for security reports. diff --git a/THREAT_MODEL.md b/THREAT_MODEL.md new file mode 100644 index 00000000000..cdea5cae823 --- /dev/null +++ b/THREAT_MODEL.md @@ -0,0 +1,348 @@ + + +# Apache TinkerPop — Threat Model (v0 draft) + +## §1 Header + +- **Project:** Apache TinkerPop (`apache/tinkerpop`) — a graph computing framework: the Gremlin query + language, the traversal machine, Gremlin Server (remote query execution), the GraphSON / Gryo / + GraphBinary serialization formats, and the Gremlin Language Variants (Java, Python, .NET, Go, JS). + *(documented — `README.md`, repo layout)* +- **Scope of this model:** the **`apache/tinkerpop` monorepo**, active branches `master`, `3.7-dev`, + `3.8-dev` *(maintainer — colegreer, scope confirmation)*. The model focuses on the network-facing and + deserialization surfaces (see §2); provider graph databases that embed TinkerPop are out of scope (§3). +- **Date:** 2026-06-05. **Status:** DRAFT v0 — first pass by the ASF Security team via the + threat-model-producer rubric, for the TinkerPop PMC to react to. **Author:** ASF Security team, for PMC + ratification. +- **Version binding:** versioned with the project; a report against release *N* is triaged against the + model as it stood at *N*, not against `HEAD`. +- **Reporting cross-reference:** §8-property violations → report privately per the ASF process + (`security@apache.org`); §3 / §9 findings are closed citing this document. +- **Provenance legend:** *(documented)* = stated in TinkerPop's own docs / reference / source; + *(maintainer)* = confirmed by a TinkerPop PMC member through this process; *(inferred)* = reasoned from + the reference docs, architecture, and graph-server domain knowledge, **not yet confirmed** — every + *(inferred)* claim has a matching §14 open question. +- **Draft confidence:** a v0 with **no PMC confirmation folded in yet** — the deployment posture, the + default auth/TLS stance, the script-execution sandboxing story, and the serialization (Gryo) + disposition are the highest-value items for the PMC to confirm in §14. +- **What TinkerPop is:** TinkerPop is an embeddable graph-computing framework. Applications and graph + databases (providers) embed `gremlin-core` to run Gremlin traversals; **Gremlin Server** exposes that + capability over the network (WebSocket sub-protocol + HTTP), accepting both **string-based Gremlin + scripts** (evaluated by the `gremlin-groovy` Groovy script engine) and **bytecode-based traversals** + (the GLVs). *(documented — reference docs)* + +## §2 Scope and intended use + +- **Primary use:** an operator-deployed graph-traversal engine. In production it is typically embedded by + a graph database (a "provider") or run as **Gremlin Server** behind the application tier, on a + **trusted network** *(inferred — confirm deployment posture, §14 Q1)*. Gremlin Server is the main + network-facing trust boundary. +- **Caller roles** (Gremlin Server is a network service): + - **remote client** — connects over the WebSocket sub-protocol or HTTP, submits Gremlin (script or + bytecode). Trust level depends entirely on whether the operator enabled authentication, authorization, + and script restrictions. *(inferred — §14 Q2)* + - **embedding application / provider** — links `gremlin-core` in-process and drives traversals directly. + Trusted within the compile-time + configuration boundary. *(inferred)* + - **operator** — runs Gremlin Server, controls `gremlin-server.yaml`, serializers, auth, TLS, the script + engine configuration, and the host. Trusted for the instance. *(inferred)* + +**Component-family table** *(in/out = in/out of this model; all rows inferred unless noted)*: + +| Family | Entry point | Touches outside the process | In model? | +| --- | --- | --- | --- | +| **Gremlin Server** (`gremlin-server`) | WebSocket sub-protocol + HTTP request handlers | network (listens), invokes the script engine + traversal machine | **In** *(documented: remote execution endpoint)* | +| **Script engine** (`gremlin-groovy`) | `GremlinGroovyScriptEngine` evaluating string scripts | runs supplied Groovy in-process | **In — central code-execution surface; see §9** *(documented: scripts are evaluated)* | +| **Gremlin language parser** (`gremlin-language`) | ANTLR grammar for string Gremlin (script-engine-free) | — | **In** — parser robustness on untrusted Gremlin strings *(inferred)* | +| **Core traversal machine + structure API** (`gremlin-core`) | `GraphTraversal`, strategies, `Vertex`/`Edge` | filesystem (IO formats) | **In** *(documented)* | +| **Serialization** (`gremlin-core`/`gremlin-util`/`gremlin-shaded`) | GraphSON (JSON), GraphBinary, **Gryo (Kryo-based)** readers | deserializes wire/file bytes | **In — deserialization of untrusted bytes; see §9** *(documented: 3 formats; Gryo wraps shaded Kryo)* | +| **Java driver** (`gremlin-driver`) | client connecting to Gremlin Server; deserializes server responses | network (connects) | **In** (client side) *(inferred)* | +| **Gremlin Language Variants** (`gremlin-python`, `-dotnet`, `-go`, `-js`) | build bytecode, serialize/deserialize | network (connect) | **In, lower priority** — client-side; deserialize server responses *(inferred — §14 Q9)* | +| **Reference graph** (`tinkergraph-gremlin`) | in-memory graph implementation | filesystem (persistence option) | **In** — the reference provider *(inferred)* | +| **OLAP** (`hadoop-gremlin`, `spark-gremlin`) | `GraphComputer` over Hadoop/Spark | network, filesystem, cluster | **In if deployed; operator cluster infra** *(inferred — §14 Q10)* | +| **Operator tooling** (`gremlin-console`) | interactive REPL run by the operator | — | **Out** — operator-trusted local tool *(inferred)* | +| `gremlin-test`, `gremlin-examples`, `gremlin-tools`, `gremlin-annotations` | test/build/example code | — | **Out** *(see §3)* | + +## §3 Out of scope (explicit non-goals) + +- **Provider graph databases that embed TinkerPop** (e.g. third-party graph DBs implementing the + structure/`GraphComputer` SPI). A vulnerability in a provider's own code is routed to that provider, not + here; this model covers TinkerPop's own code and its *use* of the SPI. *(inferred — §14 Q3)* +- **The embedding application's own authentication / authorization of its end users.** TinkerPop has no + concept of the embedding application's end users. *(inferred)* +- **Attackers who already control the host, the Gremlin Server process, `gremlin-server.yaml`, or the + graph data directory.** They have the operator's authority by definition. *(inferred)* +- **`gremlin-test/`, `gremlin-examples/`, build/distribution tooling, `gremlin-console`** as a production + trust surface. *(inferred)* +- **Confidentiality of data in transit when the operator has not enabled TLS** — see §10; the TLS posture + is the operator's deployment responsibility unless the project claims TLS-by-default (§14 Q5). *(inferred)* + +## §4 Trust boundaries and data flow + +- **Primary trust boundary: the Gremlin Server remote request surface.** Bytes arriving over the WebSocket + sub-protocol or HTTP — whether a **string script** or **bytecode traversal**, plus the serialized request + payload — are untrusted. The script engine, traversal machine, and structure/storage layer sit behind + this boundary. *(documented: remote endpoint; trust posture inferred — §14 Q2)* +- **The script-execution boundary (the highest-stakes one).** A string-based request is evaluated by the + Groovy script engine. Absent a configured sandbox / allow-list, **evaluating an attacker-supplied script + is arbitrary code execution on the server** — this is inherent to the script-based interface, not a bug + (§9). The question for triage is whether a given deployment restricts scripting (bytecode-only, + allow-list, sandbox) and who is authorized to submit scripts. *(documented: scripts are evaluated; + sandbox specifics inferred — §14 Q4)* +- **The deserialization boundary.** GraphSON, GraphBinary, and Gryo readers parse untrusted request bytes + (and, on the driver/GLV side, untrusted server-response bytes). Gryo is Kryo-based; Kryo deserialization + of untrusted input is a well-known RCE class unless type registration is locked down. *(documented: + formats exist; default-enabled set + Gryo hardening inferred — §14 Q6)* +- **Reachability preconditions** (the test a triager applies first): + - A finding reachable only by submitting a **script** is in-model only subject to the §9 ruling: if the + deployment allows scripting to the principal, server-side code execution is by-design. *(inferred)* + - A finding in the **Gremlin parser / bytecode / traversal machine / deserializers** reachable from a + client operating within its privileges (or pre-auth) is **in-model** for memory-safety / bounded- + resource robustness. *(inferred)* + - A finding requiring control of `gremlin-server.yaml` or host access is **out-of-model: trusted-input**. + *(inferred)* + +## §5 Assumptions about the environment + +- **Runtime:** JVM (server, core, groovy, driver); the GLVs run on their respective runtimes + (CPython, .NET, Go, Node). *(documented — README / build)* +- **Deployment:** Gremlin Server is assumed to run inside a **trusted network**, fronted by the + application tier, not exposed directly to the public internet. *(inferred — §14 Q1)* +- **Filesystem:** the graph data / config directories are private to the server process and not writable by + untrusted local users. *(inferred)* +- **Concurrency:** the server is multi-threaded and serves concurrent sessions; thread-safety of the + traversal/IO path is a correctness assumption. *(inferred)* +- **What the server does to its host** (negative inventory — predominantly inferred, a confirmation + target): listens on network ports; reads/writes its configured graph + data directories; reads config; + **executes supplied Groovy when script requests are enabled**; loads provider graph implementations the + operator configured. *(inferred — §14 Q4)* + +## §5a Build-time and configuration variants + +Knobs that change which security properties hold (Gremlin Server, `gremlin-server.yaml`): + +- **Authentication** — `Authenticator` (PlainText/SASL with a credentials graph; `Krb5Authenticator` for + Kerberos). Whether authentication is **off by default** (the shipped sample/getting-started configs) is + the key question. *(documented: mechanisms exist; default-off inferred — §14 Q2)* +- **Authorization** — an `Authorizer` interface (e.g. allow-listing which Gremlin a principal may run). + In-model only when configured; default likely none. *(documented: section exists; default inferred — §14 Q2)* +- **TLS/SSL** — configurable on the server connector; whether it is **off by default** drives the + transport-confidentiality property (§9). *(inferred — §14 Q5)* +- **Script execution restriction** — "Protecting Script Execution": sandbox / compilation customizers / + allow-list / preferring bytecode-only. Whether any restriction is on by default, or scripting is + unrestricted out of the box, is the single highest-stakes config question. *(documented: guidance exists; + default inferred — §14 Q4)* +- **Enabled serializers** — which of GraphSON / GraphBinary / **Gryo** are registered by default, and + whether Gryo (Kryo) is locked to registered types. *(documented: formats exist; defaults inferred — §14 Q6)* + +## §6 Assumptions about inputs + +Per-surface trust table *(inferred unless noted)*: + +| Surface | Input | Attacker-controllable? | Caller/operator must enforce | +| --- | --- | --- | --- | +| Gremlin Server — string script request | Groovy/Gremlin script text | **yes** (pre-auth if auth off) | auth; script restriction / sandbox / bytecode-only; who may script | +| Gremlin Server — bytecode/traversal request | serialized traversal bytecode | **yes**, within privileges | auth; traversal-step allow-list; resource limits | +| Request deserialization (GraphSON / GraphBinary / Gryo) | serialized bytes | **yes** (pre-auth) | enabled-serializer choice; Gryo type-registration lockdown | +| Gremlin string parser (`gremlin-language` ANTLR) | Gremlin string | **yes** | parser robustness (no crash/OOM/hang) | +| Driver / GLV — server response | serialized bytes from the server | yes if the server (or a MITM without TLS) is hostile | TLS; trust in the server | +| `gremlin-server.yaml`, host, data dir | local | no — operator-trusted | filesystem permissions | + +- **Shape / rate:** whether Gremlin Server bounds per-request CPU/memory, result-set size, traversal depth, + or concurrent requests — and the line between a bug and operator-managed capacity — is an open item + (§8 resource line, §14 Q7). *(inferred)* + +## §7 Adversary model + +- **Primary adversary:** a network client that can reach the Gremlin Server port from within the deployment + — either **unauthenticated** (if auth is off / pre-auth) or **authenticated with limited privileges** — + trying to execute code on the server (via scripts), read/write graph data outside its intent, crash or + exhaust the server with malformed requests or expensive traversals, or exploit a deserializer. *(inferred + — §14 Q2/Q7)* +- **Capabilities assumed:** can open connections, send arbitrary protocol bytes / scripts / bytecode / + serialized payloads, and supply large/malformed input. *(inferred)* +- **Out of scope:** anyone with operator/host/config control (already authoritative); a client that only + reaches the server because it was directly publicly exposed against guidance (non-supported posture, §3); + side-channel/timing adversaries unless the PMC wants them in. *(inferred — §14 Q1)* + +## §8 Security properties the project provides + +*(Inferred pending PMC confirmation — a property only counts once the project commits to it.)* + +- **Authentication + authorization enforcement (when configured).** With an `Authenticator`/`Authorizer` + set, an unauthenticated or unauthorized client cannot execute requests beyond its grants. *Violation + symptom:* auth/authz bypass. *Severity:* security-critical. *(inferred — §14 Q2)* +- **Memory / availability safety on the request + deserialization surface.** Malformed or pre-auth input + (protocol frames, Gremlin strings, serialized payloads) yields a clean error, not a crash, OOM, hang, or + unbounded allocation of the server. *Violation symptom:* server crash / unbounded allocation / deadlock + from malformed or pre-auth input. *Severity:* security-critical (remote DoS) if pre-auth. *(inferred — + §14 Q7)* +- **Deserializer integrity.** GraphSON / GraphBinary / Gryo reading attacker bytes does not lead to + arbitrary object instantiation / code execution beyond the documented type set. *Violation symptom:* + deserialization gadget / RCE. *Severity:* critical. **The strength of this property for Gryo (Kryo) + depends on type-registration lockdown** — see §9 / §14 Q6. *(inferred)* +- **Resource bounds — split, not unspecified.** Malformed/pre-auth input that crashes/OOMs/hangs the server + is **in-model** (above). Ordinary expensive traversals are **operator capacity/resource management**, NOT + in-model — unless a specific bug applies (super-linear amplification, a missing limit where one is + expected, an unbounded traversal). *(inferred — §14 Q7)* + +## §9 Security properties the project does *not* provide + +*(The highest-value section for integrators — inferred unless tagged; confirm each.)* + +- **Script execution is arbitrary code execution by design — not a sandbox.** When string-script requests + are enabled, the Groovy script engine evaluates attacker-supplied code in the server process. Submitting + a script that runs server-side code is **`BY-DESIGN`** for a principal the deployment permits to script; + it is the operator's job to restrict scripting (bytecode-only, allow-list, sandbox) and to authenticate + who may script. A scan reporting "Gremlin Server allows arbitrary code execution via scripts" is + by-design unless it bypasses a *configured* restriction. *(inferred — §14 Q4)* +- **No transport confidentiality/integrity unless TLS is enabled.** If TLS is off (see §5a/§14 Q5), the + server does not defend against a network attacker reading/modifying client traffic. *(inferred)* +- **No authentication or authorization by default (assumed).** If the shipped/default configuration runs + with auth off, an exposed server is reachable by anyone on the network — an operator deployment concern, + not a code bug. *(inferred — §14 Q2)* +- **Gryo / Kryo deserialization is not a safe boundary against untrusted input** unless type registration is + locked down. Operators who enable Gryo on an untrusted surface own that risk; prefer GraphBinary. *(inferred + — §14 Q6)* +- **Ordinary resource exhaustion is not a defended property.** Expensive traversals / large results that + consume CPU/memory are an operator capacity concern unless a specific bug applies (§8). *(inferred — §14 Q7)* +- **No defense against a malicious operator / host.** *(inferred)* +- **False friends:** + - "Authentication is available" does **not** mean it is **on** — it must be configured (§5a). *(inferred)* + - **Bytecode is safer than scripts but is not a sandbox** — a bytecode traversal still executes traversal + steps server-side; injection/abuse via an embedding app that builds traversals from its own untrusted + input is the embedding app's responsibility. *(inferred)* + - "Gremlin is just a query language" — the **string** form runs through a Groovy engine, so it is code, + not a constrained query. *(inferred)* + +## §10 Downstream responsibilities (operator/deployer) + +*(Inferred unless tagged — confirm.)* + +- Deploy Gremlin Server inside a trusted network; do **not** expose it directly to an untrusted/public + network, especially with scripting enabled and auth off. *(inferred — §14 Q1)* +- Enable authentication (`Authenticator`) and authorization (`Authorizer`) for any non-trivial deployment. + *(inferred — §14 Q2)* +- **Restrict script execution:** prefer bytecode-based traversals; if scripts are needed, apply the + "Protecting Script Execution" controls (sandbox / compilation customizers / allow-list) and restrict who + may submit scripts. *(documented: guidance exists — §14 Q4)* +- Enable TLS where traffic crosses an untrusted segment. *(inferred — §14 Q5)* +- Prefer **GraphBinary**; only enable **Gryo** on a trusted surface, with type registration locked down. + *(inferred — §14 Q6)* +- Apply per-request / per-traversal resource limits and result-size caps appropriate to capacity. *(inferred + — §14 Q7)* +- Set filesystem permissions so only the server user can read the config / data directories. *(inferred)* + +## §11 Known misuse patterns + +*(Draft one-liners — expand before publishing.)* + +- Exposing Gremlin Server to an untrusted network with scripting enabled and authentication off. *(inferred)* +- Treating the script-engine sandbox as a complete RCE boundary rather than restricting who may script. + *(inferred)* +- Building Gremlin (string or bytecode) by concatenating the embedding application's untrusted input + (Gremlin-injection). *(inferred)* +- Enabling Gryo on an untrusted request surface without type-registration lockdown. *(inferred)* + +## §11a Known non-findings (recurring false positives) + +*(Inferred unless tagged; the PMC's confirmations here are the highest-leverage suppression input.)* + +- "Gremlin Server executes arbitrary code via scripts" — by-design when scripting is enabled for the + principal (§9); not a finding unless it bypasses a *configured* restriction. *(inferred — §14 Q4)* +- "No authentication / no TLS by default" — operator deployment responsibility (§9/§10); not a code bug in + itself. *(inferred — §14 Q2/Q5)* +- "Gryo/Kryo deserialization can be exploited" — when the operator enabled Gryo on an untrusted surface; + operator responsibility, prefer GraphBinary (§9/§10). *(inferred — §14 Q6)* +- "Expensive traversal consumes CPU/memory" — operator capacity concern, unless a specific bug applies + (§8/§9). *(inferred — §14 Q7)* +- "Embedding app builds a traversal from untrusted input (Gremlin-injection)" — the embedding app's + responsibility (§9). *(inferred)* +- Findings in `gremlin-test/`, `gremlin-examples/`, tooling — out of scope (§3). *(inferred)* + +## §12 Conditions that would change this model + +- A change to the default auth / TLS / scripting posture (e.g. auth-on-by-default, scripts-off-by-default). +- A new client-reachable surface or protocol on Gremlin Server. +- A change to the default-enabled serializer set, or Gryo type-registration policy. +- Promoting `gremlin-console` / `gremlin-examples` into a production trust surface. +- A change to the OLAP (Hadoop/Spark) trust posture. +- A report that cannot be routed to a single §13 disposition (→ revise the model). + +## §13 Triage dispositions + +| Disposition | Meaning | Licensed by | +| --- | --- | --- | +| `VALID` | Violates a §8 property via an in-scope adversary/input (auth/authz bypass, pre-auth/malformed-input crash/OOM/hang, deserializer RCE beyond the documented type set, parser memory-safety). | §8, §6, §7 | +| `VALID-HARDENING` | No §8 property broken, but a §11 misuse is easy enough to harden. | §11 | +| `OUT-OF-MODEL: trusted-input` | Requires operator/host/config control. | §6, §7 | +| `OUT-OF-MODEL: adversary-not-in-scope` | Requires a capability the model excludes (host control, side channel, direct public exposure against guidance). | §3, §7 | +| `OUT-OF-MODEL: unsupported-component` | Lands in `gremlin-test/`, `gremlin-examples/`, tooling, or a separate provider graph DB. | §3 | +| `OUT-OF-MODEL: non-default-build` | Only manifests under a discouraged/non-default §5a setting (e.g. Gryo enabled on an untrusted surface, scripting unrestricted where the deployment intends bytecode-only). | §5a | +| `BY-DESIGN: property-disclaimed` | Concerns a §9-disclaimed property (script execution within its grant, no-TLS/no-auth default, ordinary resource exhaustion, malicious operator). | §9 | +| `KNOWN-NON-FINDING` | Matches a §11a entry. | §11a | +| `MODEL-GAP` | Cannot be cleanly routed — triggers a §12 revision. | §12 | + +## §14 Open questions for the maintainers + +Every *(inferred)* claim in the body maps to one of these. Proposed answers are inline; please confirm, +correct, or strike. + +1. **Deployment posture.** Is "Gremlin Server inside a trusted network, behind the app tier, not directly + public" the right §2/§5 framing? *Proposed: yes.* +2. **Default auth/authz posture.** Do the shipped/default Gremlin Server configs run with authentication + and authorization **off**, leaving it to the operator to enable? Is an unauthenticated exposed server an + operator-misconfiguration (`OUT-OF-MODEL`) rather than a code bug? *Proposed: yes — auth/authz are + opt-in.* +3. **Provider SPI boundary.** Confirm that vulnerabilities in third-party provider graph databases that + embed TinkerPop route to those providers, not here (this model covers TinkerPop's own code + its use of + the SPI). *Proposed: yes.* +4. **Script execution (highest-stakes).** Confirm that string-script evaluation = server-side code + execution by design, that restricting it (sandbox / allow-list / bytecode-only / who-may-script) is the + operator's responsibility, and what — if any — restriction is **on by default**. Is "Gremlin Server runs + arbitrary code via scripts" `BY-DESIGN` unless a *configured* restriction is bypassed? *Proposed: yes, + by-design; restriction is operator-configured.* +5. **TLS default.** Is TLS **off by default** on the server connector? Is no-TLS-by-default an operator + responsibility (§9/§10)? *Proposed: off by default; operator enables.* +6. **Serialization / Gryo.** Which serializers are registered by default? Is **Gryo (Kryo)** locked to + registered types, and is a Gryo-deserialization finding on an operator-enabled untrusted surface + `OUT-OF-MODEL: non-default-build` (with GraphBinary the recommended default)? Conversely, is a + deserializer flaw in the **default** set `VALID`? *Proposed: prefer GraphBinary; Gryo-on-untrusted is + operator responsibility; a flaw in the default set is VALID.* +7. **Resource line.** Confirm the split: malformed/pre-auth input causing crash/OOM/hang is `VALID`; + ordinary expensive traversals / large results are operator capacity unless a specific bug applies + (super-linear amplification, missing-expected-limit, unbounded traversal/recursion). Are there built-in + per-request limits (timeout, result cap, traversal depth)? *Proposed: split as stated.* +8. **Parser robustness.** Confirm that memory-safety / bounded-resource on the `gremlin-language` ANTLR + parser and the bytecode path against malformed input is a property TinkerPop commits to (§8). *Proposed: + yes.* +9. **GLV (driver) scope.** Are the Gremlin Language Variants (`gremlin-python`/`-dotnet`/`-go`/`-js`) in + scope for this batch, or deferred? They are client-side and deserialize server responses. *Proposed: + in-scope but lower priority; flag if any should be deferred.* +10. **OLAP scope.** Are `hadoop-gremlin` / `spark-gremlin` (`GraphComputer` over a cluster) in scope, or + treated as operator cluster infrastructure out of this model? *Proposed: operator infra; flag if you + want them fully in.* +11. **§11a seeds.** What do scanners/researchers most often report that the PMC considers a non-finding, + beyond the seed list above? *(seeds §11a)* +12. **Canonical location / triage policy.** Confirm this model lives as root `THREAT_MODEL.md` referenced + from a new `SECURITY.md`, wired from `AGENTS.md`, and that the PMC owns revisions. *Proposed: yes.* + +## §15 Machine-readable companion + +Deferred for v0. A `threat-model.yaml` can later encode the §6 trust table, §2/§3 component scoping, §8 +property/severity/symptom rows, §9 false friends, §11a non-findings, and §13 dispositions for automated +triage. From 66660e825d529e065ca98ad0ff00c4d3c816e4f3 Mon Sep 17 00:00:00 2001 From: Ken Hu <106191785+kenhuuu@users.noreply.github.com> Date: Thu, 9 Jul 2026 11:21:59 -0700 Subject: [PATCH 2/4] revisions to threat model --- AGENTS.md | 12 +- SECURITY.md | 26 +- THREAT_MODEL.md | 680 ++++++++++++++++++++++++++++++++---------------- 3 files changed, 472 insertions(+), 246 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 186da57b900..04ddbad2e15 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -25,6 +25,12 @@ Apache TinkerPop is licensed under Apache License 2.0. Contributions must meet t * *The contributor remains responsible for what they submit.* Review generated output for licensing, correctness, and style before committing. +## Security + +For Apache TinkerPop's threat model — trust boundaries, in-scope / out-of-scope, the security properties +the project does and does not provide, and known non-findings — see [SECURITY.md](SECURITY.md), which +points to [THREAT_MODEL.md](THREAT_MODEL.md). Consult it before triaging or reporting security issues. + *** ## 1. Project overview @@ -242,9 +248,3 @@ If AGENTS.md does not clearly cover a situation: This file is intended to help tools act like a careful, well‑informed contributor. When in doubt, defer to human judgment and the canonical project documentation. - -## Security - -For Apache TinkerPop's threat model — trust boundaries, in-scope / out-of-scope, the security properties -the project does and does not provide, and known non-findings — see [SECURITY.md](SECURITY.md), which -points to [THREAT_MODEL.md](THREAT_MODEL.md). Consult it before triaging or reporting security issues. diff --git a/SECURITY.md b/SECURITY.md index 825da471588..a22ee8eb3ae 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -1,18 +1,20 @@ # Security Policy diff --git a/THREAT_MODEL.md b/THREAT_MODEL.md index cdea5cae823..61caf196982 100644 --- a/THREAT_MODEL.md +++ b/THREAT_MODEL.md @@ -1,18 +1,20 @@ # Apache TinkerPop — Threat Model (v0 draft) @@ -20,266 +22,460 @@ limitations under the License. ## §1 Header - **Project:** Apache TinkerPop (`apache/tinkerpop`) — a graph computing framework: the Gremlin query - language, the traversal machine, Gremlin Server (remote query execution), the GraphSON / Gryo / - GraphBinary serialization formats, and the Gremlin Language Variants (Java, Python, .NET, Go, JS). + language, the traversal machine, Gremlin Server (remote query execution), the GraphSON and GraphBinary + wire serializers and the Gryo IO format, and the Gremlin Language Variants (Java, Python, .NET, Go, JS). *(documented — `README.md`, repo layout)* -- **Scope of this model:** the **`apache/tinkerpop` monorepo**, active branches `master`, `3.7-dev`, - `3.8-dev` *(maintainer — colegreer, scope confirmation)*. The model focuses on the network-facing and - deserialization surfaces (see §2); provider graph databases that embed TinkerPop are out of scope (§3). -- **Date:** 2026-06-05. **Status:** DRAFT v0 — first pass by the ASF Security team via the +- **Scope of this model:** the **`apache/tinkerpop` monorepo**, active branch `3.7-dev`. The model focuses on + the network-facing and deserialization surfaces (see §2). Provider graph databases that make use of + reference code are out of scope (§3). +- **Date:** 2026-06-05. **Status:** DRAFT v0, a first pass by the ASF Security team via the threat-model-producer rubric, for the TinkerPop PMC to react to. **Author:** ASF Security team, for PMC ratification. -- **Version binding:** versioned with the project; a report against release *N* is triaged against the +- **Version binding:** versioned with the project. A report against release *N* is triaged against the model as it stood at *N*, not against `HEAD`. - **Reporting cross-reference:** §8-property violations → report privately per the ASF process - (`security@apache.org`); §3 / §9 findings are closed citing this document. -- **Provenance legend:** *(documented)* = stated in TinkerPop's own docs / reference / source; - *(maintainer)* = confirmed by a TinkerPop PMC member through this process; *(inferred)* = reasoned from - the reference docs, architecture, and graph-server domain knowledge, **not yet confirmed** — every + (`security@apache.org`). §3 / §9 findings are closed citing this document. +- **Provenance legend:** *(documented)* = stated in TinkerPop's own docs / reference / source. + *(maintainer)* = confirmed by a TinkerPop PMC member through this process. *(inferred)* = reasoned from + the reference docs, architecture, and graph-server domain knowledge, **not yet confirmed**. Every *(inferred)* claim has a matching §14 open question. -- **Draft confidence:** a v0 with **no PMC confirmation folded in yet** — the deployment posture, the - default auth/TLS stance, the script-execution sandboxing story, and the serialization (Gryo) - disposition are the highest-value items for the PMC to confirm in §14. -- **What TinkerPop is:** TinkerPop is an embeddable graph-computing framework. Applications and graph - databases (providers) embed `gremlin-core` to run Gremlin traversals; **Gremlin Server** exposes that - capability over the network (WebSocket sub-protocol + HTTP), accepting both **string-based Gremlin - scripts** (evaluated by the `gremlin-groovy` Groovy script engine) and **bytecode-based traversals** - (the GLVs). *(documented — reference docs)* +- **Draft confidence:** a v0 with **no PMC confirmation folded in yet**. The deployment posture, the + default auth/TLS stance, the script-execution sandboxing story, and the serialization disposition are the + highest-value items for the PMC to confirm in §14. +- **What TinkerPop is:** a graph computing framework for graph databases (OLTP) and graph analytic systems + (OLAP). It **defines** a common interface/language (Gremlin) plus protocols/APIs, and ships + production-ready reference implementations. This includes a reference in-memory graph (TinkerGraph), Gremlin + Server, and the language variants (GLVs). Gremlin traversals run in one of two environments. In + **embedded**, the caller runs traversals directly in their application via `gremlin-core`. In **remote**, + Gremlin is sent over the wire (as **bytecode** or a **string script**) to a server that executes it. + *(documented)* ## §2 Scope and intended use -- **Primary use:** an operator-deployed graph-traversal engine. In production it is typically embedded by - a graph database (a "provider") or run as **Gremlin Server** behind the application tier, on a - **trusted network** *(inferred — confirm deployment posture, §14 Q1)*. Gremlin Server is the main - network-facing trust boundary. +- **Primary use:** TinkerPop runs in two environments (§1). In **embedded**, the caller runs traversals in + its own JVM via `gremlin-core`, so there is no network trust boundary and the caller owns the whole + surface. In **remote**, Gremlin is sent over the wire to a server. This model focuses on the **remote** + case, where **Gremlin Server** is the main network-facing trust boundary. Gremlin Server **is intended to + be deployable on the public internet**, but the shipped `gremlin-server.yaml` is a getting-started/testing + config that **must not be deployed as-is**. A public or otherwise untrusted-facing deployment is expected + to layer on TLS and an authenticator. *(maintainer)* +- **Embedded surface.** Embedded has **no network, transport, auth, or protocol/frame surface** (those exist + only with a server). It exposes the same input-consuming primitives as remote, and only where the caller + feeds them untrusted data: the **`io()` readers** (GraphSON, Gryo, GraphML), the **`gremlin-language` + parser** if untrusted strings are parsed, and **expensive steps** (e.g. `regex`). The responsibility rules + are **identical to remote** because they attach to the primitive, not the environment. *(maintainer)* - **Caller roles** (Gremlin Server is a network service): - **remote client** — connects over the WebSocket sub-protocol or HTTP, submits Gremlin (script or bytecode). Trust level depends entirely on whether the operator enabled authentication, authorization, - and script restrictions. *(inferred — §14 Q2)* - - **embedding application / provider** — links `gremlin-core` in-process and drives traversals directly. - Trusted within the compile-time + configuration boundary. *(inferred)* + and script restrictions, **all off in the shipped default** (`gremlin-server.yaml` has no + `authentication` block, and the default is `AllowAllAuthenticator`, per `gremlin-applications.asciidoc` + "Security"). *(documented)* + - **application / provider** — runs `gremlin-core` directly, whether embedded in its own process or as + Gremlin Server. A provider may also supply the `Authenticator`/`Authorizer` implementations the server + runs. Trusted within the compile-time + configuration boundary. *(maintainer)* - **operator** — runs Gremlin Server, controls `gremlin-server.yaml`, serializers, auth, TLS, the script - engine configuration, and the host. Trusted for the instance. *(inferred)* + engine configuration, and the host. Trusted for the instance. *(maintainer)* -**Component-family table** *(in/out = in/out of this model; all rows inferred unless noted)*: +**Component-family table** *(in/out = in/out of this model)*: | Family | Entry point | Touches outside the process | In model? | | --- | --- | --- | --- | -| **Gremlin Server** (`gremlin-server`) | WebSocket sub-protocol + HTTP request handlers | network (listens), invokes the script engine + traversal machine | **In** *(documented: remote execution endpoint)* | -| **Script engine** (`gremlin-groovy`) | `GremlinGroovyScriptEngine` evaluating string scripts | runs supplied Groovy in-process | **In — central code-execution surface; see §9** *(documented: scripts are evaluated)* | -| **Gremlin language parser** (`gremlin-language`) | ANTLR grammar for string Gremlin (script-engine-free) | — | **In** — parser robustness on untrusted Gremlin strings *(inferred)* | +| **Gremlin Server** (`gremlin-server`) | WebSocket sub-protocol (`RequestMessage`/`ResponseMessage` frames, GraphBinary/GraphSON), the default, plus a non-default HTTP request endpoint (script **and** bytecode) | network (listens), invokes the script engine + traversal machine | **In**, both transports *(documented: remote execution endpoint, channelizers)* | +| **Script engine** (`gremlin-groovy`) | `GremlinGroovyScriptEngine` evaluating string scripts | runs supplied Groovy in-process | **In**, the central code-execution surface (see §9) *(documented: scripts are evaluated)* | +| **Gremlin language parser** (`gremlin-language`) | ANTLR grammar for string Gremlin (script-engine-free) | — | **In**, parser must not crash/hang/OOM on malformed input, nor let a crafted string break out of a literal to inject steps *(maintainer)* | | **Core traversal machine + structure API** (`gremlin-core`) | `GraphTraversal`, strategies, `Vertex`/`Edge` | filesystem (IO formats) | **In** *(documented)* | -| **Serialization** (`gremlin-core`/`gremlin-util`/`gremlin-shaded`) | GraphSON (JSON), GraphBinary, **Gryo (Kryo-based)** readers | deserializes wire/file bytes | **In — deserialization of untrusted bytes; see §9** *(documented: 3 formats; Gryo wraps shaded Kryo)* | -| **Java driver** (`gremlin-driver`) | client connecting to Gremlin Server; deserializes server responses | network (connects) | **In** (client side) *(inferred)* | -| **Gremlin Language Variants** (`gremlin-python`, `-dotnet`, `-go`, `-js`) | build bytecode, serialize/deserialize | network (connect) | **In, lower priority** — client-side; deserialize server responses *(inferred — §14 Q9)* | -| **Reference graph** (`tinkergraph-gremlin`) | in-memory graph implementation | filesystem (persistence option) | **In** — the reference provider *(inferred)* | -| **OLAP** (`hadoop-gremlin`, `spark-gremlin`) | `GraphComputer` over Hadoop/Spark | network, filesystem, cluster | **In if deployed; operator cluster infra** *(inferred — §14 Q10)* | -| **Operator tooling** (`gremlin-console`) | interactive REPL run by the operator | — | **Out** — operator-trusted local tool *(inferred)* | -| `gremlin-test`, `gremlin-examples`, `gremlin-tools`, `gremlin-annotations` | test/build/example code | — | **Out** *(see §3)* | +| **Serialization** (`gremlin-core`/`gremlin-util`/`gremlin-shaded`) | GraphSON + GraphBinary wire readers. Gryo (Kryo) is IO-format-only, not on the wire | deserializes untrusted wire bytes. Gryo only reads IO/file bytes | **In** (see §9) *(documented — `Serializers` enum: GraphSON + GraphBinary)* | +| **Gremlin Language Variants** (Java `gremlin-driver`, `gremlin-python`, `-dotnet`, `-go`, `-js`) | deserialize server responses | network (connect) | **In**, response-deserialization robustness + TLS cert validation. Java shares the JVM serializers. The others have own per-language deserializers not covered by hardening the JVM server *(documented — each GLV ships its own GraphSON/GraphBinary code)* | +| **Reference graph** (`tinkergraph-gremlin`) | in-memory graph, the **shipped default graph**, with optional file persistence via the `io()` readers | filesystem (persistence) | **In**, TinkerPop reference code on the default reachable path, so a defect in unmodified TinkerGraph is `VALID` (§3). Loading a persisted graph uses the same GraphSON/Gryo/GraphML readers (§6 IO surface). A custom `graphFormat` reader is provider code (§3) *(documented — `gremlin-server.yaml` → `tinkergraph-empty.properties`, `AbstractTinkerGraph.loadGraph()`)* | +| **OLAP** (`gremlin-core`/`tinkergraph` computer, `hadoop-gremlin`, `spark-gremlin`) | `GraphComputer`, remote-reachable via `withComputer()` bytecode | network, filesystem, cluster | **In**, reference code (incl. Hadoop/Spark modules). The third-party Hadoop/Spark runtime + cluster config are out (§3) *(documented — `withComputer` is a bytecode source instruction; in-tree modules)* | +| **`gremlin-console`** | local Groovy REPL. `:remote console` submits **arbitrary Groovy scripts** to a server and deserializes responses. `:install` loads plugins (credentials/hadoop/spark) via Grape | network (connects) | Local REPL **Out** (operator-trusted). As a remote client, submitting arbitrary Groovy = server-side RCE **by-design** (§9), subject to server auth. Response-deserialization robustness + TLS cert validation **In** (like a GLV). Plugins are operator-installed code in the operator's own JVM, so the install decision and third-party plugin behavior are **Out** (trusted-input, §3) *(documented — `DriverRemoteAcceptor`, `InstallCommand`/Grape; `gremlin-applications.asciidoc`)* | +| Test-only / example / build modules (e.g. `gremlin-test`, `gremlin-examples`, `gremlin-tools`, `gremlin-annotations`) | test/build/example code | — | **Out** *(see §3)* | ## §3 Out of scope (explicit non-goals) -- **Provider graph databases that embed TinkerPop** (e.g. third-party graph DBs implementing the - structure/`GraphComputer` SPI). A vulnerability in a provider's own code is routed to that provider, not - here; this model covers TinkerPop's own code and its *use* of the SPI. *(inferred — §14 Q3)* -- **The embedding application's own authentication / authorization of its end users.** TinkerPop has no - concept of the embedding application's end users. *(inferred)* +- **Provider code and provider modifications to the reference code.** TinkerPop ships production-ready + **reference code** (`gremlin-core`, `gremlin-server`, `tinkergraph-gremlin`, etc.) that providers + are free to use in whole, in part, or not at all. The boundary is drawn by **what code the defect + reproduces in**, not by where that code runs (provider code typically runs in the same process as TinkerPop + code): + - **In-model:** a defect reproducible in **unmodified TinkerPop reference code**, even when it is running + as part of a provider's product. + - **Out-of-model → route to the provider:** a defect that depends on the **provider's own code or their + modifications** to the reference code, such as a modified `Channelizer`/`OpProcessor`/`GraphManager`, an + added Netty handler, their `Graph`/`GraphComputer` implementation, a **`call()` service they register** + (`ServiceRegistry` is empty by default), or their `Authenticator`/`Authorizer` decision logic. + - **Hard case (→ `MODEL-GAP`, §12/§13):** a defect that reproduces only under a **mix** of reference and + provider code and cannot be cleanly attributed to either. This is not silently dropped to the provider. + It triggers a joint determination and a §12 model revision. + + Triage test: *does it reproduce on the unmodified TinkerPop reference distribution?* If yes, in-model. If + only with provider code/modifications, it is the provider's. If it genuinely can't be told, `MODEL-GAP`. + *(maintainer)* +- **The calling application's own authentication / authorization of its end users.** TinkerPop has no + concept of the calling application's end users. *(maintainer)* - **Attackers who already control the host, the Gremlin Server process, `gremlin-server.yaml`, or the - graph data directory.** They have the operator's authority by definition. *(inferred)* -- **`gremlin-test/`, `gremlin-examples/`, build/distribution tooling, `gremlin-console`** as a production - trust surface. *(inferred)* -- **Confidentiality of data in transit when the operator has not enabled TLS** — see §10; the TLS posture - is the operator's deployment responsibility unless the project claims TLS-by-default (§14 Q5). *(inferred)* + graph data directory.** They have the operator's authority by definition. *(maintainer)* +- **Operator-configured code-execution/side-effect surfaces:** the Script I/O Format (`ScriptInputFormat`/ + `ScriptOutputFormat` run operator-supplied Groovy from HDFS) and `EventStrategy` `MutationListener` + callbacks. The code comes from operator/provider config, not a remote request (though a remote mutating + traversal can *trigger* an already-registered listener). Trusted-input. *(documented — Script I/O Format; + `EventStrategy`)* +- **Any test-only module, any example-only module, and build/distribution tooling** as a production trust + surface (e.g. `gremlin-test`, `gremlin-examples`, `gremlin-tools`). *(maintainer)* +- **Confidentiality of data in transit when the operator has not enabled TLS** (see §10). TLS is **off by + default** (`ssl.enabled: false`), so the TLS posture is the operator's deployment responsibility. + *(documented — `gremlin-server.yaml`, `gremlin-applications.asciidoc` config table)* +- **`neo4j-gremlin`** — deprecated (not compatible past Neo4j 3.4, EOL 2020). *(documented — reference docs)* +- **`sparql-gremlin`** — a standalone SPARQL→Gremlin translator the user invokes in their own application + code. The server does not use it, and it parses SPARQL via third-party Apache Jena, so malformed SPARQL is + the caller's responsibility. *(documented — `gremlin-server` has no `sparql-gremlin` dependency; Jena parser)* +- **`gremlint` (incl. hosted gremlint)** — a client-side Gremlin formatter (parse-and-reprint, no query + execution). When hosted, input stays in the user's own browser (no URL-shared state, no XSS sink, no + backend), so a user is responsible for what they input, and a pathological input only affects their own + browser tab. *(documented — `gremlint/src`, `docs/gremlint`)* ## §4 Trust boundaries and data flow +Data flow for a remote request. The `‖` marks the trust boundary, and everything right of it runs on the server. + +``` + TRUST BOUNDARY + calling app → GLV (build bytecode / ‖ + opaque script) → transport ────────────────▶ ‖ transport decode (HTTP / WS frame) + ‖ → authenticate → deserialize (GraphSON/GraphBinary) + ‖ → op-select → authorize → execute (script engine / + ‖ traversal machine) → graph + storage / io() files + GLV ◀──────── deserialize response ──────────── ‖ → iterate results → encode response +``` + +- Untrusted input crosses left-to-right at the boundary (request bytes). The response path crosses + right-to-left back to the GLV (server-response bytes are untrusted from the client's standpoint, §8). +- The **embedded** environment has no boundary, since the caller runs `gremlin-core` in-process (§2). +- The **`io()` / file surface** feeds the graph/storage box from disk, untrusted only if the caller loads + untrusted files (§6). + - **Primary trust boundary: the Gremlin Server remote request surface.** Bytes arriving over the WebSocket - sub-protocol or HTTP — whether a **string script** or **bytecode traversal**, plus the serialized request - payload — are untrusted. The script engine, traversal machine, and structure/storage layer sit behind - this boundary. *(documented: remote endpoint; trust posture inferred — §14 Q2)* + sub-protocol or HTTP are untrusted, whether a **string script** or **bytecode traversal**, along with the + serialized request payload. The script engine, traversal machine, and structure/storage layer sit behind + this boundary. There are two in-scope transports. The **WebSocket sub-protocol** (default) frames + `RequestMessage`/`ResponseMessage`, so the **WebSocket protocol layer and frame decoder are a pre-auth + surface distinct from payload deserialization** — the whole WS state machine (control frames, + fragmentation, handshake), not just the sub-protocol payload, must be safe (§8 memory-safety covers it). + **HTTP** (non-default) is a second request transport carrying script *and* bytecode, in-model as TinkerPop + code, and since it carries scripts the §9 by-design RCE ruling applies to it too. *(documented — + channelizers, remote endpoint)* - **The script-execution boundary (the highest-stakes one).** A string-based request is evaluated by the Groovy script engine. Absent a configured sandbox / allow-list, **evaluating an attacker-supplied script - is arbitrary code execution on the server** — this is inherent to the script-based interface, not a bug - (§9). The question for triage is whether a given deployment restricts scripting (bytecode-only, - allow-list, sandbox) and who is authorized to submit scripts. *(documented: scripts are evaluated; - sandbox specifics inferred — §14 Q4)* -- **The deserialization boundary.** GraphSON, GraphBinary, and Gryo readers parse untrusted request bytes - (and, on the driver/GLV side, untrusted server-response bytes). Gryo is Kryo-based; Kryo deserialization - of untrusted input is a well-known RCE class unless type registration is locked down. *(documented: - formats exist; default-enabled set + Gryo hardening inferred — §14 Q6)* + is arbitrary code execution on the server**. This is inherent to the script-based interface, not a bug + (§9). **No script restriction is on by default.** The shipped `gremlin-server.yaml` runs `gremlin-groovy` + with no sandbox, and the sandbox controls (`GroovyCompilerGremlinPlugin` with `COMPILE_STATIC` + + `SimpleSandboxExtension`, `timedInterrupt`) appear only in `gremlin-server-secure.yaml`. The question for + triage is whether a given deployment restricts scripting (bytecode-only, allow-list, sandbox, or the + Groovy-free `GremlinLangScriptEngine`) and who is authorized to submit scripts. *(documented — + `gremlin-server.yaml` vs. `gremlin-server-secure.yaml`, `gremlin-applications.asciidoc` "Protecting Script + Execution")* +- **The deserialization boundary.** GraphSON and GraphBinary readers parse untrusted request bytes (and, on + the driver/GLV side, untrusted server-response bytes). Gryo is IO-only (not on the wire). *(documented — + wire set is GraphSON + GraphBinary)* - **Reachability preconditions** (the test a triager applies first): - A finding reachable only by submitting a **script** is in-model only subject to the §9 ruling: if the - deployment allows scripting to the principal, server-side code execution is by-design. *(inferred)* + deployment allows scripting to the principal, server-side code execution is by-design. *(documented)* - A finding in the **Gremlin parser / bytecode / traversal machine / deserializers** reachable from a client operating within its privileges (or pre-auth) is **in-model** for memory-safety / bounded- - resource robustness. *(inferred)* + resource robustness. *(maintainer)* - A finding requiring control of `gremlin-server.yaml` or host access is **out-of-model: trusted-input**. - *(inferred)* + *(maintainer)* ## §5 Assumptions about the environment -- **Runtime:** JVM (server, core, groovy, driver); the GLVs run on their respective runtimes +- **Runtime:** JVM (server, core, groovy, driver). The GLVs run on their respective runtimes (CPython, .NET, Go, Node). *(documented — README / build)* -- **Deployment:** Gremlin Server is assumed to run inside a **trusted network**, fronted by the - application tier, not exposed directly to the public internet. *(inferred — §14 Q1)* +- **Deployment:** Gremlin Server is safe enough to run on the public internet **once hardened** beyond the + shipped `gremlin-server.yaml`, at minimum with TLS plus an authenticator (either built-in mechanism + suffices over TLS). The insecure getting-started default is a testing convenience, not a supported + production posture. Realistically, a production deployment also layers a provider's + `Authenticator`/`Authorizer` on top (no authorizer ships, see §5a), so auth/authz is a **shared + responsibility**. The provider supplies the policy (the plugin implementations), while Gremlin Server owns + the enforcement machinery that runs them on every request (§8). *(maintainer)* - **Filesystem:** the graph data / config directories are private to the server process and not writable by - untrusted local users. *(inferred)* -- **Concurrency:** the server is multi-threaded and serves concurrent sessions; thread-safety of the - traversal/IO path is a correctness assumption. *(inferred)* -- **What the server does to its host** (negative inventory — predominantly inferred, a confirmation - target): listens on network ports; reads/writes its configured graph + data directories; reads config; - **executes supplied Groovy when script requests are enabled**; loads provider graph implementations the - operator configured. *(inferred — §14 Q4)* + untrusted local users. Securing the host is the operator's responsibility, and an attacker who can write + them already has the operator's authority (§3). *(maintainer)* +- **Concurrency:** the standard request path is isolated per request (thread-pool dispatch, per-request + script bindings), so one request should not affect another. Sessions are the deliberate stateful + exception (§11b). *(maintainer)* +- **What the server does to its host** (negative inventory): listens on network ports; reads/writes its + configured graph + data directories; reads config; **executes supplied Groovy when script requests are + enabled** *(documented — script evaluation, `gremlin-applications.asciidoc`)*; loads provider graph + implementations the operator configured. *(maintainer)* ## §5a Build-time and configuration variants Knobs that change which security properties hold (Gremlin Server, `gremlin-server.yaml`): -- **Authentication** — `Authenticator` (PlainText/SASL with a credentials graph; `Krb5Authenticator` for - Kerberos). Whether authentication is **off by default** (the shipped sample/getting-started configs) is - the key question. *(documented: mechanisms exist; default-off inferred — §14 Q2)* -- **Authorization** — an `Authorizer` interface (e.g. allow-listing which Gremlin a principal may run). - In-model only when configured; default likely none. *(documented: section exists; default inferred — §14 Q2)* -- **TLS/SSL** — configurable on the server connector; whether it is **off by default** drives the - transport-confidentiality property (§9). *(inferred — §14 Q5)* -- **Script execution restriction** — "Protecting Script Execution": sandbox / compilation customizers / - allow-list / preferring bytecode-only. Whether any restriction is on by default, or scripting is - unrestricted out of the box, is the single highest-stakes config question. *(documented: guidance exists; - default inferred — §14 Q4)* -- **Enabled serializers** — which of GraphSON / GraphBinary / **Gryo** are registered by default, and - whether Gryo (Kryo) is locked to registered types. *(documented: formats exist; defaults inferred — §14 Q6)* +- **Authentication** — `Authenticator` (PlainText/SASL against a credentials graph via `SimpleAuthenticator`, + or `Krb5Authenticator` for Kerberos). Authentication is **off by default** (the shipped `gremlin-server.yaml` + has no `authentication` block, and the default is `AllowAllAuthenticator`). Both built-in mechanisms, + username/password (`SimpleAuthenticator`) and Kerberos (`Krb5Authenticator`), are **secure enough for a + public-internet deployment when combined with TLS**. In practice, though, deployments layer a provider's + own `Authenticator` on top (e.g. SSO / bearer-token), so the built-ins are more often a baseline than the + production mechanism. *(documented — `SimpleAuthenticator` + `Krb5Authenticator` ship; auth off by default)* +- **Authorization** — an `Authorizer` SPI exists, but **no implementation ships** with Gremlin Server (the + only one in-tree, `AllowListAuthorizer`, is a test example), so there is **no meaningful authorization + control out of the box**, and providing it is expected to fall to the provider. Authorization is + also **only feasible for bytecode requests**, not script requests (a script request gets full access to + the execution environment, §9). In-model only when configured, and the default is none. *(documented — + `gremlin-applications.asciidoc` "Authorization": no impl ships, bytecode-only)* +- **Restricting traversals via `TraversalStrategy`** — an operator/`Authorizer` can constrain a + `GraphTraversalSource` with `ReadOnlyStrategy` (block mutations), `SubgraphStrategy`/`PartitionStrategy` + (scope visible data / tenancy), and `VertexProgramDenyStrategy` (block OLAP). These are **traversal-layer, + not storage-layer**: `PartitionStrategy` is bypassable via direct `Vertex.property()`, and + `ReservedKeysVerificationStrategy` does not cover `mergeV`/`mergeE`. In-model only when configured. + *(documented — `the-traversal.asciidoc` "TraversalStrategy")* +- **TLS/SSL** — configurable on the server connector, **off by default** (`ssl.enabled: false` in the + shipped `gremlin-server.yaml`, enabled only in `gremlin-server-secure.yaml`). Drives the + transport-confidentiality property (§9). *(documented)* +- **Audit logging** — `enableAuditLog` records the authenticated user, remote address, and gremlin query. + It is **off by default** (`false`, "for privacy reasons") and absent from the shipped config, so a default + deployment keeps no attribution record (§9). *(documented — `gremlin-applications.asciidoc` config table; + `Settings.enableAuditLog`)* +- **Script execution restriction** — sandbox / compilation customizers / allow-list / preferring + bytecode-only (plus `LambdaRestrictionStrategy` to close the bytecode-lambda vector) / the Groovy-free + `GremlinLangScriptEngine`. **Scripting and bytecode lambdas are unrestricted out of the box:** the shipped + `gremlin-server.yaml` configures no sandbox and no lambda restriction, and the sandbox controls ship only in + `gremlin-server-secure.yaml`. The docs are explicit that TinkerPop offers no complete out-of-the-box + protection against nefarious scripts. *(documented — `gremlin-applications.asciidoc` "Protecting Script + Execution", the two sample configs)* +- **Enabled serializers** — wire set is GraphSON 3.0 + GraphBinary. Gryo is IO-format-only, not on the wire, + and defaults to a locked registration allow-list (`registrationRequired=true`). Disabling that lock removes + the untrusted-input protection (§9). *(documented — sample configs, `gremlin-applications.asciidoc` + "Serialization")* ## §6 Assumptions about inputs -Per-surface trust table *(inferred unless noted)*: +Per-surface trust table: | Surface | Input | Attacker-controllable? | Caller/operator must enforce | | --- | --- | --- | --- | | Gremlin Server — string script request | Groovy/Gremlin script text | **yes** (pre-auth if auth off) | auth; script restriction / sandbox / bytecode-only; who may script | | Gremlin Server — bytecode/traversal request | serialized traversal bytecode | **yes**, within privileges | auth; traversal-step allow-list; resource limits | -| Request deserialization (GraphSON / GraphBinary / Gryo) | serialized bytes | **yes** (pre-auth) | enabled-serializer choice; Gryo type-registration lockdown | -| Gremlin string parser (`gremlin-language` ANTLR) | Gremlin string | **yes** | parser robustness (no crash/OOM/hang) | -| Driver / GLV — server response | serialized bytes from the server | yes if the server (or a MITM without TLS) is hostile | TLS; trust in the server | +| Gremlin Server — session id (`SessionOpProcessor`) | client-supplied session string | **yes** | keyed by the string with no owning-user check, so any client presenting the id shares the session (see §11b) | +| Request deserialization (GraphSON / GraphBinary) | serialized bytes | **yes** (pre-auth) | robustness of the wire serializers | +| Graph IO — Gryo/GraphSON/GraphML files (`io()` step, persistence, OLAP) | on-disk / cluster bytes | only if the caller loads untrusted files | GraphSON, locked Gryo (`registrationRequired=true`), and GraphML with the default XML factory owe deserializer integrity. Unlocked Gryo and a caller-supplied unhardened `XMLInputFactory` (XXE) are the caller's responsibility | +| Gremlin string parser (`gremlin-language` ANTLR) | Gremlin string | **yes** | parser robustness, no crash/hang/OOM on malformed input and no grammar breakout / step injection (distinct from execution cost, §8/Q7) | +| Any string the grammar accepts as an argument (e.g. a `regex` pattern) | Gremlin string / bytecode | **yes** | a grammatically valid string must not enable DoS (e.g. ReDoS via a pathological pattern), the Q7 super-linear-amplification carve-out (§8) | +| GLV (client) — server response | serialized bytes from the server | yes if the server is malicious/compromised, or a MITM (TLS off / cert not validated) | response-deserialization robustness; TLS with cert validation | | `gremlin-server.yaml`, host, data dir | local | no — operator-trusted | filesystem permissions | -- **Shape / rate:** whether Gremlin Server bounds per-request CPU/memory, result-set size, traversal depth, - or concurrent requests — and the line between a bug and operator-managed capacity — is an open item - (§8 resource line, §14 Q7). *(inferred)* +- **Shape / rate:** Gremlin Server has per-request limits (`evaluationTimeout`, `maxContentLength`, + `maxParameters`, `maxWorkQueueSize`, session timeouts) but **no traversal-depth or result-count cap**. + The bug-vs-capacity line is the §8 resource split. *(documented — `gremlin-applications.asciidoc` config + table)* +- **Script-cache growth:** a client submitting many distinct, unparameterized scripts grows the compiled + script / global-function cache toward `OutOfMemoryError`. The operator mitigates by bounding + `classMapCacheSpecification` and preferring parameterized scripts. This is the Q7 resource split (a + documented remote DoS unless bounded). *(documented — `gremlin-applications.asciidoc` "Cache Management")* ## §7 Adversary model -- **Primary adversary:** a network client that can reach the Gremlin Server port from within the deployment - — either **unauthenticated** (if auth is off / pre-auth) or **authenticated with limited privileges** — - trying to execute code on the server (via scripts), read/write graph data outside its intent, crash or - exhaust the server with malformed requests or expensive traversals, or exploit a deserializer. *(inferred - — §14 Q2/Q7)* -- **Capabilities assumed:** can open connections, send arbitrary protocol bytes / scripts / bytecode / - serialized payloads, and supply large/malformed input. *(inferred)* -- **Out of scope:** anyone with operator/host/config control (already authoritative); a client that only - reaches the server because it was directly publicly exposed against guidance (non-supported posture, §3); - side-channel/timing adversaries unless the PMC wants them in. *(inferred — §14 Q1)* +- **Client → server (primary):** the highest-stakes surface (most code, most exposure). A network client + that can reach the Gremlin Server port from within the deployment, either **unauthenticated** (if auth is + off / pre-auth) or **authenticated with limited privileges**, trying to execute code on the server (via + scripts), read/write graph data outside its intent, crash or exhaust the server with malformed requests or + expensive traversals, or exploit a deserializer. *Capabilities:* can open connections, send arbitrary + protocol bytes / scripts / bytecode / serialized payloads, and supply large/malformed input. *(maintainer)* +- **Server → client:** a malicious or compromised server, or a network attacker on the wire when TLS is off, + targeting a GLV through the response it returns — crafted response bytes to crash/OOM/RCE the client, or a + server-issued auth challenge to harvest the client's credentials (§8/§9). *(maintainer)* +- **Untrusted input → embedded:** in the embedded environment there is no network client, but untrusted data + can still reach an in-process `gremlin-core` primitive through the calling application — an `io()` reader + fed an untrusted file, the `gremlin-language` parser fed an untrusted string, or an expensive step fed a + crafted argument. The relevant robustness properties (§8) apply identically, since they attach to the + primitive, not the environment (§2/§6). *(maintainer)* +- **Out of scope:** anyone with operator/host/config control (already authoritative). Also an attacker who + reaches a server left on the **insecure getting-started default** (no TLS, no auth), which is an operator + hardening failure (§9/§10), not a code bug, though the pre-auth robustness properties (§8) still apply to + the exposed surface. Side-channel/timing adversaries are out unless the PMC wants them in. *(maintainer — + public exposure of a hardened server is in scope, whereas exposure of the insecure default is an operator + failure)* ## §8 Security properties the project provides *(Inferred pending PMC confirmation — a property only counts once the project commits to it.)* +**Assets these properties defend (what is being protected):** + +- **Server availability** — the Gremlin Server process staying up under malformed or pre-auth input. +- **Server-side code-execution / host integrity** — not running attacker code beyond what scripting/lambdas + grant by design (§9). +- **Graph data confidentiality and integrity** — read/write/delete of the graph, bounded by auth/authz and + traversal strategies (§5a/§9). +- **Credentials** — the server-side credential store and the credentials a client presents (§5a/§9). +- **Client (GLV) integrity** — a client not harmed by hostile server responses. + +**Properties:** + - **Authentication + authorization enforcement (when configured).** With an `Authenticator`/`Authorizer` set, an unauthenticated or unauthorized client cannot execute requests beyond its grants. *Violation - symptom:* auth/authz bypass. *Severity:* security-critical. *(inferred — §14 Q2)* + symptom:* auth/authz bypass. *Severity:* security-critical. *(maintainer)* - **Memory / availability safety on the request + deserialization surface.** Malformed or pre-auth input (protocol frames, Gremlin strings, serialized payloads) yields a clean error, not a crash, OOM, hang, or - unbounded allocation of the server. *Violation symptom:* server crash / unbounded allocation / deadlock - from malformed or pre-auth input. *Severity:* security-critical (remote DoS) if pre-auth. *(inferred — - §14 Q7)* -- **Deserializer integrity.** GraphSON / GraphBinary / Gryo reading attacker bytes does not lead to - arbitrary object instantiation / code execution beyond the documented type set. *Violation symptom:* - deserialization gadget / RCE. *Severity:* critical. **The strength of this property for Gryo (Kryo) - depends on type-registration lockdown** — see §9 / §14 Q6. *(inferred)* -- **Resource bounds — split, not unspecified.** Malformed/pre-auth input that crashes/OOMs/hangs the server - is **in-model** (above). Ordinary expensive traversals are **operator capacity/resource management**, NOT - in-model — unless a specific bug applies (super-linear amplification, a missing limit where one is - expected, an unbounded traversal). *(inferred — §14 Q7)* + unbounded allocation of the server. This covers not just the sub-protocol payload but the **transport + layer itself**: any valid WebSocket frame (ping/pong/continuation/close, fragmentation, the upgrade + handshake) or HTTP request, however crafted or sequenced, must be handled safely, with no path to access + or DoS the server. *Violation symptom:* server crash / unbounded allocation / deadlock from malformed or + pre-auth input, or a crafted frame sequence. *Severity:* security-critical (remote DoS) if pre-auth. + *(maintainer)* +- **Parser integrity (`gremlin-language`).** A crafted Gremlin string cannot break out of a string literal + to inject additional traversal steps. This is the safe string-to-traversal path, distinct from building a + Groovy string by concatenation, which is the calling application's concern (§9). *Violation symptom:* + grammar breakout / step injection from a value that should stay a literal. *Severity:* critical. + *(maintainer)* +- **Deserializer integrity.** The wire deserializers (GraphSON, GraphBinary) and **Gryo in its locked default + (`registrationRequired=true`)** reading attacker bytes do not lead to arbitrary object instantiation / code + execution beyond the registered type set. Because `inject()` and value arguments let a request carry any + supported type, a bug in a **registered** type's (de)serializer that crashes/OOMs the reader is also + in-model, on **both** the server (request) and the GLV (response) side. The GraphML reader disables + external entities and DTDs by default (XXE-safe). *Violation symptom:* deserialization gadget / RCE / XXE, + or a registered-type serializer crashing/OOMing either end. *Severity:* critical. Gryo is not on the wire, + and unlocked Gryo or a caller-supplied unhardened XML factory is out-of-model (user responsibility, §9). + *(documented)* +- **Resource bounds — split.** Malformed/pre-auth input that crashes/OOMs/hangs the server is **in-model** + (above). Ordinary expensive traversals / large results are **operator capacity**, NOT in-model, unless a + specific bug applies (super-linear amplification, a missing-where-expected limit, an unbounded traversal). + A grammatically valid string the parser accepts must not itself enable DoS from small input, e.g. ReDoS via + a pathological `regex` pattern, which is the in-model amplification case. *(maintainer)* +- **Client (GLV) robustness against hostile server responses.** A GLV deserializing response bytes from a + malicious/compromised server (or a MITM) does not crash/OOM/execute code. *Violation symptom:* client + crash/OOM/RCE from crafted responses. *Severity:* critical. *(maintainer)* +- **No traversal or script can harm the GLV.** No traversal or script, however malformed or hostile, may + cause code execution, a crash, a hang, or resource exhaustion **in the GLV itself**. How a GLV processes + the input is unconstrained. *Violation symptom:* a traversal or script that harms the GLV that submitted + it. *Severity:* high. *(maintainer)* ## §9 Security properties the project does *not* provide -*(The highest-value section for integrators — inferred unless tagged; confirm each.)* - -- **Script execution is arbitrary code execution by design — not a sandbox.** When string-script requests - are enabled, the Groovy script engine evaluates attacker-supplied code in the server process. Submitting - a script that runs server-side code is **`BY-DESIGN`** for a principal the deployment permits to script; - it is the operator's job to restrict scripting (bytecode-only, allow-list, sandbox) and to authenticate - who may script. A scan reporting "Gremlin Server allows arbitrary code execution via scripts" is - by-design unless it bypasses a *configured* restriction. *(inferred — §14 Q4)* -- **No transport confidentiality/integrity unless TLS is enabled.** If TLS is off (see §5a/§14 Q5), the - server does not defend against a network attacker reading/modifying client traffic. *(inferred)* +*(The highest-value section for integrators. Inferred unless tagged, so confirm each.)* + +- **Script execution is arbitrary code execution by design, not a sandbox.** When string-script requests + are enabled, the Groovy script engine evaluates attacker-supplied code in the server process. The docs + state scripts have "access to the full power of their language and the JVM." Submitting a script that runs + server-side code is **`BY-DESIGN`** for a principal the deployment permits to script. It is the operator's + job to restrict scripting (bytecode-only, allow-list, sandbox, or the Groovy-free `GremlinLangScriptEngine`) + and to authenticate who may script. A scan reporting "Gremlin Server allows arbitrary code execution via + scripts" is by-design unless it bypasses a *configured* restriction. *(documented — + `gremlin-applications.asciidoc` "Protecting Script Execution")* +- **No transport confidentiality/integrity unless TLS is enabled.** TLS is off in the shipped default (see + §5a). Until the operator enables it, the server does not defend against a network attacker reading or + modifying client traffic. On the client side, a GLV sends its credentials in response to a server-issued + auth challenge, so without TLS (or against a MITM) a driver can disclose those credentials to a hostile + endpoint. A driver configured without TLS / certificate validation is a misconfiguration and is + out-of-scope. *(documented — TLS off by default)* - **No authentication or authorization by default (assumed).** If the shipped/default configuration runs - with auth off, an exposed server is reachable by anyone on the network — an operator deployment concern, - not a code bug. *(inferred — §14 Q2)* -- **Gryo / Kryo deserialization is not a safe boundary against untrusted input** unless type registration is - locked down. Operators who enable Gryo on an untrusted surface own that risk; prefer GraphBinary. *(inferred - — §14 Q6)* + with auth off, an exposed server is reachable by anyone on the network. This is an operator deployment + concern, not a code bug. *(documented)* +- **No attribution/audit by default.** Audit logging is off by default (§5a), so a default deployment keeps + no record of who ran which query. Enabling it (`enableAuditLog`) is the operator's responsibility (§10). + *(documented)* +- **No built-in data-access control.** There is no element-level (CRUD) authorization on graph data. + Access is bounded only by which traversal sources the operator exposes and by a provider-supplied + `Authorizer` (which does not ship, §5a). A client within its grants can read/write/delete any data the + exposed traversal source reaches. *(documented — no authorizer ships, authz is traversal-source/step-level)* +- **Gryo / Kryo deserializer integrity holds only under the locked default.** Gryo defaults to a registration + allow-list (`registrationRequired=true`), so it is not an arbitrary-instantiation sink, and a break within + that locked config is a `VALID` bug like any deserializer. **Running Gryo unlocked + (`registrationRequired=false`) is not a safe boundary against untrusted bytes and is the user's + responsibility.** (A few registered types use Java native serialization, a gadget caveat even when locked.) + Gryo is not on the wire, so this is an IO/file-surface concern (`io()` step, persistence, OLAP). + *(documented)* - **Ordinary resource exhaustion is not a defended property.** Expensive traversals / large results that - consume CPU/memory are an operator capacity concern unless a specific bug applies (§8). *(inferred — §14 Q7)* -- **No defense against a malicious operator / host.** *(inferred)* + consume CPU/memory are an operator capacity concern unless a specific bug applies (§8). *(maintainer)* +- **No defense against a malicious operator / host.** *(maintainer)* - **False friends:** - - "Authentication is available" does **not** mean it is **on** — it must be configured (§5a). *(inferred)* - - **Bytecode is safer than scripts but is not a sandbox** — a bytecode traversal still executes traversal - steps server-side; injection/abuse via an embedding app that builds traversals from its own untrusted - input is the embedding app's responsibility. *(inferred)* - - "Gremlin is just a query language" — the **string** form runs through a Groovy engine, so it is code, - not a constrained query. *(inferred)* + - "Authentication is available" does **not** mean it is **on**. It must be configured (§5a). *(maintainer)* + - **Bytecode is safer than scripts but is not a sandbox.** A bytecode traversal still executes traversal + steps server-side. Injection/abuse via a calling application that builds a Gremlin string by concatenating + its own untrusted input (e.g. into Groovy) is the calling application's responsibility. Note this is + distinct from a `gremlin-language` parser breakout, which is an in-model TinkerPop bug (§8). *(maintainer)* + - **Bytecode-only is not RCE-free unless lambdas are restricted.** A bytecode traversal can carry a + **string lambda** that is script-evaluated server-side by a `GremlinScriptEngine`. `LambdaRestrictionStrategy` + blocks this but is **not enabled by default**, so not enabling it means the operator/provider accepts the + code-execution risk (`BY-DESIGN`, like scripting, §9). *(documented — `Lambda`, `LambdaRestrictionStrategy`)* + - "Gremlin is just a query language" is misleading. The **string** form can run through a Groovy engine, so + it is code, not a constrained query. *(maintainer)* ## §10 Downstream responsibilities (operator/deployer) -*(Inferred unless tagged — confirm.)* +*(Inferred unless tagged. Confirm.)* -- Deploy Gremlin Server inside a trusted network; do **not** expose it directly to an untrusted/public - network, especially with scripting enabled and auth off. *(inferred — §14 Q1)* +- Do **not** deploy the shipped getting-started `gremlin-server.yaml` as-is. Before exposing Gremlin Server + on a public/untrusted network, harden it: enable TLS and an authenticator, and (given no authorizer ships) + supply an `Authorizer`, especially if scripting is enabled. *(maintainer)* - Enable authentication (`Authenticator`) and authorization (`Authorizer`) for any non-trivial deployment. - *(inferred — §14 Q2)* -- **Restrict script execution:** prefer bytecode-based traversals; if scripts are needed, apply the - "Protecting Script Execution" controls (sandbox / compilation customizers / allow-list) and restrict who - may submit scripts. *(documented: guidance exists — §14 Q4)* -- Enable TLS where traffic crosses an untrusted segment. *(inferred — §14 Q5)* -- Prefer **GraphBinary**; only enable **Gryo** on a trusted surface, with type registration locked down. - *(inferred — §14 Q6)* -- Apply per-request / per-traversal resource limits and result-size caps appropriate to capacity. *(inferred - — §14 Q7)* -- Set filesystem permissions so only the server user can read the config / data directories. *(inferred)* + *(documented)* +- **Restrict script execution:** prefer bytecode-based traversals (or the Groovy-free + `GremlinLangScriptEngine`). If Groovy scripts are needed, apply the "Protecting Script Execution" controls + (sandbox / compilation customizers / allow-list) and restrict who may submit scripts. **For bytecode, + enable `LambdaRestrictionStrategy`**, since bytecode-only is not RCE-free while string lambdas are allowed + (§9). *(documented)* +- Enable TLS (off by default) where traffic crosses an untrusted segment. *(documented)* +- Enable audit logging (`enableAuditLog`, off by default) where attribution of requests is needed. *(documented)* +- Use the wire serializers (GraphBinary recommended for drivers). Keep Gryo locked + (`registrationRequired=true`), and do not read untrusted files through **unlocked** Gryo. *(documented)* +- Apply per-request resource limits (`evaluationTimeout`, `maxContentLength`, etc.) appropriate to capacity. + *(documented)* +- Set filesystem permissions so only the server user can read the config / data directories. *(maintainer)* ## §11 Known misuse patterns -*(Draft one-liners — expand before publishing.)* +*(Draft one-liners, expand before publishing.)* -- Exposing Gremlin Server to an untrusted network with scripting enabled and authentication off. *(inferred)* +- Exposing the shipped getting-started default (no TLS, no auth) on an untrusted network instead of + hardening it first, especially with scripting enabled. *(maintainer)* - Treating the script-engine sandbox as a complete RCE boundary rather than restricting who may script. - *(inferred)* -- Building Gremlin (string or bytecode) by concatenating the embedding application's untrusted input - (Gremlin-injection). *(inferred)* -- Enabling Gryo on an untrusted request surface without type-registration lockdown. *(inferred)* + *(maintainer)* +- Building Gremlin by concatenating the calling application's untrusted input into a Groovy string + (Gremlin-injection). This is distinct from a `gremlin-language` parser breakout, which is in-model (§8). + *(maintainer)* +- Reading untrusted files through **unlocked** Gryo (`registrationRequired=false`). *(documented)* ## §11a Known non-findings (recurring false positives) -*(Inferred unless tagged; the PMC's confirmations here are the highest-leverage suppression input.)* +*(Inferred unless tagged. The PMC's confirmations here are the highest-leverage suppression input.)* - "Gremlin Server executes arbitrary code via scripts" — by-design when scripting is enabled for the - principal (§9); not a finding unless it bypasses a *configured* restriction. *(inferred — §14 Q4)* -- "No authentication / no TLS by default" — operator deployment responsibility (§9/§10); not a code bug in - itself. *(inferred — §14 Q2/Q5)* -- "Gryo/Kryo deserialization can be exploited" — when the operator enabled Gryo on an untrusted surface; - operator responsibility, prefer GraphBinary (§9/§10). *(inferred — §14 Q6)* + principal (§9), and not a finding unless it bypasses a *configured* restriction. *(documented)* +- "Bytecode lambdas enable code execution" — by-design when `LambdaRestrictionStrategy` is not enabled + (§9), and not a finding unless it bypasses a *configured* restriction. *(documented)* +- "No authentication / no TLS by default" — operator deployment responsibility (§9/§10), not a code bug in + itself. *(documented)* +- "Gryo/Kryo deserialization can be exploited" — Gryo is not on the wire, so a request-path report is a + non-finding. Under the locked default (`registrationRequired=true`) a real break is `VALID`, whereas + exploiting **unlocked** Gryo on untrusted files is the user's responsibility (§9/§10). *(documented)* - "Expensive traversal consumes CPU/memory" — operator capacity concern, unless a specific bug applies - (§8/§9). *(inferred — §14 Q7)* -- "Embedding app builds a traversal from untrusted input (Gremlin-injection)" — the embedding app's - responsibility (§9). *(inferred)* -- Findings in `gremlin-test/`, `gremlin-examples/`, tooling — out of scope (§3). *(inferred)* + (§8/§9). *(maintainer)* +- "Calling application concatenates untrusted input into a Groovy string (Gremlin-injection)" — the calling + application's responsibility (§9). A `gremlin-language` parser breakout is the opposite: an in-model + TinkerPop bug (§8). *(maintainer)* +- Findings in any test-only module, any module containing examples, or build tooling (e.g. `gremlin-test`, + `gremlin-examples`, `gremlin-tools`) — out of scope (§3). *(maintainer)* + +## §11b Known problems + +*(Acknowledged, accepted weaknesses of Gremlin Server as it works today.)* + +- **Sessions can be shared.** A session (`SessionOpProcessor`) is identified by a client-supplied string and + keyed with **no owning-user check**, so any client presenting the id accesses the session's state. Sessions + predate authentication and exist even with no users. The default server provides no per-user isolation, and + this is accepted as how Gremlin Server works. A deployment needing isolation must use a provider that adds + it. *(documented — `SessionOpProcessor`, `Session`)* ## §12 Conditions that would change this model - A change to the default auth / TLS / scripting posture (e.g. auth-on-by-default, scripts-off-by-default). - A new client-reachable surface or protocol on Gremlin Server. -- A change to the default-enabled serializer set, or Gryo type-registration policy. -- Promoting `gremlin-console` / `gremlin-examples` into a production trust surface. +- A change to the default-enabled wire serializer set, or the reintroduction of Gryo (or any Kryo-based + format) as a wire serializer. +- Promoting a test-only / example module into a production trust surface. - A change to the OLAP (Hadoop/Spark) trust posture. - A report that cannot be routed to a single §13 disposition (→ revise the model). @@ -287,59 +483,87 @@ Per-surface trust table *(inferred unless noted)*: | Disposition | Meaning | Licensed by | | --- | --- | --- | -| `VALID` | Violates a §8 property via an in-scope adversary/input (auth/authz bypass, pre-auth/malformed-input crash/OOM/hang, deserializer RCE beyond the documented type set, parser memory-safety). | §8, §6, §7 | +| `VALID` | Violates a §8 property via an in-scope adversary/input (auth/authz bypass, pre-auth/malformed-input crash/OOM/hang, deserializer RCE beyond the documented type set, parser memory-safety or grammar breakout / step injection). | §8, §6, §7 | | `VALID-HARDENING` | No §8 property broken, but a §11 misuse is easy enough to harden. | §11 | | `OUT-OF-MODEL: trusted-input` | Requires operator/host/config control. | §6, §7 | -| `OUT-OF-MODEL: adversary-not-in-scope` | Requires a capability the model excludes (host control, side channel, direct public exposure against guidance). | §3, §7 | -| `OUT-OF-MODEL: unsupported-component` | Lands in `gremlin-test/`, `gremlin-examples/`, tooling, or a separate provider graph DB. | §3 | -| `OUT-OF-MODEL: non-default-build` | Only manifests under a discouraged/non-default §5a setting (e.g. Gryo enabled on an untrusted surface, scripting unrestricted where the deployment intends bytecode-only). | §5a | -| `BY-DESIGN: property-disclaimed` | Concerns a §9-disclaimed property (script execution within its grant, no-TLS/no-auth default, ordinary resource exhaustion, malicious operator). | §9 | +| `OUT-OF-MODEL: adversary-not-in-scope` | Requires a capability the model excludes (host control, side channel, or reaching a server left on the insecure getting-started default rather than a hardened deployment). | §3, §7 | +| `OUT-OF-MODEL: unsupported-component` | Lands in a test-only / example module or build tooling, or in provider code / provider modifications to the reference code (including a code-sharing-free remote graph provider). Reproduces only with provider code, not on the unmodified reference distribution. | §3 | +| `OUT-OF-MODEL: non-default-build` | Only manifests under a discouraged/non-default §5a setting (e.g. scripting unrestricted where the deployment intends bytecode-only). | §5a | +| `BY-DESIGN: property-disclaimed` | Concerns a §9-disclaimed property (script or bytecode-lambda execution within its grant, no-TLS/no-auth default, ordinary resource exhaustion, malicious operator). | §9 | | `KNOWN-NON-FINDING` | Matches a §11a entry. | §11a | -| `MODEL-GAP` | Cannot be cleanly routed — triggers a §12 revision. | §12 | +| `MODEL-GAP` | Cannot be cleanly routed, so triggers a §12 revision. | §12 | ## §14 Open questions for the maintainers -Every *(inferred)* claim in the body maps to one of these. Proposed answers are inline; please confirm, -correct, or strike. - -1. **Deployment posture.** Is "Gremlin Server inside a trusted network, behind the app tier, not directly - public" the right §2/§5 framing? *Proposed: yes.* -2. **Default auth/authz posture.** Do the shipped/default Gremlin Server configs run with authentication - and authorization **off**, leaving it to the operator to enable? Is an unauthenticated exposed server an - operator-misconfiguration (`OUT-OF-MODEL`) rather than a code bug? *Proposed: yes — auth/authz are - opt-in.* -3. **Provider SPI boundary.** Confirm that vulnerabilities in third-party provider graph databases that - embed TinkerPop route to those providers, not here (this model covers TinkerPop's own code + its use of - the SPI). *Proposed: yes.* -4. **Script execution (highest-stakes).** Confirm that string-script evaluation = server-side code +1. **Deployment posture.** ~~Is "Gremlin Server inside a trusted network, behind the app tier, not directly + public" the right §2/§5 framing?~~ **RESOLVED (maintainer):** Gremlin Server *is* intended to be + deployable on the public internet, but only after hardening beyond the shipped getting-started + `gremlin-server.yaml`, meaning TLS plus an authenticator (either built-in mechanism is adequate over TLS). + Reaching a server left on the insecure default is an operator hardening failure (`OUT-OF-MODEL`), not + evidence of a trusted-network assumption. Auth/authz is a shared responsibility. Providers typically + supply the `Authenticator`/`Authorizer` policy (no authorizer ships), while Gremlin Server owns the + enforcement machinery (§2/§5/§5a/§7/§8/§10). +2. **Default auth/authz posture.** ~~Do the shipped/default Gremlin Server configs run with authentication + and authorization **off**?~~ **RESOLVED (documented):** yes. `gremlin-server.yaml` has no + `authentication` block (default `AllowAllAuthenticator`) and ships no authorizer. The docs state "Gremlin + Server is configured to allow all requests to be processed (i.e. no authentication)" and "not shipped + with `Authorizer` implementations" (`gremlin-applications.asciidoc` "Security"). Reaching a server left + on that default is an operator hardening failure (`OUT-OF-MODEL`), not a code bug (per §14 Q1). The + framework's duty to *run* a configured authenticator/authorizer remains an in-model §8 property. +3. **Provider boundary.** ~~Confirm that vulnerabilities in third-party provider graph databases route to + those providers.~~ **RESOLVED (maintainer):** the boundary is drawn by **what code the defect reproduces + in**, not where it runs. Unmodified TinkerPop **reference code** (`gremlin-core`, `gremlin-server`, the + reference graph, serializers) is in-model even when running as part of a provider's product. A defect + requiring the **provider's own code or their modifications** routes to the provider. This includes a + **remote graph provider** that shares no TinkerPop code and only conforms to the WebSocket sub-protocol + + serializer specs. A defect reproducible only under a **mix** of the two is `MODEL-GAP` (joint + determination, not silently the provider's). Triage test: *does it reproduce on the unmodified reference + distribution?* (§3). +4. **Script execution (highest-stakes).** ~~Confirm that string-script evaluation = server-side code execution by design, that restricting it (sandbox / allow-list / bytecode-only / who-may-script) is the - operator's responsibility, and what — if any — restriction is **on by default**. Is "Gremlin Server runs - arbitrary code via scripts" `BY-DESIGN` unless a *configured* restriction is bypassed? *Proposed: yes, - by-design; restriction is operator-configured.* -5. **TLS default.** Is TLS **off by default** on the server connector? Is no-TLS-by-default an operator - responsibility (§9/§10)? *Proposed: off by default; operator enables.* -6. **Serialization / Gryo.** Which serializers are registered by default? Is **Gryo (Kryo)** locked to - registered types, and is a Gryo-deserialization finding on an operator-enabled untrusted surface - `OUT-OF-MODEL: non-default-build` (with GraphBinary the recommended default)? Conversely, is a - deserializer flaw in the **default** set `VALID`? *Proposed: prefer GraphBinary; Gryo-on-untrusted is - operator responsibility; a flaw in the default set is VALID.* -7. **Resource line.** Confirm the split: malformed/pre-auth input causing crash/OOM/hang is `VALID`; - ordinary expensive traversals / large results are operator capacity unless a specific bug applies - (super-linear amplification, missing-expected-limit, unbounded traversal/recursion). Are there built-in - per-request limits (timeout, result cap, traversal depth)? *Proposed: split as stated.* -8. **Parser robustness.** Confirm that memory-safety / bounded-resource on the `gremlin-language` ANTLR - parser and the bytecode path against malformed input is a property TinkerPop commits to (§8). *Proposed: - yes.* -9. **GLV (driver) scope.** Are the Gremlin Language Variants (`gremlin-python`/`-dotnet`/`-go`/`-js`) in - scope for this batch, or deferred? They are client-side and deserialize server responses. *Proposed: - in-scope but lower priority; flag if any should be deferred.* -10. **OLAP scope.** Are `hadoop-gremlin` / `spark-gremlin` (`GraphComputer` over a cluster) in scope, or - treated as operator cluster infrastructure out of this model? *Proposed: operator infra; flag if you - want them fully in.* -11. **§11a seeds.** What do scanners/researchers most often report that the PMC considers a non-finding, - beyond the seed list above? *(seeds §11a)* -12. **Canonical location / triage policy.** Confirm this model lives as root `THREAT_MODEL.md` referenced - from a new `SECURITY.md`, wired from `AGENTS.md`, and that the PMC owns revisions. *Proposed: yes.* + operator's responsibility, and what restriction (if any) is **on by default**. Is "Gremlin Server runs + arbitrary code via scripts" `BY-DESIGN` unless a *configured* restriction is bypassed?~~ **RESOLVED + (documented):** yes, by-design, and no restriction on by default (§4/§5a/§9/§10/§11a). +5. **TLS default.** ~~Is TLS **off by default** on the server connector? Is no-TLS-by-default an operator + responsibility (§9/§10)?~~ **RESOLVED (documented):** yes, `ssl.enabled: false` in the shipped + `gremlin-server.yaml`, and the operator enables it (§3/§5a/§9/§10). +6. **Serialization / Gryo.** ~~Which serializers are registered by default?~~ **RESOLVED (documented):** the + wire set is GraphSON 3.0 + GraphBinary. Gryo is IO-format-only (not on the wire) and defaults to a locked + registration allow-list (`registrationRequired=true`). A Gryo request-path report is a non-finding. A + flaw in a wire serializer or in **locked** Gryo is `VALID`, whereas **unlocked** Gryo on untrusted files + is the user's responsibility (§2/§4/§5a/§6/§8/§9/§10/§11a). +7. **Resource line.** ~~Confirm the split, and whether built-in per-request limits exist.~~ **RESOLVED:** + split confirmed *(maintainer)*. Malformed/pre-auth crash/OOM/hang is `VALID`. Ordinary expensive + traversals / large results are operator capacity unless a specific bug applies (super-linear + amplification, missing-where-expected limit, unbounded traversal/recursion). Built-in per-request limits + exist (`evaluationTimeout`, `maxContentLength`, `maxParameters`, `maxWorkQueueSize`, session timeouts), + with **no** traversal-depth or result-count cap *(documented)* (§6/§8/§9/§10/§11a). +8. **Parser robustness.** ~~Confirm parser/bytecode memory-safety against malformed input is a committed + property.~~ **RESOLVED (maintainer):** yes, the `gremlin-language` parser and bytecode path must not + crash/hang/OOM on **malformed input**, and a crafted string must not break out of a literal to inject + steps (`VALID`, §8). This covers parsing robustness and integrity. The *execution* cost of a well-formed + query is the Q7 capacity split, where an accidental/amplified OOM from a small query is in-model but a + legitimately huge query is operator capacity. Injection via Groovy string-concatenation in the calling + application is out-of-model (§9/§11a). +9. **GLV scope.** ~~Are the GLVs in scope, or deferred?~~ **RESOLVED (maintainer):** all GLVs (Java + `gremlin-driver` included, since it is the Java GLV) are in scope. The in-model client surface is + **response-deserialization robustness against a malicious/compromised server or MITM, plus TLS cert + validation** (§8). Outbound bytecode/message construction is **not** a surface, because its input is the + trusted calling application. Feeding untrusted end-user input into a traversal/script is Gremlin-injection + and the calling application's responsibility (§9/§11a). Sending bad bytes *to* the server folds into the + server's §8 request-robustness (§2/§6/§8). +10. **OLAP scope.** ~~Are `hadoop-gremlin` / `spark-gremlin` in scope, or operator infra?~~ **RESOLVED + (maintainer):** OLAP is **in-model as TinkerPop reference code**, covering the `gremlin-core`/`tinkergraph` + computer *and* the `hadoop-gremlin`/`spark-gremlin` reference modules (per the Q3 reference-code rule), + reachable remotely via `withComputer()` bytecode (not via the ANTLR string grammar). Only the + **third-party Hadoop/Spark runtime and the cluster deployment/config** are out-of-model (§3). The Gryo + untrusted-file risk on this IO surface stays as in Q6 (§2/§3/§6). +11. **§11a seeds.** Generalized the test/example/tooling non-finding to a **category** (any test-only or + example module, not a fixed name list). Remains a **standing invitation** for the PMC to append further + recurring false-positives (§11a). +12. **Canonical location.** ~~Confirm this model lives as root `THREAT_MODEL.md`, referenced from + `SECURITY.md`, wired from `AGENTS.md`.~~ **RESOLVED (documented):** verified. Root `THREAT_MODEL.md`, + referenced from `SECURITY.md`, which is wired from `AGENTS.md`. ## §15 Machine-readable companion From 54d57ee6f4ec495d2b1def6127c4784ebea66298 Mon Sep 17 00:00:00 2001 From: Ken Hu <106191785+kenhuuu@users.noreply.github.com> Date: Fri, 10 Jul 2026 08:40:14 -0700 Subject: [PATCH 3/4] More revisions Assisted-by: Claude Code:claude-fable-5 --- THREAT_MODEL.md | 153 +++++++++++++++++++++++++++++++++--------------- 1 file changed, 107 insertions(+), 46 deletions(-) diff --git a/THREAT_MODEL.md b/THREAT_MODEL.md index 61caf196982..40fd145adf3 100644 --- a/THREAT_MODEL.md +++ b/THREAT_MODEL.md @@ -88,7 +88,7 @@ under the License. | **Gremlin Language Variants** (Java `gremlin-driver`, `gremlin-python`, `-dotnet`, `-go`, `-js`) | deserialize server responses | network (connect) | **In**, response-deserialization robustness + TLS cert validation. Java shares the JVM serializers. The others have own per-language deserializers not covered by hardening the JVM server *(documented — each GLV ships its own GraphSON/GraphBinary code)* | | **Reference graph** (`tinkergraph-gremlin`) | in-memory graph, the **shipped default graph**, with optional file persistence via the `io()` readers | filesystem (persistence) | **In**, TinkerPop reference code on the default reachable path, so a defect in unmodified TinkerGraph is `VALID` (§3). Loading a persisted graph uses the same GraphSON/Gryo/GraphML readers (§6 IO surface). A custom `graphFormat` reader is provider code (§3) *(documented — `gremlin-server.yaml` → `tinkergraph-empty.properties`, `AbstractTinkerGraph.loadGraph()`)* | | **OLAP** (`gremlin-core`/`tinkergraph` computer, `hadoop-gremlin`, `spark-gremlin`) | `GraphComputer`, remote-reachable via `withComputer()` bytecode | network, filesystem, cluster | **In**, reference code (incl. Hadoop/Spark modules). The third-party Hadoop/Spark runtime + cluster config are out (§3) *(documented — `withComputer` is a bytecode source instruction; in-tree modules)* | -| **`gremlin-console`** | local Groovy REPL. `:remote console` submits **arbitrary Groovy scripts** to a server and deserializes responses. `:install` loads plugins (credentials/hadoop/spark) via Grape | network (connects) | Local REPL **Out** (operator-trusted). As a remote client, submitting arbitrary Groovy = server-side RCE **by-design** (§9), subject to server auth. Response-deserialization robustness + TLS cert validation **In** (like a GLV). Plugins are operator-installed code in the operator's own JVM, so the install decision and third-party plugin behavior are **Out** (trusted-input, §3) *(documented — `DriverRemoteAcceptor`, `InstallCommand`/Grape; `gremlin-applications.asciidoc`)* | +| **`gremlin-console`** | local Groovy REPL. `:remote console` submits **arbitrary Groovy scripts** to a server and deserializes responses. `:install` loads plugins (credentials/hadoop/spark) via Grape | network (connects) | Local REPL **Out** (operator-trusted). As a remote client, submitting arbitrary Groovy = server-side RCE **by-design** (§9), subject to server auth. Response-deserialization robustness + TLS cert validation **In** (like a GLV). Plugins are operator-installed code in the operator's own JVM, so the install decision, the third-party download channel (Grape), and plugin behavior are **Out** (trusted-input, §3) *(documented — `DriverRemoteAcceptor`, `InstallCommand`/Grape; `gremlin-applications.asciidoc`)* | | Test-only / example / build modules (e.g. `gremlin-test`, `gremlin-examples`, `gremlin-tools`, `gremlin-annotations`) | test/build/example code | — | **Out** *(see §3)* | ## §3 Out of scope (explicit non-goals) @@ -108,9 +108,9 @@ under the License. provider code and cannot be cleanly attributed to either. This is not silently dropped to the provider. It triggers a joint determination and a §12 model revision. - Triage test: *does it reproduce on the unmodified TinkerPop reference distribution?* If yes, in-model. If - only with provider code/modifications, it is the provider's. If it genuinely can't be told, `MODEL-GAP`. - *(maintainer)* + Triage test: *is the defective code TinkerPop's?* A defect in unmodified TinkerPop reference code is + in-model, whatever product or graph it runs inside. A defect that depends on the provider's own code or + modifications is the provider's. If it genuinely can't be told, `MODEL-GAP`. *(maintainer)* - **The calling application's own authentication / authorization of its end users.** TinkerPop has no concept of the calling application's end users. *(maintainer)* - **Attackers who already control the host, the Gremlin Server process, `gremlin-server.yaml`, or the @@ -142,7 +142,7 @@ Data flow for a remote request. The `‖` marks the trust boundary, and everythi TRUST BOUNDARY calling app → GLV (build bytecode / ‖ opaque script) → transport ────────────────▶ ‖ transport decode (HTTP / WS frame) - ‖ → authenticate → deserialize (GraphSON/GraphBinary) + ‖ → deserialize (GraphSON/GraphBinary) → authenticate ‖ → op-select → authorize → execute (script engine / ‖ traversal machine) → graph + storage / io() files GLV ◀──────── deserialize response ──────────── ‖ → iterate results → encode response @@ -229,8 +229,10 @@ Knobs that change which security properties hold (Gremlin Server, `gremlin-serve `GraphTraversalSource` with `ReadOnlyStrategy` (block mutations), `SubgraphStrategy`/`PartitionStrategy` (scope visible data / tenancy), and `VertexProgramDenyStrategy` (block OLAP). These are **traversal-layer, not storage-layer**: `PartitionStrategy` is bypassable via direct `Vertex.property()`, and - `ReservedKeysVerificationStrategy` does not cover `mergeV`/`mergeE`. In-model only when configured. - *(documented — `the-traversal.asciidoc` "TraversalStrategy")* + `ReservedKeysVerificationStrategy` does not cover `mergeV`/`mergeE`. A client can also remove an applied + strategy outright, whether by bytecode source instruction (`withoutStrategies()`) or by script, unless + an `Authorizer` denies it (§9). In-model only when pinned by a configured `Authorizer`. *(documented — + `the-traversal.asciidoc` "TraversalStrategy"; `gremlin-applications.asciidoc` "Authorization")* - **TLS/SSL** — configurable on the server connector, **off by default** (`ssl.enabled: false` in the shipped `gremlin-server.yaml`, enabled only in `gremlin-server-secure.yaml`). Drives the transport-confidentiality property (§9). *(documented)* @@ -239,7 +241,8 @@ Knobs that change which security properties hold (Gremlin Server, `gremlin-serve deployment keeps no attribution record (§9). *(documented — `gremlin-applications.asciidoc` config table; `Settings.enableAuditLog`)* - **Script execution restriction** — sandbox / compilation customizers / allow-list / preferring - bytecode-only (plus `LambdaRestrictionStrategy` to close the bytecode-lambda vector) / the Groovy-free + bytecode-only (plus `LambdaRestrictionStrategy`, pinned by an `Authorizer`, to close the bytecode-lambda + vector, since unpinned it is removable like any strategy, §9) / the Groovy-free `GremlinLangScriptEngine`. **Scripting and bytecode lambdas are unrestricted out of the box:** the shipped `gremlin-server.yaml` configures no sandbox and no lambda restriction, and the sandbox controls ship only in `gremlin-server-secure.yaml`. The docs are explicit that TinkerPop offers no complete out-of-the-box @@ -266,6 +269,11 @@ Per-surface trust table: | GLV (client) — server response | serialized bytes from the server | yes if the server is malicious/compromised, or a MITM (TLS off / cert not validated) | response-deserialization robustness; TLS with cert validation | | `gremlin-server.yaml`, host, data dir | local | no — operator-trusted | filesystem permissions | +- **Deserialization is pre-auth (ordering note):** the server deserializes a request **before** + authenticating it (§4 diagram), because authentication itself rides inside a deserialized + `RequestMessage` (the SASL op). The wire deserializers therefore face fully anonymous input on every + deployment, hardened or not, and a deserializer defect is reachable without credentials, which is what + makes it security-critical under §8. *(documented — channelizer pipeline order)* - **Shape / rate:** Gremlin Server has per-request limits (`evaluationTimeout`, `maxContentLength`, `maxParameters`, `maxWorkQueueSize`, session timeouts) but **no traversal-depth or result-count cap**. The bug-vs-capacity line is the §8 resource split. *(documented — `gremlin-applications.asciidoc` config @@ -294,14 +302,12 @@ Per-surface trust table: - **Out of scope:** anyone with operator/host/config control (already authoritative). Also an attacker who reaches a server left on the **insecure getting-started default** (no TLS, no auth), which is an operator hardening failure (§9/§10), not a code bug, though the pre-auth robustness properties (§8) still apply to - the exposed surface. Side-channel/timing adversaries are out unless the PMC wants them in. *(maintainer — + the exposed surface. Side-channel/timing adversaries are **out of scope**. *(maintainer — public exposure of a hardened server is in scope, whereas exposure of the insecure default is an operator failure)* ## §8 Security properties the project provides -*(Inferred pending PMC confirmation — a property only counts once the project commits to it.)* - **Assets these properties defend (what is being protected):** - **Server availability** — the Gremlin Server process staying up under malformed or pre-auth input. @@ -314,9 +320,22 @@ Per-surface trust table: **Properties:** -- **Authentication + authorization enforcement (when configured).** With an `Authenticator`/`Authorizer` - set, an unauthenticated or unauthorized client cannot execute requests beyond its grants. *Violation - symptom:* auth/authz bypass. *Severity:* security-critical. *(maintainer)* +- **Enforcement of configured restrictions.** With an `Authenticator`/`Authorizer` set, an + unauthenticated or unauthorized client cannot execute requests beyond its grants (§11b session sharing + excepted). The same holds for restrictions living in server configuration that no request can touch: a + *configured* script sandbox / compilation customizer / allow-list must not be evadable from a request. + A restrictive `TraversalStrategy` (`ReadOnlyStrategy`, `SubgraphStrategy`, ...) counts as a configured + restriction **only when an `Authorizer` denies its removal or modification**, because a request can + otherwise remove strategies itself, whether by bytecode source instruction or by script (§9). This is + the §8 anchor for the "bypasses a *configured* restriction" rulings in §9/§11a. *Violation symptom:* + auth/authz bypass, or a request evading a configured restriction. *Severity:* security-critical. + *(maintainer)* +- **Credential-store handling (when the built-in mechanism is used).** Where a deployment uses + `SimpleAuthenticator` with the credentials graph, passwords are stored and verified as BCrypt hashes, + never as plaintext. `Krb5Authenticator` holds no credential store (the KDC does), and a provider's own + `Authenticator` and credential store are the provider's (§3). *Violation symptom:* plaintext credential + storage, or disclosure of stored credential material to a client. *Severity:* high. *(documented — + `SimpleAuthenticator`, credentials DSL)* - **Memory / availability safety on the request + deserialization surface.** Malformed or pre-auth input (protocol frames, Gremlin strings, serialized payloads) yields a clean error, not a crash, OOM, hang, or unbounded allocation of the server. This covers not just the sub-protocol payload but the **transport @@ -325,6 +344,10 @@ Per-surface trust table: or DoS the server. *Violation symptom:* server crash / unbounded allocation / deadlock from malformed or pre-auth input, or a crafted frame sequence. *Severity:* security-critical (remote DoS) if pre-auth. *(maintainer)* +- **Request isolation.** One request's bindings and state must not leak into or alter another's + (per-request bindings, thread-pool dispatch, §5). Sessions are the deliberate stateful exception + (§11b). *Violation symptom:* cross-request state leakage or interference. *Severity:* + security-critical. *(maintainer)* - **Parser integrity (`gremlin-language`).** A crafted Gremlin string cannot break out of a string literal to inject additional traversal steps. This is the safe string-to-traversal path, distinct from building a Groovy string by concatenation, which is the calling application's concern (§9). *Violation symptom:* @@ -347,14 +370,17 @@ Per-surface trust table: - **Client (GLV) robustness against hostile server responses.** A GLV deserializing response bytes from a malicious/compromised server (or a MITM) does not crash/OOM/execute code. *Violation symptom:* client crash/OOM/RCE from crafted responses. *Severity:* critical. *(maintainer)* -- **No traversal or script can harm the GLV.** No traversal or script, however malformed or hostile, may - cause code execution, a crash, a hang, or resource exhaustion **in the GLV itself**. How a GLV processes - the input is unconstrained. *Violation symptom:* a traversal or script that harms the GLV that submitted - it. *Severity:* high. *(maintainer)* +- **No pass-through input can harm the GLV.** Outbound bytecode/message construction is otherwise not a + surface, because its input is the trusted calling application. This property covers only the + pass-through case: an application forwarding an untrusted end-user string verbatim (e.g. + `client.submit(userString)`) must not cause code execution, a crash, a hang, or resource exhaustion + **in the GLV itself**. Its *server-side* effect is Gremlin-injection, the application's responsibility + (§9). *Violation symptom:* a submitted string that harms the GLV that submitted it. *Severity:* high. + *(maintainer)* ## §9 Security properties the project does *not* provide -*(The highest-value section for integrators. Inferred unless tagged, so confirm each.)* +*(The highest-value section for integrators.)* - **Script execution is arbitrary code execution by design, not a sandbox.** When string-script requests are enabled, the Groovy script engine evaluates attacker-supplied code in the server process. The docs @@ -387,6 +413,12 @@ Per-surface trust table: responsibility.** (A few registered types use Java native serialization, a gadget caveat even when locked.) Gryo is not on the wire, so this is an IO/file-surface concern (`io()` step, persistence, OLAP). *(documented)* +- **A `TraversalStrategy` is not an access-control boundary on its own.** A remote request can remove or + replace strategies on its traversal source, whether by bytecode source instruction + (`withoutStrategies()`) or by script, so a strategy applied by the operator only restricts a client + when an `Authorizer` denies strategy removal/modification, as the reference documentation states. + Strategies also act at the traversal layer, not the storage layer (§5a). *(documented — `Bytecode` + source instructions; `gremlin-applications.asciidoc` "Authorization")* - **Ordinary resource exhaustion is not a defended property.** Expensive traversals / large results that consume CPU/memory are an operator capacity concern unless a specific bug applies (§8). *(maintainer)* - **No defense against a malicious operator / host.** *(maintainer)* @@ -397,16 +429,16 @@ Per-surface trust table: its own untrusted input (e.g. into Groovy) is the calling application's responsibility. Note this is distinct from a `gremlin-language` parser breakout, which is an in-model TinkerPop bug (§8). *(maintainer)* - **Bytecode-only is not RCE-free unless lambdas are restricted.** A bytecode traversal can carry a - **string lambda** that is script-evaluated server-side by a `GremlinScriptEngine`. `LambdaRestrictionStrategy` - blocks this but is **not enabled by default**, so not enabling it means the operator/provider accepts the - code-execution risk (`BY-DESIGN`, like scripting, §9). *(documented — `Lambda`, `LambdaRestrictionStrategy`)* + **string lambda** that is script-evaluated server-side by a `GremlinScriptEngine`. + `LambdaRestrictionStrategy` blocks this but is **not enabled by default**, and, being a strategy, is + removable by the request itself unless an `Authorizer` pins it (§8). Not enabling and pinning it means + the operator/provider accepts the code-execution risk (`BY-DESIGN`, like scripting, §9). + *(documented — `Lambda`, `LambdaRestrictionStrategy`)* - "Gremlin is just a query language" is misleading. The **string** form can run through a Groovy engine, so it is code, not a constrained query. *(maintainer)* ## §10 Downstream responsibilities (operator/deployer) -*(Inferred unless tagged. Confirm.)* - - Do **not** deploy the shipped getting-started `gremlin-server.yaml` as-is. Before exposing Gremlin Server on a public/untrusted network, harden it: enable TLS and an authenticator, and (given no authorizer ships) supply an `Authorizer`, especially if scripting is enabled. *(maintainer)* @@ -415,8 +447,9 @@ Per-surface trust table: - **Restrict script execution:** prefer bytecode-based traversals (or the Groovy-free `GremlinLangScriptEngine`). If Groovy scripts are needed, apply the "Protecting Script Execution" controls (sandbox / compilation customizers / allow-list) and restrict who may submit scripts. **For bytecode, - enable `LambdaRestrictionStrategy`**, since bytecode-only is not RCE-free while string lambdas are allowed - (§9). *(documented)* + enable `LambdaRestrictionStrategy` and pin it with an `Authorizer` that denies strategy removal**, since + bytecode-only is not RCE-free while string lambdas are allowed, and an unpinned strategy is removable by + the request itself (§9). *(documented)* - Enable TLS (off by default) where traffic crosses an untrusted segment. *(documented)* - Enable audit logging (`enableAuditLog`, off by default) where attribution of requests is needed. *(documented)* - Use the wire serializers (GraphBinary recommended for drivers). Keep Gryo locked @@ -443,21 +476,31 @@ Per-surface trust table: *(Inferred unless tagged. The PMC's confirmations here are the highest-leverage suppression input.)* - "Gremlin Server executes arbitrary code via scripts" — by-design when scripting is enabled for the - principal (§9), and not a finding unless it bypasses a *configured* restriction. *(documented)* -- "Bytecode lambdas enable code execution" — by-design when `LambdaRestrictionStrategy` is not enabled - (§9), and not a finding unless it bypasses a *configured* restriction. *(documented)* + principal (§9), and not a finding unless it bypasses a *configured* restriction. Disposition: + `BY-DESIGN: property-disclaimed`. *(documented)* +- "Bytecode lambdas enable code execution" — a string lambda is server-side code like any script, so this + is by-design when lambdas are permitted (§9), and not a finding unless it bypasses a *configured* + restriction. Disposition: `BY-DESIGN: property-disclaimed`. *(documented)* - "No authentication / no TLS by default" — operator deployment responsibility (§9/§10), not a code bug in - itself. *(documented)* -- "Gryo/Kryo deserialization can be exploited" — Gryo is not on the wire, so a request-path report is a - non-finding. Under the locked default (`registrationRequired=true`) a real break is `VALID`, whereas - exploiting **unlocked** Gryo on untrusted files is the user's responsibility (§9/§10). *(documented)* + itself. Disposition: `BY-DESIGN: property-disclaimed`. *(documented)* +- "Gryo/Kryo deserialization can be exploited" — Gryo is not on the wire, so a request-path report is + factually mistaken and needs no disposition (§13). Under the locked default + (`registrationRequired=true`) a real break is `VALID`, whereas exploiting **unlocked** Gryo on + untrusted files is the user's responsibility (§9/§10). Disposition for the unlocked-file variant: + `BY-DESIGN: property-disclaimed`. *(documented)* - "Expensive traversal consumes CPU/memory" — operator capacity concern, unless a specific bug applies - (§8/§9). *(maintainer)* + (§8/§9). Disposition: `BY-DESIGN: property-disclaimed`. *(maintainer)* +- "A configured `ReadOnlyStrategy` / `SubgraphStrategy` / `PartitionStrategy` was bypassed" — whether by + removing the strategy (bytecode `withoutStrategies()` or script) or by stepping around it (direct + `Vertex.property()`, `mergeV`/`mergeE`), a strategy is not an access-control boundary on its own (§9). + A finding only exists where a configured `Authorizer` pins the strategy and the pin is evaded (§8). + Disposition: `BY-DESIGN: property-disclaimed`. *(documented)* - "Calling application concatenates untrusted input into a Groovy string (Gremlin-injection)" — the calling application's responsibility (§9). A `gremlin-language` parser breakout is the opposite: an in-model - TinkerPop bug (§8). *(maintainer)* + TinkerPop bug (§8). Disposition: `BY-DESIGN: property-disclaimed`. *(maintainer)* - Findings in any test-only module, any module containing examples, or build tooling (e.g. `gremlin-test`, - `gremlin-examples`, `gremlin-tools`) — out of scope (§3). *(maintainer)* + `gremlin-examples`, `gremlin-tools`) — out of scope (§3). Disposition: + `OUT-OF-MODEL: unsupported-component`. *(maintainer)* ## §11b Known problems @@ -481,17 +524,35 @@ Per-surface trust table: ## §13 Triage dispositions -| Disposition | Meaning | Licensed by | +A disposition is the answer a security report receives after triage: a TinkerPop bug that needs a fix, +some other party's responsibility, or known and accepted behavior. A report may contain more than one +finding, and each finding gets **exactly one** disposition from the table below. The rows do not +overlap. A finding that seems to match two rows, or none, means the table itself has a defect and takes +`MODEL-GAP` so that the model gets fixed (§12). A report claiming something the code simply does not do +(for example, Gryo reachable over the network) is answered by correcting the facts and needs no +disposition at all. + +A finding is classified by **what the attack needs in order to work**, not by the setup it happened to +be demonstrated on. For example, a crash bug shown against a server running the insecure +getting-started configuration does not need that configuration. The same crash works, without +credentials, against a hardened server too, so it is `VALID`. The insecure default decides the outcome +only when the finding is, in substance, "this particular server was left unhardened." + +The lists in §11a and §11b exist to save repeated work: each entry is a finding that keeps coming back, +recorded together with the disposition it takes. They are worked examples of this table, not extra +rules on top of it. + +| Disposition | Meaning | Based on | | --- | --- | --- | -| `VALID` | Violates a §8 property via an in-scope adversary/input (auth/authz bypass, pre-auth/malformed-input crash/OOM/hang, deserializer RCE beyond the documented type set, parser memory-safety or grammar breakout / step injection). | §8, §6, §7 | -| `VALID-HARDENING` | No §8 property broken, but a §11 misuse is easy enough to harden. | §11 | -| `OUT-OF-MODEL: trusted-input` | Requires operator/host/config control. | §6, §7 | -| `OUT-OF-MODEL: adversary-not-in-scope` | Requires a capability the model excludes (host control, side channel, or reaching a server left on the insecure getting-started default rather than a hardened deployment). | §3, §7 | -| `OUT-OF-MODEL: unsupported-component` | Lands in a test-only / example module or build tooling, or in provider code / provider modifications to the reference code (including a code-sharing-free remote graph provider). Reproduces only with provider code, not on the unmodified reference distribution. | §3 | -| `OUT-OF-MODEL: non-default-build` | Only manifests under a discouraged/non-default §5a setting (e.g. scripting unrestricted where the deployment intends bytecode-only). | §5a | -| `BY-DESIGN: property-disclaimed` | Concerns a §9-disclaimed property (script or bytecode-lambda execution within its grant, no-TLS/no-auth default, ordinary resource exhaustion, malicious operator). | §9 | -| `KNOWN-NON-FINDING` | Matches a §11a entry. | §11a | -| `MODEL-GAP` | Cannot be cleanly routed, so triggers a §12 revision. | §12 | +| `VALID` | A real TinkerPop bug: it breaks one of the §8 security properties through an attacker or input the model covers. Examples: bypassing authentication/authorization or slipping past a configured restriction, crashing or hanging the server with malformed or unauthenticated input, a deserializer executing code it should not, or the parser mishandling crafted input or letting a value escape its string literal. | §8, §6, §7 | +| `VALID-HARDENING` | Not a vulnerability, but still useful: no §8 property is broken, and the report points at a practical change that would make one of the §11 misuse patterns harder to fall into. Example: "no authentication by default" is by design, but a startup warning when `AllowAllAuthenticator` is active would be a sensible outcome of that report and takes this disposition. Tracked as an ordinary improvement. | §11 | +| `OUT-OF-MODEL: trusted-input` | The attack only works for someone who already has the operator's own access: the host, the server process, `gremlin-server.yaml`, or the data directory. This includes a malicious operator (§9). Someone with that access can already do anything the operator can, so the attack gains nothing new. | §6, §7 | +| `OUT-OF-MODEL: adversary-not-in-scope` | The attack needs an attacker the model deliberately leaves out (side channels/timing), or the whole finding is that one specific deployment was left on the insecure getting-started default, which is that operator's mistake to fix. A complaint about the shipped default itself is `BY-DESIGN`. A real code bug that was merely demonstrated on an unhardened server is judged on its own needs, usually `VALID`. | §3, §7 | +| `OUT-OF-MODEL: unsupported-component` | The buggy code is not TinkerPop's (§3 triage test): it is in a test-only or example module, in build tooling, or in a provider's own code or their modifications to the reference code (including a remote graph provider that shares no TinkerPop code). Sent to the provider. | §3 | +| `OUT-OF-MODEL: non-default-build` | A genuine bug, but one that only exists when the operator turns on a discouraged, non-default §5a setting, for example a defect that only manifests with Gryo's registration lock disabled. Different from `BY-DESIGN`: behavior §9 already disclaims, such as scripts running code, is not a bug at all and takes that row instead. | §5a | +| `BY-DESIGN: property-disclaimed` | The behavior is exactly what §9 says TinkerPop does not defend against: scripts or bytecode lambdas executing code within what the deployment allows, the shipped no-TLS/no-auth default, ordinary resource exhaustion from expensive queries, reading untrusted files through unlocked Gryo, or an application building queries out of its own users' raw input. The software works as documented. When the scenario needs the operator's own access, `trusted-input` applies instead. | §9 | +| `KNOWN-PROBLEM` | Matches the acknowledged weakness in §11b (session sharing), which the §8 enforcement property explicitly excepts. Already known and accepted, not a new finding. A deployment that needs the missing protection must use a provider that adds it. | §11b | +| `MODEL-GAP` | Fits no row, or more than one. That is a defect in the model itself, and §12 requires revising it. | §12 | ## §14 Open questions for the maintainers From d8c2b5233c915be99e31056263e5af8242e51503 Mon Sep 17 00:00:00 2001 From: Cole Greer Date: Fri, 10 Jul 2026 17:56:14 -0700 Subject: [PATCH 4/4] Pull gremlint inside the scope of the model, remove provenance tags, minor cleanups --- THREAT_MODEL.md | 251 +++++++++++++++--------------------------------- 1 file changed, 78 insertions(+), 173 deletions(-) diff --git a/THREAT_MODEL.md b/THREAT_MODEL.md index 40fd145adf3..7de00e8778b 100644 --- a/THREAT_MODEL.md +++ b/THREAT_MODEL.md @@ -24,31 +24,19 @@ under the License. - **Project:** Apache TinkerPop (`apache/tinkerpop`) — a graph computing framework: the Gremlin query language, the traversal machine, Gremlin Server (remote query execution), the GraphSON and GraphBinary wire serializers and the Gryo IO format, and the Gremlin Language Variants (Java, Python, .NET, Go, JS). - *(documented — `README.md`, repo layout)* - **Scope of this model:** the **`apache/tinkerpop` monorepo**, active branch `3.7-dev`. The model focuses on the network-facing and deserialization surfaces (see §2). Provider graph databases that make use of reference code are out of scope (§3). -- **Date:** 2026-06-05. **Status:** DRAFT v0, a first pass by the ASF Security team via the - threat-model-producer rubric, for the TinkerPop PMC to react to. **Author:** ASF Security team, for PMC - ratification. -- **Version binding:** versioned with the project. A report against release *N* is triaged against the - model as it stood at *N*, not against `HEAD`. +- **Version binding:** versioned with the project. A report against release *x.y.z* is triaged against the + model as it stood at *x.y.z*, not against `HEAD`. - **Reporting cross-reference:** §8-property violations → report privately per the ASF process (`security@apache.org`). §3 / §9 findings are closed citing this document. -- **Provenance legend:** *(documented)* = stated in TinkerPop's own docs / reference / source. - *(maintainer)* = confirmed by a TinkerPop PMC member through this process. *(inferred)* = reasoned from - the reference docs, architecture, and graph-server domain knowledge, **not yet confirmed**. Every - *(inferred)* claim has a matching §14 open question. -- **Draft confidence:** a v0 with **no PMC confirmation folded in yet**. The deployment posture, the - default auth/TLS stance, the script-execution sandboxing story, and the serialization disposition are the - highest-value items for the PMC to confirm in §14. - **What TinkerPop is:** a graph computing framework for graph databases (OLTP) and graph analytic systems (OLAP). It **defines** a common interface/language (Gremlin) plus protocols/APIs, and ships production-ready reference implementations. This includes a reference in-memory graph (TinkerGraph), Gremlin Server, and the language variants (GLVs). Gremlin traversals run in one of two environments. In **embedded**, the caller runs traversals directly in their application via `gremlin-core`. In **remote**, Gremlin is sent over the wire (as **bytecode** or a **string script**) to a server that executes it. - *(documented)* ## §2 Scope and intended use @@ -58,37 +46,38 @@ under the License. case, where **Gremlin Server** is the main network-facing trust boundary. Gremlin Server **is intended to be deployable on the public internet**, but the shipped `gremlin-server.yaml` is a getting-started/testing config that **must not be deployed as-is**. A public or otherwise untrusted-facing deployment is expected - to layer on TLS and an authenticator. *(maintainer)* + to layer on TLS and an authenticator. - **Embedded surface.** Embedded has **no network, transport, auth, or protocol/frame surface** (those exist only with a server). It exposes the same input-consuming primitives as remote, and only where the caller feeds them untrusted data: the **`io()` readers** (GraphSON, Gryo, GraphML), the **`gremlin-language` parser** if untrusted strings are parsed, and **expensive steps** (e.g. `regex`). The responsibility rules - are **identical to remote** because they attach to the primitive, not the environment. *(maintainer)* + are **identical to remote** because they attach to the primitive, not the environment. - **Caller roles** (Gremlin Server is a network service): - **remote client** — connects over the WebSocket sub-protocol or HTTP, submits Gremlin (script or bytecode). Trust level depends entirely on whether the operator enabled authentication, authorization, and script restrictions, **all off in the shipped default** (`gremlin-server.yaml` has no `authentication` block, and the default is `AllowAllAuthenticator`, per `gremlin-applications.asciidoc` - "Security"). *(documented)* + "Security"). - **application / provider** — runs `gremlin-core` directly, whether embedded in its own process or as Gremlin Server. A provider may also supply the `Authenticator`/`Authorizer` implementations the server - runs. Trusted within the compile-time + configuration boundary. *(maintainer)* + runs. Trusted within the compile-time + configuration boundary. - **operator** — runs Gremlin Server, controls `gremlin-server.yaml`, serializers, auth, TLS, the script - engine configuration, and the host. Trusted for the instance. *(maintainer)* + engine configuration, and the host. Trusted for the instance. **Component-family table** *(in/out = in/out of this model)*: | Family | Entry point | Touches outside the process | In model? | | --- | --- | --- | --- | -| **Gremlin Server** (`gremlin-server`) | WebSocket sub-protocol (`RequestMessage`/`ResponseMessage` frames, GraphBinary/GraphSON), the default, plus a non-default HTTP request endpoint (script **and** bytecode) | network (listens), invokes the script engine + traversal machine | **In**, both transports *(documented: remote execution endpoint, channelizers)* | -| **Script engine** (`gremlin-groovy`) | `GremlinGroovyScriptEngine` evaluating string scripts | runs supplied Groovy in-process | **In**, the central code-execution surface (see §9) *(documented: scripts are evaluated)* | -| **Gremlin language parser** (`gremlin-language`) | ANTLR grammar for string Gremlin (script-engine-free) | — | **In**, parser must not crash/hang/OOM on malformed input, nor let a crafted string break out of a literal to inject steps *(maintainer)* | -| **Core traversal machine + structure API** (`gremlin-core`) | `GraphTraversal`, strategies, `Vertex`/`Edge` | filesystem (IO formats) | **In** *(documented)* | -| **Serialization** (`gremlin-core`/`gremlin-util`/`gremlin-shaded`) | GraphSON + GraphBinary wire readers. Gryo (Kryo) is IO-format-only, not on the wire | deserializes untrusted wire bytes. Gryo only reads IO/file bytes | **In** (see §9) *(documented — `Serializers` enum: GraphSON + GraphBinary)* | -| **Gremlin Language Variants** (Java `gremlin-driver`, `gremlin-python`, `-dotnet`, `-go`, `-js`) | deserialize server responses | network (connect) | **In**, response-deserialization robustness + TLS cert validation. Java shares the JVM serializers. The others have own per-language deserializers not covered by hardening the JVM server *(documented — each GLV ships its own GraphSON/GraphBinary code)* | -| **Reference graph** (`tinkergraph-gremlin`) | in-memory graph, the **shipped default graph**, with optional file persistence via the `io()` readers | filesystem (persistence) | **In**, TinkerPop reference code on the default reachable path, so a defect in unmodified TinkerGraph is `VALID` (§3). Loading a persisted graph uses the same GraphSON/Gryo/GraphML readers (§6 IO surface). A custom `graphFormat` reader is provider code (§3) *(documented — `gremlin-server.yaml` → `tinkergraph-empty.properties`, `AbstractTinkerGraph.loadGraph()`)* | -| **OLAP** (`gremlin-core`/`tinkergraph` computer, `hadoop-gremlin`, `spark-gremlin`) | `GraphComputer`, remote-reachable via `withComputer()` bytecode | network, filesystem, cluster | **In**, reference code (incl. Hadoop/Spark modules). The third-party Hadoop/Spark runtime + cluster config are out (§3) *(documented — `withComputer` is a bytecode source instruction; in-tree modules)* | -| **`gremlin-console`** | local Groovy REPL. `:remote console` submits **arbitrary Groovy scripts** to a server and deserializes responses. `:install` loads plugins (credentials/hadoop/spark) via Grape | network (connects) | Local REPL **Out** (operator-trusted). As a remote client, submitting arbitrary Groovy = server-side RCE **by-design** (§9), subject to server auth. Response-deserialization robustness + TLS cert validation **In** (like a GLV). Plugins are operator-installed code in the operator's own JVM, so the install decision, the third-party download channel (Grape), and plugin behavior are **Out** (trusted-input, §3) *(documented — `DriverRemoteAcceptor`, `InstallCommand`/Grape; `gremlin-applications.asciidoc`)* | +| **Gremlin Server** (`gremlin-server`) | WebSocket sub-protocol (`RequestMessage`/`ResponseMessage` frames, GraphBinary/GraphSON), the default, plus a non-default HTTP request endpoint (script **and** bytecode) | network (listens), invokes the script engine + traversal machine | **In**, both transports | +| **Script engine** (`gremlin-groovy`) | `GremlinGroovyScriptEngine` evaluating string scripts | runs supplied Groovy in-process | **In**, the central code-execution surface (see §9) | +| **Gremlin language parser** (`gremlin-language`) | ANTLR grammar for string Gremlin (script-engine-free) | — | **In**, parser must not crash/hang/OOM on malformed input, nor let a crafted string break out of a literal to inject steps | +| **Core traversal machine + structure API** (`gremlin-core`) | `GraphTraversal`, strategies, `Vertex`/`Edge` | filesystem (IO formats) | **In** | +| **Serialization** (`gremlin-core`/`gremlin-util`/`gremlin-shaded`) | GraphSON + GraphBinary wire readers. Gryo (Kryo) is IO-format-only, not on the wire | deserializes untrusted wire bytes. Gryo only reads IO/file bytes | **In** (see §9) | +| **Gremlin Language Variants** (Java `gremlin-driver`, `gremlin-python`, `gremlin-dotnet`, `gremlin-go`, `gremlin-javascript`) | deserialize server responses | network (connect) | **In**, response-deserialization robustness + TLS cert validation. Java shares the JVM serializers. The others have own per-language deserializers not covered by hardening the JVM server | +| **Reference graph** (`tinkergraph-gremlin`) | in-memory graph, the **shipped default graph**, with optional file persistence via the `io()` readers | filesystem (persistence) | **In**, TinkerPop reference code on the default reachable path, so a defect in unmodified TinkerGraph is `VALID` (§3). Loading a persisted graph uses the same GraphSON/Gryo/GraphML readers (§6 IO surface). A custom `graphFormat` reader is provider code (§3) | +| **OLAP** (`gremlin-core`/`tinkergraph` computer, `hadoop-gremlin`, `spark-gremlin`) | `GraphComputer`, remote-reachable via `withComputer()` bytecode | network, filesystem, cluster | **In**, reference code (incl. Hadoop/Spark modules). The third-party Hadoop/Spark runtime + cluster config are out (§3) | +| **`gremlin-console`** | local Groovy REPL. `:remote console` submits **arbitrary Groovy scripts** to a server and deserializes responses. `:install` loads plugins (credentials/hadoop/spark) via Grape | network (connects) | Local REPL **Out** (operator-trusted). As a remote client, submitting arbitrary Groovy = server-side RCE **by-design** (§9), subject to server auth. Response-deserialization robustness + TLS cert validation **In** (like a GLV). Plugins are operator-installed code in the operator's own JVM, so the install decision, the third-party download channel (Grape), and plugin behavior are **Out** (trusted-input, §3) | +| **`gremlint`** | client-side Gremlin formatter (parse-and-reprint, no query execution); published as a library for integration into client applications. | none (runs in the caller's browser/process) | **In**, valid surface for security reports since it's a published, user-integrated library | | Test-only / example / build modules (e.g. `gremlin-test`, `gremlin-examples`, `gremlin-tools`, `gremlin-annotations`) | test/build/example code | — | **Out** *(see §3)* | ## §3 Out of scope (explicit non-goals) @@ -110,29 +99,24 @@ under the License. Triage test: *is the defective code TinkerPop's?* A defect in unmodified TinkerPop reference code is in-model, whatever product or graph it runs inside. A defect that depends on the provider's own code or - modifications is the provider's. If it genuinely can't be told, `MODEL-GAP`. *(maintainer)* + modifications is the provider's. If it genuinely can't be told, `MODEL-GAP`. - **The calling application's own authentication / authorization of its end users.** TinkerPop has no - concept of the calling application's end users. *(maintainer)* + concept of the calling application's end users. - **Attackers who already control the host, the Gremlin Server process, `gremlin-server.yaml`, or the - graph data directory.** They have the operator's authority by definition. *(maintainer)* + graph data directory.** They have the operator's authority by definition. - **Operator-configured code-execution/side-effect surfaces:** the Script I/O Format (`ScriptInputFormat`/ `ScriptOutputFormat` run operator-supplied Groovy from HDFS) and `EventStrategy` `MutationListener` callbacks. The code comes from operator/provider config, not a remote request (though a remote mutating traversal can *trigger* an already-registered listener). Trusted-input. *(documented — Script I/O Format; `EventStrategy`)* - **Any test-only module, any example-only module, and build/distribution tooling** as a production trust - surface (e.g. `gremlin-test`, `gremlin-examples`, `gremlin-tools`). *(maintainer)* + surface (e.g. `gremlin-test`, `gremlin-examples`, `gremlin-tools`). - **Confidentiality of data in transit when the operator has not enabled TLS** (see §10). TLS is **off by default** (`ssl.enabled: false`), so the TLS posture is the operator's deployment responsibility. - *(documented — `gremlin-server.yaml`, `gremlin-applications.asciidoc` config table)* -- **`neo4j-gremlin`** — deprecated (not compatible past Neo4j 3.4, EOL 2020). *(documented — reference docs)* +- **`neo4j-gremlin`** — deprecated (not compatible past Neo4j 3.4, EOL 2020). - **`sparql-gremlin`** — a standalone SPARQL→Gremlin translator the user invokes in their own application code. The server does not use it, and it parses SPARQL via third-party Apache Jena, so malformed SPARQL is - the caller's responsibility. *(documented — `gremlin-server` has no `sparql-gremlin` dependency; Jena parser)* -- **`gremlint` (incl. hosted gremlint)** — a client-side Gremlin formatter (parse-and-reprint, no query - execution). When hosted, input stays in the user's own browser (no URL-shared state, no XSS sink, no - backend), so a user is responsible for what they input, and a pathological input only affects their own - browser tab. *(documented — `gremlint/src`, `docs/gremlint`)* + the caller's responsibility. ## §4 Trust boundaries and data flow @@ -179,34 +163,34 @@ Data flow for a remote request. The `‖` marks the trust boundary, and everythi wire set is GraphSON + GraphBinary)* - **Reachability preconditions** (the test a triager applies first): - A finding reachable only by submitting a **script** is in-model only subject to the §9 ruling: if the - deployment allows scripting to the principal, server-side code execution is by-design. *(documented)* + deployment allows scripting to the principal, server-side code execution is by-design. - A finding in the **Gremlin parser / bytecode / traversal machine / deserializers** reachable from a client operating within its privileges (or pre-auth) is **in-model** for memory-safety / bounded- - resource robustness. *(maintainer)* + resource robustness. - A finding requiring control of `gremlin-server.yaml` or host access is **out-of-model: trusted-input**. - *(maintainer)* + ## §5 Assumptions about the environment - **Runtime:** JVM (server, core, groovy, driver). The GLVs run on their respective runtimes - (CPython, .NET, Go, Node). *(documented — README / build)* + (CPython, .NET, Go, Node). - **Deployment:** Gremlin Server is safe enough to run on the public internet **once hardened** beyond the shipped `gremlin-server.yaml`, at minimum with TLS plus an authenticator (either built-in mechanism suffices over TLS). The insecure getting-started default is a testing convenience, not a supported production posture. Realistically, a production deployment also layers a provider's `Authenticator`/`Authorizer` on top (no authorizer ships, see §5a), so auth/authz is a **shared responsibility**. The provider supplies the policy (the plugin implementations), while Gremlin Server owns - the enforcement machinery that runs them on every request (§8). *(maintainer)* + the enforcement machinery that runs them on every request (§8). - **Filesystem:** the graph data / config directories are private to the server process and not writable by untrusted local users. Securing the host is the operator's responsibility, and an attacker who can write - them already has the operator's authority (§3). *(maintainer)* + them already has the operator's authority (§3). - **Concurrency:** the standard request path is isolated per request (thread-pool dispatch, per-request script bindings), so one request should not affect another. Sessions are the deliberate stateful - exception (§11b). *(maintainer)* + exception (§11b). - **What the server does to its host** (negative inventory): listens on network ports; reads/writes its configured graph + data directories; reads config; **executes supplied Groovy when script requests are - enabled** *(documented — script evaluation, `gremlin-applications.asciidoc`)*; loads provider graph - implementations the operator configured. *(maintainer)* + enabled**; loads provider graph + implementations the operator configured. ## §5a Build-time and configuration variants @@ -218,7 +202,7 @@ Knobs that change which security properties hold (Gremlin Server, `gremlin-serve username/password (`SimpleAuthenticator`) and Kerberos (`Krb5Authenticator`), are **secure enough for a public-internet deployment when combined with TLS**. In practice, though, deployments layer a provider's own `Authenticator` on top (e.g. SSO / bearer-token), so the built-ins are more often a baseline than the - production mechanism. *(documented — `SimpleAuthenticator` + `Krb5Authenticator` ship; auth off by default)* + production mechanism. - **Authorization** — an `Authorizer` SPI exists, but **no implementation ships** with Gremlin Server (the only one in-tree, `AllowListAuthorizer`, is a test example), so there is **no meaningful authorization control out of the box**, and providing it is expected to fall to the provider. Authorization is @@ -235,7 +219,7 @@ Knobs that change which security properties hold (Gremlin Server, `gremlin-serve `the-traversal.asciidoc` "TraversalStrategy"; `gremlin-applications.asciidoc` "Authorization")* - **TLS/SSL** — configurable on the server connector, **off by default** (`ssl.enabled: false` in the shipped `gremlin-server.yaml`, enabled only in `gremlin-server-secure.yaml`). Drives the - transport-confidentiality property (§9). *(documented)* + transport-confidentiality property (§9). - **Audit logging** — `enableAuditLog` records the authenticated user, remote address, and gremlin query. It is **off by default** (`false`, "for privacy reasons") and absent from the shipped config, so a default deployment keeps no attribution record (§9). *(documented — `gremlin-applications.asciidoc` config table; @@ -273,7 +257,7 @@ Per-surface trust table: authenticating it (§4 diagram), because authentication itself rides inside a deserialized `RequestMessage` (the SASL op). The wire deserializers therefore face fully anonymous input on every deployment, hardened or not, and a deserializer defect is reachable without credentials, which is what - makes it security-critical under §8. *(documented — channelizer pipeline order)* + makes it security-critical under §8. - **Shape / rate:** Gremlin Server has per-request limits (`evaluationTimeout`, `maxContentLength`, `maxParameters`, `maxWorkQueueSize`, session timeouts) but **no traversal-depth or result-count cap**. The bug-vs-capacity line is the §8 resource split. *(documented — `gremlin-applications.asciidoc` config @@ -281,7 +265,7 @@ Per-surface trust table: - **Script-cache growth:** a client submitting many distinct, unparameterized scripts grows the compiled script / global-function cache toward `OutOfMemoryError`. The operator mitigates by bounding `classMapCacheSpecification` and preferring parameterized scripts. This is the Q7 resource split (a - documented remote DoS unless bounded). *(documented — `gremlin-applications.asciidoc` "Cache Management")* + documented remote DoS unless bounded). ## §7 Adversary model @@ -290,15 +274,15 @@ Per-surface trust table: off / pre-auth) or **authenticated with limited privileges**, trying to execute code on the server (via scripts), read/write graph data outside its intent, crash or exhaust the server with malformed requests or expensive traversals, or exploit a deserializer. *Capabilities:* can open connections, send arbitrary - protocol bytes / scripts / bytecode / serialized payloads, and supply large/malformed input. *(maintainer)* + protocol bytes / scripts / bytecode / serialized payloads, and supply large/malformed input. - **Server → client:** a malicious or compromised server, or a network attacker on the wire when TLS is off, targeting a GLV through the response it returns — crafted response bytes to crash/OOM/RCE the client, or a - server-issued auth challenge to harvest the client's credentials (§8/§9). *(maintainer)* + server-issued auth challenge to harvest the client's credentials (§8/§9). - **Untrusted input → embedded:** in the embedded environment there is no network client, but untrusted data can still reach an in-process `gremlin-core` primitive through the calling application — an `io()` reader fed an untrusted file, the `gremlin-language` parser fed an untrusted string, or an expensive step fed a crafted argument. The relevant robustness properties (§8) apply identically, since they attach to the - primitive, not the environment (§2/§6). *(maintainer)* + primitive, not the environment (§2/§6). - **Out of scope:** anyone with operator/host/config control (already authoritative). Also an attacker who reaches a server left on the **insecure getting-started default** (no TLS, no auth), which is an operator hardening failure (§9/§10), not a code bug, though the pre-auth robustness properties (§8) still apply to @@ -329,7 +313,7 @@ Per-surface trust table: otherwise remove strategies itself, whether by bytecode source instruction or by script (§9). This is the §8 anchor for the "bypasses a *configured* restriction" rulings in §9/§11a. *Violation symptom:* auth/authz bypass, or a request evading a configured restriction. *Severity:* security-critical. - *(maintainer)* + - **Credential-store handling (when the built-in mechanism is used).** Where a deployment uses `SimpleAuthenticator` with the credentials graph, passwords are stored and verified as BCrypt hashes, never as plaintext. `Krb5Authenticator` holds no credential store (the KDC does), and a provider's own @@ -343,16 +327,16 @@ Per-surface trust table: handshake) or HTTP request, however crafted or sequenced, must be handled safely, with no path to access or DoS the server. *Violation symptom:* server crash / unbounded allocation / deadlock from malformed or pre-auth input, or a crafted frame sequence. *Severity:* security-critical (remote DoS) if pre-auth. - *(maintainer)* + - **Request isolation.** One request's bindings and state must not leak into or alter another's (per-request bindings, thread-pool dispatch, §5). Sessions are the deliberate stateful exception (§11b). *Violation symptom:* cross-request state leakage or interference. *Severity:* - security-critical. *(maintainer)* + security-critical. - **Parser integrity (`gremlin-language`).** A crafted Gremlin string cannot break out of a string literal to inject additional traversal steps. This is the safe string-to-traversal path, distinct from building a Groovy string by concatenation, which is the calling application's concern (§9). *Violation symptom:* grammar breakout / step injection from a value that should stay a literal. *Severity:* critical. - *(maintainer)* + - **Deserializer integrity.** The wire deserializers (GraphSON, GraphBinary) and **Gryo in its locked default (`registrationRequired=true`)** reading attacker bytes do not lead to arbitrary object instantiation / code execution beyond the registered type set. Because `inject()` and value arguments let a request carry any @@ -361,22 +345,22 @@ Per-surface trust table: external entities and DTDs by default (XXE-safe). *Violation symptom:* deserialization gadget / RCE / XXE, or a registered-type serializer crashing/OOMing either end. *Severity:* critical. Gryo is not on the wire, and unlocked Gryo or a caller-supplied unhardened XML factory is out-of-model (user responsibility, §9). - *(documented)* + - **Resource bounds — split.** Malformed/pre-auth input that crashes/OOMs/hangs the server is **in-model** (above). Ordinary expensive traversals / large results are **operator capacity**, NOT in-model, unless a specific bug applies (super-linear amplification, a missing-where-expected limit, an unbounded traversal). A grammatically valid string the parser accepts must not itself enable DoS from small input, e.g. ReDoS via - a pathological `regex` pattern, which is the in-model amplification case. *(maintainer)* + a pathological `regex` pattern, which is the in-model amplification case. - **Client (GLV) robustness against hostile server responses.** A GLV deserializing response bytes from a malicious/compromised server (or a MITM) does not crash/OOM/execute code. *Violation symptom:* client - crash/OOM/RCE from crafted responses. *Severity:* critical. *(maintainer)* + crash/OOM/RCE from crafted responses. *Severity:* critical. - **No pass-through input can harm the GLV.** Outbound bytecode/message construction is otherwise not a surface, because its input is the trusted calling application. This property covers only the pass-through case: an application forwarding an untrusted end-user string verbatim (e.g. `client.submit(userString)`) must not cause code execution, a crash, a hang, or resource exhaustion **in the GLV itself**. Its *server-side* effect is Gremlin-injection, the application's responsibility (§9). *Violation symptom:* a submitted string that harms the GLV that submitted it. *Severity:* high. - *(maintainer)* + ## §9 Security properties the project does *not* provide @@ -395,24 +379,24 @@ Per-surface trust table: modifying client traffic. On the client side, a GLV sends its credentials in response to a server-issued auth challenge, so without TLS (or against a MITM) a driver can disclose those credentials to a hostile endpoint. A driver configured without TLS / certificate validation is a misconfiguration and is - out-of-scope. *(documented — TLS off by default)* + out-of-scope. - **No authentication or authorization by default (assumed).** If the shipped/default configuration runs with auth off, an exposed server is reachable by anyone on the network. This is an operator deployment - concern, not a code bug. *(documented)* + concern, not a code bug. - **No attribution/audit by default.** Audit logging is off by default (§5a), so a default deployment keeps no record of who ran which query. Enabling it (`enableAuditLog`) is the operator's responsibility (§10). - *(documented)* + - **No built-in data-access control.** There is no element-level (CRUD) authorization on graph data. Access is bounded only by which traversal sources the operator exposes and by a provider-supplied `Authorizer` (which does not ship, §5a). A client within its grants can read/write/delete any data the - exposed traversal source reaches. *(documented — no authorizer ships, authz is traversal-source/step-level)* + exposed traversal source reaches. - **Gryo / Kryo deserializer integrity holds only under the locked default.** Gryo defaults to a registration allow-list (`registrationRequired=true`), so it is not an arbitrary-instantiation sink, and a break within that locked config is a `VALID` bug like any deserializer. **Running Gryo unlocked (`registrationRequired=false`) is not a safe boundary against untrusted bytes and is the user's responsibility.** (A few registered types use Java native serialization, a gadget caveat even when locked.) Gryo is not on the wire, so this is an IO/file-surface concern (`io()` step, persistence, OLAP). - *(documented)* + - **A `TraversalStrategy` is not an access-control boundary on its own.** A remote request can remove or replace strategies on its traversal source, whether by bytecode source instruction (`withoutStrategies()`) or by script, so a strategy applied by the operator only restricts a client @@ -420,56 +404,55 @@ Per-surface trust table: Strategies also act at the traversal layer, not the storage layer (§5a). *(documented — `Bytecode` source instructions; `gremlin-applications.asciidoc` "Authorization")* - **Ordinary resource exhaustion is not a defended property.** Expensive traversals / large results that - consume CPU/memory are an operator capacity concern unless a specific bug applies (§8). *(maintainer)* -- **No defense against a malicious operator / host.** *(maintainer)* + consume CPU/memory are an operator capacity concern unless a specific bug applies (§8). +- **No defense against a malicious operator / host.** - **False friends:** - - "Authentication is available" does **not** mean it is **on**. It must be configured (§5a). *(maintainer)* + - "Authentication is available" does **not** mean it is **on**. It must be configured (§5a). - **Bytecode is safer than scripts but is not a sandbox.** A bytecode traversal still executes traversal steps server-side. Injection/abuse via a calling application that builds a Gremlin string by concatenating its own untrusted input (e.g. into Groovy) is the calling application's responsibility. Note this is - distinct from a `gremlin-language` parser breakout, which is an in-model TinkerPop bug (§8). *(maintainer)* + distinct from a `gremlin-language` parser breakout, which is an in-model TinkerPop bug (§8). - **Bytecode-only is not RCE-free unless lambdas are restricted.** A bytecode traversal can carry a **string lambda** that is script-evaluated server-side by a `GremlinScriptEngine`. `LambdaRestrictionStrategy` blocks this but is **not enabled by default**, and, being a strategy, is removable by the request itself unless an `Authorizer` pins it (§8). Not enabling and pinning it means the operator/provider accepts the code-execution risk (`BY-DESIGN`, like scripting, §9). - *(documented — `Lambda`, `LambdaRestrictionStrategy`)* - "Gremlin is just a query language" is misleading. The **string** form can run through a Groovy engine, so - it is code, not a constrained query. *(maintainer)* + it is code, not a constrained query. ## §10 Downstream responsibilities (operator/deployer) - Do **not** deploy the shipped getting-started `gremlin-server.yaml` as-is. Before exposing Gremlin Server on a public/untrusted network, harden it: enable TLS and an authenticator, and (given no authorizer ships) - supply an `Authorizer`, especially if scripting is enabled. *(maintainer)* + supply an `Authorizer`, especially if scripting is enabled. - Enable authentication (`Authenticator`) and authorization (`Authorizer`) for any non-trivial deployment. - *(documented)* + - **Restrict script execution:** prefer bytecode-based traversals (or the Groovy-free `GremlinLangScriptEngine`). If Groovy scripts are needed, apply the "Protecting Script Execution" controls (sandbox / compilation customizers / allow-list) and restrict who may submit scripts. **For bytecode, enable `LambdaRestrictionStrategy` and pin it with an `Authorizer` that denies strategy removal**, since bytecode-only is not RCE-free while string lambdas are allowed, and an unpinned strategy is removable by - the request itself (§9). *(documented)* -- Enable TLS (off by default) where traffic crosses an untrusted segment. *(documented)* -- Enable audit logging (`enableAuditLog`, off by default) where attribution of requests is needed. *(documented)* + the request itself (§9). +- Enable TLS (off by default) where traffic crosses an untrusted segment. +- Enable audit logging (`enableAuditLog`, off by default) where attribution of requests is needed. - Use the wire serializers (GraphBinary recommended for drivers). Keep Gryo locked - (`registrationRequired=true`), and do not read untrusted files through **unlocked** Gryo. *(documented)* + (`registrationRequired=true`), and do not read untrusted files through **unlocked** Gryo. - Apply per-request resource limits (`evaluationTimeout`, `maxContentLength`, etc.) appropriate to capacity. - *(documented)* -- Set filesystem permissions so only the server user can read the config / data directories. *(maintainer)* + +- Set filesystem permissions so only the server user can read the config / data directories. ## §11 Known misuse patterns *(Draft one-liners, expand before publishing.)* - Exposing the shipped getting-started default (no TLS, no auth) on an untrusted network instead of - hardening it first, especially with scripting enabled. *(maintainer)* + hardening it first, especially with scripting enabled. - Treating the script-engine sandbox as a complete RCE boundary rather than restricting who may script. - *(maintainer)* + - Building Gremlin by concatenating the calling application's untrusted input into a Groovy string (Gremlin-injection). This is distinct from a `gremlin-language` parser breakout, which is in-model (§8). - *(maintainer)* -- Reading untrusted files through **unlocked** Gryo (`registrationRequired=false`). *(documented)* + +- Reading untrusted files through **unlocked** Gryo (`registrationRequired=false`). ## §11a Known non-findings (recurring false positives) @@ -477,30 +460,30 @@ Per-surface trust table: - "Gremlin Server executes arbitrary code via scripts" — by-design when scripting is enabled for the principal (§9), and not a finding unless it bypasses a *configured* restriction. Disposition: - `BY-DESIGN: property-disclaimed`. *(documented)* + `BY-DESIGN: property-disclaimed`. - "Bytecode lambdas enable code execution" — a string lambda is server-side code like any script, so this is by-design when lambdas are permitted (§9), and not a finding unless it bypasses a *configured* - restriction. Disposition: `BY-DESIGN: property-disclaimed`. *(documented)* + restriction. Disposition: `BY-DESIGN: property-disclaimed`. - "No authentication / no TLS by default" — operator deployment responsibility (§9/§10), not a code bug in - itself. Disposition: `BY-DESIGN: property-disclaimed`. *(documented)* + itself. Disposition: `BY-DESIGN: property-disclaimed`. - "Gryo/Kryo deserialization can be exploited" — Gryo is not on the wire, so a request-path report is factually mistaken and needs no disposition (§13). Under the locked default (`registrationRequired=true`) a real break is `VALID`, whereas exploiting **unlocked** Gryo on untrusted files is the user's responsibility (§9/§10). Disposition for the unlocked-file variant: - `BY-DESIGN: property-disclaimed`. *(documented)* + `BY-DESIGN: property-disclaimed`. - "Expensive traversal consumes CPU/memory" — operator capacity concern, unless a specific bug applies - (§8/§9). Disposition: `BY-DESIGN: property-disclaimed`. *(maintainer)* + (§8/§9). Disposition: `BY-DESIGN: property-disclaimed`. - "A configured `ReadOnlyStrategy` / `SubgraphStrategy` / `PartitionStrategy` was bypassed" — whether by removing the strategy (bytecode `withoutStrategies()` or script) or by stepping around it (direct `Vertex.property()`, `mergeV`/`mergeE`), a strategy is not an access-control boundary on its own (§9). A finding only exists where a configured `Authorizer` pins the strategy and the pin is evaded (§8). - Disposition: `BY-DESIGN: property-disclaimed`. *(documented)* + Disposition: `BY-DESIGN: property-disclaimed`. - "Calling application concatenates untrusted input into a Groovy string (Gremlin-injection)" — the calling application's responsibility (§9). A `gremlin-language` parser breakout is the opposite: an in-model - TinkerPop bug (§8). Disposition: `BY-DESIGN: property-disclaimed`. *(maintainer)* + TinkerPop bug (§8). Disposition: `BY-DESIGN: property-disclaimed`. - Findings in any test-only module, any module containing examples, or build tooling (e.g. `gremlin-test`, `gremlin-examples`, `gremlin-tools`) — out of scope (§3). Disposition: - `OUT-OF-MODEL: unsupported-component`. *(maintainer)* + `OUT-OF-MODEL: unsupported-component`. ## §11b Known problems @@ -510,7 +493,7 @@ Per-surface trust table: keyed with **no owning-user check**, so any client presenting the id accesses the session's state. Sessions predate authentication and exist even with no users. The default server provides no per-user isolation, and this is accepted as how Gremlin Server works. A deployment needing isolation must use a provider that adds - it. *(documented — `SessionOpProcessor`, `Session`)* + it. ## §12 Conditions that would change this model @@ -553,81 +536,3 @@ rules on top of it. | `BY-DESIGN: property-disclaimed` | The behavior is exactly what §9 says TinkerPop does not defend against: scripts or bytecode lambdas executing code within what the deployment allows, the shipped no-TLS/no-auth default, ordinary resource exhaustion from expensive queries, reading untrusted files through unlocked Gryo, or an application building queries out of its own users' raw input. The software works as documented. When the scenario needs the operator's own access, `trusted-input` applies instead. | §9 | | `KNOWN-PROBLEM` | Matches the acknowledged weakness in §11b (session sharing), which the §8 enforcement property explicitly excepts. Already known and accepted, not a new finding. A deployment that needs the missing protection must use a provider that adds it. | §11b | | `MODEL-GAP` | Fits no row, or more than one. That is a defect in the model itself, and §12 requires revising it. | §12 | - -## §14 Open questions for the maintainers - -1. **Deployment posture.** ~~Is "Gremlin Server inside a trusted network, behind the app tier, not directly - public" the right §2/§5 framing?~~ **RESOLVED (maintainer):** Gremlin Server *is* intended to be - deployable on the public internet, but only after hardening beyond the shipped getting-started - `gremlin-server.yaml`, meaning TLS plus an authenticator (either built-in mechanism is adequate over TLS). - Reaching a server left on the insecure default is an operator hardening failure (`OUT-OF-MODEL`), not - evidence of a trusted-network assumption. Auth/authz is a shared responsibility. Providers typically - supply the `Authenticator`/`Authorizer` policy (no authorizer ships), while Gremlin Server owns the - enforcement machinery (§2/§5/§5a/§7/§8/§10). -2. **Default auth/authz posture.** ~~Do the shipped/default Gremlin Server configs run with authentication - and authorization **off**?~~ **RESOLVED (documented):** yes. `gremlin-server.yaml` has no - `authentication` block (default `AllowAllAuthenticator`) and ships no authorizer. The docs state "Gremlin - Server is configured to allow all requests to be processed (i.e. no authentication)" and "not shipped - with `Authorizer` implementations" (`gremlin-applications.asciidoc` "Security"). Reaching a server left - on that default is an operator hardening failure (`OUT-OF-MODEL`), not a code bug (per §14 Q1). The - framework's duty to *run* a configured authenticator/authorizer remains an in-model §8 property. -3. **Provider boundary.** ~~Confirm that vulnerabilities in third-party provider graph databases route to - those providers.~~ **RESOLVED (maintainer):** the boundary is drawn by **what code the defect reproduces - in**, not where it runs. Unmodified TinkerPop **reference code** (`gremlin-core`, `gremlin-server`, the - reference graph, serializers) is in-model even when running as part of a provider's product. A defect - requiring the **provider's own code or their modifications** routes to the provider. This includes a - **remote graph provider** that shares no TinkerPop code and only conforms to the WebSocket sub-protocol + - serializer specs. A defect reproducible only under a **mix** of the two is `MODEL-GAP` (joint - determination, not silently the provider's). Triage test: *does it reproduce on the unmodified reference - distribution?* (§3). -4. **Script execution (highest-stakes).** ~~Confirm that string-script evaluation = server-side code - execution by design, that restricting it (sandbox / allow-list / bytecode-only / who-may-script) is the - operator's responsibility, and what restriction (if any) is **on by default**. Is "Gremlin Server runs - arbitrary code via scripts" `BY-DESIGN` unless a *configured* restriction is bypassed?~~ **RESOLVED - (documented):** yes, by-design, and no restriction on by default (§4/§5a/§9/§10/§11a). -5. **TLS default.** ~~Is TLS **off by default** on the server connector? Is no-TLS-by-default an operator - responsibility (§9/§10)?~~ **RESOLVED (documented):** yes, `ssl.enabled: false` in the shipped - `gremlin-server.yaml`, and the operator enables it (§3/§5a/§9/§10). -6. **Serialization / Gryo.** ~~Which serializers are registered by default?~~ **RESOLVED (documented):** the - wire set is GraphSON 3.0 + GraphBinary. Gryo is IO-format-only (not on the wire) and defaults to a locked - registration allow-list (`registrationRequired=true`). A Gryo request-path report is a non-finding. A - flaw in a wire serializer or in **locked** Gryo is `VALID`, whereas **unlocked** Gryo on untrusted files - is the user's responsibility (§2/§4/§5a/§6/§8/§9/§10/§11a). -7. **Resource line.** ~~Confirm the split, and whether built-in per-request limits exist.~~ **RESOLVED:** - split confirmed *(maintainer)*. Malformed/pre-auth crash/OOM/hang is `VALID`. Ordinary expensive - traversals / large results are operator capacity unless a specific bug applies (super-linear - amplification, missing-where-expected limit, unbounded traversal/recursion). Built-in per-request limits - exist (`evaluationTimeout`, `maxContentLength`, `maxParameters`, `maxWorkQueueSize`, session timeouts), - with **no** traversal-depth or result-count cap *(documented)* (§6/§8/§9/§10/§11a). -8. **Parser robustness.** ~~Confirm parser/bytecode memory-safety against malformed input is a committed - property.~~ **RESOLVED (maintainer):** yes, the `gremlin-language` parser and bytecode path must not - crash/hang/OOM on **malformed input**, and a crafted string must not break out of a literal to inject - steps (`VALID`, §8). This covers parsing robustness and integrity. The *execution* cost of a well-formed - query is the Q7 capacity split, where an accidental/amplified OOM from a small query is in-model but a - legitimately huge query is operator capacity. Injection via Groovy string-concatenation in the calling - application is out-of-model (§9/§11a). -9. **GLV scope.** ~~Are the GLVs in scope, or deferred?~~ **RESOLVED (maintainer):** all GLVs (Java - `gremlin-driver` included, since it is the Java GLV) are in scope. The in-model client surface is - **response-deserialization robustness against a malicious/compromised server or MITM, plus TLS cert - validation** (§8). Outbound bytecode/message construction is **not** a surface, because its input is the - trusted calling application. Feeding untrusted end-user input into a traversal/script is Gremlin-injection - and the calling application's responsibility (§9/§11a). Sending bad bytes *to* the server folds into the - server's §8 request-robustness (§2/§6/§8). -10. **OLAP scope.** ~~Are `hadoop-gremlin` / `spark-gremlin` in scope, or operator infra?~~ **RESOLVED - (maintainer):** OLAP is **in-model as TinkerPop reference code**, covering the `gremlin-core`/`tinkergraph` - computer *and* the `hadoop-gremlin`/`spark-gremlin` reference modules (per the Q3 reference-code rule), - reachable remotely via `withComputer()` bytecode (not via the ANTLR string grammar). Only the - **third-party Hadoop/Spark runtime and the cluster deployment/config** are out-of-model (§3). The Gryo - untrusted-file risk on this IO surface stays as in Q6 (§2/§3/§6). -11. **§11a seeds.** Generalized the test/example/tooling non-finding to a **category** (any test-only or - example module, not a fixed name list). Remains a **standing invitation** for the PMC to append further - recurring false-positives (§11a). -12. **Canonical location.** ~~Confirm this model lives as root `THREAT_MODEL.md`, referenced from - `SECURITY.md`, wired from `AGENTS.md`.~~ **RESOLVED (documented):** verified. Root `THREAT_MODEL.md`, - referenced from `SECURITY.md`, which is wired from `AGENTS.md`. - -## §15 Machine-readable companion - -Deferred for v0. A `threat-model.yaml` can later encode the §6 trust table, §2/§3 component scoping, §8 -property/severity/symptom rows, §9 false friends, §11a non-findings, and §13 dispositions for automated -triage.