From deb6514d121b985b4a63bd59e134dab187b27bed Mon Sep 17 00:00:00 2001 From: Jarek Potiuk Date: Thu, 27 Aug 2026 14:08:49 +0200 Subject: [PATCH 01/23] Add a draft threat model and wire it for discoverability MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ### Motivation The Pekko PMC asked the ASF Security team for a draft threat model to refine, as preparation for a Claude security scan. Pekko documents its security posture well in remote-security.md, serialization.md and remoting.md, but that material is spread across the docs and states the trust model implicitly rather than in a form a triager (or a scanning agent) can cite when classifying a finding. Separately, the AGENTS.md -> SECURITY.md chain a scanning agent follows to locate a project's model does not currently resolve: there is no SECURITY.md in the repo. ### Modification - THREAT_MODEL.md: a v0 draft following the Scovetta rubric. Every claim is tagged (documented) with a citation, or (inferred) with a matching open question in §14. 40 documented / 0 maintainer / 14 inferred. - SECURITY.md: reporting policy pointing at security@apache.org, plus a link to the threat model and the two assumptions that catch most reporters. - AGENTS.md: a Security section pointing at both. Nothing the existing docs assert has been dropped or weakened; §15 is a back-map from each existing statement to the section that now carries it. ### Result The discovery chain AGENTS.md -> SECURITY.md -> THREAT_MODEL.md resolves on main, and triagers have a citable document with a closed set of dispositions (§13). §14 holds 13 questions for the PMC, each stated as a proposed answer to confirm or correct. The highest-value ones are Q1 (is the plaintext `transport = tcp` default the supported production posture, given the documented network-isolation assumption?) and Q4 (are untrusted-mode and the deployment allow list security boundaries, or hardening?). The document is not ready to be treated as canonical until those are answered. ### Tests Not run - docs only ### References None - requested by the Pekko PMC chair on the ASF security scan thread --- AGENTS.md | 6 + SECURITY.md | 50 +++++++ THREAT_MODEL.md | 345 ++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 401 insertions(+) create mode 100644 SECURITY.md create mode 100644 THREAT_MODEL.md diff --git a/AGENTS.md b/AGENTS.md index 9e42d6b2369..0f92d2b5c96 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -225,3 +225,9 @@ Fixes #1234, Refs #1234, or None - - Never omit `References`. - Use `Refs #...`, `Fixes #...`, or `None - `. - Do not add `Co-authored-by` or AI-assistant trailers to commits or PR descriptions. + +## Security + +- Read [`SECURITY.md`](SECURITY.md) before reporting or acting on a security issue. +- Classify security findings against the threat model it links, + [`THREAT_MODEL.md`](THREAT_MODEL.md); §11a lists recurring false positives. diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 00000000000..a7f05bce50f --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,50 @@ +# Security Policy + +## Reporting a Vulnerability + +**Do not report security vulnerabilities through public GitHub issues, pull +requests, or the mailing lists.** + +Report them privately to the Apache Security team: + + security@apache.org + +Apache Pekko does not operate a separate project security list; reports go to +the foundation-wide address above, which routes to the Pekko PMC. + +Please follow the [guidelines laid down by the Apache Security +team](https://www.apache.org/security/). The Pekko PMC will coordinate +responsible disclosure with affected upstream maintainers where needed. + +To receive security announcements, subscribe to the [Apache Announce Mailing +List](https://lists.apache.org/list.html?announce@apache.org). + +## Security Model + +Before reporting, please read Apache Pekko's threat model: + +[THREAT_MODEL.md](THREAT_MODEL.md) + +It states what Pekko treats as a vulnerability and what it does not — in +particular its assumptions about the network remoting runs on, which +configuration defaults change the security envelope, and which properties +Pekko explicitly leaves to the operator. Reports that fall outside the model +will be closed citing the relevant section, so checking first will save you +time. + +Two points catch most reporters: + +- **Pekko remoting assumes a trusted network.** An `ActorSystem` is not + designed to be exposed to an untrusted network; it is expected to be + protected by network security such as a firewall, and optionally TLS with + mutual authentication. See §4 and §7. +- **All nodes in a Pekko cluster are trusted equally.** There is no + Byzantine-peer model — a finding whose precondition is "a cluster member + misbehaves" is out of model. See §7. + +## Further Security Documentation + +- [Apache Pekko security announcements](https://pekko.apache.org/docs/pekko/current/security/) +- [Remote Security](https://pekko.apache.org/docs/pekko/current/remote-security.html) +- [Java Serialization](https://pekko.apache.org/docs/pekko/current/serialization.html#java-serialization) +- [Remote deployment allow list](https://pekko.apache.org/docs/pekko/current/remoting.html#remote-deployment-allow-list) diff --git a/THREAT_MODEL.md b/THREAT_MODEL.md new file mode 100644 index 00000000000..2149c8b4877 --- /dev/null +++ b/THREAT_MODEL.md @@ -0,0 +1,345 @@ +# Apache Pekko — Threat Model + +**Status:** DRAFT — awaiting Pekko PMC review. Not yet maintainer-ratified. + +| | | +| --- | --- | +| **Project** | Apache Pekko (core toolkit) | +| **Written against** | commit `a054f9ce`, `main` | +| **Date** | 2026-08-27 | +| **Authors** | ASF Security team, at the request of the Pekko PMC | +| **Version binding** | This model is versioned alongside the project. A report against Pekko version *N* is triaged against the model as it stood at *N*, not at `main`. | +| **Reporting** | Findings that violate a §8 property should be reported per [`SECURITY.md`](SECURITY.md). Findings that fall under §3 or §9 will be closed citing this document. | + +**Provenance legend.** Every non-trivial claim is tagged: +*(documented)* — stated in Pekko's own docs, cited. +*(maintainer)* — stated by a Pekko maintainer in review of this document. +*(inferred)* — reasoned from code or config defaults, **not yet confirmed**; each has a matching question in §14. + +**Draft confidence:** 40 documented / 0 maintainer / 14 inferred. The documented share is unusually high because Pekko's own remoting and serialization docs already state much of the trust model explicitly. What remains inferred is concentrated in two places: the negative claims in §5, and the §5a default rulings — which are the answers that most change the model. Both are §14. + +Apache Pekko is a Scala/Java toolkit for building concurrent, distributed and resilient message-driven applications. Its unit of computation is the actor: an object with private state that communicates only by asynchronous message passing. Pekko extends that model across machines — an `ActorSystem` can address actors on remote nodes as if they were local, and a cluster of such systems provides membership, sharding, singletons and replicated data. That transparency is the source of most of this document: **the network is not incidental to Pekko, it is part of the programming model**, and the security consequences follow from where the toolkit assumes that network sits. + +--- + +## §2 Scope and intended use + +Pekko is an **in-process library** that the application embeds and configures. It is not a server, not a daemon, and ships no runnable artifact that an operator deploys on its own. There is consequently no "Pekko instance" to secure independently of the application hosting it. + +Three caller roles matter, and they are not equally trusted: + +- **The embedding application** — fully trusted. It constructs the `ActorSystem`, supplies configuration, and already shares the JVM with Pekko. +- **The operator/deployer** — trusted for the instance. Chooses transport, TLS material, and the network the node sits on. Most of §10 lands here. +- **The remote peer** — another `ActorSystem` that has associated over remoting. Its trust level is the central question of this model, and §7 answers it. + +### Component families + +| Family | Modules | Entry point | Leaves the process? | In model | +| --- | --- | --- | --- | --- | +| Actor core | `actor`, `actor-typed`, `slf4j`, `coordination` | `ActorSystem`, `ActorRef` | no | **yes** | +| Remoting | `remote` | Artery transport (`tcp` / `tls-tcp` / `aeron-udp`) | **network** | **yes — primary surface** | +| Cluster | `cluster`, `cluster-typed`, `cluster-tools`, `cluster-sharding*`, `cluster-metrics` | gossip, membership, sharding | **network** (via remoting) | **yes** | +| Replicated data | `distributed-data` | CRDT replication | **network** (via remoting) | **yes** | +| Serialization | `serialization-jackson`, `serialization-jackson3` | `Serializer` SPI | deserializes network bytes | **yes — critical** | +| Streams | `stream`, `stream-typed` | `Source`/`Flow`/`Sink` | only via connectors the app supplies | **yes** | +| Persistence | `persistence`, `persistence-typed`, `persistence-query`, `persistence-shared` | journal / snapshot plugin SPI | **storage backend** | **yes**, boundary at the SPI | +| PKI | `pki` | PEM/keystore parsing | **reads files** | **yes** | +| Discovery | `discovery` | service-discovery SPI | **network / DNS** | **yes** | +| OSGi | `osgi` | bundle activator | classloading | **yes** | +| Test kits | `*-testkit`, `multi-node-testkit`, `*-tests`, `persistence-tck`, `stream-tests-tck` | — | — | **no** — §3 | +| Benchmarks | `bench-jmh` | — | — | **no** — §3 | +| Build / docs | `docs`, `project`, `scripts`, `legal`, `kubernetes` | — | — | **no** — §3 | + +*(inferred — the in/out split is the ASF Security team's proposal; see §14 Q6)* + +--- + +## §3 Out of scope (explicit non-goals) + +- **Test kits, benchmarks, build tooling and documentation sources.** These ship in the repository but are not part of the security-supported surface. A finding in `bench-jmh` or any `*-tests` module is `OUT-OF-MODEL: unsupported-component`. *(inferred — §14 Q6)* +- **Pekko is not a sandbox.** Actors are not an isolation boundary. Any code running in the JVM can reach any actor's state by ordinary means; the actor model is a concurrency discipline, not a security control. *(inferred — §14 Q4)* +- **Pekko is not an authorization framework.** It carries no notion of a principal, role, or permission on a message. Application-level authorization is the embedding application's job. *(inferred — §14 Q11)* +- **A Pekko cluster is not a multi-tenancy boundary.** The documentation is explicit that *"you'll have to trust all cluster nodes the same in a Pekko cluster anyway"* *(documented — `remote-security.md`)*. Separating mutually-distrusting tenants across nodes of one cluster is not a supported deployment. +- **Attackers who already control the embedding process** are out of scope — they have already won. *(inferred — §14 Q11)* + +--- + +## §4 Trust boundaries and data flow + +**The primary trust boundary is the remoting network boundary**, and Pekko's documentation places it unusually explicitly: + +> "An `ActorSystem` should not be exposed via Pekko Cluster or Pekko Remote over plain Aeron/UDP or TCP to an untrusted network, such as the Internet. It should be protected by network security, such as a firewall. If that is not considered enough protection, TLS with mutual authentication should be enabled." +> — *(documented — `remote-security.md`)* + +and states the consequence of crossing it: + +> "As soon as an actor system can connect to another remotely, it may in principle send any possible message to any actor contained within that remote system." +> — *(documented — `remote-security.md`, Untrusted Mode)* + +Read together, these define the model: **remoting assumes it runs on a network the operator has already restricted.** Association is the security decision; once a peer is associated, it is inside. Pekko offers two mechanisms to tighten that (TLS mutual authentication, untrusted mode) but neither converts remoting into a boundary that is safe to face the open internet *(documented — see §9)*. + +The application's own boundaries — the HTTP endpoint, the message broker, the database — sit **outside** Pekko and are the embedding application's responsibility. + +### Reachability preconditions per family + +A finding must meet its family's precondition to be in-model: + +- **Remoting** — reachable from bytes arriving on the Artery transport, *and* the report must state whether it assumes the network-isolation assumption above is intact. A finding that requires an attacker already on the cluster network is judged under §7, not automatically valid. +- **Serialization** — reachable from a message payload deserialized by a **configured, enabled** serializer. Findings reachable only when `allow-java-serialization = on` are judged under §5a. +- **Cluster / distributed-data** — reachable from gossip or replication traffic originating at an **associated peer**. Per §7 such a peer is trusted, so these are typically out of model unless the finding shows a pre-association reach. +- **Actor core / streams** — reachable from data the embedding application passes in. Trusted by default; a finding must show the data crosses an application boundary that Pekko itself defines. +- **Persistence** — reachable from journal or snapshot contents. See §6 on the storage-trust question. +- **PKI** — reachable from PEM/keystore material. Operator-supplied and trusted; see §14 Q5. + +--- + +## §5 Assumptions about the environment + +- **Runtime.** A conformant JVM. Pekko does not defend against a hostile JVM, a hostile classpath, or an attacker with local code execution in the same process. *(inferred — §14 Q11)* +- **Network adjacency.** *"Best practice is that Pekko remoting nodes should only be accessible from the adjacent network."* *(documented — `remote-security.md`)* +- **PKI scope.** Where TLS is used, every certificate issued by the same internal PKI tree is equivalent: *"there is still a risk that an attacker can gain access to a valid certificate by compromising any node with certificates issued by the same internal PKI tree."* *(documented — `remote-security.md`)* +- **Clock.** The failure detector and gossip convergence depend on reasonably-behaved local clocks. Pekko does not defend against adversarial clock manipulation on a cluster node. *(inferred — §14 Q12)* +- **Entropy.** On Linux with SHA1PRNG, the docs recommend `-Djava.security.egd=file:/dev/urandom` to avoid blocking, noting it *"is NOT as secure because it reuses the seed."* *(documented — `remote-security.md`)* + +### What Pekko does not do to its host + +These are negative claims, rarely written down anywhere, and therefore the highest-value confirmation targets in §14 *(all inferred — §14 Q7)*: + +- Installs no signal handlers and spawns no child processes. +- Opens no listening socket unless remoting is explicitly configured — the actor core alone is purely in-process. +- Reads configuration from the classpath and supplied `Config`; does not read arbitrary environment variables of its own accord. +- Writes to logging via SLF4J as configured; does not write to stdout/stderr directly in normal operation. +- Does not mutate process-global state (locale, default `SecurityManager`, system properties) at initialization. + +--- + +## §5a Configuration variants that change the security envelope + +Pekko's security posture is set almost entirely by configuration. **Every row below whose default is the weaker value needs a maintainer ruling** — see §14. + +| Setting | Default | Effect | Maintainer stance | +| --- | --- | --- | --- | +| `pekko.actor.allow-java-serialization` | `off` | On, exposes the JVM deserialization attack surface to any message payload. Docs: *"highly discouraged to enable in production"* *(documented — `serialization.md`)* | Secure default. Enabling it is a documented deviation — see §14 Q2 | +| `pekko.remote.artery.transport` | `tcp` | **Plaintext.** No peer authentication and no confidentiality on the wire. `tls-tcp` opts into TLS | **UNRESOLVED — §14 Q1** | +| `pekko.remote.artery.ssl.config-ssl-engine.require-mutual-authentication` | `on` | Both ends present certificates *(documented)* | Secure default | +| `…ssl.config-ssl-engine.hostname-verification` | `off` | Off, a valid cert from the trusted PKI authenticates regardless of which host presents it. Docs *recommend* `on` but ship `off` *(documented)* | **UNRESOLVED — §14 Q3** | +| `pekko.remote.artery.untrusted-mode` | `off` | On, blocks inbound system messages, `PossiblyHarmful` messages, remote deployment, remote DeathWatch, and actor selections outside `trusted-selection-paths` *(documented)* | **UNRESOLVED — §14 Q4** | +| `pekko.remote.artery.trusted-selection-paths` | `[]` | Allow-list of actor paths that may receive selections under untrusted mode *(documented)* | Follows Q4 | +| `pekko.remote.deployment.enable-allow-list` | `off` | On, restricts which actor classes a peer may remote-deploy *(documented — `remoting.md`)* | **UNRESOLVED — §14 Q4** | +| `pekko.actor.serialize-messages` / `serialize-creators` | `off` | Testing aids that force serialization round-trips. Not security controls | Not a security knob *(inferred — §14 Q13)* | + +--- + +## §6 Assumptions about inputs + +For a toolkit whose surface is a wire protocol, the useful table is keyed by **message class**, not by function. + +| Source | Input | Attacker-controllable? | Who must enforce what | +| --- | --- | --- | --- | +| Artery transport | Inbound frame headers / framing | **Yes if the network-isolation assumption fails** | Operator: network isolation (§10), or TLS | +| Artery transport | Serialized user-message payload | **Yes**, from an associated peer | Pekko: only enabled serializers run. App: validate semantic content | +| Artery transport | System messages (`Create`, `Terminate`, `Watch`, `Supervise`) | **Yes**, from an associated peer unless untrusted mode is on | Operator: `untrusted-mode` if peers are less than fully trusted | +| Artery transport | `PossiblyHarmful` messages (`PoisonPill`, `Kill`) | **Yes**, same condition | Same | +| Artery transport | Remote-deployment `Props` | **Yes**, same condition | Operator: `enable-allow-list` | +| Cluster | Gossip / membership state | From an **associated** peer — trusted per §7 | — | +| `distributed-data` | Replicated CRDT deltas | From an associated peer — trusted per §7 | — | +| Persistence | Journal / snapshot contents on replay | Depends on backend trust — **§14 Q5** | App/operator: secure the store | +| `pki` | PEM / keystore files | **No** — operator-supplied, trusted | Operator: protect key material | +| `discovery` | Service-discovery responses (DNS, K8s API) | **Potentially** — depends on the resolver | Operator: trust the discovery mechanism | +| Config | `application.conf`, system properties | **No** — trusted, part of the deployment | Operator | + +**Size and rate.** Artery imposes frame-size limits and the failure detector bounds how long an unresponsive peer is tolerated. Whether these are *security* controls or tuning parameters is §14 Q8. + +--- + +## §7 Adversary model + +**In scope:** + +- **An unassociated network attacker** who can reach the remoting port — in a deployment where the operator's isolation assumption has held, this attacker should not exist; where it does, the relevant question is whether they can achieve anything **before** association completes. Pre-association reachability is the sharpest in-model attack surface. *(inferred — §14 Q1)* +- **An attacker supplying message content** to an otherwise legitimate peer — e.g. data that originates at the application's own untrusted edge and is forwarded into an actor message. *(inferred — §14 Q11)* + +**Explicitly out of scope:** + +- **An associated peer behaving arbitrarily.** There is no Byzantine-peer model. The documentation is direct: *"you'll have to trust all cluster nodes the same in a Pekko cluster anyway"* *(documented)*, and *"as soon as an actor system can connect to another remotely, it may in principle send any possible message to any actor contained within that remote system"* *(documented)*. **A finding whose precondition is "a cluster member misbehaves" is out of model** — there is no honest-majority threshold to state, because the model has no notion of a dishonest member. *(inferred that this generalises to all cluster protocols — §14 Q9)* +- **An attacker holding any certificate from the cluster's PKI tree.** Documented as equivalent to cluster access *(documented)*. +- **An attacker with code execution in the embedding JVM.** Already inside the trust boundary. +- **Side-channel observers.** Pekko makes no timing or memory-access guarantees. *(inferred — §14 Q11)* + +--- + +## §8 Security properties Pekko provides + +| # | Property & conditions | Violation symptom | Severity | Provenance | +| --- | --- | --- | --- | --- | +| P1 | **Java serialization is disabled by default**; Pekko uses it for none of its own internal messages | A payload deserialized via Java serialization under default config | **Critical** — RCE class | *(documented — `serialization.md`)* | +| P2 | The disabled Java serializer **logs rejected attempts** under the `SECURITY` marker, and those *"SHOULD be treated as potential attacks which the serializer prevented"* | Silent acceptance where rejection + log is expected | High | *(documented — `serialization.md`)* | +| P3 | **Remote deployment is not remote code loading.** The actor class must already be present on the target system | A peer causing a class absent from the target's classpath to execute | **Critical** | *(documented — `remoting.md`)* | +| P4 | With `enable-allow-list = on`, only listed actor classes may be remote-deployed onto this node | An unlisted class deployed | High | *(documented — `remoting.md`)* | +| P5 | With `untrusted-mode = on`, inbound system messages, `PossiblyHarmful` messages, remote deployment, remote DeathWatch and non-allow-listed actor selections are **dropped and logged** | Any of these taking effect despite the flag | High | *(documented — `remote-security.md`)* | +| P6 | With `transport = tls-tcp`, TLS is applied and **mutual authentication is on by default** — the server side also requests and verifies the client's certificate | Association completing without peer certificate verification | **Critical** | *(documented — `remote-security.md`)* | +| P7 | Certificate rotation is supported for mTLS in Kubernetes without cluster restart | Rotation causing association failure or silent downgrade | Medium | *(documented — `remote-security.md`)* | + +**Note the shape of this list:** P4, P5 and P6 are all *conditional on a non-default setting*. Under stock configuration, the properties Pekko actively provides at the network boundary are P1, P2 and P3 — the rest of the posture is delegated to the operator via §10. This is a deliberate design, but it is the single most important thing for a triager to understand. + +--- + +## §9 Security properties Pekko does **not** provide + +- **No peer authentication by default.** With the default `transport = tcp` there is no shared secret, no certificate, and no handshake credential. Any host that can reach the port and speak Artery can attempt association. *(inferred from config defaults — §14 Q1)* +- **No confidentiality or integrity on the wire by default.** Same cause. *(inferred — §14 Q1)* +- **No intra-cluster authorization.** Once associated, a peer may address any actor in the system. There is no per-actor, per-message, or per-peer permission model. *(documented)* +- **No Byzantine fault tolerance.** See §7. Cluster protocols assume members are honest; there is no threshold below which arbitrary member behaviour is tolerated. *(documented — `remote-security.md`)* +- **No bound on blast radius from one compromised node.** Documented explicitly for the PKI case *(documented)*. +- **No protection once Java serialization is enabled.** Turning it on re-exposes the full JVM deserialization surface; the docs place this squarely on the operator. *(documented)* + +### False friends + +These are the assumptions integrators most often bring with them, and each is wrong: + +- **Untrusted mode is not a security boundary.** *"Untrusted mode does not give full protection against attacks by itself. It makes it slightly harder to perform malicious or unintended actions"* *(documented — `remote-security.md`)*. It is hardening. Treating it as a substitute for network isolation is a §11 misuse. +- **`PossiblyHarmful` is a marker, not an authorization mechanism.** It is a compile-time trait that untrusted mode consults. It confers no protection when untrusted mode is off. +- **A service mesh is not a substitute for remoting security.** *"Encryption and authentication via a service mesh is not a replacement for Pekko Cluster remoting security"* — Pekko's peer-to-peer addressing has requirements a mesh does not satisfy *(documented — `remote-security.md`)*. +- **TLS mutual authentication does not give per-node identity guarantees by default**, because `hostname-verification` ships `off` — any cert from the trusted PKI authenticates as any node. *(documented + config default)* +- **The actor boundary is not a security boundary.** Message-passing isolation is a concurrency property, not a confidentiality one. + +### Well-known attack classes left to the caller + +- **JVM deserialization gadget chains** — mitigated by P1 only so long as Java serialization stays off, and only for payloads Pekko itself deserializes; application-level serializers are the application's problem. +- **Resource-exhaustion via message volume or size** — see §14 Q8. +- **DNS / service-discovery spoofing** — `discovery` trusts the resolver it is configured with. +- **Storage-layer tampering** on persistence journals — see §14 Q5. + +--- + +## §10 Downstream responsibilities + +The operator or embedding application must: + +1. **Keep remoting off untrusted networks.** Firewall the remoting port to the adjacent network. This is the assumption the whole model rests on *(documented)*. +2. **Enable `tls-tcp` if the network is not sufficiently trusted**, and set `hostname-verification = on` unless hostnames are genuinely dynamic *(documented)*. +3. **Leave `allow-java-serialization = off`.** If it must be enabled for legacy compatibility, treat the deployment as having no deserialization protection *(documented)*. +4. **Treat `SECURITY`-marked log entries from the Java serializer as attack indicators**, not noise *(documented)*. +5. **Scope the PKI tree to the cluster.** Any certificate it issues is cluster access *(documented)*. +6. **Consider `untrusted-mode = on` and `enable-allow-list = on`** where peers are less than fully trusted — understanding both are hardening, not boundaries. +7. **Never place mutually-distrusting tenants in one cluster** *(documented)*. +8. **Supply passwords by environment substitution, not literals in config files** *(documented)*. + +--- + +## §11 Known misuse patterns + +- **Exposing remoting or cluster ports to the internet**, on the theory that TLS or untrusted mode makes it safe. Both are explicitly documented as insufficient alone. +- **Enabling `allow-java-serialization` for prototyping convenience and shipping it.** The docs anticipate exactly this path. +- **Relying on a service mesh** for cluster authentication *(documented as insufficient)*. +- **Using cluster membership as a tenancy or privilege boundary.** +- **Enabling TLS but leaving `hostname-verification = off`** and assuming per-node identity. +- **Treating actor encapsulation as a confidentiality guarantee** within a single JVM. + +--- + +## §11a Known non-findings (recurring false positives) + +Feed this section to scanners and AI triage as a suppression list. + +- **"Remote deployment permits arbitrary class instantiation."** Remote deployment is *not* remote code loading — the class must already be on the target's classpath *(P3, documented)*. Absent a way to introduce a class, this is not RCE. +- **"Remoting transport defaults to plaintext."** Correct, and by design under the §4 network-isolation assumption. In-model only if the finding shows harm *within* an isolated network — pending §14 Q1. +- **"`PoisonPill` can be sent remotely to shut down a system."** Documented behaviour, gated by `untrusted-mode` *(documented — `remote-security.md`)*. +- **"`hostname-verification` is disabled by default."** Known and documented, with the trade-off spelled out for dynamic-hostname deployments — pending §14 Q3. +- **Findings in `*-tests`, `*-testkit`, `bench-jmh`, `docs`** — `OUT-OF-MODEL: unsupported-component` per §3. +- **"SHA1PRNG with `/dev/urandom` reuses the seed."** Documented trade-off, deliberately recommended to avoid blocking *(documented)*. + +--- + +## §12 Conditions that would change this model + +- A change to any §5a **default**, particularly `transport`, `untrusted-mode`, or `allow-java-serialization`. +- A new transport, or a new wire protocol at the remoting layer. +- Any per-peer or per-actor authorization mechanism — that would create an intra-cluster trust boundary this model says does not exist. +- Promotion of a §3 module into the supported surface. +- **A report that cannot be routed to exactly one §13 disposition.** That is evidence of a model gap; the correct response is to revise this document, not to make an ad-hoc call. + +--- + +## §13 Triage dispositions + +| Disposition | Meaning | Licensed by | +| --- | --- | --- | +| `VALID` | Violates a §8 property via an in-scope adversary and input | §6, §7, §8 | +| `VALID-HARDENING` | No §8 property violated, but the API makes a §11 misuse easy enough to warrant hardening. No CVE by default | §11 | +| `OUT-OF-MODEL: trusted-input` | Requires control of an input §6 marks trusted (config, PEM material, application-supplied data) | §6 | +| `OUT-OF-MODEL: adversary-not-in-scope` | Requires an associated peer to misbehave, a PKI-tree certificate, or in-JVM code execution | §7 | +| `OUT-OF-MODEL: unsupported-component` | Lands in a §3 module | §3 | +| `OUT-OF-MODEL: non-default-build` | Only manifests under a non-default §5a setting — most often `allow-java-serialization = on` | §5a | +| `BY-DESIGN: property-disclaimed` | Concerns a §9 property Pekko explicitly does not provide | §9 | +| `KNOWN-NON-FINDING` | Matches a §11a pattern | §11a | +| `MODEL-GAP` | Routable to none of the above — triggers §12 | §12 | + +--- + +## §14 Open questions for the maintainers + +Each states a **proposed answer**. Confirming or correcting is enough; no need to write prose. + +**Q1 — The plaintext-default ruling (highest value; reshapes §7, §9, §11a, §13).** +`transport` ships `tcp`, so a stock cluster has no peer authentication. *Proposed:* the default is the supported production posture **only under the documented network-isolation assumption**, so a report of "unauthenticated peer can associate" is `OUT-OF-MODEL: adversary-not-in-scope` when it assumes internet exposure, but `VALID` if it shows harm reachable **pre-association** from an adjacent-network host. Correct? + +**Q2 — `allow-java-serialization`.** *Proposed:* enabling it is a documented deviation, so any deserialization finding requiring it is `OUT-OF-MODEL: non-default-build`. Confirm? + +**Q3 — `hostname-verification = off`.** *Proposed:* deliberate, to support dynamic-hostname deployments; a report that "any PKI cert authenticates as any node" is `BY-DESIGN: property-disclaimed` per §9. Confirm — or is `on` the intended posture and the default simply legacy? + +**Q4 — Are `untrusted-mode` and `enable-allow-list` security boundaries or hardening?** *Proposed:* **hardening**, per the documented *"does not give full protection"*. So a bypass of either is `VALID-HARDENING`, not a CVE-class break. Confirm? + +**Q5 — Persistence backend trust.** *Proposed:* journal and snapshot stores are **trusted**; an attacker who can write to the journal is out of model. Confirm — or should replay treat stored bytes as untrusted? + +**Q6 — Module in/out split (§2 table).** *Proposed:* the split shown. Two specific checks: is `kubernetes/` deployment tooling or supported code, and should `osgi` stay in model given its classloading surface? + +**Q7 — The negative claims in §5.** These are inferred and hard to cite. Are any wrong — does Pekko open sockets, read env vars, or mutate process-global state in ways an integrator would not expect? + +**Q8 — Resource guarantees.** Is unbounded memory or CPU growth driven by a remote peer's message volume/size a **bug** or accepted given §7's trusted-peer stance? Ideally a categorical line: *"super-linear in message size is a bug; constant-factor is not"*, or *"no resource guarantee is made against an associated peer"*. + +**Q9 — Byzantine generalisation.** §7 concludes there is **no** Byzantine-peer model anywhere, generalising from remoting to cluster, sharding, singleton and `distributed-data`. Does that hold for all of them, or does any subsystem claim resilience against a misbehaving member? + +**Q10 — Coexistence (meta).** `docs/src/main/paradox/security/index.md` currently lists security documentation. *Proposed:* this document becomes canonical for **scope**, that page stays canonical for **reporting process**, and it gains a link here. Agree? + +**Q11 — The §3/§7 boundary non-goals.** *Proposed:* Pekko is not a sandbox, not an +authorization framework, and the following are out of the adversary model — an attacker +with code execution in the embedding JVM, a hostile classpath, and side-channel +observers (no timing or memory-access guarantees). Conversely, an attacker who supplies +*message content* that reaches an actor via a legitimate peer **is** in scope, since that +is the ordinary path application data takes. Confirm the split? + +**Q12 — Clock assumptions.** The failure detector and gossip convergence depend on +local clocks. *Proposed:* Pekko makes no claim against adversarial clock manipulation on +a cluster member — consistent with §7, since such a member is trusted anyway. Confirm? + +**Q13 — `serialize-messages` / `serialize-creators`.** *Proposed:* these are testing aids +that force serialization round-trips to catch non-serializable messages early, with no +security role, so they do not belong in the §5a security envelope at all. Confirm — or +does either have a security-relevant effect worth stating? + +--- + +## §15 Appendix — back-map from existing docs + +Proof that nothing the project already asserts has been dropped or weakened. + +| Existing statement | Source | Lands in | +| --- | --- | --- | +| Do not expose an `ActorSystem` to an untrusted network; firewall it | `remote-security.md` | §4, §10.1 | +| TLS with mutual authentication if network protection is insufficient | `remote-security.md` | §5a, §8 P6, §10.2 | +| Nodes should only be reachable from the adjacent network | `remote-security.md` | §5 | +| A compromised node's PKI-tree certificate grants cluster access | `remote-security.md` | §5, §7, §9 | +| Mutual authentication on by default | `remote-security.md` | §8 P6 | +| Hostname verification recommended on | `remote-security.md` | §5a, §9, §11 | +| All cluster nodes must be trusted equally | `remote-security.md` | §3, §7, §9 | +| A connected system may send any message to any actor | `remote-security.md` | §4, §7, §9 | +| Untrusted mode does not give full protection | `remote-security.md` | §9, §14 Q4 | +| Untrusted mode blocks system messages, `PossiblyHarmful`, selections | `remote-security.md` | §8 P5 | +| Service mesh is not a replacement | `remote-security.md` | §9 | +| SHA1PRNG / `urandom` trade-off | `remote-security.md` | §5, §11a | +| Java serialization disabled by default; discouraged in production | `serialization.md` | §5a, §8 P1, §10.3 | +| `SECURITY`-marked log entries indicate prevented attacks | `serialization.md` | §8 P2, §10.4 | +| Remote deployment is not remote code loading | `remoting.md` | §8 P3, §11a | +| Remote deployment allow list restricts deployable classes | `remoting.md` | §5a, §8 P4 | +| Report vulnerabilities to the private security list per ASF guidelines | `security/index.md` | `SECURITY.md`, §1 | From 82bf4259149ffce5c55b375b64ed6e3099c67467 Mon Sep 17 00:00:00 2001 From: PJ Fanning Date: Thu, 27 Aug 2026 13:54:32 +0100 Subject: [PATCH 02/23] docs: correct scope and citations in the draft threat model MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ### Motivation Review of the v0 draft found the component-scope tables understate Pekko's network surface, and two cited claims are documented only on the 2.0 line. ### Modification - §2/§4/§5/§6 - `pekko-actor` ships `org.apache.pekko.io.Tcp`/`Udp`/`Dns` and `pekko-stream` ships `Tcp`, `TLS`, `FileIO`, `Framing`, so the "actor core is purely in-process" claim and the "streams leave the process only via connectors the app supplies" row were both wrong. Added the matching input rows so a framing defect routes to a §13 disposition. - §2 - added `protobuf-v3`; listed `plugins`, `bill-of-materials` and `scala-nightly` as out of scope. - §5a - added the classic-remoting knobs, which are deprecated but shipped and CI-gated, and were previously absent. - §5a/§15 - `serialize-messages` is documented as test-only in reference.conf, so Q13 is answered; reused the slot for classic remoting's scope. - §8 P7 and §9 - flagged the cert-rotation and service-mesh citations as 2.0+ and linked the snapshot docs, since neither is in the released 1.x docs. - §1 - added the heading §15 already back-mapped to. - SECURITY.md - `remote-security.html` 404s on `current`; pointed at `remoting-artery.html#remote-security`, which resolves on both lines. - security/index.md - named `security@apache.org` to match SECURITY.md, and linked both new documents so the docs site closes the same chain. ### Result Scope tables match the code, every citation resolves for the version it claims, and §14 still holds 13 questions. ### Tests Not run - docs only ### References Refs #3478 --- SECURITY.md | 10 ++++-- THREAT_MODEL.md | 42 ++++++++++++++++--------- docs/src/main/paradox/security/index.md | 6 +++- 3 files changed, 40 insertions(+), 18 deletions(-) diff --git a/SECURITY.md b/SECURITY.md index a7f05bce50f..2c51e9d8901 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -13,8 +13,12 @@ Apache Pekko does not operate a separate project security list; reports go to the foundation-wide address above, which routes to the Pekko PMC. Please follow the [guidelines laid down by the Apache Security -team](https://www.apache.org/security/). The Pekko PMC will coordinate -responsible disclosure with affected upstream maintainers where needed. +team](https://www.apache.org/security/). + +Ideally, any issues affecting Apache Pekko and its predecessor project should +be reported to the Apache Pekko team first. The Pekko PMC will coordinate +responsible disclosure with the affected upstream maintainers when needed, so a +cross-project issue does not need to be filed twice. To receive security announcements, subscribe to the [Apache Announce Mailing List](https://lists.apache.org/list.html?announce@apache.org). @@ -45,6 +49,6 @@ Two points catch most reporters: ## Further Security Documentation - [Apache Pekko security announcements](https://pekko.apache.org/docs/pekko/current/security/) -- [Remote Security](https://pekko.apache.org/docs/pekko/current/remote-security.html) +- [Remote Security](https://pekko.apache.org/docs/pekko/current/remoting-artery.html#remote-security) - [Java Serialization](https://pekko.apache.org/docs/pekko/current/serialization.html#java-serialization) - [Remote deployment allow list](https://pekko.apache.org/docs/pekko/current/remoting.html#remote-deployment-allow-list) diff --git a/THREAT_MODEL.md b/THREAT_MODEL.md index 2149c8b4877..b991f218dd1 100644 --- a/THREAT_MODEL.md +++ b/THREAT_MODEL.md @@ -5,7 +5,7 @@ | | | | --- | --- | | **Project** | Apache Pekko (core toolkit) | -| **Written against** | commit `a054f9ce`, `main` | +| **Written against** | commit `90b02d60`, `main` | | **Date** | 2026-08-27 | | **Authors** | ASF Security team, at the request of the Pekko PMC | | **Version binding** | This model is versioned alongside the project. A report against Pekko version *N* is triaged against the model as it stood at *N*, not at `main`. | @@ -16,7 +16,9 @@ *(maintainer)* — stated by a Pekko maintainer in review of this document. *(inferred)* — reasoned from code or config defaults, **not yet confirmed**; each has a matching question in §14. -**Draft confidence:** 40 documented / 0 maintainer / 14 inferred. The documented share is unusually high because Pekko's own remoting and serialization docs already state much of the trust model explicitly. What remains inferred is concentrated in two places: the negative claims in §5, and the §5a default rulings — which are the answers that most change the model. Both are §14. +**Draft confidence:** 42 documented / 0 maintainer / 14 inferred. The documented share is unusually high because Pekko's own remoting and serialization docs already state much of the trust model explicitly. What remains inferred is concentrated in two places: the negative claims in §5, and the §5a default rulings — which are the answers that most change the model. Both are §14. + +## §1 Overview Apache Pekko is a Scala/Java toolkit for building concurrent, distributed and resilient message-driven applications. Its unit of computation is the actor: an object with private state that communicates only by asynchronous message passing. Pekko extends that model across machines — an `ActorSystem` can address actors on remote nodes as if they were local, and a cluster of such systems provides membership, sharding, singletons and replicated data. That transparency is the source of most of this document: **the network is not incidental to Pekko, it is part of the programming model**, and the security consequences follow from where the toolkit assumes that network sits. @@ -37,18 +39,20 @@ Three caller roles matter, and they are not equally trusted: | Family | Modules | Entry point | Leaves the process? | In model | | --- | --- | --- | --- | --- | | Actor core | `actor`, `actor-typed`, `slf4j`, `coordination` | `ActorSystem`, `ActorRef` | no | **yes** | +| Classic IO | `actor` (`org.apache.pekko.io`) | `IO(Tcp)`, `IO(Udp)`, `IO(Dns)` | **network — if the application binds** | **yes** | | Remoting | `remote` | Artery transport (`tcp` / `tls-tcp` / `aeron-udp`) | **network** | **yes — primary surface** | | Cluster | `cluster`, `cluster-typed`, `cluster-tools`, `cluster-sharding*`, `cluster-metrics` | gossip, membership, sharding | **network** (via remoting) | **yes** | | Replicated data | `distributed-data` | CRDT replication | **network** (via remoting) | **yes** | | Serialization | `serialization-jackson`, `serialization-jackson3` | `Serializer` SPI | deserializes network bytes | **yes — critical** | -| Streams | `stream`, `stream-typed` | `Source`/`Flow`/`Sink` | only via connectors the app supplies | **yes** | +| Wire encoding | `protobuf-v3` | shaded protobuf runtime | parses remoting and cluster wire bytes | **yes** | +| Streams | `stream`, `stream-typed` | `Source`/`Flow`/`Sink`; `Tcp`, `TLS`, `FileIO`, `Framing` | **network / filesystem — if the application uses those connectors** | **yes** | | Persistence | `persistence`, `persistence-typed`, `persistence-query`, `persistence-shared` | journal / snapshot plugin SPI | **storage backend** | **yes**, boundary at the SPI | | PKI | `pki` | PEM/keystore parsing | **reads files** | **yes** | | Discovery | `discovery` | service-discovery SPI | **network / DNS** | **yes** | | OSGi | `osgi` | bundle activator | classloading | **yes** | | Test kits | `*-testkit`, `multi-node-testkit`, `*-tests`, `persistence-tck`, `stream-tests-tck` | — | — | **no** — §3 | | Benchmarks | `bench-jmh` | — | — | **no** — §3 | -| Build / docs | `docs`, `project`, `scripts`, `legal`, `kubernetes` | — | — | **no** — §3 | +| Build / docs | `docs`, `project`, `scripts`, `legal`, `kubernetes`, `plugins`, `bill-of-materials`, `scala-nightly` | — | — | **no** — §3 | *(inferred — the in/out split is the ASF Security team's proposal; see §14 Q6)* @@ -87,7 +91,7 @@ A finding must meet its family's precondition to be in-model: - **Remoting** — reachable from bytes arriving on the Artery transport, *and* the report must state whether it assumes the network-isolation assumption above is intact. A finding that requires an attacker already on the cluster network is judged under §7, not automatically valid. - **Serialization** — reachable from a message payload deserialized by a **configured, enabled** serializer. Findings reachable only when `allow-java-serialization = on` are judged under §5a. - **Cluster / distributed-data** — reachable from gossip or replication traffic originating at an **associated peer**. Per §7 such a peer is trusted, so these are typically out of model unless the finding shows a pre-association reach. -- **Actor core / streams** — reachable from data the embedding application passes in. Trusted by default; a finding must show the data crosses an application boundary that Pekko itself defines. +- **Actor core / streams** — reachable from data the embedding application passes in. Trusted by default; a finding must show the data crosses an application boundary that Pekko itself defines. **Exception:** where the application binds `io.Tcp`/`io.Udp` or `stream.Tcp`/`stream.TLS`, the bytes arriving on that socket are attacker-controlled, and a parsing or framing defect reachable from them is in model. - **Persistence** — reachable from journal or snapshot contents. See §6 on the storage-trust question. - **PKI** — reachable from PEM/keystore material. Operator-supplied and trusted; see §14 Q5. @@ -106,7 +110,7 @@ A finding must meet its family's precondition to be in-model: These are negative claims, rarely written down anywhere, and therefore the highest-value confirmation targets in §14 *(all inferred — §14 Q7)*: - Installs no signal handlers and spawns no child processes. -- Opens no listening socket unless remoting is explicitly configured — the actor core alone is purely in-process. +- Opens no listening socket of its own accord. Remoting binds when configured; `org.apache.pekko.io.Tcp`/`Udp` (in `pekko-actor`) and `stream.scaladsl.Tcp` (in `pekko-stream`) bind only on an explicit application call. Pekko never binds a port the application did not ask for. - Reads configuration from the classpath and supplied `Config`; does not read arbitrary environment variables of its own accord. - Writes to logging via SLF4J as configured; does not write to stdout/stderr directly in normal operation. - Does not mutate process-global state (locale, default `SecurityManager`, system properties) at initialization. @@ -126,7 +130,10 @@ Pekko's security posture is set almost entirely by configuration. **Every row be | `pekko.remote.artery.untrusted-mode` | `off` | On, blocks inbound system messages, `PossiblyHarmful` messages, remote deployment, remote DeathWatch, and actor selections outside `trusted-selection-paths` *(documented)* | **UNRESOLVED — §14 Q4** | | `pekko.remote.artery.trusted-selection-paths` | `[]` | Allow-list of actor paths that may receive selections under untrusted mode *(documented)* | Follows Q4 | | `pekko.remote.deployment.enable-allow-list` | `off` | On, restricts which actor classes a peer may remote-deploy *(documented — `remoting.md`)* | **UNRESOLVED — §14 Q4** | -| `pekko.actor.serialize-messages` / `serialize-creators` | `off` | Testing aids that force serialization round-trips. Not security controls | Not a security knob *(inferred — §14 Q13)* | +| `pekko.remote.classic.untrusted-mode` | `off` | Classic-remoting equivalent of the artery flag *(reference.conf:381)* | Follows Q4 | +| `pekko.remote.classic.trusted-selection-paths` | `[]` | As above *(reference.conf:387)* | Follows Q4 | +| `pekko.remote.classic.netty.tcp.enable-ssl` | `false` | Classic's default transport is plaintext netty TCP *(reference.conf:582)* | Follows Q1 | +| `pekko.actor.serialize-messages` / `serialize-creators` | `off` | Testing aids that force serialization round-trips. Not security controls. Docs: *"this is only intended for testing"* *(documented — `actor/src/main/resources/reference.conf`)* | Not a security knob | --- @@ -146,6 +153,10 @@ For a toolkit whose surface is a wire protocol, the useful table is keyed by **m | Persistence | Journal / snapshot contents on replay | Depends on backend trust — **§14 Q5** | App/operator: secure the store | | `pki` | PEM / keystore files | **No** — operator-supplied, trusted | Operator: protect key material | | `discovery` | Service-discovery responses (DNS, K8s API) | **Potentially** — depends on the resolver | Operator: trust the discovery mechanism | +| `io.Tcp` / `io.Udp` | Bytes on an application-bound socket | **Yes** | App: it chose to bind, and owns what it exposes | +| `stream.Tcp` / `stream.TLS` | Bytes on an application-bound stream server | **Yes** | App: as above. Pekko: framing must not break on hostile input | +| `Framing` / `JsonFraming` | Delimited or length-prefixed frames | **Yes** where fed from a network source | Pekko: bounded by the caller's `maximumFrameLength` | +| `stream.FileIO` | File contents at an application-supplied path | Depends on the path | App | | Config | `application.conf`, system properties | **No** — trusted, part of the deployment | Operator | **Size and rate.** Artery imposes frame-size limits and the failure detector bounds how long an unresponsive peer is tolerated. Whether these are *security* controls or tuning parameters is §14 Q8. @@ -178,7 +189,7 @@ For a toolkit whose surface is a wire protocol, the useful table is keyed by **m | P4 | With `enable-allow-list = on`, only listed actor classes may be remote-deployed onto this node | An unlisted class deployed | High | *(documented — `remoting.md`)* | | P5 | With `untrusted-mode = on`, inbound system messages, `PossiblyHarmful` messages, remote deployment, remote DeathWatch and non-allow-listed actor selections are **dropped and logged** | Any of these taking effect despite the flag | High | *(documented — `remote-security.md`)* | | P6 | With `transport = tls-tcp`, TLS is applied and **mutual authentication is on by default** — the server side also requests and verifies the client's certificate | Association completing without peer certificate verification | **Critical** | *(documented — `remote-security.md`)* | -| P7 | Certificate rotation is supported for mTLS in Kubernetes without cluster restart | Rotation causing association failure or silent downgrade | Medium | *(documented — `remote-security.md`)* | +| P7 | Certificate rotation is supported for mTLS in Kubernetes without cluster restart | Rotation causing association failure or silent downgrade | Medium | *(documented — `remote-security.md`; **2.0+ only**, absent from the released 1.x docs — [snapshot](https://pekko.apache.org/docs/pekko/snapshot/remote-security.html#mtls-with-rotated-certificates-in-kubernetes))* | **Note the shape of this list:** P4, P5 and P6 are all *conditional on a non-default setting*. Under stock configuration, the properties Pekko actively provides at the network boundary are P1, P2 and P3 — the rest of the posture is delegated to the operator via §10. This is a deliberate design, but it is the single most important thing for a triager to understand. @@ -199,7 +210,7 @@ These are the assumptions integrators most often bring with them, and each is wr - **Untrusted mode is not a security boundary.** *"Untrusted mode does not give full protection against attacks by itself. It makes it slightly harder to perform malicious or unintended actions"* *(documented — `remote-security.md`)*. It is hardening. Treating it as a substitute for network isolation is a §11 misuse. - **`PossiblyHarmful` is a marker, not an authorization mechanism.** It is a compile-time trait that untrusted mode consults. It confers no protection when untrusted mode is off. -- **A service mesh is not a substitute for remoting security.** *"Encryption and authentication via a service mesh is not a replacement for Pekko Cluster remoting security"* — Pekko's peer-to-peer addressing has requirements a mesh does not satisfy *(documented — `remote-security.md`)*. +- **A service mesh is not a substitute for remoting security.** *"Encryption and authentication via a service mesh is not a replacement for Pekko Cluster remoting security"* — Pekko's peer-to-peer addressing has requirements a mesh does not satisfy *(documented — `remote-security.md`; **2.0+ only**, absent from the released 1.x docs — [snapshot](https://pekko.apache.org/docs/pekko/snapshot/general/remoting.html#service-mesh))*. - **TLS mutual authentication does not give per-node identity guarantees by default**, because `hostname-verification` ships `off` — any cert from the trusted PKI authenticates as any node. *(documented + config default)* - **The actor boundary is not a security boundary.** Message-passing isolation is a concurrency property, not a confidentiality one. @@ -313,10 +324,12 @@ is the ordinary path application data takes. Confirm the split? local clocks. *Proposed:* Pekko makes no claim against adversarial clock manipulation on a cluster member — consistent with §7, since such a member is trusted anyway. Confirm? -**Q13 — `serialize-messages` / `serialize-creators`.** *Proposed:* these are testing aids -that force serialization round-trips to catch non-serializable messages early, with no -security role, so they do not belong in the §5a security envelope at all. Confirm — or -does either have a security-relevant effect worth stating? +**Q13 — Classic remoting's place in the supported surface.** Classic remoting is +deprecated but still shipped and still CI-gated (the "Pekko Classic Remoting Tests" job). +It carries its own `untrusted-mode`, `trusted-selection-paths` and netty SSL settings, +now listed in §5a. *Proposed:* deprecation is not desupport, so classic stays **in model** +and its knobs follow the same Q1/Q4 rulings as their artery equivalents. Confirm — or is +classic remoting `OUT-OF-MODEL: unsupported-component` per §3? --- @@ -342,4 +355,5 @@ Proof that nothing the project already asserts has been dropped or weakened. | `SECURITY`-marked log entries indicate prevented attacks | `serialization.md` | §8 P2, §10.4 | | Remote deployment is not remote code loading | `remoting.md` | §8 P3, §11a | | Remote deployment allow list restricts deployable classes | `remoting.md` | §5a, §8 P4 | -| Report vulnerabilities to the private security list per ASF guidelines | `security/index.md` | `SECURITY.md`, §1 | +| `serialize-messages` / `serialize-creators` are "only intended for testing" | `actor` `reference.conf` | §5a | +| Report vulnerabilities privately per ASF guidelines; coordinate disclosure with upstream maintainers | `security/index.md` | `SECURITY.md`, §1 | diff --git a/docs/src/main/paradox/security/index.md b/docs/src/main/paradox/security/index.md index dff04551ad3..95da98c0b03 100644 --- a/docs/src/main/paradox/security/index.md +++ b/docs/src/main/paradox/security/index.md @@ -10,7 +10,9 @@ This mailing list also has announcements of releases for Apache projects. ## Reporting Vulnerabilities -We strongly encourage people to report such problems to our private security mailing list first, before disclosing them in a public forum. +We strongly encourage people to report such problems privately to the Apache Security team at +`security@apache.org` first, before disclosing them in a public forum. Apache Pekko does not +operate a separate project security list. Please follow the [guidelines](https://www.apache.org/security/) laid down by the Apache Security team. @@ -23,3 +25,5 @@ first. We will coordinate responsible disclosure with the affected upstream main * @ref:[Java Serialization](../serialization.md#java-serialization) * @ref:[Remote deployment allow list](../remoting.md#remote-deployment-allow-list) * @ref:[Remote Security](../remote-security.md) + * [Security Policy](https://github.com/apache/pekko/blob/main/SECURITY.md) + * [Threat Model](https://github.com/apache/pekko/blob/main/THREAT_MODEL.md) From 90305c6ba15cecbf825fbe5e0257652115b55d93 Mon Sep 17 00:00:00 2001 From: PJ Fanning Date: Thu, 27 Aug 2026 14:04:17 +0100 Subject: [PATCH 03/23] Update SECURITY.md --- SECURITY.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/SECURITY.md b/SECURITY.md index 2c51e9d8901..4bb395ee95d 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -13,7 +13,8 @@ Apache Pekko does not operate a separate project security list; reports go to the foundation-wide address above, which routes to the Pekko PMC. Please follow the [guidelines laid down by the Apache Security -team](https://www.apache.org/security/). +team](https://www.apache.org/security/). The Pekko PMC will coordinate +responsible disclosure with affected upstream maintainers where needed. Ideally, any issues affecting Apache Pekko and its predecessor project should be reported to the Apache Pekko team first. The Pekko PMC will coordinate From e790376586c5959e1596f8c25ab1ef6620b98400 Mon Sep 17 00:00:00 2001 From: PJ Fanning Date: Thu, 27 Aug 2026 14:09:58 +0100 Subject: [PATCH 04/23] docs: drop the app-bound socket scope ruling from the threat model MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ### Motivation The previous commit corrected §2/§5 to record that `pekko-actor` ships `org.apache.pekko.io.Tcp`/`Udp` and `pekko-stream` ships `Tcp`, `TLS`, `FileIO` and `Framing`. It also added a §4 sentence declaring parsing and framing defects reachable from an application-bound socket to be in model. That second part is a scope ruling, not a factual correction, and it was neither tagged `(inferred)` nor raised as a §14 question, so it entered the document without maintainer sign-off. It was also too broad: it asserted such bytes are attacker-controlled when the application may bind localhost, it did not distinguish Pekko's transport plumbing from the application's own protocol, and it swept in `stream.TLS`, which delegates to the JDK's JSSE. ### Modification - §4 - removed the exception sentence. - §6 - the application-bound socket rows now describe the inputs without asserting an obligation on Pekko; attacker-controllability is stated as conditional on where the application exposes the socket. ### Result §2, §5 and §6 describe the surface accurately, and §4 makes no scope claim about it either way. Whether defects in Pekko's own transport and framing code are in model is left open for the maintainers. ### Tests Not run - docs only ### References Refs #3478 --- THREAT_MODEL.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/THREAT_MODEL.md b/THREAT_MODEL.md index b991f218dd1..bbcc584985b 100644 --- a/THREAT_MODEL.md +++ b/THREAT_MODEL.md @@ -91,7 +91,7 @@ A finding must meet its family's precondition to be in-model: - **Remoting** — reachable from bytes arriving on the Artery transport, *and* the report must state whether it assumes the network-isolation assumption above is intact. A finding that requires an attacker already on the cluster network is judged under §7, not automatically valid. - **Serialization** — reachable from a message payload deserialized by a **configured, enabled** serializer. Findings reachable only when `allow-java-serialization = on` are judged under §5a. - **Cluster / distributed-data** — reachable from gossip or replication traffic originating at an **associated peer**. Per §7 such a peer is trusted, so these are typically out of model unless the finding shows a pre-association reach. -- **Actor core / streams** — reachable from data the embedding application passes in. Trusted by default; a finding must show the data crosses an application boundary that Pekko itself defines. **Exception:** where the application binds `io.Tcp`/`io.Udp` or `stream.Tcp`/`stream.TLS`, the bytes arriving on that socket are attacker-controlled, and a parsing or framing defect reachable from them is in model. +- **Actor core / streams** — reachable from data the embedding application passes in. Trusted by default; a finding must show the data crosses an application boundary that Pekko itself defines. - **Persistence** — reachable from journal or snapshot contents. See §6 on the storage-trust question. - **PKI** — reachable from PEM/keystore material. Operator-supplied and trusted; see §14 Q5. @@ -153,9 +153,9 @@ For a toolkit whose surface is a wire protocol, the useful table is keyed by **m | Persistence | Journal / snapshot contents on replay | Depends on backend trust — **§14 Q5** | App/operator: secure the store | | `pki` | PEM / keystore files | **No** — operator-supplied, trusted | Operator: protect key material | | `discovery` | Service-discovery responses (DNS, K8s API) | **Potentially** — depends on the resolver | Operator: trust the discovery mechanism | -| `io.Tcp` / `io.Udp` | Bytes on an application-bound socket | **Yes** | App: it chose to bind, and owns what it exposes | -| `stream.Tcp` / `stream.TLS` | Bytes on an application-bound stream server | **Yes** | App: as above. Pekko: framing must not break on hostile input | -| `Framing` / `JsonFraming` | Delimited or length-prefixed frames | **Yes** where fed from a network source | Pekko: bounded by the caller's `maximumFrameLength` | +| `io.Tcp` / `io.Udp` | Bytes on an application-bound socket | **Potentially** — depends where the application exposes it | App: it chose to bind, defines the protocol, and owns what it exposes | +| `stream.Tcp` / `stream.TLS` | Bytes on an application-bound stream server | **Potentially** — as above | App: as above. TLS itself is delegated to the JDK's JSSE | +| `Framing` / `JsonFraming` | Delimited or length-prefixed frames | **Potentially** where fed from a network source | App: supplies `maximumFrameLength`, which bounds frame size | | `stream.FileIO` | File contents at an application-supplied path | Depends on the path | App | | Config | `application.conf`, system properties | **No** — trusted, part of the deployment | Operator | From 604f59ffecd578e60097cd712930dfcaf6aa97a8 Mon Sep 17 00:00:00 2001 From: PJ Fanning Date: Thu, 27 Aug 2026 14:11:32 +0100 Subject: [PATCH 05/23] docs: keep the original upstream-coordination wording in SECURITY.md ### Motivation An earlier review commit rewrote the upstream-coordination paragraph on the assumption that the draft had dropped a statement from the docs. It had not: `docs/src/main/paradox/security/index.md` on `main` already carries the same generic wording, so the draft was faithful to it and the rewrite was unnecessary. Restoring the sentence alongside the rewrite left the same claim stated twice in consecutive paragraphs. ### Modification Removed the added paragraph, leaving the original sentence in place. `SECURITY.md` now differs from the draft only in the Remote Security link, which pointed at a page that does not exist on the released docs. ### Result No duplicated statement, and the file matches the wording already published on the documentation site. ### Tests Not run - docs only ### References Refs #3478 --- SECURITY.md | 5 ----- 1 file changed, 5 deletions(-) diff --git a/SECURITY.md b/SECURITY.md index 4bb395ee95d..e94b88f5747 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -16,11 +16,6 @@ Please follow the [guidelines laid down by the Apache Security team](https://www.apache.org/security/). The Pekko PMC will coordinate responsible disclosure with affected upstream maintainers where needed. -Ideally, any issues affecting Apache Pekko and its predecessor project should -be reported to the Apache Pekko team first. The Pekko PMC will coordinate -responsible disclosure with the affected upstream maintainers when needed, so a -cross-project issue does not need to be filed twice. - To receive security announcements, subscribe to the [Apache Announce Mailing List](https://lists.apache.org/list.html?announce@apache.org). From f26ada18c112d90caaada77d07b49b0215154dee Mon Sep 17 00:00:00 2001 From: PJ Fanning Date: Thu, 27 Aug 2026 15:19:55 +0100 Subject: [PATCH 06/23] docs: record the maintainer security posture and answer Q1, Q3 and Q4 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ### Motivation The draft left the §5a defaults marked UNRESOLVED and carried no maintainer provenance at all. Pekko's position is that it is a hardening toolkit rather than a secure-by-default one: its deployment base is inherited from Akka, and operators there have already been tasked with securing the network Pekko runs on and controlling who may reach a deployed system. Tightening a default breaks those deployments on upgrade. That position needs stating, because the rest of the model depends on it and because a threat model claiming Pekko is secure by default would not survive contact with `reference.conf`. ### Modification - **§5b** - new section stating the posture: defaults are compatibility choices, not security claims; a request to tighten one is a change request rather than a vulnerability report; such proposals are welcome on the development list and weighed in good faith; and a control that does not do what it documents once enabled is a defect Pekko fixes. - **§13** - new `BY-DESIGN: default-configuration` disposition, which closes a report while pointing at the route to propose the change. - **§14** - Q1, Q3 and Q4 answered and retained in place so cross-references still resolve. Two proposed dispositions are corrected as they did not fit §13's own definitions: Q1 routed an unassociated attacker to `adversary-not-in-scope`, which §13 reserves for an associated peer, a PKI certificate or in-JVM execution, and which §7 contradicts by listing that attacker as in scope; Q4 routed a bypass of an enabled control to `VALID-HARDENING`, which §13 defines as violating no §8 property, though such a bypass violates P5 or P4. - **§5a, §7, §9, §11a** - stance cells filled in and the claims that were inferred pending these questions retagged `(maintainer)`. ### Result Provenance moves from 42/0/14 to 41/12/9, the §5a table has no unresolved cells, and §14 holds ten open questions rather than thirteen. ### Tests Not run - docs only ### References Refs #3478 --- THREAT_MODEL.md | 51 ++++++++++++++++++++++++++++++++++--------------- 1 file changed, 36 insertions(+), 15 deletions(-) diff --git a/THREAT_MODEL.md b/THREAT_MODEL.md index bbcc584985b..58c4693f2b2 100644 --- a/THREAT_MODEL.md +++ b/THREAT_MODEL.md @@ -16,7 +16,7 @@ *(maintainer)* — stated by a Pekko maintainer in review of this document. *(inferred)* — reasoned from code or config defaults, **not yet confirmed**; each has a matching question in §14. -**Draft confidence:** 42 documented / 0 maintainer / 14 inferred. The documented share is unusually high because Pekko's own remoting and serialization docs already state much of the trust model explicitly. What remains inferred is concentrated in two places: the negative claims in §5, and the §5a default rulings — which are the answers that most change the model. Both are §14. +**Draft confidence:** 41 documented / 12 maintainer / 9 inferred. The documented share is unusually high because Pekko's own remoting and serialization docs already state much of the trust model explicitly. The §5a default rulings — previously the largest inferred block — are now answered by the §5b posture statement and §14 Q1, Q3 and Q4. What remains inferred is concentrated in the negative claims in §5 (§14 Q7) and the module in/out split (§14 Q6). ## §1 Overview @@ -61,7 +61,7 @@ Three caller roles matter, and they are not equally trusted: ## §3 Out of scope (explicit non-goals) - **Test kits, benchmarks, build tooling and documentation sources.** These ship in the repository but are not part of the security-supported surface. A finding in `bench-jmh` or any `*-tests` module is `OUT-OF-MODEL: unsupported-component`. *(inferred — §14 Q6)* -- **Pekko is not a sandbox.** Actors are not an isolation boundary. Any code running in the JVM can reach any actor's state by ordinary means; the actor model is a concurrency discipline, not a security control. *(inferred — §14 Q4)* +- **Pekko is not a sandbox.** Actors are not an isolation boundary. Any code running in the JVM can reach any actor's state by ordinary means; the actor model is a concurrency discipline, not a security control. *(maintainer — §14 Q4)* - **Pekko is not an authorization framework.** It carries no notion of a principal, role, or permission on a message. Application-level authorization is the embedding application's job. *(inferred — §14 Q11)* - **A Pekko cluster is not a multi-tenancy boundary.** The documentation is explicit that *"you'll have to trust all cluster nodes the same in a Pekko cluster anyway"* *(documented — `remote-security.md`)*. Separating mutually-distrusting tenants across nodes of one cluster is not a supported deployment. - **Attackers who already control the embedding process** are out of scope — they have already won. *(inferred — §14 Q11)* @@ -124,12 +124,12 @@ Pekko's security posture is set almost entirely by configuration. **Every row be | Setting | Default | Effect | Maintainer stance | | --- | --- | --- | --- | | `pekko.actor.allow-java-serialization` | `off` | On, exposes the JVM deserialization attack surface to any message payload. Docs: *"highly discouraged to enable in production"* *(documented — `serialization.md`)* | Secure default. Enabling it is a documented deviation — see §14 Q2 | -| `pekko.remote.artery.transport` | `tcp` | **Plaintext.** No peer authentication and no confidentiality on the wire. `tls-tcp` opts into TLS | **UNRESOLVED — §14 Q1** | +| `pekko.remote.artery.transport` | `tcp` | **Plaintext.** No peer authentication and no confidentiality on the wire. `tls-tcp` opts into TLS | Compatibility default — §5b, §14 Q1 *(maintainer)* | | `pekko.remote.artery.ssl.config-ssl-engine.require-mutual-authentication` | `on` | Both ends present certificates *(documented)* | Secure default | -| `…ssl.config-ssl-engine.hostname-verification` | `off` | Off, a valid cert from the trusted PKI authenticates regardless of which host presents it. Docs *recommend* `on` but ship `off` *(documented)* | **UNRESOLVED — §14 Q3** | -| `pekko.remote.artery.untrusted-mode` | `off` | On, blocks inbound system messages, `PossiblyHarmful` messages, remote deployment, remote DeathWatch, and actor selections outside `trusted-selection-paths` *(documented)* | **UNRESOLVED — §14 Q4** | +| `…ssl.config-ssl-engine.hostname-verification` | `off` | Off, a valid cert from the trusted PKI authenticates regardless of which host presents it. Docs *recommend* `on` but ship `off` *(documented)* | Compatibility default, warned at runtime — §5b, §14 Q3 *(maintainer)* | +| `pekko.remote.artery.untrusted-mode` | `off` | On, blocks inbound system messages, `PossiblyHarmful` messages, remote deployment, remote DeathWatch, and actor selections outside `trusted-selection-paths` *(documented)* | Hardening; adoption is the operator's call — §5b, §14 Q4 *(maintainer)* | | `pekko.remote.artery.trusted-selection-paths` | `[]` | Allow-list of actor paths that may receive selections under untrusted mode *(documented)* | Follows Q4 | -| `pekko.remote.deployment.enable-allow-list` | `off` | On, restricts which actor classes a peer may remote-deploy *(documented — `remoting.md`)* | **UNRESOLVED — §14 Q4** | +| `pekko.remote.deployment.enable-allow-list` | `off` | On, restricts which actor classes a peer may remote-deploy *(documented — `remoting.md`)* | Hardening; adoption is the operator's call — §5b, §14 Q4 *(maintainer)* | | `pekko.remote.classic.untrusted-mode` | `off` | Classic-remoting equivalent of the artery flag *(reference.conf:381)* | Follows Q4 | | `pekko.remote.classic.trusted-selection-paths` | `[]` | As above *(reference.conf:387)* | Follows Q4 | | `pekko.remote.classic.netty.tcp.enable-ssl` | `false` | Classic's default transport is plaintext netty TCP *(reference.conf:582)* | Follows Q1 | @@ -137,6 +137,19 @@ Pekko's security posture is set almost entirely by configuration. **Every row be --- +## §5b Security posture: hardening, not secure-by-default + +Pekko is a long-lived toolkit whose deployment base is inherited from Akka. Its configuration defaults are chosen for compatibility with those deployments, in which operators have already been tasked with securing the network Pekko runs on and controlling who may reach and message a deployed system. Changing a default to a more restrictive value breaks those deployments on upgrade, sometimes without a clear signal as to why. + +Pekko therefore takes the following position *(maintainer)*: + +1. **Defaults are compatibility choices, not security claims.** §5a lists every setting whose default affects the security envelope; §10 lists what the operator must do as a result. Read together they are the contract: Pekko states what it does not provide, and states what it expects of the operator instead. +2. **A report that a default should be more restrictive is not a vulnerability report.** It is a change request, and is closed as `BY-DESIGN: default-configuration` (§13). +3. **Proposals to change a default are welcome, and belong on the development list.** The PMC will weigh them in good faith on their merits — the compatibility cost, whether a migration path exists, and whether a major version is in flight. Defaults can and do change; they change through project discussion, not as the remediation of a security report. +4. **If an implementation is wrong, Pekko fixes it.** Where a control does not do what it is documented to do once enabled, that is a defect, in scope, at the severity §8 assigns. This posture governs which value ships as the default — never whether the mechanism works. + +--- + ## §6 Assumptions about inputs For a toolkit whose surface is a wire protocol, the useful table is keyed by **message class**, not by function. @@ -167,7 +180,7 @@ For a toolkit whose surface is a wire protocol, the useful table is keyed by **m **In scope:** -- **An unassociated network attacker** who can reach the remoting port — in a deployment where the operator's isolation assumption has held, this attacker should not exist; where it does, the relevant question is whether they can achieve anything **before** association completes. Pre-association reachability is the sharpest in-model attack surface. *(inferred — §14 Q1)* +- **An unassociated network attacker** who can reach the remoting port — in a deployment where the operator's isolation assumption has held, this attacker should not exist; where it does, the relevant question is whether they can achieve anything **before** association completes. Pre-association reachability is the sharpest in-model attack surface. *(maintainer — §14 Q1)* - **An attacker supplying message content** to an otherwise legitimate peer — e.g. data that originates at the application's own untrusted edge and is forwarded into an actor message. *(inferred — §14 Q11)* **Explicitly out of scope:** @@ -197,8 +210,8 @@ For a toolkit whose surface is a wire protocol, the useful table is keyed by **m ## §9 Security properties Pekko does **not** provide -- **No peer authentication by default.** With the default `transport = tcp` there is no shared secret, no certificate, and no handshake credential. Any host that can reach the port and speak Artery can attempt association. *(inferred from config defaults — §14 Q1)* -- **No confidentiality or integrity on the wire by default.** Same cause. *(inferred — §14 Q1)* +- **No peer authentication by default.** With the default `transport = tcp` there is no shared secret, no certificate, and no handshake credential. Any host that can reach the port and speak Artery can attempt association. *(maintainer — §14 Q1; §5b)* +- **No confidentiality or integrity on the wire by default.** Same cause. *(maintainer — §14 Q1; §5b)* - **No intra-cluster authorization.** Once associated, a peer may address any actor in the system. There is no per-actor, per-message, or per-peer permission model. *(documented)* - **No Byzantine fault tolerance.** See §7. Cluster protocols assume members are honest; there is no threshold below which arbitrary member behaviour is tolerated. *(documented — `remote-security.md`)* - **No bound on blast radius from one compromised node.** Documented explicitly for the PKI case *(documented)*. @@ -254,9 +267,9 @@ The operator or embedding application must: Feed this section to scanners and AI triage as a suppression list. - **"Remote deployment permits arbitrary class instantiation."** Remote deployment is *not* remote code loading — the class must already be on the target's classpath *(P3, documented)*. Absent a way to introduce a class, this is not RCE. -- **"Remoting transport defaults to plaintext."** Correct, and by design under the §4 network-isolation assumption. In-model only if the finding shows harm *within* an isolated network — pending §14 Q1. +- **"Remoting transport defaults to plaintext."** Correct, and by design under the §4 network-isolation assumption. In-model only if the finding shows harm reachable **pre-association**; a request to change the default is `BY-DESIGN: default-configuration` per §5b. - **"`PoisonPill` can be sent remotely to shut down a system."** Documented behaviour, gated by `untrusted-mode` *(documented — `remote-security.md`)*. -- **"`hostname-verification` is disabled by default."** Known and documented, with the trade-off spelled out for dynamic-hostname deployments — pending §14 Q3. +- **"`hostname-verification` is disabled by default."** Known and documented, with the trade-off spelled out for dynamic-hostname deployments, and warned at runtime under `LogMarker.Security`. A request to change the default is `BY-DESIGN: default-configuration` per §5b. - **Findings in `*-tests`, `*-testkit`, `bench-jmh`, `docs`** — `OUT-OF-MODEL: unsupported-component` per §3. - **"SHA1PRNG with `/dev/urandom` reuses the seed."** Documented trade-off, deliberately recommended to avoid blocking *(documented)*. @@ -283,6 +296,7 @@ Feed this section to scanners and AI triage as a suppression list. | `OUT-OF-MODEL: unsupported-component` | Lands in a §3 module | §3 | | `OUT-OF-MODEL: non-default-build` | Only manifests under a non-default §5a setting — most often `allow-java-serialization = on` | §5a | | `BY-DESIGN: property-disclaimed` | Concerns a §9 property Pekko explicitly does not provide | §9 | +| `BY-DESIGN: default-configuration` | Asks that a §5a default be changed to a more restrictive value. Not a vulnerability; §5b.3 invites the proposal on the development list | §5b | | `KNOWN-NON-FINDING` | Matches a §11a pattern | §11a | | `MODEL-GAP` | Routable to none of the above — triggers §12 | §12 | @@ -292,14 +306,21 @@ Feed this section to scanners and AI triage as a suppression list. Each states a **proposed answer**. Confirming or correcting is enough; no need to write prose. -**Q1 — The plaintext-default ruling (highest value; reshapes §7, §9, §11a, §13).** -`transport` ships `tcp`, so a stock cluster has no peer authentication. *Proposed:* the default is the supported production posture **only under the documented network-isolation assumption**, so a report of "unauthenticated peer can associate" is `OUT-OF-MODEL: adversary-not-in-scope` when it assumes internet exposure, but `VALID` if it shows harm reachable **pre-association** from an adjacent-network host. Correct? +Q1, Q3 and Q4 are **answered** and retained in place so that cross-references elsewhere in this document continue to resolve. The remaining questions are open. + +**Q1 — The plaintext transport default. ANSWERED *(maintainer)*.** +`transport` ships `tcp`, so a stock cluster has no peer authentication. **Answer:** the default is a compatibility choice under §5b, not a security claim. Reports route as follows: +- "the default should be `tls-tcp`" → `BY-DESIGN: default-configuration`; §5b.3 invites the proposal on the development list. +- "an unauthenticated peer can associate", assuming internet exposure → `BY-DESIGN: property-disclaimed`; §9 disclaims peer authentication by default. (The draft proposed `OUT-OF-MODEL: adversary-not-in-scope`, which does not fit: §13 defines that disposition as requiring an associated peer, a PKI-tree certificate, or in-JVM execution, and §7 lists the unassociated network attacker as **in scope**.) +- harm reachable **pre-association** from an adjacent-network host → `VALID`. **Q2 — `allow-java-serialization`.** *Proposed:* enabling it is a documented deviation, so any deserialization finding requiring it is `OUT-OF-MODEL: non-default-build`. Confirm? -**Q3 — `hostname-verification = off`.** *Proposed:* deliberate, to support dynamic-hostname deployments; a report that "any PKI cert authenticates as any node" is `BY-DESIGN: property-disclaimed` per §9. Confirm — or is `on` the intended posture and the default simply legacy? +**Q3 — `hostname-verification = off`. ANSWERED *(maintainer)*.** **Answer:** a compatibility default under §5b, deliberate rather than legacy, supporting deployments where hostnames are dynamic and not known up front. Pekko additionally warns at runtime under `LogMarker.Security` whenever TLS is enabled and verification is off, on both transports (`artery/tcp/ConfigSSLEngineProvider.scala`, `transport/netty/SSLEngineProvider.scala`), so the operator is told at startup. A report that "any PKI cert authenticates as any node" is `BY-DESIGN: property-disclaimed` per §9; a report that the default should be `on` is `BY-DESIGN: default-configuration`. + +**Q4 — Are `untrusted-mode` and `enable-allow-list` security boundaries or hardening? ANSWERED *(maintainer)*.** **Answer:** both are **hardening** features, per the documented *"does not give full protection"*, and shipping them `off` is a §5b compatibility choice — adoption is the operator's decision. A request that either default to `on` is `BY-DESIGN: default-configuration`. -**Q4 — Are `untrusted-mode` and `enable-allow-list` security boundaries or hardening?** *Proposed:* **hardening**, per the documented *"does not give full protection"*. So a bypass of either is `VALID-HARDENING`, not a CVE-class break. Confirm? +A **bypass of either once enabled** is a separate matter and is **not** covered by that. It violates §8 P5 or P4 respectively, so it is `VALID` at the severity §8 assigns, per §5b.4. The draft proposed `VALID-HARDENING` for this case, which §13 defines as *"No §8 property violated"* — that cannot apply to a bypass of a control §8 credits. **Q5 — Persistence backend trust.** *Proposed:* journal and snapshot stores are **trusted**; an attacker who can write to the journal is out of model. Confirm — or should replay treat stored bytes as untrusted? From e4615460f22e0e7d9c2c719b61a53b107ded8790 Mon Sep 17 00:00:00 2001 From: PJ Fanning Date: Thu, 27 Aug 2026 15:28:32 +0100 Subject: [PATCH 07/23] docs: answer Q2 and record who owns gadget-chain defence MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ### Motivation Q2 asked how a deserialization finding that requires `allow-java-serialization = on` should be triaged. The draft proposed `OUT-OF-MODEL: non-default-build` but left unstated what an operator who enables the flag is then responsible for, which matters because Pekko provides nothing to help them: `JavaSerializer.fromBinary` performs an unfiltered `ObjectInputStream.readObject` and there is no `ObjectInputFilter` integration anywhere in the project. ### Modification - **§14 Q2** - answered. Findings requiring the flag are out of model, gadget chains included. Two carve-outs are stated explicitly, per §5b.4: a serious defect in Pekko's own serialization implementation remains in scope and may warrant a CVE, and Java deserialization occurring despite the flag being `off` violates §8 P1 and is valid at Critical. - **§9** - records that Pekko integrates no serialization filter, cites the call site, and names the JVM-level levers an operator has instead. - **§10.3** - the operator responsibility now says to maintain a gadget-chain allow list via `-Djdk.serialFilter` or a process-wide `ObjectInputFilter`. - **§5a** - stance cell updated. ### Result §14 holds nine open questions. Provenance moves to 41 documented / 16 maintainer / 9 inferred. ### Tests Not run - docs only ### References Refs #3478 --- THREAT_MODEL.md | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/THREAT_MODEL.md b/THREAT_MODEL.md index 58c4693f2b2..4fc9bff1cfc 100644 --- a/THREAT_MODEL.md +++ b/THREAT_MODEL.md @@ -16,7 +16,7 @@ *(maintainer)* — stated by a Pekko maintainer in review of this document. *(inferred)* — reasoned from code or config defaults, **not yet confirmed**; each has a matching question in §14. -**Draft confidence:** 41 documented / 12 maintainer / 9 inferred. The documented share is unusually high because Pekko's own remoting and serialization docs already state much of the trust model explicitly. The §5a default rulings — previously the largest inferred block — are now answered by the §5b posture statement and §14 Q1, Q3 and Q4. What remains inferred is concentrated in the negative claims in §5 (§14 Q7) and the module in/out split (§14 Q6). +**Draft confidence:** 41 documented / 16 maintainer / 9 inferred. The documented share is unusually high because Pekko's own remoting and serialization docs already state much of the trust model explicitly. The §5a default rulings — previously the largest inferred block — are now answered by the §5b posture statement and §14 Q1 to Q4. What remains inferred is concentrated in the negative claims in §5 (§14 Q7) and the module in/out split (§14 Q6). ## §1 Overview @@ -123,7 +123,7 @@ Pekko's security posture is set almost entirely by configuration. **Every row be | Setting | Default | Effect | Maintainer stance | | --- | --- | --- | --- | -| `pekko.actor.allow-java-serialization` | `off` | On, exposes the JVM deserialization attack surface to any message payload. Docs: *"highly discouraged to enable in production"* *(documented — `serialization.md`)* | Secure default. Enabling it is a documented deviation — see §14 Q2 | +| `pekko.actor.allow-java-serialization` | `off` | On, exposes the JVM deserialization attack surface to any message payload. Docs: *"highly discouraged to enable in production"* *(documented — `serialization.md`)* | Secure default. Enabling it transfers gadget-chain defence to the operator — §5b, §14 Q2 *(maintainer)* | | `pekko.remote.artery.transport` | `tcp` | **Plaintext.** No peer authentication and no confidentiality on the wire. `tls-tcp` opts into TLS | Compatibility default — §5b, §14 Q1 *(maintainer)* | | `pekko.remote.artery.ssl.config-ssl-engine.require-mutual-authentication` | `on` | Both ends present certificates *(documented)* | Secure default | | `…ssl.config-ssl-engine.hostname-verification` | `off` | Off, a valid cert from the trusted PKI authenticates regardless of which host presents it. Docs *recommend* `on` but ship `off` *(documented)* | Compatibility default, warned at runtime — §5b, §14 Q3 *(maintainer)* | @@ -229,7 +229,7 @@ These are the assumptions integrators most often bring with them, and each is wr ### Well-known attack classes left to the caller -- **JVM deserialization gadget chains** — mitigated by P1 only so long as Java serialization stays off, and only for payloads Pekko itself deserializes; application-level serializers are the application's problem. +- **JVM deserialization gadget chains** — mitigated by P1 only so long as Java serialization stays off, and only for payloads Pekko itself deserializes; application-level serializers are the application's problem. Pekko integrates **no** serialization filter: `JavaSerializer.fromBinary` performs an unfiltered `ObjectInputStream.readObject` (`actor/.../serialization/Serializer.scala`). An operator who enables Java serialization must supply the allow list themselves through the JVM — `-Djdk.serialFilter` or a process-wide `ObjectInputFilter` — and owns that entirely *(maintainer — §14 Q2)*. - **Resource-exhaustion via message volume or size** — see §14 Q8. - **DNS / service-discovery spoofing** — `discovery` trusts the resolver it is configured with. - **Storage-layer tampering** on persistence journals — see §14 Q5. @@ -242,7 +242,7 @@ The operator or embedding application must: 1. **Keep remoting off untrusted networks.** Firewall the remoting port to the adjacent network. This is the assumption the whole model rests on *(documented)*. 2. **Enable `tls-tcp` if the network is not sufficiently trusted**, and set `hostname-verification = on` unless hostnames are genuinely dynamic *(documented)*. -3. **Leave `allow-java-serialization = off`.** If it must be enabled for legacy compatibility, treat the deployment as having no deserialization protection *(documented)*. +3. **Leave `allow-java-serialization = off`.** If it must be enabled for legacy compatibility, treat the deployment as having no deserialization protection *(documented)*, and **maintain your own gadget-chain allow list** via `-Djdk.serialFilter` or a process-wide `ObjectInputFilter`. Pekko supplies no filter of its own, and findings that require the flag to be on are out of model *(maintainer — §14 Q2)*. 4. **Treat `SECURITY`-marked log entries from the Java serializer as attack indicators**, not noise *(documented)*. 5. **Scope the PKI tree to the cluster.** Any certificate it issues is cluster access *(documented)*. 6. **Consider `untrusted-mode = on` and `enable-allow-list = on`** where peers are less than fully trusted — understanding both are hardening, not boundaries. @@ -306,7 +306,7 @@ Feed this section to scanners and AI triage as a suppression list. Each states a **proposed answer**. Confirming or correcting is enough; no need to write prose. -Q1, Q3 and Q4 are **answered** and retained in place so that cross-references elsewhere in this document continue to resolve. The remaining questions are open. +Q1 to Q4 are **answered** and retained in place so that cross-references elsewhere in this document continue to resolve. The remaining questions are open. **Q1 — The plaintext transport default. ANSWERED *(maintainer)*.** `transport` ships `tcp`, so a stock cluster has no peer authentication. **Answer:** the default is a compatibility choice under §5b, not a security claim. Reports route as follows: @@ -314,7 +314,11 @@ Q1, Q3 and Q4 are **answered** and retained in place so that cross-references el - "an unauthenticated peer can associate", assuming internet exposure → `BY-DESIGN: property-disclaimed`; §9 disclaims peer authentication by default. (The draft proposed `OUT-OF-MODEL: adversary-not-in-scope`, which does not fit: §13 defines that disposition as requiring an associated peer, a PKI-tree certificate, or in-JVM execution, and §7 lists the unassociated network attacker as **in scope**.) - harm reachable **pre-association** from an adjacent-network host → `VALID`. -**Q2 — `allow-java-serialization`.** *Proposed:* enabling it is a documented deviation, so any deserialization finding requiring it is `OUT-OF-MODEL: non-default-build`. Confirm? +**Q2 — `allow-java-serialization`. ANSWERED *(maintainer)*.** Enabling it is not recommended. **Answer:** any finding that requires `allow-java-serialization = on` to manifest is `OUT-OF-MODEL: non-default-build`, including gadget-chain deserialization. An operator who enables it takes on gadget-chain defence **entirely**: Pekko integrates no serialization filter, so the only lever is the JVM's own — `-Djdk.serialFilter`, or an `ObjectInputFilter` installed process-wide. Maintaining that allow list is the operator's responsibility, not Pekko's. + +Two things this does **not** dispose of, per §5b.4: +- A serious defect in Pekko's own serialization implementation is in scope and may warrant a CVE, whatever the flag is set to. +- Java deserialization occurring **despite** `allow-java-serialization = off` violates §8 P1 and is `VALID`, Critical. **Q3 — `hostname-verification = off`. ANSWERED *(maintainer)*.** **Answer:** a compatibility default under §5b, deliberate rather than legacy, supporting deployments where hostnames are dynamic and not known up front. Pekko additionally warns at runtime under `LogMarker.Security` whenever TLS is enabled and verification is off, on both transports (`artery/tcp/ConfigSSLEngineProvider.scala`, `transport/netty/SSLEngineProvider.scala`), so the operator is told at startup. A report that "any PKI cert authenticates as any node" is `BY-DESIGN: property-disclaimed` per §9; a report that the default should be `on` is `BY-DESIGN: default-configuration`. From be3517c8f5fa4225754c481da397ed11374bad09 Mon Sep 17 00:00:00 2001 From: PJ Fanning Date: Thu, 27 Aug 2026 15:30:41 +0100 Subject: [PATCH 08/23] docs: answer Q5 - the persistence store is trusted MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ### Motivation Q5 asked whether replay should treat journal and snapshot bytes as untrusted. It should not. Securing the database or persistence store belongs to whoever administers it, and Pekko does not take responsibility for treating persisted values as potentially compromised. ### Modification - **§14 Q5** - answered. A finding whose precondition is write access to the journal or snapshot store is `OUT-OF-MODEL: trusted-input`. The answer is scoped to the store: a defect in Pekko's own replay handling stays in scope per §5b.4. - **§4, §6, §9** - the storage-trust question is replaced by the ruling. - **§13** - journal and snapshot contents named in the `trusted-input` disposition. - **§10** - new item 9, since §5b makes this list the operator contract and it carried nothing about the persistence store. Also corrected a cross-reference: §4's PKI bullet cited §14 Q5, which is the persistence question. PKI material is stated as trusted in §6. ### Result §14 holds eight open questions. Provenance moves to 41 documented / 21 maintainer / 9 inferred. ### Tests Not run - docs only ### References Refs #3478 --- THREAT_MODEL.md | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/THREAT_MODEL.md b/THREAT_MODEL.md index 4fc9bff1cfc..cbadb1646fe 100644 --- a/THREAT_MODEL.md +++ b/THREAT_MODEL.md @@ -16,7 +16,7 @@ *(maintainer)* — stated by a Pekko maintainer in review of this document. *(inferred)* — reasoned from code or config defaults, **not yet confirmed**; each has a matching question in §14. -**Draft confidence:** 41 documented / 16 maintainer / 9 inferred. The documented share is unusually high because Pekko's own remoting and serialization docs already state much of the trust model explicitly. The §5a default rulings — previously the largest inferred block — are now answered by the §5b posture statement and §14 Q1 to Q4. What remains inferred is concentrated in the negative claims in §5 (§14 Q7) and the module in/out split (§14 Q6). +**Draft confidence:** 41 documented / 21 maintainer / 9 inferred. The documented share is unusually high because Pekko's own remoting and serialization docs already state much of the trust model explicitly. The §5a default rulings — previously the largest inferred block — are now answered by the §5b posture statement and §14 Q1 to Q5. What remains inferred is concentrated in the negative claims in §5 (§14 Q7) and the module in/out split (§14 Q6). ## §1 Overview @@ -92,8 +92,8 @@ A finding must meet its family's precondition to be in-model: - **Serialization** — reachable from a message payload deserialized by a **configured, enabled** serializer. Findings reachable only when `allow-java-serialization = on` are judged under §5a. - **Cluster / distributed-data** — reachable from gossip or replication traffic originating at an **associated peer**. Per §7 such a peer is trusted, so these are typically out of model unless the finding shows a pre-association reach. - **Actor core / streams** — reachable from data the embedding application passes in. Trusted by default; a finding must show the data crosses an application boundary that Pekko itself defines. -- **Persistence** — reachable from journal or snapshot contents. See §6 on the storage-trust question. -- **PKI** — reachable from PEM/keystore material. Operator-supplied and trusted; see §14 Q5. +- **Persistence** — reachable from journal or snapshot contents. The store is trusted *(maintainer — §14 Q5)*, so a finding requiring write access to it is out of model; a defect in Pekko's replay handling is not. +- **PKI** — reachable from PEM/keystore material. Operator-supplied and trusted per §6. --- @@ -163,7 +163,7 @@ For a toolkit whose surface is a wire protocol, the useful table is keyed by **m | Artery transport | Remote-deployment `Props` | **Yes**, same condition | Operator: `enable-allow-list` | | Cluster | Gossip / membership state | From an **associated** peer — trusted per §7 | — | | `distributed-data` | Replicated CRDT deltas | From an associated peer — trusted per §7 | — | -| Persistence | Journal / snapshot contents on replay | Depends on backend trust — **§14 Q5** | App/operator: secure the store | +| Persistence | Journal / snapshot contents on replay | **No** — the store is trusted *(maintainer — §14 Q5)* | Operator/DBA: secure the store | | `pki` | PEM / keystore files | **No** — operator-supplied, trusted | Operator: protect key material | | `discovery` | Service-discovery responses (DNS, K8s API) | **Potentially** — depends on the resolver | Operator: trust the discovery mechanism | | `io.Tcp` / `io.Udp` | Bytes on an application-bound socket | **Potentially** — depends where the application exposes it | App: it chose to bind, defines the protocol, and owns what it exposes | @@ -232,7 +232,7 @@ These are the assumptions integrators most often bring with them, and each is wr - **JVM deserialization gadget chains** — mitigated by P1 only so long as Java serialization stays off, and only for payloads Pekko itself deserializes; application-level serializers are the application's problem. Pekko integrates **no** serialization filter: `JavaSerializer.fromBinary` performs an unfiltered `ObjectInputStream.readObject` (`actor/.../serialization/Serializer.scala`). An operator who enables Java serialization must supply the allow list themselves through the JVM — `-Djdk.serialFilter` or a process-wide `ObjectInputFilter` — and owns that entirely *(maintainer — §14 Q2)*. - **Resource-exhaustion via message volume or size** — see §14 Q8. - **DNS / service-discovery spoofing** — `discovery` trusts the resolver it is configured with. -- **Storage-layer tampering** on persistence journals — see §14 Q5. +- **Storage-layer tampering** on persistence journals and snapshot stores — the store is trusted, and securing it belongs to whoever administers it *(maintainer — §14 Q5)*. --- @@ -248,6 +248,7 @@ The operator or embedding application must: 6. **Consider `untrusted-mode = on` and `enable-allow-list = on`** where peers are less than fully trusted — understanding both are hardening, not boundaries. 7. **Never place mutually-distrusting tenants in one cluster** *(documented)*. 8. **Supply passwords by environment substitution, not literals in config files** *(documented)*. +9. **Secure the persistence store.** Pekko trusts journal and snapshot contents on replay and does not validate them as potentially hostile, so access control and integrity for the database or store are the administrator's responsibility *(maintainer — §14 Q5)*. --- @@ -291,7 +292,7 @@ Feed this section to scanners and AI triage as a suppression list. | --- | --- | --- | | `VALID` | Violates a §8 property via an in-scope adversary and input | §6, §7, §8 | | `VALID-HARDENING` | No §8 property violated, but the API makes a §11 misuse easy enough to warrant hardening. No CVE by default | §11 | -| `OUT-OF-MODEL: trusted-input` | Requires control of an input §6 marks trusted (config, PEM material, application-supplied data) | §6 | +| `OUT-OF-MODEL: trusted-input` | Requires control of an input §6 marks trusted (config, PEM material, journal and snapshot contents, application-supplied data) | §6 | | `OUT-OF-MODEL: adversary-not-in-scope` | Requires an associated peer to misbehave, a PKI-tree certificate, or in-JVM code execution | §7 | | `OUT-OF-MODEL: unsupported-component` | Lands in a §3 module | §3 | | `OUT-OF-MODEL: non-default-build` | Only manifests under a non-default §5a setting — most often `allow-java-serialization = on` | §5a | @@ -306,7 +307,7 @@ Feed this section to scanners and AI triage as a suppression list. Each states a **proposed answer**. Confirming or correcting is enough; no need to write prose. -Q1 to Q4 are **answered** and retained in place so that cross-references elsewhere in this document continue to resolve. The remaining questions are open. +Q1 to Q5 are **answered** and retained in place so that cross-references elsewhere in this document continue to resolve. The remaining questions are open. **Q1 — The plaintext transport default. ANSWERED *(maintainer)*.** `transport` ships `tcp`, so a stock cluster has no peer authentication. **Answer:** the default is a compatibility choice under §5b, not a security claim. Reports route as follows: @@ -326,7 +327,9 @@ Two things this does **not** dispose of, per §5b.4: A **bypass of either once enabled** is a separate matter and is **not** covered by that. It violates §8 P5 or P4 respectively, so it is `VALID` at the severity §8 assigns, per §5b.4. The draft proposed `VALID-HARDENING` for this case, which §13 defines as *"No §8 property violated"* — that cannot apply to a bypass of a control §8 credits. -**Q5 — Persistence backend trust.** *Proposed:* journal and snapshot stores are **trusted**; an attacker who can write to the journal is out of model. Confirm — or should replay treat stored bytes as untrusted? +**Q5 — Persistence backend trust. ANSWERED *(maintainer)*.** **Answer:** journal and snapshot stores are **trusted**. Securing the database or persistence store is the responsibility of whoever administers it, and Pekko does not treat persisted values as potentially compromised on replay. A finding whose precondition is an attacker who can write to the journal or snapshot store is `OUT-OF-MODEL: trusted-input`. + +This is a trust statement about the **store**, not a licence for the plugin SPI: a defect in Pekko's own replay handling is in scope per §5b.4. **Q6 — Module in/out split (§2 table).** *Proposed:* the split shown. Two specific checks: is `kubernetes/` deployment tooling or supported code, and should `osgi` stay in model given its classloading surface? From a00d9617d3c8656e4ac9d85f90c10298b7f8e125 Mon Sep 17 00:00:00 2001 From: PJ Fanning Date: Thu, 27 Aug 2026 15:32:30 +0100 Subject: [PATCH 09/23] docs: answer the osgi half of Q6 - in model, but a removal candidate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ### Motivation Q6 asked two things about the §2 module split: whether `kubernetes/` is supported code, and whether `osgi` should stay in model given its classloading surface. The `osgi` half is settled: it is a barely used feature and the project may eventually remove it rather than carry the maintenance overhead, but while it ships, security reports against it are accepted. ### Modification - **§14 Q6** - marked partly answered. `osgi` stays in model and a finding there is not `OUT-OF-MODEL: unsupported-component`. The `kubernetes/` question and the rest of the split remain open. - **§2** - the `osgi` row and the table footnote record the ruling. - **§12** - the list of conditions that would change the model covered only promotion of a module into the supported surface. Removal is now named too, since `osgi` is a candidate and dropping it would change how findings there are triaged. ### Result §14 holds seven fully open questions and one partly answered. Provenance moves to 41 documented / 23 maintainer / 10 inferred. ### Tests Not run - docs only ### References Refs #3478 --- THREAT_MODEL.md | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/THREAT_MODEL.md b/THREAT_MODEL.md index cbadb1646fe..677060b2b78 100644 --- a/THREAT_MODEL.md +++ b/THREAT_MODEL.md @@ -16,7 +16,7 @@ *(maintainer)* — stated by a Pekko maintainer in review of this document. *(inferred)* — reasoned from code or config defaults, **not yet confirmed**; each has a matching question in §14. -**Draft confidence:** 41 documented / 21 maintainer / 9 inferred. The documented share is unusually high because Pekko's own remoting and serialization docs already state much of the trust model explicitly. The §5a default rulings — previously the largest inferred block — are now answered by the §5b posture statement and §14 Q1 to Q5. What remains inferred is concentrated in the negative claims in §5 (§14 Q7) and the module in/out split (§14 Q6). +**Draft confidence:** 41 documented / 23 maintainer / 10 inferred. The documented share is unusually high because Pekko's own remoting and serialization docs already state much of the trust model explicitly. The §5a default rulings — previously the largest inferred block — are now answered by the §5b posture statement and §14 Q1 to Q5. What remains inferred is concentrated in the negative claims in §5 (§14 Q7) and the module in/out split (§14 Q6). ## §1 Overview @@ -49,12 +49,12 @@ Three caller roles matter, and they are not equally trusted: | Persistence | `persistence`, `persistence-typed`, `persistence-query`, `persistence-shared` | journal / snapshot plugin SPI | **storage backend** | **yes**, boundary at the SPI | | PKI | `pki` | PEM/keystore parsing | **reads files** | **yes** | | Discovery | `discovery` | service-discovery SPI | **network / DNS** | **yes** | -| OSGi | `osgi` | bundle activator | classloading | **yes** | +| OSGi | `osgi` | bundle activator | classloading | **yes** *(maintainer — §14 Q6)* | | Test kits | `*-testkit`, `multi-node-testkit`, `*-tests`, `persistence-tck`, `stream-tests-tck` | — | — | **no** — §3 | | Benchmarks | `bench-jmh` | — | — | **no** — §3 | | Build / docs | `docs`, `project`, `scripts`, `legal`, `kubernetes`, `plugins`, `bill-of-materials`, `scala-nightly` | — | — | **no** — §3 | -*(inferred — the in/out split is the ASF Security team's proposal; see §14 Q6)* +*(inferred — the in/out split is the ASF Security team's proposal, except `osgi`, which is confirmed in model; see §14 Q6)* --- @@ -281,7 +281,7 @@ Feed this section to scanners and AI triage as a suppression list. - A change to any §5a **default**, particularly `transport`, `untrusted-mode`, or `allow-java-serialization`. - A new transport, or a new wire protocol at the remoting layer. - Any per-peer or per-actor authorization mechanism — that would create an intra-cluster trust boundary this model says does not exist. -- Promotion of a §3 module into the supported surface. +- Promotion of a §3 module into the supported surface, or **removal of a module from it** — `osgi` is a candidate for removal (§14 Q6), and dropping it would move findings there to `OUT-OF-MODEL: unsupported-component`. - **A report that cannot be routed to exactly one §13 disposition.** That is evidence of a model gap; the correct response is to revise this document, not to make an ad-hoc call. --- @@ -307,7 +307,7 @@ Feed this section to scanners and AI triage as a suppression list. Each states a **proposed answer**. Confirming or correcting is enough; no need to write prose. -Q1 to Q5 are **answered** and retained in place so that cross-references elsewhere in this document continue to resolve. The remaining questions are open. +Q1 to Q5 are **answered** and Q6 is partly answered, all retained in place so that cross-references elsewhere in this document continue to resolve. The remaining questions are open. **Q1 — The plaintext transport default. ANSWERED *(maintainer)*.** `transport` ships `tcp`, so a stock cluster has no peer authentication. **Answer:** the default is a compatibility choice under §5b, not a security claim. Reports route as follows: @@ -331,7 +331,11 @@ A **bypass of either once enabled** is a separate matter and is **not** covered This is a trust statement about the **store**, not a licence for the plugin SPI: a defect in Pekko's own replay handling is in scope per §5b.4. -**Q6 — Module in/out split (§2 table).** *Proposed:* the split shown. Two specific checks: is `kubernetes/` deployment tooling or supported code, and should `osgi` stay in model given its classloading surface? +**Q6 — Module in/out split (§2 table). PARTLY ANSWERED.** + +- **`osgi` — answered *(maintainer)*.** It stays **in model**: security reports against it are accepted. It is a barely used feature, and the project may in future remove it rather than carry the maintenance overhead — but while it ships it is supported, and a finding in it is not `OUT-OF-MODEL: unsupported-component`. If it is removed, §12 applies. +- **`kubernetes/` — still open.** Is it deployment tooling, or supported code? The §2 table currently places it out of scope with the build and docs sources. +- The rest of the in/out split shown in §2 remains the ASF Security team's proposal *(inferred)*. **Q7 — The negative claims in §5.** These are inferred and hard to cite. Are any wrong — does Pekko open sockets, read env vars, or mutate process-global state in ways an integrator would not expect? From c544582edb2e16a7266474e263f9b160a387b98e Mon Sep 17 00:00:00 2001 From: PJ Fanning Date: Thu, 27 Aug 2026 15:36:31 +0100 Subject: [PATCH 10/23] docs: scope the model to this repository and finish Q6 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ### Motivation Q6's remaining half asked whether `kubernetes/` is supported code. It is not a module at all: four files that provision a GKE test cluster, with no build definition. The Kubernetes functionality people would report against - discovery, bootstrap, lease - is not in this repository; it ships from Apache Pekko Management. That exposed a larger gap. Nothing in the document said which repository it covers. Outside the header's "core toolkit", a triager had no way to tell where a Pekko Management or Pekko HTTP finding belongs, and §13 offered no routing for one. ### Modification - **§14 Q6** - answered. `kubernetes/` stays out of scope as build tooling, and the Kubernetes implementation is recorded as living in a separate deliverable. - **§3** - new non-goal stating that this document covers `apache/pekko` only. Add-on deliverables are named, with the routing made explicit: out of model *for this document* is not a dismissal, reports go to the same address, and they are triaged on the same terms - an implementation defect is Pekko's to fix, a request to change a default is a §5b change request. - **§2** - the build row says what `kubernetes/` actually contains. ### Result §14 holds seven open questions. Provenance moves to 41 documented / 26 maintainer / 10 inferred. ### Tests Not run - docs only ### References Refs #3478 --- THREAT_MODEL.md | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/THREAT_MODEL.md b/THREAT_MODEL.md index 677060b2b78..e4de798c50b 100644 --- a/THREAT_MODEL.md +++ b/THREAT_MODEL.md @@ -16,7 +16,7 @@ *(maintainer)* — stated by a Pekko maintainer in review of this document. *(inferred)* — reasoned from code or config defaults, **not yet confirmed**; each has a matching question in §14. -**Draft confidence:** 41 documented / 23 maintainer / 10 inferred. The documented share is unusually high because Pekko's own remoting and serialization docs already state much of the trust model explicitly. The §5a default rulings — previously the largest inferred block — are now answered by the §5b posture statement and §14 Q1 to Q5. What remains inferred is concentrated in the negative claims in §5 (§14 Q7) and the module in/out split (§14 Q6). +**Draft confidence:** 41 documented / 26 maintainer / 10 inferred. The documented share is unusually high because Pekko's own remoting and serialization docs already state much of the trust model explicitly. The §5a default rulings — previously the largest inferred block — are now answered by the §5b posture statement and §14 Q1 to Q5. What remains inferred is concentrated in the negative claims in §5 (§14 Q7) and the module in/out split (§14 Q6). ## §1 Overview @@ -52,7 +52,7 @@ Three caller roles matter, and they are not equally trusted: | OSGi | `osgi` | bundle activator | classloading | **yes** *(maintainer — §14 Q6)* | | Test kits | `*-testkit`, `multi-node-testkit`, `*-tests`, `persistence-tck`, `stream-tests-tck` | — | — | **no** — §3 | | Benchmarks | `bench-jmh` | — | — | **no** — §3 | -| Build / docs | `docs`, `project`, `scripts`, `legal`, `kubernetes`, `plugins`, `bill-of-materials`, `scala-nightly` | — | — | **no** — §3 | +| Build / docs | `docs`, `project`, `scripts`, `legal`, `kubernetes` (test-cluster provisioning scripts), `plugins`, `bill-of-materials`, `scala-nightly` | — | — | **no** — §3 | *(inferred — the in/out split is the ASF Security team's proposal, except `osgi`, which is confirmed in model; see §14 Q6)* @@ -65,6 +65,7 @@ Three caller roles matter, and they are not equally trusted: - **Pekko is not an authorization framework.** It carries no notion of a principal, role, or permission on a message. Application-level authorization is the embedding application's job. *(inferred — §14 Q11)* - **A Pekko cluster is not a multi-tenancy boundary.** The documentation is explicit that *"you'll have to trust all cluster nodes the same in a Pekko cluster anyway"* *(documented — `remote-security.md`)*. Separating mutually-distrusting tenants across nodes of one cluster is not a supported deployment. - **Attackers who already control the embedding process** are out of scope — they have already won. *(inferred — §14 Q11)* +- **Add-on modules are separate deliverables.** This document covers the `apache/pekko` core toolkit. Apache Pekko Management, Pekko HTTP, Pekko gRPC, Pekko Connectors, Pekko Projection and the persistence plugins each ship from their own repository and are outside its scope — a finding in one of them is `OUT-OF-MODEL: unsupported-component` *for this model*, which is a routing statement and not a dismissal. Such reports are accepted, go to the same address (see [`SECURITY.md`](SECURITY.md)), and are triaged on the same terms: an implementation defect is Pekko's to fix, while a request to change a default is a change request under §5b, not a vulnerability. *(maintainer — §14 Q6)* --- @@ -307,7 +308,7 @@ Feed this section to scanners and AI triage as a suppression list. Each states a **proposed answer**. Confirming or correcting is enough; no need to write prose. -Q1 to Q5 are **answered** and Q6 is partly answered, all retained in place so that cross-references elsewhere in this document continue to resolve. The remaining questions are open. +Q1 to Q6 are **answered**, all retained in place so that cross-references elsewhere in this document continue to resolve. The remaining questions are open. **Q1 — The plaintext transport default. ANSWERED *(maintainer)*.** `transport` ships `tcp`, so a stock cluster has no peer authentication. **Answer:** the default is a compatibility choice under §5b, not a security claim. Reports route as follows: @@ -331,10 +332,10 @@ A **bypass of either once enabled** is a separate matter and is **not** covered This is a trust statement about the **store**, not a licence for the plugin SPI: a defect in Pekko's own replay handling is in scope per §5b.4. -**Q6 — Module in/out split (§2 table). PARTLY ANSWERED.** +**Q6 — Module in/out split (§2 table). ANSWERED *(maintainer)*, except the residual split noted below.** - **`osgi` — answered *(maintainer)*.** It stays **in model**: security reports against it are accepted. It is a barely used feature, and the project may in future remove it rather than carry the maintenance overhead — but while it ships it is supported, and a finding in it is not `OUT-OF-MODEL: unsupported-component`. If it is removed, §12 applies. -- **`kubernetes/` — still open.** Is it deployment tooling, or supported code? The §2 table currently places it out of scope with the build and docs sources. +- **`kubernetes/` — answered *(maintainer)*.** It is test-cluster provisioning tooling — four files (`setup.sh`, `create-cluster-gke.sh`, `test-node-base.yaml`, `.gitignore`), not a build module. It is correctly out of scope with the build and docs sources. Kubernetes *functionality* — discovery, bootstrap, lease — is not in this repository at all: it lives in **Apache Pekko Management**, a separate deliverable, and is therefore outside this document's scope per §3. Reports against it are accepted on the same terms stated there. - The rest of the in/out split shown in §2 remains the ASF Security team's proposal *(inferred)*. **Q7 — The negative claims in §5.** These are inferred and hard to cite. Are any wrong — does Pekko open sockets, read env vars, or mutate process-global state in ways an integrator would not expect? From 61d8172b692e0176717dcc961963476b61e7d4e0 Mon Sep 17 00:00:00 2001 From: PJ Fanning Date: Thu, 27 Aug 2026 15:37:22 +0100 Subject: [PATCH 11/23] docs: correct how findings in other Pekko repositories are routed MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ### Motivation The previous commit sent a finding in another Apache Pekko repository to `OUT-OF-MODEL: unsupported-component`. That is wrong. §13 defines that disposition as landing in a §3 component - test kits, benchmarks, build tooling - and applying it to code that simply ships from a different repository reads as a dismissal of a report that is in fact accepted normally. Pekko Management issues are reported the same way core issues are. The repository differs; the process does not. ### Modification Reworded the §3 entry so it limits coverage rather than standing: this document enumerates the core toolkit's surfaces and does not enumerate the other deliverables', but reporting, triage, the address and the §5b posture are identical across all of them. States explicitly that such a finding is not `OUT-OF-MODEL: unsupported-component`, so the disposition is not misapplied. §14 Q6 follows the same correction. ### Result No disposition implies a report is out of scope purely because of which Pekko repository it lands in. ### Tests Not run - docs only ### References Refs #3478 --- THREAT_MODEL.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/THREAT_MODEL.md b/THREAT_MODEL.md index e4de798c50b..fe4f2dc6870 100644 --- a/THREAT_MODEL.md +++ b/THREAT_MODEL.md @@ -65,7 +65,9 @@ Three caller roles matter, and they are not equally trusted: - **Pekko is not an authorization framework.** It carries no notion of a principal, role, or permission on a message. Application-level authorization is the embedding application's job. *(inferred — §14 Q11)* - **A Pekko cluster is not a multi-tenancy boundary.** The documentation is explicit that *"you'll have to trust all cluster nodes the same in a Pekko cluster anyway"* *(documented — `remote-security.md`)*. Separating mutually-distrusting tenants across nodes of one cluster is not a supported deployment. - **Attackers who already control the embedding process** are out of scope — they have already won. *(inferred — §14 Q11)* -- **Add-on modules are separate deliverables.** This document covers the `apache/pekko` core toolkit. Apache Pekko Management, Pekko HTTP, Pekko gRPC, Pekko Connectors, Pekko Projection and the persistence plugins each ship from their own repository and are outside its scope — a finding in one of them is `OUT-OF-MODEL: unsupported-component` *for this model*, which is a routing statement and not a dismissal. Such reports are accepted, go to the same address (see [`SECURITY.md`](SECURITY.md)), and are triaged on the same terms: an implementation defect is Pekko's to fix, while a request to change a default is a change request under §5b, not a vulnerability. *(maintainer — §14 Q6)* +- **This document describes the `apache/pekko` core toolkit.** Its technical content — the §2 component families, the §5a defaults, the §8 properties — is about that codebase. Other Apache Pekko deliverables (Pekko Management, Pekko HTTP, Pekko gRPC, Pekko Connectors, Pekko Projection, the persistence plugins) ship from their own repositories, and this model does not enumerate their surfaces. + + That is a limit on **coverage, not on standing**. Reporting and triage are identical across all Pekko repositories: the same address (see [`SECURITY.md`](SECURITY.md)), the same PMC, and the same posture — an implementation defect is Pekko's to fix, while a request to change a default is a change request under §5b, not a vulnerability. A finding in another Pekko repository is **not** `OUT-OF-MODEL: unsupported-component`; that disposition is for the §3 components above, not for code that simply lives elsewhere. *(maintainer — §14 Q6)* --- @@ -335,7 +337,7 @@ This is a trust statement about the **store**, not a licence for the plugin SPI: **Q6 — Module in/out split (§2 table). ANSWERED *(maintainer)*, except the residual split noted below.** - **`osgi` — answered *(maintainer)*.** It stays **in model**: security reports against it are accepted. It is a barely used feature, and the project may in future remove it rather than carry the maintenance overhead — but while it ships it is supported, and a finding in it is not `OUT-OF-MODEL: unsupported-component`. If it is removed, §12 applies. -- **`kubernetes/` — answered *(maintainer)*.** It is test-cluster provisioning tooling — four files (`setup.sh`, `create-cluster-gke.sh`, `test-node-base.yaml`, `.gitignore`), not a build module. It is correctly out of scope with the build and docs sources. Kubernetes *functionality* — discovery, bootstrap, lease — is not in this repository at all: it lives in **Apache Pekko Management**, a separate deliverable, and is therefore outside this document's scope per §3. Reports against it are accepted on the same terms stated there. +- **`kubernetes/` — answered *(maintainer)*.** It is test-cluster provisioning tooling — four files (`setup.sh`, `create-cluster-gke.sh`, `test-node-base.yaml`, `.gitignore`), not a build module. It is correctly out of scope with the build and docs sources. Kubernetes *functionality* — discovery, bootstrap, lease — is not in this repository at all: it lives in **Apache Pekko Management**. That is a different repository, not a different security process: reports are made and triaged exactly as they are for core, per §3. - The rest of the in/out split shown in §2 remains the ASF Security team's proposal *(inferred)*. **Q7 — The negative claims in §5.** These are inferred and hard to cite. Are any wrong — does Pekko open sockets, read env vars, or mutate process-global state in ways an integrator would not expect? From 8dc516d9257158c40964170372354711f50fd4a8 Mon Sep 17 00:00:00 2001 From: PJ Fanning Date: Thu, 27 Aug 2026 15:45:38 +0100 Subject: [PATCH 12/23] docs: answer Q8, and Q7's environment-variable claim MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ### Motivation Q7's §5 negative claims were written as bare assertions with nothing a reviewer could check, and one of the five had already proved wrong. Q8 asked for a categorical line on resource growth driven by a remote peer. ### Modification **Q8 - answered.** Super-linear in message size is a bug and is valid; constant-factor is not. Two caveats recorded: artery already bounds message size by configuration, so the rule's input is bounded on the remoting path; and the rule speaks to size, leaving exhaustion driven by message volume from an associated peer subject to §7. **Q7 - partly answered.** Environment variables are read during configuration startup and never modified. Pekko calls neither `System.getenv` nor `sys.env` anywhere in the main sources; values arrive only through HOCON substitution when `ConfigFactory.load` resolves the configuration, which is the documented way to supply passwords. The remaining §5 claims now carry citations, and two were narrower than written: `CoordinatedShutdown` registers a JVM shutdown hook, and `StandardOutLogger` prints to stdout during early startup, bounded by `pekko.stdout-loglevel`. Verified absent from the main sources: `sun.misc.Signal`, `SignalHandler`, `Runtime.exec`, `ProcessBuilder`, `System.setProperty`, `Locale.setDefault`, `TimeZone.setDefault`. ### Result §14 holds five open questions. Provenance moves to 41 documented / 29 maintainer / 10 inferred. ### Tests Not run - docs only ### References Refs #3478 --- THREAT_MODEL.md | 24 ++++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/THREAT_MODEL.md b/THREAT_MODEL.md index fe4f2dc6870..add50d845b9 100644 --- a/THREAT_MODEL.md +++ b/THREAT_MODEL.md @@ -16,7 +16,7 @@ *(maintainer)* — stated by a Pekko maintainer in review of this document. *(inferred)* — reasoned from code or config defaults, **not yet confirmed**; each has a matching question in §14. -**Draft confidence:** 41 documented / 26 maintainer / 10 inferred. The documented share is unusually high because Pekko's own remoting and serialization docs already state much of the trust model explicitly. The §5a default rulings — previously the largest inferred block — are now answered by the §5b posture statement and §14 Q1 to Q5. What remains inferred is concentrated in the negative claims in §5 (§14 Q7) and the module in/out split (§14 Q6). +**Draft confidence:** 41 documented / 29 maintainer / 10 inferred. The documented share is unusually high because Pekko's own remoting and serialization docs already state much of the trust model explicitly. The §5a default rulings — previously the largest inferred block — are now answered by the §5b posture statement and §14 Q1 to Q5. What remains inferred is concentrated in the residual negative claims in §5 (§14 Q7), each of which now carries a code citation, and the module in/out split (§14 Q6). ## §1 Overview @@ -112,11 +112,11 @@ A finding must meet its family's precondition to be in-model: These are negative claims, rarely written down anywhere, and therefore the highest-value confirmation targets in §14 *(all inferred — §14 Q7)*: -- Installs no signal handlers and spawns no child processes. +- Installs no signal handlers and spawns no child processes — no `sun.misc.Signal`/`SignalHandler`, `Runtime.exec` or `ProcessBuilder` in the main sources. It does register **one JVM shutdown hook**, via `CoordinatedShutdown` (`actor/.../actor/CoordinatedShutdown.scala:381`). - Opens no listening socket of its own accord. Remoting binds when configured; `org.apache.pekko.io.Tcp`/`Udp` (in `pekko-actor`) and `stream.scaladsl.Tcp` (in `pekko-stream`) bind only on an explicit application call. Pekko never binds a port the application did not ask for. -- Reads configuration from the classpath and supplied `Config`; does not read arbitrary environment variables of its own accord. -- Writes to logging via SLF4J as configured; does not write to stdout/stderr directly in normal operation. -- Does not mutate process-global state (locale, default `SecurityManager`, system properties) at initialization. +- **Reads environment variables during configuration startup, but never modifies them** *(maintainer — §14 Q7)*. Pekko itself calls neither `System.getenv` nor `sys.env` anywhere in the main sources; environment values reach it only through HOCON `${?VAR}` substitution when `ConfigFactory.load` resolves the configuration (`actor/.../actor/ActorSystem.scala:281`). That path is deliberate and documented — it is how the docs tell operators to supply passwords (§10.8). +- Writes to logging via the configured logger. One exception: `StandardOutLogger` prints to stdout with `println` (`actor/.../event/Logging.scala:1024` onward). It carries the very early startup log, before the configured loggers are running, and is bounded by `pekko.stdout-loglevel`, which defaults to `WARNING`. +- Does not mutate process-global state at initialization — no `System.setProperty`, `Locale.setDefault` or `TimeZone.setDefault` in the main sources. --- @@ -310,7 +310,7 @@ Feed this section to scanners and AI triage as a suppression list. Each states a **proposed answer**. Confirming or correcting is enough; no need to write prose. -Q1 to Q6 are **answered**, all retained in place so that cross-references elsewhere in this document continue to resolve. The remaining questions are open. +Q1 to Q6 and Q8 are **answered**, and Q7 partly, all retained in place so that cross-references elsewhere in this document continue to resolve. The remaining questions are open. **Q1 — The plaintext transport default. ANSWERED *(maintainer)*.** `transport` ships `tcp`, so a stock cluster has no peer authentication. **Answer:** the default is a compatibility choice under §5b, not a security claim. Reports route as follows: @@ -340,9 +340,17 @@ This is a trust statement about the **store**, not a licence for the plugin SPI: - **`kubernetes/` — answered *(maintainer)*.** It is test-cluster provisioning tooling — four files (`setup.sh`, `create-cluster-gke.sh`, `test-node-base.yaml`, `.gitignore`), not a build module. It is correctly out of scope with the build and docs sources. Kubernetes *functionality* — discovery, bootstrap, lease — is not in this repository at all: it lives in **Apache Pekko Management**. That is a different repository, not a different security process: reports are made and triaged exactly as they are for core, per §3. - The rest of the in/out split shown in §2 remains the ASF Security team's proposal *(inferred)*. -**Q7 — The negative claims in §5.** These are inferred and hard to cite. Are any wrong — does Pekko open sockets, read env vars, or mutate process-global state in ways an integrator would not expect? +**Q7 — The negative claims in §5. PARTLY ANSWERED.** -**Q8 — Resource guarantees.** Is unbounded memory or CPU growth driven by a remote peer's message volume/size a **bug** or accepted given §7's trusted-peer stance? Ideally a categorical line: *"super-linear in message size is a bug; constant-factor is not"*, or *"no resource guarantee is made against an associated peer"*. +- **Environment variables — answered *(maintainer)*.** Pekko reads them as part of configuration startup and does not modify them. §5 now states the mechanism. +- **Sockets — answered.** The original claim was wrong and is corrected in §2, §5 and §6: `pekko-actor` ships `org.apache.pekko.io.Tcp`/`Udp` and `pekko-stream` ships `Tcp`, `TLS` and `FileIO`, all of which bind or open only on an explicit application call. +- **Signal handlers, child processes, stdout, process-global state — still open**, but each claim in §5 now carries the citation, or the exception, that a reviewer needs in order to confirm it. Two are narrower than first written: a JVM shutdown hook is registered by `CoordinatedShutdown`, and `StandardOutLogger` does print to stdout during early startup. + +**Q8 — Resource guarantees. ANSWERED *(maintainer)*.** **Answer:** **super-linear in message size is a bug; constant-factor is not.** Memory or CPU that grows super-linearly in the size of an inbound message is a defect and is `VALID`; a constant-factor overhead proportional to the message is expected and is not. + +Two notes on applying this line: +- Artery already bounds message size by configuration — `maximum-frame-size` defaults to 256 KiB and `maximum-large-frame-size` to 2 MiB (`remote/src/main/resources/reference.conf`) — so the input to the rule is bounded on the remoting path. +- The rule is stated in terms of **size**. Exhaustion driven by message **volume** from an associated peer is not covered by it and remains subject to §7, under which such a peer is trusted. **Q9 — Byzantine generalisation.** §7 concludes there is **no** Byzantine-peer model anywhere, generalising from remoting to cluster, sharding, singleton and `distributed-data`. Does that hold for all of them, or does any subsystem claim resilience against a misbehaving member? From 7eee33e2dda8e555a17cd95e2e310c0a0c3fa2fd Mon Sep 17 00:00:00 2001 From: PJ Fanning Date: Thu, 27 Aug 2026 15:47:08 +0100 Subject: [PATCH 13/23] docs: answer Q9 - no compromised-node detection anywhere MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ### Motivation Q9 asked whether §7's no-Byzantine-peer conclusion, reached from the remoting documentation, generalises to cluster membership, sharding, singleton and `distributed-data`. It does, and for a stronger reason than the draft gave: Pekko has no guarantee of being able to recognise a compromised node at all. ### Modification - **§14 Q9** - answered. No subsystem claims resilience against a misbehaving member. A finding whose precondition is "a cluster member misbehaves" is `OUT-OF-MODEL: adversary-not-in-scope`, and so is one that assumes Pekko should have detected the compromise. - **§7** - the generalisation is now stated rather than inferred, with the reason: failure detection is heartbeat-based (`PhiAccrualFailureDetector.scala`), so it identifies members that stop responding, not members that respond dishonestly. A compromised node that keeps heartbeating is indistinguishable from a healthy one. - **§9** - the property is widened from "no Byzantine fault tolerance" to include the absence of any compromised-node detection, which is the part a reporter is more likely to assume exists. ### Result §14 holds four open questions. Provenance moves to 41 documented / 32 maintainer / 9 inferred. ### Tests Not run - docs only ### References Refs #3478 --- THREAT_MODEL.md | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/THREAT_MODEL.md b/THREAT_MODEL.md index add50d845b9..037284d1e6b 100644 --- a/THREAT_MODEL.md +++ b/THREAT_MODEL.md @@ -16,7 +16,7 @@ *(maintainer)* — stated by a Pekko maintainer in review of this document. *(inferred)* — reasoned from code or config defaults, **not yet confirmed**; each has a matching question in §14. -**Draft confidence:** 41 documented / 29 maintainer / 10 inferred. The documented share is unusually high because Pekko's own remoting and serialization docs already state much of the trust model explicitly. The §5a default rulings — previously the largest inferred block — are now answered by the §5b posture statement and §14 Q1 to Q5. What remains inferred is concentrated in the residual negative claims in §5 (§14 Q7), each of which now carries a code citation, and the module in/out split (§14 Q6). +**Draft confidence:** 41 documented / 32 maintainer / 9 inferred. The documented share is unusually high because Pekko's own remoting and serialization docs already state much of the trust model explicitly. The §5a default rulings — previously the largest inferred block — are now answered by the §5b posture statement and §14 Q1 to Q5. What remains inferred is concentrated in the residual negative claims in §5 (§14 Q7), each of which now carries a code citation, and the module in/out split (§14 Q6). ## §1 Overview @@ -188,7 +188,7 @@ For a toolkit whose surface is a wire protocol, the useful table is keyed by **m **Explicitly out of scope:** -- **An associated peer behaving arbitrarily.** There is no Byzantine-peer model. The documentation is direct: *"you'll have to trust all cluster nodes the same in a Pekko cluster anyway"* *(documented)*, and *"as soon as an actor system can connect to another remotely, it may in principle send any possible message to any actor contained within that remote system"* *(documented)*. **A finding whose precondition is "a cluster member misbehaves" is out of model** — there is no honest-majority threshold to state, because the model has no notion of a dishonest member. *(inferred that this generalises to all cluster protocols — §14 Q9)* +- **An associated peer behaving arbitrarily.** There is no Byzantine-peer model. The documentation is direct: *"you'll have to trust all cluster nodes the same in a Pekko cluster anyway"* *(documented)*, and *"as soon as an actor system can connect to another remotely, it may in principle send any possible message to any actor contained within that remote system"* *(documented)*. **A finding whose precondition is "a cluster member misbehaves" is out of model** — there is no honest-majority threshold to state, because the model has no notion of a dishonest member. This generalises to every cluster protocol — cluster membership, sharding, singleton and `distributed-data` alike: **Pekko makes no guarantee of being able to recognise a compromised node.** Failure detection is heartbeat-based (`remote/.../PhiAccrualFailureDetector.scala`), so it identifies members that stop responding, not members that respond dishonestly; a compromised node that keeps heartbeating is indistinguishable from a healthy one. *(maintainer — §14 Q9)* - **An attacker holding any certificate from the cluster's PKI tree.** Documented as equivalent to cluster access *(documented)*. - **An attacker with code execution in the embedding JVM.** Already inside the trust boundary. - **Side-channel observers.** Pekko makes no timing or memory-access guarantees. *(inferred — §14 Q11)* @@ -216,7 +216,7 @@ For a toolkit whose surface is a wire protocol, the useful table is keyed by **m - **No peer authentication by default.** With the default `transport = tcp` there is no shared secret, no certificate, and no handshake credential. Any host that can reach the port and speak Artery can attempt association. *(maintainer — §14 Q1; §5b)* - **No confidentiality or integrity on the wire by default.** Same cause. *(maintainer — §14 Q1; §5b)* - **No intra-cluster authorization.** Once associated, a peer may address any actor in the system. There is no per-actor, per-message, or per-peer permission model. *(documented)* -- **No Byzantine fault tolerance.** See §7. Cluster protocols assume members are honest; there is no threshold below which arbitrary member behaviour is tolerated. *(documented — `remote-security.md`)* +- **No Byzantine fault tolerance, and no compromised-node detection.** See §7. Cluster protocols assume members are honest; there is no threshold below which arbitrary member behaviour is tolerated *(documented — `remote-security.md`)*, and Pekko offers no mechanism that would identify a member as compromised in the first place *(maintainer — §14 Q9)*. - **No bound on blast radius from one compromised node.** Documented explicitly for the PKI case *(documented)*. - **No protection once Java serialization is enabled.** Turning it on re-exposes the full JVM deserialization surface; the docs place this squarely on the operator. *(documented)* @@ -310,7 +310,7 @@ Feed this section to scanners and AI triage as a suppression list. Each states a **proposed answer**. Confirming or correcting is enough; no need to write prose. -Q1 to Q6 and Q8 are **answered**, and Q7 partly, all retained in place so that cross-references elsewhere in this document continue to resolve. The remaining questions are open. +Q1 to Q6, Q8 and Q9 are **answered**, and Q7 partly, all retained in place so that cross-references elsewhere in this document continue to resolve. The remaining questions are open. **Q1 — The plaintext transport default. ANSWERED *(maintainer)*.** `transport` ships `tcp`, so a stock cluster has no peer authentication. **Answer:** the default is a compatibility choice under §5b, not a security claim. Reports route as follows: @@ -352,7 +352,9 @@ Two notes on applying this line: - Artery already bounds message size by configuration — `maximum-frame-size` defaults to 256 KiB and `maximum-large-frame-size` to 2 MiB (`remote/src/main/resources/reference.conf`) — so the input to the rule is bounded on the remoting path. - The rule is stated in terms of **size**. Exhaustion driven by message **volume** from an associated peer is not covered by it and remains subject to §7, under which such a peer is trusted. -**Q9 — Byzantine generalisation.** §7 concludes there is **no** Byzantine-peer model anywhere, generalising from remoting to cluster, sharding, singleton and `distributed-data`. Does that hold for all of them, or does any subsystem claim resilience against a misbehaving member? +**Q9 — Byzantine generalisation. ANSWERED *(maintainer)*.** **Answer:** it holds for all of them. No subsystem — cluster membership, sharding, singleton or `distributed-data` — claims resilience against a misbehaving member, and Pekko has no guarantee of being able to recognise a compromised node at all. Failure detection is heartbeat-based and answers "is this member responding?", not "is this member honest". + +Consequently a finding whose precondition is "a cluster member misbehaves" is `OUT-OF-MODEL: adversary-not-in-scope`, and so is one that assumes Pekko should have detected the compromise. **Q10 — Coexistence (meta).** `docs/src/main/paradox/security/index.md` currently lists security documentation. *Proposed:* this document becomes canonical for **scope**, that page stays canonical for **reporting process**, and it gains a link here. Agree? From 8d8e619faccfadd3b7249ebdc48dc20444dadb22 Mon Sep 17 00:00:00 2001 From: PJ Fanning Date: Thu, 27 Aug 2026 15:51:54 +0100 Subject: [PATCH 14/23] docs: answer Q7, Q10 and Q11 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ### Motivation Q7's remaining claims now each carry a code citation, so confirming them is a read rather than an investigation. Q10 and Q11 were policy questions with no code dependency. ### Modification - **Q7 - answered.** No signal handlers, child processes or process-global mutation, verified absent from the main sources. Two claims stand narrower than first written and say so in §5: `CoordinatedShutdown` registers one JVM shutdown hook, and `StandardOutLogger` prints to stdout during early startup bounded by `pekko.stdout-loglevel`. - **Q10 - answered.** Records which of the three security documents is canonical for what, and that the other two link to this one for scope rather than restating it. Carries a maintenance note: the reporting wording is duplicated between `SECURITY.md` and `security/index.md` and has already drifted once, so until one becomes a pointer to the other, both must be changed together. - **Q11 - answered.** The non-goals are confirmed. Two clarifications added to the draft's wording: the side-channel disclaimer is scoped to Pekko's own guarantees, since TLS is delegated to the JDK's JSSE; and "message content is in scope" is scoped to defects in Pekko's handling of that content, not the application's interpretation of it, which §6 assigns to the application. ### Result §14 holds two open questions, Q12 and Q13. Provenance moves to 41 documented / 43 maintainer / 4 inferred. ### Tests Not run - docs only ### References Refs #3478 --- THREAT_MODEL.md | 47 +++++++++++++++++++++++++++++------------------ 1 file changed, 29 insertions(+), 18 deletions(-) diff --git a/THREAT_MODEL.md b/THREAT_MODEL.md index 037284d1e6b..e4a8be4e967 100644 --- a/THREAT_MODEL.md +++ b/THREAT_MODEL.md @@ -16,7 +16,7 @@ *(maintainer)* — stated by a Pekko maintainer in review of this document. *(inferred)* — reasoned from code or config defaults, **not yet confirmed**; each has a matching question in §14. -**Draft confidence:** 41 documented / 32 maintainer / 9 inferred. The documented share is unusually high because Pekko's own remoting and serialization docs already state much of the trust model explicitly. The §5a default rulings — previously the largest inferred block — are now answered by the §5b posture statement and §14 Q1 to Q5. What remains inferred is concentrated in the residual negative claims in §5 (§14 Q7), each of which now carries a code citation, and the module in/out split (§14 Q6). +**Draft confidence:** 41 documented / 43 maintainer / 4 inferred. The documented share is unusually high because Pekko's own remoting and serialization docs already state much of the trust model explicitly. The §5a default rulings — previously the largest inferred block — are now answered by the §5b posture statement and §14 Q1 to Q5. What remains inferred is the residual module in/out split (§14 Q6) and the clock assumption (§14 Q12). ## §1 Overview @@ -62,9 +62,9 @@ Three caller roles matter, and they are not equally trusted: - **Test kits, benchmarks, build tooling and documentation sources.** These ship in the repository but are not part of the security-supported surface. A finding in `bench-jmh` or any `*-tests` module is `OUT-OF-MODEL: unsupported-component`. *(inferred — §14 Q6)* - **Pekko is not a sandbox.** Actors are not an isolation boundary. Any code running in the JVM can reach any actor's state by ordinary means; the actor model is a concurrency discipline, not a security control. *(maintainer — §14 Q4)* -- **Pekko is not an authorization framework.** It carries no notion of a principal, role, or permission on a message. Application-level authorization is the embedding application's job. *(inferred — §14 Q11)* +- **Pekko is not an authorization framework.** It carries no notion of a principal, role, or permission on a message. Application-level authorization is the embedding application's job. *(maintainer — §14 Q11)* - **A Pekko cluster is not a multi-tenancy boundary.** The documentation is explicit that *"you'll have to trust all cluster nodes the same in a Pekko cluster anyway"* *(documented — `remote-security.md`)*. Separating mutually-distrusting tenants across nodes of one cluster is not a supported deployment. -- **Attackers who already control the embedding process** are out of scope — they have already won. *(inferred — §14 Q11)* +- **Attackers who already control the embedding process** are out of scope — they have already won. *(maintainer — §14 Q11)* - **This document describes the `apache/pekko` core toolkit.** Its technical content — the §2 component families, the §5a defaults, the §8 properties — is about that codebase. Other Apache Pekko deliverables (Pekko Management, Pekko HTTP, Pekko gRPC, Pekko Connectors, Pekko Projection, the persistence plugins) ship from their own repositories, and this model does not enumerate their surfaces. That is a limit on **coverage, not on standing**. Reporting and triage are identical across all Pekko repositories: the same address (see [`SECURITY.md`](SECURITY.md)), the same PMC, and the same posture — an implementation defect is Pekko's to fix, while a request to change a default is a change request under §5b, not a vulnerability. A finding in another Pekko repository is **not** `OUT-OF-MODEL: unsupported-component`; that disposition is for the §3 components above, not for code that simply lives elsewhere. *(maintainer — §14 Q6)* @@ -102,7 +102,7 @@ A finding must meet its family's precondition to be in-model: ## §5 Assumptions about the environment -- **Runtime.** A conformant JVM. Pekko does not defend against a hostile JVM, a hostile classpath, or an attacker with local code execution in the same process. *(inferred — §14 Q11)* +- **Runtime.** A conformant JVM. Pekko does not defend against a hostile JVM, a hostile classpath, or an attacker with local code execution in the same process. *(maintainer — §14 Q11)* - **Network adjacency.** *"Best practice is that Pekko remoting nodes should only be accessible from the adjacent network."* *(documented — `remote-security.md`)* - **PKI scope.** Where TLS is used, every certificate issued by the same internal PKI tree is equivalent: *"there is still a risk that an attacker can gain access to a valid certificate by compromising any node with certificates issued by the same internal PKI tree."* *(documented — `remote-security.md`)* - **Clock.** The failure detector and gossip convergence depend on reasonably-behaved local clocks. Pekko does not defend against adversarial clock manipulation on a cluster node. *(inferred — §14 Q12)* @@ -110,7 +110,7 @@ A finding must meet its family's precondition to be in-model: ### What Pekko does not do to its host -These are negative claims, rarely written down anywhere, and therefore the highest-value confirmation targets in §14 *(all inferred — §14 Q7)*: +These are negative claims, rarely written down anywhere. Each is confirmed and carries the citation, or the exception, behind it *(maintainer — §14 Q7)*: - Installs no signal handlers and spawns no child processes — no `sun.misc.Signal`/`SignalHandler`, `Runtime.exec` or `ProcessBuilder` in the main sources. It does register **one JVM shutdown hook**, via `CoordinatedShutdown` (`actor/.../actor/CoordinatedShutdown.scala:381`). - Opens no listening socket of its own accord. Remoting binds when configured; `org.apache.pekko.io.Tcp`/`Udp` (in `pekko-actor`) and `stream.scaladsl.Tcp` (in `pekko-stream`) bind only on an explicit application call. Pekko never binds a port the application did not ask for. @@ -184,14 +184,14 @@ For a toolkit whose surface is a wire protocol, the useful table is keyed by **m **In scope:** - **An unassociated network attacker** who can reach the remoting port — in a deployment where the operator's isolation assumption has held, this attacker should not exist; where it does, the relevant question is whether they can achieve anything **before** association completes. Pre-association reachability is the sharpest in-model attack surface. *(maintainer — §14 Q1)* -- **An attacker supplying message content** to an otherwise legitimate peer — e.g. data that originates at the application's own untrusted edge and is forwarded into an actor message. *(inferred — §14 Q11)* +- **An attacker supplying message content** to an otherwise legitimate peer — e.g. data that originates at the application's own untrusted edge and is forwarded into an actor message. *(maintainer — §14 Q11)* **Explicitly out of scope:** - **An associated peer behaving arbitrarily.** There is no Byzantine-peer model. The documentation is direct: *"you'll have to trust all cluster nodes the same in a Pekko cluster anyway"* *(documented)*, and *"as soon as an actor system can connect to another remotely, it may in principle send any possible message to any actor contained within that remote system"* *(documented)*. **A finding whose precondition is "a cluster member misbehaves" is out of model** — there is no honest-majority threshold to state, because the model has no notion of a dishonest member. This generalises to every cluster protocol — cluster membership, sharding, singleton and `distributed-data` alike: **Pekko makes no guarantee of being able to recognise a compromised node.** Failure detection is heartbeat-based (`remote/.../PhiAccrualFailureDetector.scala`), so it identifies members that stop responding, not members that respond dishonestly; a compromised node that keeps heartbeating is indistinguishable from a healthy one. *(maintainer — §14 Q9)* - **An attacker holding any certificate from the cluster's PKI tree.** Documented as equivalent to cluster access *(documented)*. - **An attacker with code execution in the embedding JVM.** Already inside the trust boundary. -- **Side-channel observers.** Pekko makes no timing or memory-access guarantees. *(inferred — §14 Q11)* +- **Side-channel observers.** Pekko makes no timing or memory-access guarantees. *(maintainer — §14 Q11)* --- @@ -310,7 +310,7 @@ Feed this section to scanners and AI triage as a suppression list. Each states a **proposed answer**. Confirming or correcting is enough; no need to write prose. -Q1 to Q6, Q8 and Q9 are **answered**, and Q7 partly, all retained in place so that cross-references elsewhere in this document continue to resolve. The remaining questions are open. +Q1 to Q11 are **answered**, retained in place so that cross-references elsewhere in this document continue to resolve. Q12 and Q13 remain open. **Q1 — The plaintext transport default. ANSWERED *(maintainer)*.** `transport` ships `tcp`, so a stock cluster has no peer authentication. **Answer:** the default is a compatibility choice under §5b, not a security claim. Reports route as follows: @@ -340,11 +340,13 @@ This is a trust statement about the **store**, not a licence for the plugin SPI: - **`kubernetes/` — answered *(maintainer)*.** It is test-cluster provisioning tooling — four files (`setup.sh`, `create-cluster-gke.sh`, `test-node-base.yaml`, `.gitignore`), not a build module. It is correctly out of scope with the build and docs sources. Kubernetes *functionality* — discovery, bootstrap, lease — is not in this repository at all: it lives in **Apache Pekko Management**. That is a different repository, not a different security process: reports are made and triaged exactly as they are for core, per §3. - The rest of the in/out split shown in §2 remains the ASF Security team's proposal *(inferred)*. -**Q7 — The negative claims in §5. PARTLY ANSWERED.** +**Q7 — The negative claims in §5. ANSWERED.** -- **Environment variables — answered *(maintainer)*.** Pekko reads them as part of configuration startup and does not modify them. §5 now states the mechanism. +- **Environment variables — *(maintainer)*.** Read during configuration startup, never modified. §5 states the mechanism. - **Sockets — answered.** The original claim was wrong and is corrected in §2, §5 and §6: `pekko-actor` ships `org.apache.pekko.io.Tcp`/`Udp` and `pekko-stream` ships `Tcp`, `TLS` and `FileIO`, all of which bind or open only on an explicit application call. -- **Signal handlers, child processes, stdout, process-global state — still open**, but each claim in §5 now carries the citation, or the exception, that a reviewer needs in order to confirm it. Two are narrower than first written: a JVM shutdown hook is registered by `CoordinatedShutdown`, and `StandardOutLogger` does print to stdout during early startup. +- **Signal handlers and child processes — *(maintainer)*.** None: no `sun.misc.Signal`, `SignalHandler`, `Runtime.exec` or `ProcessBuilder` in the main sources. One JVM shutdown hook is registered by `CoordinatedShutdown`, disclosed in §5; a shutdown hook is not a signal handler. +- **stdout/stderr — *(maintainer)*.** Logging goes to the configured logger. `StandardOutLogger` prints to stdout during early startup, bounded by `pekko.stdout-loglevel` (default `WARNING`), disclosed in §5. +- **Process-global state — *(maintainer)*.** Not mutated at initialization: no `System.setProperty`, `Locale.setDefault` or `TimeZone.setDefault`. **Q8 — Resource guarantees. ANSWERED *(maintainer)*.** **Answer:** **super-linear in message size is a bug; constant-factor is not.** Memory or CPU that grows super-linearly in the size of an inbound message is a defect and is `VALID`; a constant-factor overhead proportional to the message is expected and is not. @@ -356,14 +358,23 @@ Two notes on applying this line: Consequently a finding whose precondition is "a cluster member misbehaves" is `OUT-OF-MODEL: adversary-not-in-scope`, and so is one that assumes Pekko should have detected the compromise. -**Q10 — Coexistence (meta).** `docs/src/main/paradox/security/index.md` currently lists security documentation. *Proposed:* this document becomes canonical for **scope**, that page stays canonical for **reporting process**, and it gains a link here. Agree? +**Q10 — Coexistence. ANSWERED *(maintainer)*.** Three documents carry security information, each canonical for one thing: -**Q11 — The §3/§7 boundary non-goals.** *Proposed:* Pekko is not a sandbox, not an -authorization framework, and the following are out of the adversary model — an attacker -with code execution in the embedding JVM, a hostile classpath, and side-channel -observers (no timing or memory-access guarantees). Conversely, an attacker who supplies -*message content* that reaches an actor via a legitimate peer **is** in scope, since that -is the ordinary path application data takes. Confirm the split? +| Document | Canonical for | Reached by | +| --- | --- | --- | +| `docs/src/main/paradox/security/index.md` | Security announcements; the reporting process as published | Readers of the documentation site | +| [`SECURITY.md`](SECURITY.md) | The reporting policy as GitHub presents it | Anyone arriving via the repository | +| `THREAT_MODEL.md` (this document) | **Scope** — what is and is not a vulnerability, and how a report is triaged | Reporters, triagers, scanning tools | + +The other two link to this document rather than restating it, so a change in scope is made in one place. Neither attempts to state scope itself. + +**Maintenance note.** The reporting wording is currently duplicated between `SECURITY.md` and `security/index.md`, and the two have drifted apart once already. Until one is reduced to a pointer to the other, a change to either must be made to both. + +**Q11 — The §3/§7 boundary non-goals. ANSWERED *(maintainer)*.** Confirmed: + +- Pekko is **not a sandbox** and **not an authorization framework** (§3). +- Out of the adversary model: an attacker with code execution in the embedding JVM, a hostile classpath, and side-channel observers — Pekko makes no timing or memory-access guarantees of its own. TLS is delegated to the JDK's JSSE, whose own guarantees are unaffected by this disclaimer. +- **In** the adversary model: attacker-influenced *message content* arriving by the ordinary path, since that is how application data travels. This covers defects in **Pekko's own handling** of that content — a serializer, codec or framing defect reachable from a well-formed message is `VALID` per §5b.4. It does not extend to the application's interpretation of the content, which is the application's responsibility per §6. **Q12 — Clock assumptions.** The failure detector and gossip convergence depend on local clocks. *Proposed:* Pekko makes no claim against adversarial clock manipulation on From c73df1f3956790dc4694eeefd78d01655e744a60 Mon Sep 17 00:00:00 2001 From: PJ Fanning Date: Thu, 27 Aug 2026 15:54:19 +0100 Subject: [PATCH 15/23] docs: answer Q12 - no node reads another node's clock, except LWW MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ### Motivation Q12 assumed the failure detector and gossip convergence depend on clocks in a way an adversary on a cluster member could exploit. Reading the code, they largely do not, and the one place wall-clock time does cross nodes is already documented on the type. ### Modification **§14 Q12 - answered**, with the findings: - The failure detector reads only the local clock (`FailureDetector.scala:60`, used at `PhiAccrualFailureDetector.scala:144` and `:188`). No remote timestamp reaches it. - `Heartbeat.creationTimeNanos` is the sender's own `System.nanoTime()` (`ClusterHeartbeat.scala:130`), echoed back verbatim by the receiver (`:58`) and subtracted against the sender's own clock (`:251`). No node interprets another node's clock value, and the result feeds only a debug log guarded by `verboseHeartbeat`. - Gossip ordering uses logical `VectorClock` counters (`VectorClock.scala:90-92`), with no wall-clock component. - `LWWRegister` and `LWWMap` do merge by wall-clock timestamp across nodes. The requirement for synchronised clocks, and the alternatives, are stated in the `LWWRegister` scaladoc. §5's clock assumption is rewritten to match: it previously implied a dependency that the failure detector and gossip do not have. ### Result §14 holds one open question, Q13. Provenance moves to 43 documented / 44 maintainer / 3 inferred. ### Tests Not run - docs only ### References Refs #3478 --- THREAT_MODEL.md | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/THREAT_MODEL.md b/THREAT_MODEL.md index e4a8be4e967..ae68c29be76 100644 --- a/THREAT_MODEL.md +++ b/THREAT_MODEL.md @@ -16,7 +16,7 @@ *(maintainer)* — stated by a Pekko maintainer in review of this document. *(inferred)* — reasoned from code or config defaults, **not yet confirmed**; each has a matching question in §14. -**Draft confidence:** 41 documented / 43 maintainer / 4 inferred. The documented share is unusually high because Pekko's own remoting and serialization docs already state much of the trust model explicitly. The §5a default rulings — previously the largest inferred block — are now answered by the §5b posture statement and §14 Q1 to Q5. What remains inferred is the residual module in/out split (§14 Q6) and the clock assumption (§14 Q12). +**Draft confidence:** 43 documented / 44 maintainer / 3 inferred. The documented share is unusually high because Pekko's own remoting and serialization docs already state much of the trust model explicitly. The §5a default rulings — previously the largest inferred block — are now answered by the §5b posture statement and §14 Q1 to Q5. What remains inferred is the residual module in/out split (§14 Q6). ## §1 Overview @@ -105,7 +105,7 @@ A finding must meet its family's precondition to be in-model: - **Runtime.** A conformant JVM. Pekko does not defend against a hostile JVM, a hostile classpath, or an attacker with local code execution in the same process. *(maintainer — §14 Q11)* - **Network adjacency.** *"Best practice is that Pekko remoting nodes should only be accessible from the adjacent network."* *(documented — `remote-security.md`)* - **PKI scope.** Where TLS is used, every certificate issued by the same internal PKI tree is equivalent: *"there is still a risk that an attacker can gain access to a valid certificate by compromising any node with certificates issued by the same internal PKI tree."* *(documented — `remote-security.md`)* -- **Clock.** The failure detector and gossip convergence depend on reasonably-behaved local clocks. Pekko does not defend against adversarial clock manipulation on a cluster node. *(inferred — §14 Q12)* +- **Clock.** Failure detection and gossip do **not** depend on other nodes' clocks: the failure detector reads only the local clock, and gossip ordering uses logical `VectorClock` counters. The exception is `LWWRegister`/`LWWMap`, which merge by wall-clock timestamp and therefore assume reasonably synchronised clocks across writers *(documented — `LWWRegister` scaladoc)*. Pekko does not defend against adversarial clock manipulation on a cluster node *(maintainer — §14 Q12; see Q9)*. - **Entropy.** On Linux with SHA1PRNG, the docs recommend `-Djava.security.egd=file:/dev/urandom` to avoid blocking, noting it *"is NOT as secure because it reuses the seed."* *(documented — `remote-security.md`)* ### What Pekko does not do to its host @@ -310,7 +310,7 @@ Feed this section to scanners and AI triage as a suppression list. Each states a **proposed answer**. Confirming or correcting is enough; no need to write prose. -Q1 to Q11 are **answered**, retained in place so that cross-references elsewhere in this document continue to resolve. Q12 and Q13 remain open. +Q1 to Q12 are **answered**, retained in place so that cross-references elsewhere in this document continue to resolve. Q13 remains open. **Q1 — The plaintext transport default. ANSWERED *(maintainer)*.** `transport` ships `tcp`, so a stock cluster has no peer authentication. **Answer:** the default is a compatibility choice under §5b, not a security claim. Reports route as follows: @@ -376,9 +376,14 @@ The other two link to this document rather than restating it, so a change in sco - Out of the adversary model: an attacker with code execution in the embedding JVM, a hostile classpath, and side-channel observers — Pekko makes no timing or memory-access guarantees of its own. TLS is delegated to the JDK's JSSE, whose own guarantees are unaffected by this disclaimer. - **In** the adversary model: attacker-influenced *message content* arriving by the ordinary path, since that is how application data travels. This covers defects in **Pekko's own handling** of that content — a serializer, codec or framing defect reachable from a well-formed message is `VALID` per §5b.4. It does not extend to the application's interpretation of the content, which is the application's responsibility per §6. -**Q12 — Clock assumptions.** The failure detector and gossip convergence depend on -local clocks. *Proposed:* Pekko makes no claim against adversarial clock manipulation on -a cluster member — consistent with §7, since such a member is trusted anyway. Confirm? +**Q12 — Clock assumptions. ANSWERED.** **Pekko has no code that validates or cross-checks another node's clock**, and almost nothing that reads one. Verified: + +- **Failure detection uses the local clock only.** `PhiAccrualFailureDetector` records arrival times through its own `clock()` (`remote/.../FailureDetector.scala:60`, used at `PhiAccrualFailureDetector.scala:144` and `:188`). No remote timestamp enters it, so a peer misreporting its clock cannot influence reachability. +- **The heartbeat timestamp is the sender's own, round-tripped.** `Heartbeat` carries `System.nanoTime()` taken by the sender (`cluster/.../ClusterHeartbeat.scala:130`); the receiver echoes it back verbatim in `HeartbeatRsp` (`:58`); the original sender subtracts it from its own clock (`:251`). No node ever interprets another node's clock value, and the result feeds only a debug log guarded by `verboseHeartbeat` — a peer echoing a false value changes nothing else. +- **Gossip ordering is logical, not wall-clock.** `VectorClock` is a per-node counter incremented on update (`cluster/.../VectorClock.scala:90-92`). +- **One exception, and it is documented.** `LWWRegister` and `LWWMap` in `distributed-data` default to `System.currentTimeMillis()` and merge by highest timestamp, so they *do* compare wall-clock values written on different nodes. The class states the constraint: *"Merge takes the register with highest timestamp. Note that this relies on synchronized clocks. `LWWRegister` should only be used when the choice of value is not important for concurrent updates occurring within the clock skew"* *(documented — `LWWRegister` scaladoc)*. A custom `Clock`, or a single-writer pattern such as a Cluster Singleton, is the documented alternative. + +**Disposition.** Pekko makes no claim against adversarial clock manipulation on a cluster member: such a member is misbehaving, and Q9 places it out of model. A report that LWW data diverges under clock skew is `BY-DESIGN: property-disclaimed` — the constraint is stated on the type itself. **Q13 — Classic remoting's place in the supported surface.** Classic remoting is deprecated but still shipped and still CI-gated (the "Pekko Classic Remoting Tests" job). From b577f7d7cc8b26543da51a2b820d6bdbcaa806f7 Mon Sep 17 00:00:00 2001 From: PJ Fanning Date: Thu, 27 Aug 2026 16:18:30 +0100 Subject: [PATCH 16/23] docs: answer Q13 - classic remoting is still supported MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ### Motivation Q13 was the last open question. Classic remoting is deprecated but still shipped and still CI-gated, and deprecation is not desupport. ### Modification - **§14 Q13 - answered.** Security reports against classic remoting are accepted on the same terms as artery: configuration is the operator's per §5b, and implementation bugs will be looked at per §5b.4. Its §5a settings follow the same rulings as their artery equivalents. - **§2** - the remoting row names classic remoting alongside artery, rather than describing the family as artery only. - **§14** - retitled, since every question now carries a ruling. The section keeps the questions in place so the reasoning behind each answer stays visible and cross-references continue to resolve. ### Result All thirteen questions are answered. Provenance moves to 43 documented / 48 maintainer / 3 inferred, the remaining three being the residual module in/out split noted in §2 and Q6. ### Tests Not run - docs only ### References Refs #3478 --- THREAT_MODEL.md | 23 +++++++++-------------- 1 file changed, 9 insertions(+), 14 deletions(-) diff --git a/THREAT_MODEL.md b/THREAT_MODEL.md index ae68c29be76..b58a1a21404 100644 --- a/THREAT_MODEL.md +++ b/THREAT_MODEL.md @@ -16,7 +16,7 @@ *(maintainer)* — stated by a Pekko maintainer in review of this document. *(inferred)* — reasoned from code or config defaults, **not yet confirmed**; each has a matching question in §14. -**Draft confidence:** 43 documented / 44 maintainer / 3 inferred. The documented share is unusually high because Pekko's own remoting and serialization docs already state much of the trust model explicitly. The §5a default rulings — previously the largest inferred block — are now answered by the §5b posture statement and §14 Q1 to Q5. What remains inferred is the residual module in/out split (§14 Q6). +**Draft confidence:** 43 documented / 48 maintainer / 3 inferred. The documented share is unusually high because Pekko's own remoting and serialization docs already state much of the trust model explicitly. The §5a default rulings — previously the largest inferred block — are now answered by the §5b posture statement and §14 Q1 to Q5. What remains inferred is the residual module in/out split (§14 Q6). ## §1 Overview @@ -40,7 +40,7 @@ Three caller roles matter, and they are not equally trusted: | --- | --- | --- | --- | --- | | Actor core | `actor`, `actor-typed`, `slf4j`, `coordination` | `ActorSystem`, `ActorRef` | no | **yes** | | Classic IO | `actor` (`org.apache.pekko.io`) | `IO(Tcp)`, `IO(Udp)`, `IO(Dns)` | **network — if the application binds** | **yes** | -| Remoting | `remote` | Artery transport (`tcp` / `tls-tcp` / `aeron-udp`) | **network** | **yes — primary surface** | +| Remoting | `remote` | Artery transport (`tcp` / `tls-tcp` / `aeron-udp`); classic remoting (netty), deprecated but supported *(maintainer — §14 Q13)* | **network** | **yes — primary surface** | | Cluster | `cluster`, `cluster-typed`, `cluster-tools`, `cluster-sharding*`, `cluster-metrics` | gossip, membership, sharding | **network** (via remoting) | **yes** | | Replicated data | `distributed-data` | CRDT replication | **network** (via remoting) | **yes** | | Serialization | `serialization-jackson`, `serialization-jackson3` | `Serializer` SPI | deserializes network bytes | **yes — critical** | @@ -133,9 +133,9 @@ Pekko's security posture is set almost entirely by configuration. **Every row be | `pekko.remote.artery.untrusted-mode` | `off` | On, blocks inbound system messages, `PossiblyHarmful` messages, remote deployment, remote DeathWatch, and actor selections outside `trusted-selection-paths` *(documented)* | Hardening; adoption is the operator's call — §5b, §14 Q4 *(maintainer)* | | `pekko.remote.artery.trusted-selection-paths` | `[]` | Allow-list of actor paths that may receive selections under untrusted mode *(documented)* | Follows Q4 | | `pekko.remote.deployment.enable-allow-list` | `off` | On, restricts which actor classes a peer may remote-deploy *(documented — `remoting.md`)* | Hardening; adoption is the operator's call — §5b, §14 Q4 *(maintainer)* | -| `pekko.remote.classic.untrusted-mode` | `off` | Classic-remoting equivalent of the artery flag *(reference.conf:381)* | Follows Q4 | +| `pekko.remote.classic.untrusted-mode` | `off` | Classic-remoting equivalent of the artery flag *(reference.conf:381)* | Follows Q4 *(maintainer — §14 Q13)* | | `pekko.remote.classic.trusted-selection-paths` | `[]` | As above *(reference.conf:387)* | Follows Q4 | -| `pekko.remote.classic.netty.tcp.enable-ssl` | `false` | Classic's default transport is plaintext netty TCP *(reference.conf:582)* | Follows Q1 | +| `pekko.remote.classic.netty.tcp.enable-ssl` | `false` | Classic's default transport is plaintext netty TCP *(reference.conf:582)* | Follows Q1 *(maintainer — §14 Q13)* | | `pekko.actor.serialize-messages` / `serialize-creators` | `off` | Testing aids that force serialization round-trips. Not security controls. Docs: *"this is only intended for testing"* *(documented — `actor/src/main/resources/reference.conf`)* | Not a security knob | --- @@ -306,11 +306,9 @@ Feed this section to scanners and AI triage as a suppression list. --- -## §14 Open questions for the maintainers +## §14 Maintainer rulings (formerly open questions) -Each states a **proposed answer**. Confirming or correcting is enough; no need to write prose. - -Q1 to Q12 are **answered**, retained in place so that cross-references elsewhere in this document continue to resolve. Q13 remains open. +**All thirteen questions are answered.** They are retained in place, with their answers, so that cross-references elsewhere in this document continue to resolve and the reasoning behind each ruling stays visible. What remains inferred is noted in §2 and Q6: the residual module in/out split. **Q1 — The plaintext transport default. ANSWERED *(maintainer)*.** `transport` ships `tcp`, so a stock cluster has no peer authentication. **Answer:** the default is a compatibility choice under §5b, not a security claim. Reports route as follows: @@ -385,12 +383,9 @@ The other two link to this document rather than restating it, so a change in sco **Disposition.** Pekko makes no claim against adversarial clock manipulation on a cluster member: such a member is misbehaving, and Q9 places it out of model. A report that LWW data diverges under clock skew is `BY-DESIGN: property-disclaimed` — the constraint is stated on the type itself. -**Q13 — Classic remoting's place in the supported surface.** Classic remoting is -deprecated but still shipped and still CI-gated (the "Pekko Classic Remoting Tests" job). -It carries its own `untrusted-mode`, `trusted-selection-paths` and netty SSL settings, -now listed in §5a. *Proposed:* deprecation is not desupport, so classic stays **in model** -and its knobs follow the same Q1/Q4 rulings as their artery equivalents. Confirm — or is -classic remoting `OUT-OF-MODEL: unsupported-component` per §3? +**Q13 — Classic remoting's place in the supported surface. ANSWERED *(maintainer)*.** **Answer:** classic remoting is still **supported**. Deprecation is not desupport: security reports against it are accepted on exactly the same terms as artery. Configuration is the operator's, per §5b — its defaults are compatibility choices and a request to tighten one is a change request. Bugs in the implementation will be looked at, per §5b.4. + +Its settings in §5a therefore follow the same rulings as their artery equivalents: `pekko.remote.classic.untrusted-mode` and `trusted-selection-paths` follow Q4, and `netty.tcp.enable-ssl` follows Q1. --- From 90d7eb448b4ac4493985a9860a1ddcb3114964e7 Mon Sep 17 00:00:00 2001 From: PJ Fanning Date: Thu, 27 Aug 2026 16:21:06 +0100 Subject: [PATCH 17/23] docs: make SECURITY.md canonical for the reporting policy ### Motivation Q10 recorded three documents each canonical for something, and noted that the reporting wording was duplicated between `SECURITY.md` and the documentation site's security index, which had already drifted apart once. ### Modification `SECURITY.md` is now the strongest and canonical statement of the reporting policy. Other documents link to it rather than restating it, and link to the threat model for scope. Where another document carries a reporting statement that `SECURITY.md` does not, it is referred to the maintainers to decide whether to promote or drop it, rather than being migrated silently. ### Result The reporting policy has one owner. The documentation-site page keeps announcements and its index of security material. ### Tests Not run - docs only ### References Refs #3478 --- THREAT_MODEL.md | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/THREAT_MODEL.md b/THREAT_MODEL.md index b58a1a21404..507a3e5106c 100644 --- a/THREAT_MODEL.md +++ b/THREAT_MODEL.md @@ -360,13 +360,11 @@ Consequently a finding whose precondition is "a cluster member misbehaves" is `O | Document | Canonical for | Reached by | | --- | --- | --- | -| `docs/src/main/paradox/security/index.md` | Security announcements; the reporting process as published | Readers of the documentation site | -| [`SECURITY.md`](SECURITY.md) | The reporting policy as GitHub presents it | Anyone arriving via the repository | +| [`SECURITY.md`](SECURITY.md) | **The reporting policy.** The strongest and canonical statement of how to report and what the project undertakes | Anyone arriving via the repository, and every other document | | `THREAT_MODEL.md` (this document) | **Scope** — what is and is not a vulnerability, and how a report is triaged | Reporters, triagers, scanning tools | +| `docs/src/main/paradox/security/index.md` | Security announcements, and the documentation-site index of security material | Readers of the documentation site | -The other two link to this document rather than restating it, so a change in scope is made in one place. Neither attempts to state scope itself. - -**Maintenance note.** The reporting wording is currently duplicated between `SECURITY.md` and `security/index.md`, and the two have drifted apart once already. Until one is reduced to a pointer to the other, a change to either must be made to both. +Every other document **links** to `SECURITY.md` for the reporting policy and to this document for scope, rather than restating either. A change to the policy is therefore made in one place. Where another document currently carries a reporting statement that `SECURITY.md` does not, that statement is referred to the maintainers for a decision before it is either promoted into `SECURITY.md` or dropped — it is not silently migrated. **Q11 — The §3/§7 boundary non-goals. ANSWERED *(maintainer)*.** Confirmed: From d8a0d3beb948bfdf5652f8a757e7193fc5cef033 Mon Sep 17 00:00:00 2001 From: PJ Fanning Date: Thu, 27 Aug 2026 16:21:57 +0100 Subject: [PATCH 18/23] docs: cite the paradox docs for the LWW clock requirement MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ### Motivation Q12 and §5 cited only the `LWWRegister` scaladoc for the synchronised-clock requirement. The published documentation states it more fully, in the section on `LWWMap` and again under `LWWRegister`, along with the custom `Clock` and `reverseClock` alternatives. ### Modification Both citations now name `typed/distributed-data.md` alongside the scaladoc. ### Result The citation points where a reader would look first. ### Tests Not run - docs only ### References Refs #3478 --- THREAT_MODEL.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/THREAT_MODEL.md b/THREAT_MODEL.md index 507a3e5106c..386e6d4b530 100644 --- a/THREAT_MODEL.md +++ b/THREAT_MODEL.md @@ -105,7 +105,7 @@ A finding must meet its family's precondition to be in-model: - **Runtime.** A conformant JVM. Pekko does not defend against a hostile JVM, a hostile classpath, or an attacker with local code execution in the same process. *(maintainer — §14 Q11)* - **Network adjacency.** *"Best practice is that Pekko remoting nodes should only be accessible from the adjacent network."* *(documented — `remote-security.md`)* - **PKI scope.** Where TLS is used, every certificate issued by the same internal PKI tree is equivalent: *"there is still a risk that an attacker can gain access to a valid certificate by compromising any node with certificates issued by the same internal PKI tree."* *(documented — `remote-security.md`)* -- **Clock.** Failure detection and gossip do **not** depend on other nodes' clocks: the failure detector reads only the local clock, and gossip ordering uses logical `VectorClock` counters. The exception is `LWWRegister`/`LWWMap`, which merge by wall-clock timestamp and therefore assume reasonably synchronised clocks across writers *(documented — `LWWRegister` scaladoc)*. Pekko does not defend against adversarial clock manipulation on a cluster node *(maintainer — §14 Q12; see Q9)*. +- **Clock.** Failure detection and gossip do **not** depend on other nodes' clocks: the failure detector reads only the local clock, and gossip ordering uses logical `VectorClock` counters. The exception is `LWWRegister`/`LWWMap`, which merge by wall-clock timestamp and therefore assume reasonably synchronised clocks across writers *(documented — `typed/distributed-data.md`, and the `LWWRegister` scaladoc)*. Pekko does not defend against adversarial clock manipulation on a cluster node *(maintainer — §14 Q12; see Q9)*. - **Entropy.** On Linux with SHA1PRNG, the docs recommend `-Djava.security.egd=file:/dev/urandom` to avoid blocking, noting it *"is NOT as secure because it reuses the seed."* *(documented — `remote-security.md`)* ### What Pekko does not do to its host @@ -377,7 +377,7 @@ Every other document **links** to `SECURITY.md` for the reporting policy and to - **Failure detection uses the local clock only.** `PhiAccrualFailureDetector` records arrival times through its own `clock()` (`remote/.../FailureDetector.scala:60`, used at `PhiAccrualFailureDetector.scala:144` and `:188`). No remote timestamp enters it, so a peer misreporting its clock cannot influence reachability. - **The heartbeat timestamp is the sender's own, round-tripped.** `Heartbeat` carries `System.nanoTime()` taken by the sender (`cluster/.../ClusterHeartbeat.scala:130`); the receiver echoes it back verbatim in `HeartbeatRsp` (`:58`); the original sender subtracts it from its own clock (`:251`). No node ever interprets another node's clock value, and the result feeds only a debug log guarded by `verboseHeartbeat` — a peer echoing a false value changes nothing else. - **Gossip ordering is logical, not wall-clock.** `VectorClock` is a per-node counter incremented on update (`cluster/.../VectorClock.scala:90-92`). -- **One exception, and it is documented.** `LWWRegister` and `LWWMap` in `distributed-data` default to `System.currentTimeMillis()` and merge by highest timestamp, so they *do* compare wall-clock values written on different nodes. The class states the constraint: *"Merge takes the register with highest timestamp. Note that this relies on synchronized clocks. `LWWRegister` should only be used when the choice of value is not important for concurrent updates occurring within the clock skew"* *(documented — `LWWRegister` scaladoc)*. A custom `Clock`, or a single-writer pattern such as a Cluster Singleton, is the documented alternative. +- **One exception, and it is documented.** `LWWRegister` and `LWWMap` in `distributed-data` default to `System.currentTimeMillis()` and merge by highest timestamp, so they *do* compare wall-clock values written on different nodes. The class states the constraint: *"Merge takes the register with highest timestamp. Note that this relies on synchronized clocks. `LWWRegister` should only be used when the choice of value is not important for concurrent updates occurring within the clock skew"* *(documented — `typed/distributed-data.md`, and the `LWWRegister` scaladoc)*. A custom `Clock`, or a single-writer pattern such as a Cluster Singleton, is the documented alternative. **Disposition.** Pekko makes no claim against adversarial clock manipulation on a cluster member: such a member is misbehaving, and Q9 places it out of model. A report that LWW data diverges under clock skew is `BY-DESIGN: property-disclaimed` — the constraint is stated on the type itself. From 945bb49849b2e9740fd70aa626affd131e149e7a Mon Sep 17 00:00:00 2001 From: PJ Fanning Date: Thu, 27 Aug 2026 16:22:28 +0100 Subject: [PATCH 19/23] =?UTF-8?q?docs:=20complete=20the=20=C2=A710=20opera?= =?UTF-8?q?tor=20contract?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ### Motivation §5b makes §10 the operator's half of the contract: Pekko states what it does not provide, and states what it expects instead. That only holds if §10 is complete. Auditing it against `reference.conf` and the remoting documentation found four obligations the model relies on but never stated, and two existing items that were narrower than the code. ### Modification Corrected: - **10.4** - the `SECURITY` marker is not only the Java serializer's. The TLS providers use it to report a disabled hostname verification and surviving default keystore passwords, so the item now covers the marker rather than one emitter. - **10.8** - environment substitution was only half the password obligation. Both SSL blocks ship `changeme` as the keystore, key and truststore password (`reference.conf:701`, `:1205`), and Pekko warns under the `SECURITY` marker if those survive into a running system. Added: - **10.10** - protect the key material itself. §6 trusts keystores, truststores and PEM material as operator-supplied; §10.5 scoped what a certificate grants but nothing stated the file-level counterpart. - **10.11** - keep message-content logging off. `log-received-messages`, `log-sent-messages`, `pekko.actor.debug.receive` and `log-config-on-start` all default to `off`; turning any on moves payloads, or the secrets that §10.8 places in the environment, into the logging system. - **10.12** - transport encryption exists only on `tls-tcp`. On `tcp` and `aeron-udp` network isolation is not one control among several, it is the only one. - **10.13** - trust, or secure, the discovery mechanism. §6 marks resolver responses as potentially attacker-influenced; §10 asked nothing of the operator about it. ### Result Every input §6 marks as trusted, and every §9 property Pekko disclaims, now has a matching operator responsibility in §10. ### Tests Not run - docs only ### References Refs #3478 --- THREAT_MODEL.md | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/THREAT_MODEL.md b/THREAT_MODEL.md index 386e6d4b530..cf9185e1978 100644 --- a/THREAT_MODEL.md +++ b/THREAT_MODEL.md @@ -246,12 +246,16 @@ The operator or embedding application must: 1. **Keep remoting off untrusted networks.** Firewall the remoting port to the adjacent network. This is the assumption the whole model rests on *(documented)*. 2. **Enable `tls-tcp` if the network is not sufficiently trusted**, and set `hostname-verification = on` unless hostnames are genuinely dynamic *(documented)*. 3. **Leave `allow-java-serialization = off`.** If it must be enabled for legacy compatibility, treat the deployment as having no deserialization protection *(documented)*, and **maintain your own gadget-chain allow list** via `-Djdk.serialFilter` or a process-wide `ObjectInputFilter`. Pekko supplies no filter of its own, and findings that require the flag to be on are out of model *(maintainer — §14 Q2)*. -4. **Treat `SECURITY`-marked log entries from the Java serializer as attack indicators**, not noise *(documented)*. +4. **Treat `SECURITY`-marked log entries as security signals**, not noise. The disabled Java serializer marks rejected attempts this way *(documented)*; the TLS providers use the same marker to report a disabled hostname verification and the use of default keystore passwords *(`ConfigSSLEngineProvider`)*. 5. **Scope the PKI tree to the cluster.** Any certificate it issues is cluster access *(documented)*. 6. **Consider `untrusted-mode = on` and `enable-allow-list = on`** where peers are less than fully trusted — understanding both are hardening, not boundaries. 7. **Never place mutually-distrusting tenants in one cluster** *(documented)*. -8. **Supply passwords by environment substitution, not literals in config files** *(documented)*. +8. **Supply passwords by environment substitution, not literals in config files** *(documented)*, and **replace the shipped defaults**. Both SSL blocks ship `key-store-password`, `key-password` and `trust-store-password` as `"changeme"` (`reference.conf:701`, `:1205`); Pekko warns under the `SECURITY` marker if they survive into a running system. 9. **Secure the persistence store.** Pekko trusts journal and snapshot contents on replay and does not validate them as potentially hostile, so access control and integrity for the database or store are the administrator's responsibility *(maintainer — §14 Q5)*. +10. **Protect the key material itself.** §10.5 scopes what a certificate grants; this is the file-level counterpart. Keystores, truststores and PEM material are operator-supplied and trusted by §6 — Pekko validates neither their provenance nor their permissions. +11. **Keep message-content logging off in production.** `log-received-messages` and `log-sent-messages` (`reference.conf:862`, `:866`, and `:393`, `:397` for classic) and `pekko.actor.debug.receive` (`actor/.../reference.conf:787`) all write message contents to the log, and `pekko.log-config-on-start` (`:50`) writes the resolved configuration — which, per §10.8, is where secrets pulled in by environment substitution end up. All four default to `off`; turning any on moves payloads or credentials into the logging system, whose protection is the operator's. +12. **Understand that transport encryption exists only on `tls-tcp`.** Neither `tcp` nor `aeron-udp` offers an encryption option *(documented — `remote-security.md`)*, so on those transports network isolation is not one control among several, it is the only one. +13. **Trust, or secure, the discovery mechanism.** `discovery` accepts whatever its configured resolver returns (§6); Pekko does not authenticate DNS or service-registry responses. --- From 8e98b3b865130363d3b3770332c314ed70f63fbb Mon Sep 17 00:00:00 2001 From: PJ Fanning Date: Thu, 27 Aug 2026 17:21:13 +0100 Subject: [PATCH 20/23] docs: narrow Q8 - CPU cost is not in itself a security concern MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ### Motivation Q8 was recorded as "super-linear in message size is a bug" and routed to `VALID`, which made a whole class of CPU reports security findings. That was wider than intended, in two ways. First, it conflated "a bug" with "a vulnerability". §5b.4 already provides for defects Pekko fixes without their being security issues, and a performance defect belongs there. Second, the trigger did not match the intent. "Super-linear in message size" fires on an ordinary message that is merely large, whereas the concern is an input crafted so that cost explodes disproportionately to it. ### Modification - **§14 Q8** - CPU cost is not in itself a security concern; a report of extra work or degraded throughput is a performance issue. The exception is disproportionate cost, where work grows super-linearly in the size or structure of a single input. The test is the cost, not the appearance of the input: an input that looks out of the ordinary is the usual sign of such a path rather than a separate condition. Constant-factor overhead is never a finding, and volume from an associated peer stays out of model per §7. - **§6** - the size and rate note said the question was open. Frame-size limits and failure-detector bounds are recorded as tuning parameters, not security controls. - **§9** - the resource-exhaustion entry carries the ruling rather than deferring to it. ### Result Performance reports are triaged as performance, and only disproportionate cost reaches a security disposition. ### Tests Not run - docs only ### References Refs #3478 --- THREAT_MODEL.md | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/THREAT_MODEL.md b/THREAT_MODEL.md index cf9185e1978..14db1b93967 100644 --- a/THREAT_MODEL.md +++ b/THREAT_MODEL.md @@ -175,7 +175,7 @@ For a toolkit whose surface is a wire protocol, the useful table is keyed by **m | `stream.FileIO` | File contents at an application-supplied path | Depends on the path | App | | Config | `application.conf`, system properties | **No** — trusted, part of the deployment | Operator | -**Size and rate.** Artery imposes frame-size limits and the failure detector bounds how long an unresponsive peer is tolerated. Whether these are *security* controls or tuning parameters is §14 Q8. +**Size and rate.** Artery imposes frame-size limits and the failure detector bounds how long an unresponsive peer is tolerated. These are tuning parameters, not security controls *(maintainer — §14 Q8)*. --- @@ -233,7 +233,7 @@ These are the assumptions integrators most often bring with them, and each is wr ### Well-known attack classes left to the caller - **JVM deserialization gadget chains** — mitigated by P1 only so long as Java serialization stays off, and only for payloads Pekko itself deserializes; application-level serializers are the application's problem. Pekko integrates **no** serialization filter: `JavaSerializer.fromBinary` performs an unfiltered `ObjectInputStream.readObject` (`actor/.../serialization/Serializer.scala`). An operator who enables Java serialization must supply the allow list themselves through the JVM — `-Djdk.serialFilter` or a process-wide `ObjectInputFilter` — and owns that entirely *(maintainer — §14 Q2)*. -- **Resource-exhaustion via message volume or size** — see §14 Q8. +- **Resource-exhaustion via message volume or size** — CPU cost is not in itself a security concern, and volume from an associated peer is out of model. Only disproportionate cost, where work explodes relative to a single input, is a defect. See §14 Q8. - **DNS / service-discovery spoofing** — `discovery` trusts the resolver it is configured with. - **Storage-layer tampering** on persistence journals and snapshot stores — the store is trusted, and securing it belongs to whoever administers it *(maintainer — §14 Q5)*. @@ -350,11 +350,14 @@ This is a trust statement about the **store**, not a licence for the plugin SPI: - **stdout/stderr — *(maintainer)*.** Logging goes to the configured logger. `StandardOutLogger` prints to stdout during early startup, bounded by `pekko.stdout-loglevel` (default `WARNING`), disclosed in §5. - **Process-global state — *(maintainer)*.** Not mutated at initialization: no `System.setProperty`, `Locale.setDefault` or `TimeZone.setDefault`. -**Q8 — Resource guarantees. ANSWERED *(maintainer)*.** **Answer:** **super-linear in message size is a bug; constant-factor is not.** Memory or CPU that grows super-linearly in the size of an inbound message is a defect and is `VALID`; a constant-factor overhead proportional to the message is expected and is not. +**Q8 — Resource guarantees. ANSWERED *(maintainer)*.** **CPU cost is not in itself a security concern.** A report that Pekko does more work than a reporter expected, or that load from a peer degrades throughput, is a performance issue and is handled as one — not a vulnerability. Constant-factor overhead proportional to a message is expected and is never a finding. -Two notes on applying this line: -- Artery already bounds message size by configuration — `maximum-frame-size` defaults to 256 KiB and `maximum-large-frame-size` to 2 MiB (`remote/src/main/resources/reference.conf`) — so the input to the rule is bounded on the remoting path. -- The rule is stated in terms of **size**. Exhaustion driven by message **volume** from an associated peer is not covered by it and remains subject to §7, under which such a peer is trusted. +The exception is an input that is **completely out of the ordinary**: one crafted so that cost explodes disproportionately to it — an algorithmic-complexity path where work grows super-linearly in the size or structure of a single message. That is a defect per §5b.4, and where it is reachable from an adversary §7 admits, it is `VALID`. + +The test is **disproportionate cost**, not the appearance of the input; an input that looks out of the ordinary is the usual sign of such a path rather than a separate condition to meet. Two notes on applying it: + +- Artery already bounds message size by configuration — `maximum-frame-size` defaults to 256 KiB and `maximum-large-frame-size` to 2 MiB (`remote/src/main/resources/reference.conf`) — so the input is bounded on the remoting path. +- Exhaustion driven by message **volume** from an associated peer is not covered, and remains subject to §7, under which such a peer is trusted. **Q9 — Byzantine generalisation. ANSWERED *(maintainer)*.** **Answer:** it holds for all of them. No subsystem — cluster membership, sharding, singleton or `distributed-data` — claims resilience against a misbehaving member, and Pekko has no guarantee of being able to recognise a compromised node at all. Failure detection is heartbeat-based and answers "is this member responding?", not "is this member honest". From 3c50f8f112d73708cf5049a386face6f1f2f341f Mon Sep 17 00:00:00 2001 From: Jarek Potiuk Date: Thu, 27 Aug 2026 21:57:16 +0200 Subject: [PATCH 21/23] docs: promote the threat model out of draft status MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ### Motivation The header still read "DRAFT - awaiting Pekko PMC review. Not yet maintainer-ratified." That stopped being true once the PMC chair answered all thirteen open questions: 48 of the document's claims now carry (maintainer) provenance. A triager citing §9 to close a report should not be citing a document that describes itself as unratified. ### Modification - Status: replaced the draft banner with "Reviewed by the Apache Pekko PMC, 2026-08-27", pointing at §14 for the rulings and naming the one item that is still open (the residual module in/out split, §2 and Q6). - Renamed "Draft confidence" to "Provenance mix"; the counts and the explanation beneath it were already current and are unchanged. No claim, ruling, citation or disposition is altered. ### Result The document no longer disclaims its own authority. The single remaining inferred item stays visible in the header rather than being smoothed over. ### Tests Not run - docs only ### References Refs #3478 --- THREAT_MODEL.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/THREAT_MODEL.md b/THREAT_MODEL.md index 14db1b93967..c9cc77f179d 100644 --- a/THREAT_MODEL.md +++ b/THREAT_MODEL.md @@ -1,6 +1,6 @@ # Apache Pekko — Threat Model -**Status:** DRAFT — awaiting Pekko PMC review. Not yet maintainer-ratified. +**Status:** Reviewed by the Apache Pekko PMC, 2026-08-27. The thirteen questions this document was drafted around are answered in §14 and carry *(maintainer)* provenance; the residual module in/out split noted in §2 and Q6 is the one open item. | | | | --- | --- | @@ -16,7 +16,7 @@ *(maintainer)* — stated by a Pekko maintainer in review of this document. *(inferred)* — reasoned from code or config defaults, **not yet confirmed**; each has a matching question in §14. -**Draft confidence:** 43 documented / 48 maintainer / 3 inferred. The documented share is unusually high because Pekko's own remoting and serialization docs already state much of the trust model explicitly. The §5a default rulings — previously the largest inferred block — are now answered by the §5b posture statement and §14 Q1 to Q5. What remains inferred is the residual module in/out split (§14 Q6). +**Provenance mix:** 43 documented / 48 maintainer / 3 inferred. The documented share is unusually high because Pekko's own remoting and serialization docs already state much of the trust model explicitly. The §5a default rulings — previously the largest inferred block — are now answered by the §5b posture statement and §14 Q1 to Q5. What remains inferred is the residual module in/out split (§14 Q6). ## §1 Overview From eed22da1d64349a9d69ddcc541bc228a402c2e1e Mon Sep 17 00:00:00 2001 From: Jarek Potiuk Date: Fri, 28 Aug 2026 22:43:03 +0200 Subject: [PATCH 22/23] docs: correct the shutdown-hook count - there are two, not one MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ### Motivation Review catch from @samueleresca on #3478. §5 and the Q7 answer both stated that Pekko registers one JVM shutdown hook, via CoordinatedShutdown. That is wrong for any node running Artery. ### Modification grep over the main sources returns two addShutdownHook sites: actor/.../actor/CoordinatedShutdown.scala:381 - the one already documented remote/.../artery/ArteryTransport.scala:392 - registered on transport start The second is gated on system.settings.JvmShutdownHooks, and actor/src/main/resources/reference.conf:98 ships jvm-shutdown-hooks = on, so both hooks are present under default configuration whenever remoting starts. §5 and Q7 now say two, name both sites, and record the config gate - the conditionality is the part an integrator needs, not the count on its own. The two other hits (cluster NodeChurnSpec, StressSpec) are multi-jvm test sources and out of model per §3. Also drops one sentence of editorialising from the provenance line, per review: it described how much of the v0 draft rested on inference, which stopped being true once the maintainer rulings landed. The counts are unchanged. ### Result The "what Pekko does not do to its host" inventory is accurate. This was the section flagged in the original draft as the highest-priority confirmation target precisely because negative claims are hard to cite - and it took a maintainer reading the code to find the gap. ### Tests Not run - docs only ### References Refs #3478 --- THREAT_MODEL.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/THREAT_MODEL.md b/THREAT_MODEL.md index c9cc77f179d..df1af878613 100644 --- a/THREAT_MODEL.md +++ b/THREAT_MODEL.md @@ -16,7 +16,7 @@ *(maintainer)* — stated by a Pekko maintainer in review of this document. *(inferred)* — reasoned from code or config defaults, **not yet confirmed**; each has a matching question in §14. -**Provenance mix:** 43 documented / 48 maintainer / 3 inferred. The documented share is unusually high because Pekko's own remoting and serialization docs already state much of the trust model explicitly. The §5a default rulings — previously the largest inferred block — are now answered by the §5b posture statement and §14 Q1 to Q5. What remains inferred is the residual module in/out split (§14 Q6). +**Provenance mix:** 43 documented / 48 maintainer / 3 inferred. The §5a default rulings — previously the largest inferred block — are now answered by the §5b posture statement and §14 Q1 to Q5. What remains inferred is the residual module in/out split (§14 Q6). ## §1 Overview @@ -112,7 +112,7 @@ A finding must meet its family's precondition to be in-model: These are negative claims, rarely written down anywhere. Each is confirmed and carries the citation, or the exception, behind it *(maintainer — §14 Q7)*: -- Installs no signal handlers and spawns no child processes — no `sun.misc.Signal`/`SignalHandler`, `Runtime.exec` or `ProcessBuilder` in the main sources. It does register **one JVM shutdown hook**, via `CoordinatedShutdown` (`actor/.../actor/CoordinatedShutdown.scala:381`). +- Installs no signal handlers and spawns no child processes — no `sun.misc.Signal`/`SignalHandler`, `Runtime.exec` or `ProcessBuilder` in the main sources. It does register **two JVM shutdown hooks**: one via `CoordinatedShutdown` (`actor/.../actor/CoordinatedShutdown.scala:381`), and one in remoting (`remote/.../artery/ArteryTransport.scala:392`) registered on transport start. The second is conditional on `pekko.jvm-shutdown-hooks`, which `actor/src/main/resources/reference.conf:98` ships as `on`, so both are present under default configuration on any node running Artery. *(maintainer — corrected in review, samueleresca 2026-08-27)* - Opens no listening socket of its own accord. Remoting binds when configured; `org.apache.pekko.io.Tcp`/`Udp` (in `pekko-actor`) and `stream.scaladsl.Tcp` (in `pekko-stream`) bind only on an explicit application call. Pekko never binds a port the application did not ask for. - **Reads environment variables during configuration startup, but never modifies them** *(maintainer — §14 Q7)*. Pekko itself calls neither `System.getenv` nor `sys.env` anywhere in the main sources; environment values reach it only through HOCON `${?VAR}` substitution when `ConfigFactory.load` resolves the configuration (`actor/.../actor/ActorSystem.scala:281`). That path is deliberate and documented — it is how the docs tell operators to supply passwords (§10.8). - Writes to logging via the configured logger. One exception: `StandardOutLogger` prints to stdout with `println` (`actor/.../event/Logging.scala:1024` onward). It carries the very early startup log, before the configured loggers are running, and is bounded by `pekko.stdout-loglevel`, which defaults to `WARNING`. @@ -346,7 +346,7 @@ This is a trust statement about the **store**, not a licence for the plugin SPI: - **Environment variables — *(maintainer)*.** Read during configuration startup, never modified. §5 states the mechanism. - **Sockets — answered.** The original claim was wrong and is corrected in §2, §5 and §6: `pekko-actor` ships `org.apache.pekko.io.Tcp`/`Udp` and `pekko-stream` ships `Tcp`, `TLS` and `FileIO`, all of which bind or open only on an explicit application call. -- **Signal handlers and child processes — *(maintainer)*.** None: no `sun.misc.Signal`, `SignalHandler`, `Runtime.exec` or `ProcessBuilder` in the main sources. One JVM shutdown hook is registered by `CoordinatedShutdown`, disclosed in §5; a shutdown hook is not a signal handler. +- **Signal handlers and child processes — *(maintainer)*.** None: no `sun.misc.Signal`, `SignalHandler`, `Runtime.exec` or `ProcessBuilder` in the main sources. **Two** JVM shutdown hooks are registered — `CoordinatedShutdown` and, when `pekko.jvm-shutdown-hooks` is on (the shipped default), Artery's transport — both disclosed in §5; a shutdown hook is not a signal handler. - **stdout/stderr — *(maintainer)*.** Logging goes to the configured logger. `StandardOutLogger` prints to stdout during early startup, bounded by `pekko.stdout-loglevel` (default `WARNING`), disclosed in §5. - **Process-global state — *(maintainer)*.** Not mutated at initialization: no `System.setProperty`, `Locale.setDefault` or `TimeZone.setDefault`. From 83fc684e8964e8ae82b29a39b2ee8120255572c3 Mon Sep 17 00:00:00 2001 From: PJ Fanning Date: Sun, 30 Aug 2026 20:06:45 +0100 Subject: [PATCH 23/23] docs: tighten threat model on association, node loss, DNS parsing and remote deployment MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Motivation: Reviewing the draft against the code surfaced four places where the wording let real findings fall between dispositions. Modification: - §7/Q1: define "associated" as authenticated. On tcp the handshake is unauthenticated, so completing it must not grant trusted-peer standing. - Q8: fatal errors in the shared Artery inbound stream escalate to ActorSystem termination, so disproportionate cost includes node loss. - §9: discovery trusts resolver answers, but parsing of a DNS packet is Pekko's own and is done before the transaction-id check. - P3/§11a: remote deployment is remote instantiation of classpath actor classes with peer-supplied constructor arguments, bounded by P4. Result: Each of the four cases now routes to exactly one §13 disposition. Tests: Not run - docs only References: Refs #3478, Refs #3490 --- THREAT_MODEL.md | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/THREAT_MODEL.md b/THREAT_MODEL.md index df1af878613..c880f8f36a4 100644 --- a/THREAT_MODEL.md +++ b/THREAT_MODEL.md @@ -184,11 +184,13 @@ For a toolkit whose surface is a wire protocol, the useful table is keyed by **m **In scope:** - **An unassociated network attacker** who can reach the remoting port — in a deployment where the operator's isolation assumption has held, this attacker should not exist; where it does, the relevant question is whether they can achieve anything **before** association completes. Pre-association reachability is the sharpest in-model attack surface. *(maintainer — §14 Q1)* + + **"Associated" in this section means *authenticated*.** On the default `tcp` transport, association is a single unauthenticated `HandshakeReq` that the receiver accepts if it names the receiver's own address (`remote/.../artery/Handshake.scala`) — it establishes no identity and admits nothing about the sender. A host that completes that handshake is still the unassociated network attacker above; it does not acquire the trusted-peer standing that the exclusions below grant. Trusted-peer standing comes from the operator's admission decision — network isolation holding, or a certificate from the cluster's PKI on `tls-tcp` — not from the wire protocol. *(maintainer)* - **An attacker supplying message content** to an otherwise legitimate peer — e.g. data that originates at the application's own untrusted edge and is forwarded into an actor message. *(maintainer — §14 Q11)* **Explicitly out of scope:** -- **An associated peer behaving arbitrarily.** There is no Byzantine-peer model. The documentation is direct: *"you'll have to trust all cluster nodes the same in a Pekko cluster anyway"* *(documented)*, and *"as soon as an actor system can connect to another remotely, it may in principle send any possible message to any actor contained within that remote system"* *(documented)*. **A finding whose precondition is "a cluster member misbehaves" is out of model** — there is no honest-majority threshold to state, because the model has no notion of a dishonest member. This generalises to every cluster protocol — cluster membership, sharding, singleton and `distributed-data` alike: **Pekko makes no guarantee of being able to recognise a compromised node.** Failure detection is heartbeat-based (`remote/.../PhiAccrualFailureDetector.scala`), so it identifies members that stop responding, not members that respond dishonestly; a compromised node that keeps heartbeating is indistinguishable from a healthy one. *(maintainer — §14 Q9)* +- **An authenticated peer behaving arbitrarily.** There is no Byzantine-peer model. The documentation is direct: *"you'll have to trust all cluster nodes the same in a Pekko cluster anyway"* *(documented)*, and *"as soon as an actor system can connect to another remotely, it may in principle send any possible message to any actor contained within that remote system"* *(documented)*. **A finding whose precondition is "a cluster member misbehaves" is out of model** — there is no honest-majority threshold to state, because the model has no notion of a dishonest member. This generalises to every cluster protocol — cluster membership, sharding, singleton and `distributed-data` alike: **Pekko makes no guarantee of being able to recognise a compromised node.** Failure detection is heartbeat-based (`remote/.../PhiAccrualFailureDetector.scala`), so it identifies members that stop responding, not members that respond dishonestly; a compromised node that keeps heartbeating is indistinguishable from a healthy one. *(maintainer — §14 Q9)* - **An attacker holding any certificate from the cluster's PKI tree.** Documented as equivalent to cluster access *(documented)*. - **An attacker with code execution in the embedding JVM.** Already inside the trust boundary. - **Side-channel observers.** Pekko makes no timing or memory-access guarantees. *(maintainer — §14 Q11)* @@ -201,7 +203,7 @@ For a toolkit whose surface is a wire protocol, the useful table is keyed by **m | --- | --- | --- | --- | --- | | P1 | **Java serialization is disabled by default**; Pekko uses it for none of its own internal messages | A payload deserialized via Java serialization under default config | **Critical** — RCE class | *(documented — `serialization.md`)* | | P2 | The disabled Java serializer **logs rejected attempts** under the `SECURITY` marker, and those *"SHOULD be treated as potential attacks which the serializer prevented"* | Silent acceptance where rejection + log is expected | High | *(documented — `serialization.md`)* | -| P3 | **Remote deployment is not remote code loading.** The actor class must already be present on the target system | A peer causing a class absent from the target's classpath to execute | **Critical** | *(documented — `remoting.md`)* | +| P3 | **Remote deployment is not remote code loading.** The actor class must already be present on the target system. It **is** remote instantiation of any actor class on the target's classpath with peer-supplied constructor arguments (`remote/.../serialization/DaemonMsgCreateSerializer.scala`), bounded only by P4 when `enable-allow-list` is on *(maintainer)* | A peer causing a class absent from the target's classpath to execute | **Critical** | *(documented — `remoting.md`)* | | P4 | With `enable-allow-list = on`, only listed actor classes may be remote-deployed onto this node | An unlisted class deployed | High | *(documented — `remoting.md`)* | | P5 | With `untrusted-mode = on`, inbound system messages, `PossiblyHarmful` messages, remote deployment, remote DeathWatch and non-allow-listed actor selections are **dropped and logged** | Any of these taking effect despite the flag | High | *(documented — `remote-security.md`)* | | P6 | With `transport = tls-tcp`, TLS is applied and **mutual authentication is on by default** — the server side also requests and verifies the client's certificate | Association completing without peer certificate verification | **Critical** | *(documented — `remote-security.md`)* | @@ -234,7 +236,7 @@ These are the assumptions integrators most often bring with them, and each is wr - **JVM deserialization gadget chains** — mitigated by P1 only so long as Java serialization stays off, and only for payloads Pekko itself deserializes; application-level serializers are the application's problem. Pekko integrates **no** serialization filter: `JavaSerializer.fromBinary` performs an unfiltered `ObjectInputStream.readObject` (`actor/.../serialization/Serializer.scala`). An operator who enables Java serialization must supply the allow list themselves through the JVM — `-Djdk.serialFilter` or a process-wide `ObjectInputFilter` — and owns that entirely *(maintainer — §14 Q2)*. - **Resource-exhaustion via message volume or size** — CPU cost is not in itself a security concern, and volume from an associated peer is out of model. Only disproportionate cost, where work explodes relative to a single input, is a defect. See §14 Q8. -- **DNS / service-discovery spoofing** — `discovery` trusts the resolver it is configured with. +- **DNS / service-discovery spoofing** — `discovery` trusts the *answers* of the resolver it is configured with. That trust covers the content of a response, not its parsing: a DNS packet is unauthenticated network input, `pekko-actor`'s async resolver parses it (`actor/.../io/dns/internal`) before matching the transaction id, and a defect in that parsing is Pekko's own per §14 Q11. *(maintainer)* - **Storage-layer tampering** on persistence journals and snapshot stores — the store is trusted, and securing it belongs to whoever administers it *(maintainer — §14 Q5)*. --- @@ -274,7 +276,7 @@ The operator or embedding application must: Feed this section to scanners and AI triage as a suppression list. -- **"Remote deployment permits arbitrary class instantiation."** Remote deployment is *not* remote code loading — the class must already be on the target's classpath *(P3, documented)*. Absent a way to introduce a class, this is not RCE. +- **"Remote deployment permits arbitrary class instantiation."** Remote deployment is *not* remote code loading — the class must already be on the target's classpath *(P3, documented)*. Absent a way to introduce a class, this is not RCE. It is remote instantiation of classpath actor classes with peer-chosen arguments (P3); a report that names a specific classpath class whose construction has a harmful side effect is judged under §7 for the peer and P4 for the allow list, not as this non-finding. - **"Remoting transport defaults to plaintext."** Correct, and by design under the §4 network-isolation assumption. In-model only if the finding shows harm reachable **pre-association**; a request to change the default is `BY-DESIGN: default-configuration` per §5b. - **"`PoisonPill` can be sent remotely to shut down a system."** Documented behaviour, gated by `untrusted-mode` *(documented — `remote-security.md`)*. - **"`hostname-verification` is disabled by default."** Known and documented, with the trade-off spelled out for dynamic-hostname deployments, and warned at runtime under `LogMarker.Security`. A request to change the default is `BY-DESIGN: default-configuration` per §5b. @@ -319,6 +321,7 @@ Feed this section to scanners and AI triage as a suppression list. - "the default should be `tls-tcp`" → `BY-DESIGN: default-configuration`; §5b.3 invites the proposal on the development list. - "an unauthenticated peer can associate", assuming internet exposure → `BY-DESIGN: property-disclaimed`; §9 disclaims peer authentication by default. (The draft proposed `OUT-OF-MODEL: adversary-not-in-scope`, which does not fit: §13 defines that disposition as requiring an associated peer, a PKI-tree certificate, or in-JVM execution, and §7 lists the unassociated network attacker as **in scope**.) - harm reachable **pre-association** from an adjacent-network host → `VALID`. +- harm reachable from an adjacent-network host that has completed the **unauthenticated** `tcp` handshake but holds no operator-granted standing (§7: *associated* means *authenticated*) → `VALID`. Completing that handshake does not convert the attacker into the trusted peer §7 excludes; "pre-association" is read as "pre-authentication". *(maintainer)* **Q2 — `allow-java-serialization`. ANSWERED *(maintainer)*.** Enabling it is not recommended. **Answer:** any finding that requires `allow-java-serialization = on` to manifest is `OUT-OF-MODEL: non-default-build`, including gadget-chain deserialization. An operator who enables it takes on gadget-chain defence **entirely**: Pekko integrates no serialization filter, so the only lever is the JVM's own — `-Djdk.serialFilter`, or an `ObjectInputFilter` installed process-wide. Maintaining that allow list is the operator's responsibility, not Pekko's. @@ -357,7 +360,8 @@ The exception is an input that is **completely out of the ordinary**: one crafte The test is **disproportionate cost**, not the appearance of the input; an input that looks out of the ordinary is the usual sign of such a path rather than a separate condition to meet. Two notes on applying it: - Artery already bounds message size by configuration — `maximum-frame-size` defaults to 256 KiB and `maximum-large-frame-size` to 2 MiB (`remote/src/main/resources/reference.conf`) — so the input is bounded on the remoting path. -- Exhaustion driven by message **volume** from an associated peer is not covered, and remains subject to §7, under which such a peer is trusted. +- Exhaustion driven by message **volume** from an authenticated peer is not covered, and remains subject to §7, under which such a peer is trusted. +- **Disproportionate cost includes node loss.** Framing failures on one Artery TCP connection tear down only that connection, but the decoder and deserializer stages downstream of the `MergeHub` are shared by every inbound connection. They drop messages on `NonFatal` errors; a **fatal** JVM error there (`OutOfMemoryError`, `StackOverflowError`) fails the shared inbound stream, and `ArteryTransport.attachInboundStreamRestart` terminates the whole `ActorSystem` after `inbound-max-restarts` (default 5) failures within `inbound-restart-timeout` (default 5 seconds). A single input that provokes a fatal error on that path therefore costs the node, not the message, and is judged as disproportionate cost under this ruling. *(maintainer)* **Q9 — Byzantine generalisation. ANSWERED *(maintainer)*.** **Answer:** it holds for all of them. No subsystem — cluster membership, sharding, singleton or `distributed-data` — claims resilience against a misbehaving member, and Pekko has no guarantee of being able to recognise a compromised node at all. Failure detection is heartbeat-based and answers "is this member responding?", not "is this member honest".