This is an automated email from the ASF dual-hosted git repository.
jongyoul pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/zeppelin.git
The following commit(s) were added to refs/heads/master by this push:
new 78255fd960 Add security threat model and wire AGENTS.md -> SECURITY.md
-> THREAT_MODEL.md
78255fd960 is described below
commit 78255fd960234022e4f6149239c43ae963cffa9e
Author: Jarek Potiuk <[email protected]>
AuthorDate: Fri Jun 12 03:38:47 2026 +0200
Add security threat model and wire AGENTS.md -> SECURITY.md ->
THREAT_MODEL.md
**This is a v0 draft proposal for the Zeppelin PMC to review — please
correct, reject, or discuss as needed.** The maintainer is the decision-maker;
nothing here is a requirement. The threat model does not need to be "finished"
for anything downstream — it just makes automated security review (and triage
of inbound reports) far less noisy.
**Context.** The ASF Security team is preparing the project for an
automated agentic security scan we're piloting. Those scans run against a
threat model that tells the scanner what's in scope, what's by-design, and what
counts as a real finding — without one, the output buries maintainers in noise.
This PR proposes the discoverable model plus the wiring the scanner needs.
**What's in this PR:**
- **`THREAT_MODEL.md`** (new) — a v0 security threat model written from
Zeppelin's public docs + codebase, following the [threat-model-producer
rubric](https://gist.github.com/potiuk/da14a826283038ddfe38cc9fe6310573). Every
claim carries a provenance tag: *(documented)* (from your docs/site) or
*(inferred)* (our guess from code/docs, for you to confirm / correct / strike).
Draft confidence ~18 documented / 24 inferred.
- **`SECURITY.md`** (was an empty file) — disclosure pointer + link to the
threat model.
- **`AGENTS.md`** — a `## Security` section so the `AGENTS.md → SECURITY.md
→ THREAT_MODEL.md` chain resolves for automated tooling. The existing developer
guidance is unchanged.
**The framing to sanity-check first:** Apache Zeppelin runs user notebook
code by design, so RBAC (Shiro + notebook ACL + URL ACL + impersonation) is the
boundary, **not a sandbox** — a `%sh` command from a run-capable user is the
product working, not RCE. The model treats interpreter execution as in-scope
only when it crosses an authn/authz or tenant boundary.
**What we'd need from the PMC:**
1. **§14 wave 1 (the important one):** rule on the insecure defaults — is
anonymous-by-default / public-notebooks / impersonation-off the *supported
production posture* (a report against it is `VALID`), or a dev-convenience
operators are expected to change (`OUT-OF-MODEL: non-default-build`)? This
reshapes the whole model.
2. Walk the §14 questions (waves 1–3) — a one-line confirm / correct /
strike per question is enough; each *(inferred)* tag becomes *(maintainer)* as
you answer.
If you'd rather own the drafting yourselves, close the PR and we'll wait —
entirely your call.
Closes #5268 from potiuk/asf-security/threat-model-2026-06-05.
Signed-off-by: Jongyoul Lee <[email protected]>
---
AGENTS.md | 13 ++
SECURITY.md | 44 +++++++
THREAT_MODEL.md | 383 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++
3 files changed, 440 insertions(+)
diff --git a/AGENTS.md b/AGENTS.md
index ff974bed7e..a9a559817e 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -558,3 +558,16 @@ Key conventions:
- **Testing**: JUnit 5 (Jupiter) + Mockito (Java; a small number of legacy
JUnit 4 tests still exist), Playwright (frontend E2E)
- **Logging**: SLF4J + Log4j2
- **License**: Apache License 2.0 — all new files need the ASF header
+
+## Security
+
+Security model: [SECURITY.md](./SECURITY.md), which links to the project's
+threat model at [THREAT_MODEL.md](./THREAT_MODEL.md).
+
+Agents that scan this repository should consult `THREAT_MODEL.md` for the
+project's in-scope / out-of-scope declarations, the security properties it
+provides and disclaims, the configuration knobs whose defaults change the
+security envelope, and the known non-findings (recurring false positives)
+before reporting issues. In particular, Apache Zeppelin executes user-supplied
+notebook code through its interpreters by design — that is the product's
+function, not a vulnerability; see `THREAT_MODEL.md` §3, §9, and §11a.
diff --git a/SECURITY.md b/SECURITY.md
new file mode 100644
index 0000000000..b4d40854d0
--- /dev/null
+++ b/SECURITY.md
@@ -0,0 +1,44 @@
+<!--
+Licensed to the Apache Software Foundation (ASF) under one or more
+contributor license agreements. See the NOTICE file distributed with
+this work for additional information regarding copyright ownership.
+The ASF licenses this file to you under the Apache License, Version 2.0
+(the "License"); you may not use this file except in compliance with
+the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+-->
+
+# Security Policy
+
+## Reporting a Vulnerability
+
+Please report suspected security vulnerabilities in Apache Zeppelin privately
+to the Apache Security Team at <[email protected]>, following the ASF process
+at <https://www.apache.org/security/>. Do not open public GitHub issues or pull
+requests for security reports.
+
+## Threat Model
+
+Apache Zeppelin's security threat model — what is in and out of scope, the
+security properties the project provides and disclaims, the adversary model,
+the configuration knobs whose defaults change the security envelope, and how
+findings are triaged — is documented in [THREAT_MODEL.md](./THREAT_MODEL.md).
+
+Note that Apache Zeppelin executes user-supplied notebook code through its
+interpreters by design; that capability is the product's primary function, and
+the threat model is about *who may reach it and with what isolation*. See
+`THREAT_MODEL.md` §3, §9, and §11a.
+
+## Operational Security Setup
+
+Operator-facing security configuration — authentication via Apache Shiro,
+notebook authorization, interpreter user impersonation, and HTTPS / HTTP
+security headers — is documented on the project website:
+<https://zeppelin.apache.org/docs/latest/setup/security/>
diff --git a/THREAT_MODEL.md b/THREAT_MODEL.md
new file mode 100644
index 0000000000..285a7b3cb4
--- /dev/null
+++ b/THREAT_MODEL.md
@@ -0,0 +1,383 @@
+<!--
+Licensed to the Apache Software Foundation (ASF) under one or more
+contributor license agreements. See the NOTICE file distributed with
+this work for additional information regarding copyright ownership.
+The ASF licenses this file to you under the Apache License, Version 2.0
+(the "License"); you may not use this file except in compliance with
+the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+-->
+
+# Apache Zeppelin — Security Threat Model
+
+## §1 Header
+
+- **Project:** Apache Zeppelin
+- **Modeled against:** `master` HEAD as of 2026-06-05 (latest released docs
line).
+- **Authors:** ASF Security team (v0 draft, generated via the
+ `threat-model-producer` rubric), for the Apache Zeppelin PMC to review.
+- **Status:** **DRAFT v0 — draft-first, not yet maintainer-ratified.** Most
+ claims are *(inferred)* from public documentation and the codebase and
+ must be confirmed; see §14.
+- **Version binding:** this model is versioned with the project. A report
+ against Zeppelin release *N* is triaged against the model as it stood at *N*.
+- **Reporting cross-reference:** findings that violate a §8 property should be
+ reported privately per the project's disclosure channel
+ (`[email protected]`); findings that fall under §3 or §9 are closed citing
+ this document.
+- **Provenance legend:** *(documented)* = stated in Zeppelin's own docs/site;
+ *(maintainer)* = confirmed by a Zeppelin PMC member; *(inferred)* = reasoned
+ from code/docs/domain knowledge, not yet confirmed (each has a §14 question).
+- **Draft confidence:** ~18 documented / 0 maintainer / ~24 inferred.
+
+**What Zeppelin is.** Apache Zeppelin is a web-based, multi-user notebook
+server for interactive data analytics. Users open notebooks in a browser and
+run "paragraphs" of code against pluggable *interpreters* (Spark, Flink,
+Python, JDBC, shell, etc.); the Zeppelin server launches and talks to
+interpreter processes over a Thrift IPC channel and returns results to the
+browser over a websocket. **Executing user-supplied code on the server is the
+product's primary function, not a vulnerability** — the security model is
+about *who is allowed to reach that capability and with what isolation*, not
+about preventing code execution.
+
+## §2 Scope and intended use
+
+- **Primary intended use** *(inferred)*: an operator-deployed, multi-tenant
+ analytics notebook server, run inside an organization's trusted network and
+ fronted by authentication, where authorized analysts author and run
+ notebooks against backend compute (Spark/Flink/etc.).
+- **Deployment shape** *(documented)*: a long-running JVM server
+ (`zeppelin-server`) plus one or more interpreter processes; reached via HTTP
+ + websocket, optionally behind a reverse proxy (NGINX).
+- **Caller roles** (a network service, so the role splits):
+ - **client / notebook user** — untrusted until authenticated; once
+ authenticated, trusted only up to their notebook/role permissions.
+ - **operator / admin** — trusted for the instance; owns `shiro.ini`,
+ interpreter settings, `zeppelin-site.xml`, the host.
+ - **anonymous visitor** — present **by default** (see §5a); trusted at
+ whatever level the deployment's authorization grants anonymous, which by
+ default is full access.
+
+**Component-family table** *(inferred — confirm in §14)*:
+
+| Family | Entry point | Touches outside process? | In model? |
+| --- | --- | --- | --- |
+| Web/REST/websocket server | `org.apache.zeppelin.rest.*`, websocket |
network | **yes** |
+| AuthN/AuthZ (Shiro + notebook ACL + URL ACL) | `shiro.ini`,
`NotebookAuthorization`, `SecurityRestApi` | filesystem (config) | **yes** |
+| Interpreter launch + IPC | Thrift `RemoteInterpreterServer`, process
launcher | child processes, network | **yes** (the launch/isolation boundary) |
+| Interpreter-executed user code | `%spark`, `%sh`, `%python`, `%jdbc`, … |
arbitrary (by design) | **boundary only** — the *code* is by-design;
reaching/isolating it is in model |
+| Credentials / datasource auth | `CredentialRestApi`, credential injection |
filesystem, backends | **yes** |
+| Notebook storage / repos | `NotebookRepo` (local FS, S3, Git, etc.) |
filesystem / cloud | **yes** |
+| Bundled interpreters / examples / web UI assets | `*-interpreter` modules,
demos | varies | **per-interpreter** — confirm which are supported (§14) |
+
+## §3 Out of scope (explicit non-goals)
+
+- **Sandboxing the code a permitted user runs.** A user with run permission on
+ a notebook can execute arbitrary code (`%sh`, Spark driver code, etc.) by
+ design; Zeppelin does not attempt to confine what that code does on the host
+ or backend. *(inferred — §14)*
+- **Defending a deployment that disables authentication and is exposed to an
+ untrusted network.** The docs direct operators to enable Shiro *or* deploy
+ only in a secured/trusted environment *(documented)*; an unauthenticated,
+ internet-exposed instance is an operator misconfiguration, not a Zeppelin
+ defect (pending the §5a/§14 ruling on whether anonymous is a supported
+ posture).
+- **Security of third-party interpreter backends** (the Spark cluster, the
+ JDBC database, the host shell) — Zeppelin brokers access; it does not own
+ those systems' security. *(inferred)*
+- **Bundled examples / demo notebooks / unsupported interpreters** — threat-
+ modeled separately if at all; integrators should not extend core guarantees
+ to them. *(inferred — §14: which interpreters are first-class?)*
+
+## §4 Trust boundaries and data flow
+
+The **primary trust boundary is the authentication + authorization layer**
+(Shiro realm → notebook ACL → URL ACL), not the API surface itself. Data flow
+and the trust transitions it crosses:
+
+1. Browser → **HTTP/websocket** → server: crosses the network boundary.
+ Untrusted until Shiro authenticates the session. *(documented)*
+2. Authenticated session → **notebook operation** (read/write/run): crosses
+ the notebook-ACL boundary (owner/reader/writer/runner). *(documented)*
+3. Run request → **interpreter process** over Thrift IPC: the server hands
+ user code to an interpreter. Whether this crosses an OS-user boundary
+ depends on **impersonation** (off by default → runs as the *server* OS
+ user). *(documented)*
+4. Interpreter → backend (Spark/JDBC/FS/shell): leaves Zeppelin's boundary
+ entirely. *(inferred)*
+
+**Reachability preconditions per family** (the triager's first test):
+- A finding in the web/REST/websocket family is in-model only if reachable by
+ a network client **before** the Shiro auth gate, or by an authenticated user
+ **beyond** their granted role/notebook permission.
+- A finding in interpreter-launch/IPC is in-model only if it lets a user cross
+ a boundary the model claims (e.g., one tenant reaching another tenant's
+ interpreter/credentials, or escaping the impersonation user when
impersonation
+ is on).
+- A finding that is "authenticated run-capable user executes code / reads files
+ as the interpreter's OS user" is **out of model** (that is the granted
+ capability) unless it crosses into another tenant or the operator boundary.
+
+## §5 Assumptions about the environment
+
+- **Operator-controlled host and config** *(inferred)*: `shiro.ini`,
+ `zeppelin-site.xml`, credential stores, and the interpreter settings are
+ trusted inputs written by the operator, not attacker-controllable.
+- **Network placement** *(documented)*: Zeppelin expects to sit in a secured/
+ trusted network or behind an authenticating proxy; HTTPS is "highly
+ recommended" for the web/websocket channel.
+- **Backend trust** *(inferred)*: the Spark/Flink/JDBC/SSH backends an
+ interpreter reaches are provisioned by the operator.
+- **What the server does to its host** *(inferred — §14)*: launches child
+ interpreter processes; reads config + notebook storage; opens listening
+ sockets (HTTP/websocket, Thrift); with impersonation, performs SSH/`setuid`-
+ style user switching. It is *not* expected to run as root.
+
+## §5a Build-time and configuration variants (the security-envelope knobs)
+
+This is the heart of Zeppelin's model — several **defaults are the less-secure
+value**, so the model is ambiguous until the PMC rules on each (see §14 wave
1):
+
+| Knob | Default | Effect on model | Maintainer stance |
+| --- | --- | --- | --- |
+| Shiro authentication (`conf/shiro.ini`) | **absent → anonymous**
*(documented)* | No auth boundary at all; every §8 authn/authz property is void
| **?** supported posture vs dev-only — §14.1 |
+| `zeppelin.notebook.public` / `ZEPPELIN_NOTEBOOK_PUBLIC` | **`true` → new
notes public** *(documented)* | Empty-ACL note is readable/runnable by any
authenticated (or anonymous) user | **?** §14.2 |
+| Interpreter user impersonation | **off → runs as server OS user**
*(documented)* | Without it, every run-capable user's code shares the
*server's* OS identity/privileges and filesystem | **?** §14.3 |
+| Interpreter binding mode (shared / scoped / isolated) | **shared**
*(inferred)* | Process-level separation between users/notes; "isolated" is a
*stability/resource* boundary, **not** a security sandbox | **?** §14.4 |
+| URL ACLs (`[urls]` in shiro.ini) gating `/interpreter`, `/credential`,
`/configurations` | **not restricted unless operator adds them** *(documented)*
| Sensitive admin endpoints open to any authenticated role absent explicit
`[urls]` rules | **?** §14.5 |
+| HTTPS / security headers (`http_security_headers`) | **off/plain unless
configured** *(documented)* | Credentials + session over plaintext; missing
CSP/XFO | operator responsibility (§10) |
+
+**Insecure-default ruling needed.** For each row whose default is the less-
+secure value, the PMC must rule: is the default the *supported production
+posture* (→ a report against it is `VALID`), or a *dev-convenience operators
+must change* (→ `OUT-OF-MODEL: non-default-build`, and the requirement moves to
+§10)? The public docs lean toward the latter ("strongly recommended… or only
+deploy… in a secured and trusted environment"), but this needs an explicit PMC
+call because it reshapes §8/§10/§11a/§13 at once.
+
+## §6 Assumptions about inputs
+
+Inputs and their trust (network-service shape — rows are endpoints/messages):
+
+| Surface | Input | Attacker-controllable? | Caller/operator must enforce |
+| --- | --- | --- | --- |
+| `POST` login / Shiro filter | credentials | **yes** (pre-auth) | strong
realm config; lockout/rate-limit at proxy *(inferred)* |
+| Websocket ops (run/edit/move paragraph) | notebook + paragraph payload |
**yes** (authenticated user) | notebook ACL + run permission enforced
server-side *(documented)* |
+| `NotebookRestApi` / `InterpreterRestApi` | note id, interpreter settings |
**yes** (authenticated user) | URL ACL + ownership checks *(inferred — §14.6)* |
+| `CredentialRestApi` | per-user credentials | **yes** (authenticated user) |
per-user credential isolation *(inferred — §14.7)* |
+| Paragraph code body | arbitrary code | **yes — by design** | this is the
granted capability, not validated input |
+| `shiro.ini`, `zeppelin-site.xml`, interpreter JSON | config | **no —
operator-trusted** | filesystem perms on config/secret files *(inferred)* |
+| Notebook storage backend contents | persisted notes | **mostly trusted**
(written via the app) | integrity of the repo (S3/Git/FS) *(inferred)* |
+
+Size/shape/rate: *(inferred — §14)* no documented limits on paragraph size,
+result size, or websocket message rate; resource exhaustion via large
+results / many interpreter launches is plausible and needs a §8 resource line.
+
+## §7 Adversary model
+
+**In scope:**
+- **Unauthenticated network client** (when Shiro is enabled): tries to reach
+ any authenticated capability without valid credentials — bypass the login
+ filter, forge/steal a session, reach a websocket/REST op pre-auth.
+- **Authenticated lower-privileged user**: a legitimate user trying to exceed
+ their grant — read/edit/run another user's note they lack ACL for, read
+ another user's credentials, reach admin endpoints (`/interpreter`,
+ `/credential`) their role shouldn't, or (impersonation on) break out of their
+ impersonation identity into another user's or the server's.
+
+**Capabilities:** can send arbitrary HTTP/websocket traffic; can author
+arbitrary code in paragraphs they may run; cannot (assumed) read the operator's
+config/secret files or the host outside what their interpreter identity grants.
+
+**Explicitly out of scope:**
+- The **operator/admin** and anyone with write access to `shiro.ini` /
+ interpreter config / the host — they have already won.
+- A user **executing code within their own granted run permission** — that is
+ the product working as designed, even though the code can be `%sh rm -rf`.
+- Anyone reaching a **default anonymous** instance over an untrusted network —
+ out of model pending the §5a/§14 ruling.
+
+## §8 Security properties the project provides
+
+Each conditional on the relevant §5a knob being set securely. *(All
+*(inferred)* pending §14 — Zeppelin documents the mechanisms but does not
+publish them as committed "properties".)*
+
+1. **Authentication of the web/REST/websocket surface** *when Shiro is
+ configured*. Violation symptom: an unauthenticated client performs an
+ operation requiring a session. Severity: **critical**. *(inferred)*
+2. **Authorization of notebook operations per the owner/reader/writer/runner
+ ACL** *when auth is on*. Violation symptom: a user reads/edits/runs a note
+ they lack permission for. Severity: **critical**. *(documented mechanism /
+ inferred as a committed property)*
+3. **URL-level access control** for sensitive endpoints via `[urls]`.
+ Violation symptom: a non-admin reaches `/interpreter`, `/credential`, or
+ `/configurations` despite a restricting rule. Severity: **high**.
+ *(documented mechanism)*
+4. **Per-user credential isolation** (one user cannot read another's injected
+ datasource credentials). Violation symptom: cross-user credential read.
+ Severity: **critical**. *(inferred — §14.7)*
+5. **Impersonation confinement** *when enabled*: interpreter code runs as the
+ logged-in user, not the server user, and not as another user. Violation
+ symptom: code runs as a different identity than the session's. Severity:
+ **high**. *(documented mechanism / inferred property)*
+6. **Resource/availability** *(inferred — §14)*: **needs a line.** Is an
+ unauthenticated request able to spawn interpreters / exhaust memory a bug?
+ Propose: pre-auth resource exhaustion is in-model; an authenticated user
+ running an expensive query is not. Confirm threshold in §14.
+
+## §9 Security properties the project does *not* provide
+
+- **No sandbox of permitted code.** A run-capable user's paragraph executes
+ with the full authority of the interpreter's OS identity (the *server* user
+ unless impersonation is on). "Zeppelin runs my shell command" is not a
+ vulnerability. *(inferred — §14.3)*
+- **No protection in anonymous/default mode.** With no `shiro.ini`, there is no
+ authn/authz boundary; everything in §8 is void. *(documented)*
+- **No transport security by default.** Plaintext HTTP/websocket unless the
+ operator configures TLS; credentials transit in the clear otherwise.
+ *(documented)*
+
+**False-friend properties (call out explicitly):**
+- **Interpreter "isolated" binding mode is not a security sandbox.** It gives
+ each user/note a separate interpreter *process* for stability and resource
+ separation; it does **not** confine what the code in that process can do to
+ the host or to shared backends, and absent impersonation all those processes
+ still run as the **same server OS user**. *(inferred — §14.4)*
+- **Notebook permissions are an application-layer ACL, not OS isolation.** A
+ user denied *read* on a note in the UI may still reach data through an
+ interpreter they *can* run if backends aren't separately access-controlled.
+ *(inferred — §14)*
+
+**Well-known attack classes left to the operator/integrator:** SSRF from
+interpreter code reaching internal services; secrets-in-notebooks; XSS/CSRF on
+the notebook web UI (mitigated only if `http_security_headers` + CSRF defenses
+are enabled — confirm coverage in §14); websocket cross-origin. One line each;
+the point is to put integrators on notice.
+
+## §10 Downstream / operator responsibilities
+
+For Zeppelin the "user" is the **operator** deploying it:
+- **Enable Shiro authentication** (or keep Zeppelin strictly inside a trusted,
+ network-isolated perimeter). *(documented)*
+- **Add `[urls]` rules** restricting `/interpreter`, `/credential`,
+ `/configurations` (and other admin paths) to admin roles. *(documented)*
+- **Set `zeppelin.notebook.public=false`** if notebooks should default to
+ private. *(documented)*
+- **Enable interpreter impersonation** for genuine multi-tenant isolation; do
+ not rely on binding mode alone. *(documented)*
+- **Terminate TLS** and enable `http_security_headers`. *(documented)*
+- **Protect `shiro.ini`, credential stores, and notebook storage** with host
+ filesystem permissions; do not run the server as root. *(inferred)*
+- **Treat backends (Spark/JDBC/SSH) as reachable by any run-capable user** and
+ access-control them independently. *(inferred)*
+
+## §11 Known misuse patterns
+
+- Exposing a **default (anonymous) Zeppelin to the public internet** — turns
+ the by-design code-execution surface into unauthenticated RCE-equivalent.
+- Relying on **notebook ACLs while interpreters run shared/as-server-user**, so
+ a run-capable user reaches data the ACL meant to hide.
+- Treating **"isolated" binding as a security boundary** between tenants.
+- Storing **long-lived secrets in notebook source** instead of the credential
+ store, then sharing the note.
+- Leaving **`/interpreter` and `/credential` reachable** by all authenticated
+ users (no `[urls]` rules).
+
+## §11a Known non-findings (recurring false positives)
+
+The highest-leverage section for keeping scan output signal-heavy:
+
+- **"`%sh` / interpreter executes arbitrary shell or driver code → RCE."**
+ By design for a run-capable user; `OUT-OF-MODEL` / `BY-DESIGN` unless it
+ crosses a tenant or the operator boundary. (§3, §9) *(inferred — §14.3)*
+- **"Interpreter process runs as the Zeppelin server OS user / can read server
+ files."** Documented default behavior without impersonation; operator config,
+ not a defect. (§5a, §10) *(documented)*
+- **"Anonymous user can do X"** reported against a deployment with **no
+ `shiro.ini`.** Out of model — auth is operator-enabled. (§5a, §9)
+ *(documented)*
+- **"No TLS / credentials in plaintext"** against a deployment the operator did
+ not configure for HTTPS. Operator responsibility. (§10) *(documented)*
+- **Static-analysis "command injection / code execution" hits on the
+ interpreter execution path.** That path *is* the feature; in-model only if it
+ bypasses the authn/authz gate. (§4 reachability test) *(inferred)*
+
+## §12 Conditions that would change this model
+
+- A new network surface or REST/websocket endpoint; a new first-class
+ interpreter; a change of any §5a default (e.g., shipping `shiro.ini` enabled,
+ or `notebook.public=false` by default); adding a built-in sandbox for
+ interpreter code; a new notebook-storage backend with different trust.
+- **A report that cannot be routed to one §13 disposition** is itself evidence
+ the model is incomplete — revise §8/§9 rather than make an ad-hoc call.
+
+## §13 Triage dispositions
+
+| Disposition | Meaning | Licensed by |
+| --- | --- | --- |
+| `VALID` | Bypasses the authn/authz gate, or lets a user exceed their
notebook/role grant, or crosses a tenant/credential/impersonation boundary,
with auth configured. | §8, §6, §7 |
+| `VALID-HARDENING` | No §8 property broken, but the API makes a §11 misuse
too easy; hardened at maintainer discretion. | §11 |
+| `OUT-OF-MODEL: trusted-input` | Requires control of operator config
(`shiro.ini`, interpreter JSON, host). | §6 |
+| `OUT-OF-MODEL: adversary-not-in-scope` | Requires operator/admin privilege,
or is a run-capable user executing code within their grant. | §7 |
+| `OUT-OF-MODEL: non-default-build` | Only manifests under an insecure §5a
default the PMC rules dev-only (e.g., anonymous mode, public notebooks, no
impersonation). | §5a |
+| `BY-DESIGN: property-disclaimed` | Concerns interpreter code execution or a
property §9 disclaims. | §9 |
+| `KNOWN-NON-FINDING` | Matches a §11a pattern. | §11a |
+| `MODEL-GAP` | Cannot be routed above → revise the model. | §12 |
+
+## §14 Open questions for the maintainers
+
+Grouped in waves; each states a **proposed answer** to confirm/correct/strike.
+Every *(inferred)* tag above maps to one of these.
+
+**Wave 1 — scope & the insecure defaults (these reshape everything):**
+1. **Anonymous default.** Proposed: anonymous/no-`shiro.ini` is a *dev-
+ convenience*; the supported production posture requires Shiro **or** a
+ trusted isolated network. So reports against an internet-exposed anonymous
+ instance are `OUT-OF-MODEL: non-default-build`. Correct? (→ §5a, §3, §11a)
+2. **`notebook.public=true` default.** Proposed: public-by-default is intended
+ convenience; operators needing isolation set it false. A "any user can read
+ an empty-ACL note" report is by-design, not a bug. Correct? (→ §5a, §2)
+3. **Impersonation off by default.** Proposed: without impersonation, all
+ interpreter code legitimately runs as the **server** OS user; this is the
+ documented default and not a vulnerability; multi-tenant OS isolation
+ requires enabling impersonation. Correct? (→ §3, §5a, §9, §11a)
+4. **Binding mode as boundary.** Proposed: shared/scoped/isolated are
+ stability/resource controls, **not** security sandboxes; we should state
+ that explicitly in §9. Agree? Which is the default? (→ §5a, §9)
+
+**Wave 2 — properties & enforcement:**
+5. **URL ACL default.** Are `/interpreter`, `/credential`, `/configurations`
+ open to any authenticated role unless `[urls]` restricts them, or is there a
+ built-in admin gate? (→ §5a, §8)
+6. **Server-side ACL enforcement.** Are notebook ACLs + role checks enforced on
+ the **server** for every websocket/REST op (not just hidden in the UI)? Any
+ ops that check only client-side? (→ §6, §8)
+7. **Credential isolation.** Does the credential store guarantee one user
+ cannot read another user's injected credentials, including via a shared
+ interpreter process? (→ §8, §9)
+
+**Wave 3 — surfaces & limits:**
+8. **First-class interpreters.** Which interpreters/modules are supported for
+ security purposes vs. community/unsupported (→ §2/§3 carve-out)?
+9. **Resource limits.** Any limits on paragraph/result size, websocket rate, or
+ concurrent interpreter launches? Where's the line between in-model pre-auth
+ exhaustion and by-design expensive queries? (→ §6, §8)
+10. **Web-UI hardening.** Does enabling `http_security_headers` give CSRF + XSS
+ + clickjacking coverage, or are those partly the operator's job? (→ §9)
+11. **Coexistence.** This is a new `THREAT_MODEL.md`; `SECURITY.md` (currently
a
+ stub) should point at it as canonical, and the website security pages stay
+ the operator how-to. Agree? (→ meta)
+
+## §15 Machine-readable companion
+
+Deferred for v0; a `threat-model.yaml` sidecar (entry points → trust, §5a
+defaults, §8 properties, §11a suppressions, §13 labels) can be generated once
+the prose is ratified.