Skip to content

v2.9.1: admin password, security fixes, working restore

Choose a tag to compare

@maxg10 maxg10 released this 22 Oct 21:02
· 12 commits to main since this release

A security and reliability release. Recommended for everyone, and especially
if people you do not fully trust can reach your MeshPulse: several of the fixes
below close holes that anyone on the network, or anyone who could name a node,
was able to use.

Optional admin password

MeshPulse trusts its LAN, so until now anyone who could open the map could also
install a plugin (which runs Python on your Pi), restore a backup, read the
channel keys, arm remote admin and transmit as your node.

Set a password in Config > 🔒 Security and the map, Stats and Messages stay
open to everyone, while everything that changes the system, reads its secrets or
goes out over the air asks for it first: plugins, settings, channels (including
reading the channel keys), backup and restore, clearing stats, favourites,
remote admin, sending messages, traceroute and position requests.

  • Only a PBKDF2-SHA256 hash is stored, in config.json (owner-only). The
    password never appears in the log.
  • A browser that logs in stays unlocked for 14 days. Changing the password or
    Sign out every browser ends every session, open ones included.
  • Five wrong passwords from one address within 10 minutes lock it out for 5
    minutes.
  • Forgot it? Remove admin_password_hash from config.json over SSH and
    restart the service.
  • Without a password nothing changes. It does not encrypt traffic: MeshPulse
    serves plain HTTP and WebSocket, so use a VPN or an authenticating reverse
    proxy with TLS before exposing it to the internet.

Security fixes

  • A node name could run JavaScript in your browser. Names come from the air,
    and the DM, traceroute and Admin buttons put them into an onclick attribute
    escaping only apostrophes. A 30-character long name was enough to run code in
    every open map. Those arguments are now JSON-encoded and HTML-escaped, and the
    DM list, favourites, channel name and plugin messages escape what they show.
  • Uninstalling a plugin could delete your home directory. uninstall_plugin
    removed whatever plugins/<id> resolved to, so an id like x/../../.. went
    far outside the plugins directory; a package with such an id did the same on
    install. Plugin ids must now be author/name and stay inside the plugins
    directory.
  • A crafted backup could write any file, including code. Restore now writes
    only what a backup contains: config.json, nodes.json, stats.db,
    plugins/enabled.json and plugins/<author>/<name>/config.json.
  • The web server handed out stats.db, with the remote admin audit and
    client addresses, to anyone on the network. It is owner-only now, like
    config.json.
  • The WebSocket server buffered up to 10 MB per message and connection. It
    now stops anything over 128 KB before reading it.
  • Leaflet and Chart.js load with Subresource Integrity, and Chart.js is pinned to
    4.6.1 instead of whatever version the CDN serves today.

Backup and restore that work

  • Restoring a full backup never worked. A WebSocket message is 64 KB at most
    and anything bigger was dropped without a word, so a backup with stats.db
    (or a larger plugin package installed from a file) simply never arrived. The
    Config page now uploads files in 40 KB pieces with a progress display, and the
    Pi writes them to disk instead of memory: up to 512 MB for a backup and 32 MB
    for a plugin, checked against free disk space first.
  • A full backup now takes a consistent snapshot of stats.db instead of copying
    a live file that could catch a write half done.

Faster on SD cards

stats.db runs in WAL mode. Logging a packet on a Raspberry Pi 2 SD card went
from 28.9 ms to 5.3 ms (median) and from 66 ms to 6.5 ms (p95), and that is
time spent holding the lock the rest of MeshPulse waits on. You will see
stats.db-wal and stats.db-shm next to stats.db: that is normal.

Plugins

  • Updating a plugin deleted its database. The upgrade kept config.json and
    a data/ directory, but not data.db, the file get_database() creates, so
    every plugin using the documented API started from scratch after an update.
  • on_position now carries via_mqtt, speed (m/s), heading (degrees),
    precision_bits and pos_time. The guide promised speed and heading for a
    long time; the core never sent them. Trails uses them for real GPS speed.

Everything above was tested live on a Raspberry Pi 2 before shipping. Full list
in docs/changelog.md.

Updating

cd ~/meshpulse && git pull origin main && ./install.sh && sudo systemctl restart meshpulse

Docker

docker compose pull && docker compose up -d

Fresh Install

git clone https://github.com/maxg10/meshpulse.git
cd meshpulse
./install.sh