Skip to content

Security: 10thfloor/aerolite

Security

SECURITY.md

Security

aerolite is pre-1.0 and not hardened for untrusted networks. Please read the exposure section below before you deploy it anywhere reachable.

Reporting a vulnerability

Report privately through GitHub's private vulnerability reporting: Security → Report a vulnerability.

Please don't open a public issue for anything exploitable. Include what you did, what happened, and the commit you tested — a curl or short script is worth more than a description.

Expect a first response within a week. There is no bounty; this is a solo-maintained project.

Only main is supported. There are no backports.

Known exposure — by design, for now

These are not vulnerabilities to report. They are the current design, and they are documented here so nobody discovers them the hard way in production.

The data plane is unauthenticated

The core registers builtin methods that any caller may invoke, over DDP (/websocket) or plain HTTP (POST /rpc), with no credentials:

collection.list   collection.find    collection.docs
collection.insert collection.update  collection.remove
collection.ensureIndex
durable.call
workflow.start    workflow.remove    workflow.status   workflow.list

So anyone who can reach the port can enumerate your collections, read every document in them, write and delete documents, invoke durable-object methods, and start or delete workflow runs — including internal collections such as _workflowRuns.

Three specific misconceptions worth naming:

  • Publications do not protect your data. They gate subscriptions. A client that skips sub and calls collection.find reads the underlying documents regardless of how the publication filters or projects them.
  • This is not Meteor's insecure package. That one can be removed, and allow/deny rules put in its place. aerolite has no equivalent hook yet.
  • You cannot write the authorization yourself, either. See below — the request path carries no caller identity, so a handler cannot tell who is calling it.

No caller identity reaches your code

This is the structural gap behind everything above, and it is easy to miss:

pub async fn dispatch_method(&self, method: &str, params: Vec<Value>) -> Result<Value, Error>

Method handlers are Fn(Arc<Server>, Vec<Value>). Publication handlers are Fn(Arc<Server>, Vec<Value>). Server plus params — no caller identity is passed to either. The DDP session has an id, but it is used for log lines and never reaches dispatch.

Two consequences:

  • An identity system alone would not close the data plane. There is nowhere in a handler to consult "who is calling", so authentication without the plumbing below buys you nothing.
  • Per-user publications are not possible today. In Meteor, scoping data to the caller is Meteor.publish('myDocs', function () { return Docs.find({owner: this.userId}) }), where this.userId is server-trusted. An aerolite publication sees only params, which the client supplies — so a client subscribing with someone else's id receives that user's documents. There is currently no way to write a correct per-user publication, however careful the app author is.

Other unauthenticated endpoints

Endpoint Exposure
GET /logs In-memory log buffer: method names, errors, and any values your app puts in log fields
GET /stats Collection names and document counts, workflow run counts, process metrics
POST /raft/vote, POST /raft/append, GET /raft/status Inter-node consensus. A caller who can reach these can inject Raft RPCs
POST /cluster/method Method forwarding to the leader

/worker is the exception: it is locked to a token minted per run under aerolite run, and to AEROLITE_WORKER_TOKEN under --no-worker when set.

Resource limits

No request-body or WebSocket message size limit is configured, so a single large frame can drive allocation on the server.

Deploying with this in mind

  • Keep the default loopback bind. 127.0.0.1:3000 is currently the main thing between a node and the open internet.
  • Put an authenticating reverse proxy in front of anything public, and let it terminate TLS. aerolite speaks plaintext HTTP/WS.
  • Keep /raft/* and /cluster/* on a private network. Consensus traffic is unauthenticated between nodes.
  • Don't log secrets. /logs is readable by anyone who reaches the port.
  • Treat the data as world-writable until an authorization layer exists.

What would change this

"Add auth" is not one feature here; it is a prerequisite refactor and then a feature, in this order:

  1. Thread a caller context through the request path — session → dispatch_method → handler, and the same for publications. This is the load-bearing step and a breaking SDK change: app.method((ctx, ...args) => …) and publications that can read ctx instead of trusting client params. Nothing else can be built correctly until this exists.
  2. Consult that context in the builtin methods, and give apps a way to disable the builtin collection writes outright so they can expose only their own methods.
  3. Then an identity system — accounts, sessions, tokens — becomes an ordinary feature on top, rather than something with nowhere to plug in.

Until at least (1) and (2) land, aerolite is best treated as a development and trusted-network system, and no amount of care in application code can make a publicly-bound node safe.

There aren't any published security advisories