mcap-encrypt bridge decrypts an encrypted MCAP file, loads all messages into memory, then serves them over the Foxglove WebSocket protocol. Foxglove Studio connects to it exactly as it connects to a live ROS 2 robot running foxglove-bridge: same protocol, same UI, same workflow. No persistent decrypted file remains on disk.
Step 1: start the bridge
mcap-encrypt bridge --key analyst.priv.pem recording.mcapOutput:
loading: recording.mcap
/ decrypting 2.1s
done 2.1s
listening: ws://localhost:8765
Open Foxglove Studio → Add connection → Foxglove WebSocket → ws://localhost:8765
Press Ctrl-C to stop.
Step 2: connect Foxglove Studio
- Open Foxglove Studio (desktop or
studio.foxglove.dev). - Click Open data source.
- Select Foxglove WebSocket.
- Enter
ws://localhost:8765. - Click Open.
All topics, schemas, and messages appear immediately. Camera feeds, lidar point clouds, plots, and diagnostics render exactly as they do with a live ROS 2 source. You can scrub the timeline, jump to timestamps, and use all Foxglove panels.
foxglove-bridge (ROS 2 live) |
mcap-encrypt bridge (encrypted file) |
|
|---|---|---|
| Data source | Live ROS 2 node graph | Encrypted MCAP file |
| Protocol | Foxglove WebSocket v1 | Foxglove WebSocket v1 |
| Connect in Studio | ws://localhost:8765 |
ws://localhost:8765 |
| Key required | No | Yes (your private key) |
| Decrypted file on disk | n/a | Never |
| Multiple clients | Yes | Yes (each gets its own stream) |
The Studio UI is identical. Switch between a live robot and an encrypted log by changing the WebSocket URL.
# Specific port
mcap-encrypt bridge --key analyst.priv.pem --addr localhost:9090 recording.mcap
# All interfaces (use with a TLS reverse proxy in production)
mcap-encrypt bridge --key analyst.priv.pem --addr 0.0.0.0:8765 recording.mcapBy default the bridge listens only on localhost. The decrypted stream is unencrypted over the WebSocket connection. If you expose the bridge on a non-localhost address, put a TLS-terminating reverse proxy (nginx, Caddy) in front.
On startup the bridge decrypts the entire file into memory and loads all schemas, channels, and messages. When Foxglove Studio connects and subscribes to topics, the bridge streams binary MESSAGE_DATA frames in log-time order. Multiple Studio instances can connect simultaneously; each gets an independent stream. Press Ctrl-C to stop.
The private key never leaves your machine. The decrypted content exists only in RAM.
Pass --streaming to switch the bridge to on-demand decryption:
mcap-encrypt bridge --key analyst.priv.pem --streaming recording.mcapIn streaming mode the bridge reads the file's summary at load time (schemas, channels, per-chunk time ranges) but does not decrypt any chunk bodies. Chunks are decrypted lazily as Studio requests time ranges. A small in-process LRU caches recently used chunks, so RAM use is bounded by the cache size rather than by the file size. The wire protocol is identical to the default bridge, so Foxglove Studio sees no difference. The 5 GiB MaxBridgeFileSize sanity cap is still enforced as a conservative bound until concurrent-client stress tests prove it's safe to remove.
When to use which:
| Mode | Best for | Trade-off |
|---|---|---|
| default | Files comfortably below your RAM budget; cold-start latency matters | Decrypts every chunk on startup; full file resident in RAM |
--streaming |
Large files or many concurrent bridges per host | Per-subscription decrypt latency; first scrub through a region pays a one-time decompress cost |
import (
"context"
"github.com/remete618/mcap-encrypt/pkg/mcapencrypt"
)
// Load once, serve many connections.
state, err := mcapencrypt.LoadBridgeState("recording.mcap", "analyst.priv.pem")
if err != nil { log.Fatal(err) }
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
// Blocks until ctx is cancelled or the server fails.
if err := mcapencrypt.ServeBridge(ctx, state, "localhost:8765"); err != nil {
log.Fatal(err)
}LoadBridgeState decrypts the file into memory. ServeBridge starts the WebSocket server. Separating the two lets you show a progress indicator during load before announcing the server address.
The format supports adding Foxglove as a recipient at encrypt time. Once Foxglove publishes a stable public key, this becomes a single extra --key flag:
mcap-encrypt encrypt \
--key your.pub.pem \
--key foxglove.pub.pem \
recording.mcap encrypted.mcap
# You decrypt locally with your.priv.pem.
# Foxglove decrypts on ingest with its own key.
# Ciphertext is identical for both recipients.The library supports this today. The integration requires Foxglove to publish a public key and wire up iterateMessages() from the npm package on their ingest side.