This is an automated email from the ASF dual-hosted git repository.
pjfanning pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/pekko.git
The following commit(s) were added to refs/heads/main by this push:
new c9cba616eb Add a draft threat model and wire it for discoverability
(#3478)
c9cba616eb is described below
commit c9cba616ebcd700c266424ff5403735c735477ed
Author: Jarek Potiuk <[email protected]>
AuthorDate: Mon Sep 7 19:12:13 2026 +0200
Add a draft threat model and wire it for discoverability (#3478)
* Add a draft threat model and wire it for discoverability
### 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 [email protected], 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
* docs: correct scope and citations in the draft threat model
### 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 `[email protected]` 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
* Update SECURITY.md
* docs: drop the app-bound socket scope ruling from the threat model
### 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
* 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
* docs: record the maintainer security posture and answer Q1, Q3 and Q4
### 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
* docs: answer Q2 and record who owns gadget-chain defence
### 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
* docs: answer Q5 - the persistence store is trusted
### 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
* docs: answer the osgi half of Q6 - in model, but a removal candidate
### 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
* docs: scope the model to this repository and finish Q6
### 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
* docs: correct how findings in other Pekko repositories are routed
### 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
* docs: answer Q8, and Q7's environment-variable claim
### 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
* docs: answer Q9 - no compromised-node detection anywhere
### 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
* docs: answer Q7, Q10 and Q11
### 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
* docs: answer Q12 - no node reads another node's clock, except LWW
### 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
* docs: answer Q13 - classic remoting is still supported
### 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
* 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
* docs: cite the paradox docs for the LWW clock requirement
### 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
* docs: complete the §10 operator contract
### 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
* docs: narrow Q8 - CPU cost is not in itself a security concern
### 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
* docs: promote the threat model out of draft status
### 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
* docs: correct the shutdown-hook count - there are two, not one
### 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
* docs: tighten threat model on association, node loss, DNS parsing and
remote deployment
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
---------
Co-authored-by: PJ Fanning <[email protected]>
---
AGENTS.md | 6 +
SECURITY.md | 50 ++++
THREAT_MODEL.md | 424 ++++++++++++++++++++++++++++++++
docs/src/main/paradox/security/index.md | 6 +-
4 files changed, 485 insertions(+), 1 deletion(-)
diff --git a/AGENTS.md b/AGENTS.md
index 9e42d6b236..0f92d2b5c9 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -225,3 +225,9 @@ Fixes #1234, Refs #1234, or None - <short context>
- Never omit `References`.
- Use `Refs #...`, `Fixes #...`, or `None - <short context>`.
- 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 0000000000..e94b88f574
--- /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:
+
+ [email protected]
+
+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/[email protected]).
+
+## 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/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
new file mode 100644
index 0000000000..c880f8f36a
--- /dev/null
+++ b/THREAT_MODEL.md
@@ -0,0 +1,424 @@
+# Apache Pekko — Threat Model
+
+**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.
+
+| | |
+| --- | --- |
+| **Project** | Apache Pekko (core toolkit) |
+| **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`. |
+| **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.
+
+**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
+
+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 o [...]
+
+---
+
+## §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** |
+| 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`);
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** |
+| 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** *(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`
(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)*
+
+---
+
+## §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. *(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. *(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. *(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 [...]
+
+---
+
+## §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. 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.
+
+---
+
+## §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. *(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 — `typed/distributed-data.md`,
and the `LWWRegister` scaladoc)*. Pekko does not defend against adversarial
clock manipulation on a cluster node * [...]
+- **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. 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 **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` shi [...]
+- 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`.
+- Does not mutate process-global state at initialization — no
`System.setProperty`, `Locale.setDefault` or `TimeZone.setDefault` in the main
sources.
+
+---
+
+## §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 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)* |
+| `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 *(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
*(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 |
+
+---
+
+## §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.
+
+| 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 | **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 |
+| `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 |
+
+**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)*.
+
+---
+
+## §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. *(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 [...]
+- **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 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-maj [...]
+- **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)*
+
+---
+
+## §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. 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`)* |
+| 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.
+
+---
+
+## §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.
*(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, 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)*
+
+### 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`; **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.
+
+### 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 — `-Djd [...]
+- **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 *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)*.
+
+---
+
+## §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)*, 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 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)*, 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 cred [...]
+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.
+
+---
+
+## §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. 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- [...]
+- **"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.
+- **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, 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.
+
+---
+
+## §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, 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 |
+| `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 |
+
+---
+
+## §14 Maintainer rulings (formerly open questions)
+
+**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:
+- "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 [...]
+
+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 [...]
+
+**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`.
+
+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. 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). 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**. That is a different repository, not a different
security process: reports are made and triaged exact [...]
+- The rest of the in/out split shown in §2 remains the ASF Security team's
proposal *(inferred)*.
+
+**Q7 — The negative claims in §5. ANSWERED.**
+
+- **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. **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`.
+
+**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.
+
+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 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` [...]
+
+**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. ANSWERED *(maintainer)*.** Three documents carry security
information, each canonical for one thing:
+
+| Document | Canonical for | Reached by |
+| --- | --- | --- |
+| [`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 |
+
+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:
+
+- 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. 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" [...]
+
+**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. 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.
+
+---
+
+## §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 |
+| `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 dff04551ad..95da98c0b0 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
+`[email protected]` 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)
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]