Skip to content

Security Model

Gabor Galazzo edited this page Jul 25, 2026 · 1 revision

Security Model

Vulnerability reports go through SECURITY.md — please do not open a public issue for a security problem.

There are two distinct trust boundaries: the wire (bundles connecting to the bus, port 3030) and the HTTP surface (GUI and REST API, port 3000). They authenticate independently.


1. Wire authentication

Hello.authToken: String → HandshakeHandler → TokenValidator.validate(token). Two built-in implementations:

Implementation Behaviour
acceptAll() Default. Any bundle that can reach the port may register
sharedSecret(token) Constant-time comparison against a configured secret

Configured by:

evento.server.bus.auth-token=${EVENTO_SERVER_BUS_AUTH_TOKEN:}

Blank means accept-all. A failed check yields Reject(CODE_AUTH_FAILED), and the bundle's BundleClient.start() future fails with that reason — the client does not retry into a rejection, and it does not call System.exit; the caller decides.

Set EVENTO_SERVER_BUS_AUTH_TOKEN for any deployment that isn't a laptop. An unauthenticated bus port means anyone who can reach it can register handlers for your command and query types — and the broker routes by payload type string.


2. TLS

NettyTransportConfig.sslContext is nullable. When set, the pipeline prepends Netty's SslHandler. The same config record serves both ends — SslContextBuilder.forClient() versus forServer().

TLS is not on by default. The shared-secret token crosses the wire in the Hello frame, so without TLS it is exposed to anyone who can observe the network path. Treat TLS as required wherever the bus traverses an untrusted network.


3. Deserialization hardening

CBOR decoding is bounded by an explicit type whitelist. MessageTypeRegistry maps byte tags to specific classes under the sealed Message hierarchy: allowIfSubType(Message.class) plus explicit per-type registration. There is no "accept everything under this package" mode.

This is the defence against gadget-chain deserialization attacks, and it works because the hierarchy is sealed — the compiler enumerates the permitted set.

What is not a security control: FAIL_ON_UNKNOWN_PROPERTIES. All four codecs disable it deliberately for version tolerance. Skipping an unrecognised property on an already-whitelisted type instantiates nothing; the type bound is what matters. See Wire Protocol § 8 for the outage that strictness caused.

The server never deserializes business payloads at all. It routes on payloadType: String and carries payload: byte[] opaquely, so a malicious payload has no server-side class to instantiate — it can only reach a bundle that already declares a handler for that type.


4. HTTP surface

Authentication is HTTP Basic against a static Spring Boot in-memory user:

spring.security.user.name=${SPRING_SECURITY_USER_NAME:evento}
spring.security.user.password=${SPRING_SECURITY_USER_PASSWORD:secret}
spring.security.user.roles=WEB,ADMIN

WebConfig requires authentication on /api/** and the actuator endpoints, except health and info. It answers a plain 401 without a WWW-Authenticate header, so the browser does not raise its native credential dialog and the GUI's own login page handles the failure.

Per-endpoint enforcement is @Secured("ROLE_WEB") / @Secured("ROLE_ADMIN") on the controllers.

The default password is secret. Override both SPRING_SECURITY_USER_NAME and SPRING_SECURITY_USER_PASSWORD before the server is reachable by anyone else.

The former JWT stack — AuthFilter, AuthService, TokenRole, AuthController — was removed along with the explorative read-only pivot. There is no token endpoint any more.


5. Attack-surface notes

Surface Status
Deploy-by-upload / /spawn / /kill Removed in 2.0. The server no longer accepts JAR uploads or spawns processes
Business class loading server-side None by design
SQL injection in the command lock PgDistributedLock is hardened
Multipart upload Still present for artifacts — spring.servlet.multipart.max-file-size=200MB

6. Hardening checklist

  • EVENTO_SERVER_BUS_AUTH_TOKEN set to a real secret
  • TLS configured if the bus crosses an untrusted network
  • SPRING_SECURITY_USER_PASSWORD changed from secret
  • Port 3030 reachable only from your bundles, not the internet
  • Port 3000 behind whatever fronts your other internal tooling
  • Database credentials supplied by environment/secret manager, not baked into an image
  • Actuator not publicly exposed (show-details=when_authorized is only part of the story)

7. Supply chain

The repository runs CI, CodeQL and publishes an OpenSSF Scorecard. The Docker image pins its base images by digest, not just tag.


See also

Clone this wiki locally