Migration from LSL to Secure LSL
This guide helps existing LSL users transition to Secure LSL with minimal friction.
What Changes
| Aspect | Regular LSL | Secure LSL |
|---|---|---|
| Binary name | liblsl.dylib |
liblsl-secure.dylib |
| Configuration | Optional lsl_api.cfg |
Required [security] section |
| Your code | - | No changes needed (dynamically linked apps) |
| Network traffic | Plaintext | Encrypted |
| Discovery | Works | Works (with security metadata) |
What Stays the Same
- All API functions (C, C++, Python, MATLAB)
- Stream discovery mechanism
- XDF file format (data is decrypted before recording)
- Channel formats and timestamps
Dynamic vs Static Linking
Dynamically linked applications (pylsl, MATLAB, most LSL apps) require no code changes; just point them to liblsl-secure.
Statically linked C++ applications must be recompiled against liblsl-secure. See the C++ migration section below for details.
App Compatibility Reference
The table below lists LSL applications from the labstreaminglayer organization and their linking strategy. Nearly all apps load liblsl dynamically and work as drop-in replacements.
Status Key
Verified = run end-to-end against liblsl-secure on real hardware. Unverified (source-verified) = source code confirms dynamic linking but app has not been run. Rebuild Required = static linking; must recompile against liblsl-secure. See Drop-In Testing Protocol for test procedures and how to contribute results.
Language Bindings
| Binding | Language | Linking | Migration | Status | How It Loads liblsl |
|---|---|---|---|---|---|
| pylsl | Python | Dynamic | Drop-in | Verified | ctypes.CDLL() via PYLSL_LIB env var or system path |
| liblsl-Matlab | MATLAB | Dynamic | Drop-in | Unverified (source-verified) | MEX wrappers using dlopen()/LoadLibrary() |
| liblsl-Csharp | C# | Dynamic | Drop-in | Unverified (source-verified) | [DllImport("lsl")] P/Invoke |
| LSL4Unity | C# (Unity) | Dynamic | Drop-in | Unverified (source-verified) | [DllImport("lsl")]; replace native plugin in Plugins/ |
| liblsl-Java | Java | Dynamic | Drop-in | Unverified (source-verified) | JNA Native.load() |
| liblsl-rust | Rust | Static | Rebuild | Rebuild Required | build.rs compiles liblsl with LSL_BUILD_STATIC=ON |
| liblsl-Android | Java/C++ | Static | Rebuild | Rebuild Required | Builds liblsl from source via CMake NDK |
Recording and Visualization
| App | Language | Linking | Migration | Status | Notes |
|---|---|---|---|---|---|
| LabRecorder | C++ / Qt6 | Dynamic | Drop-in | Verified | Links LSL::lsl via CMake find_package(LSL) |
| SigVisualizer | Python | Dynamic | Drop-in | Verified | Uses pylsl; set PYLSL_LIB to liblsl-secure |
| MATLABViewer | MATLAB | Dynamic | Drop-in | Unverified (source-verified) | Uses liblsl-Matlab MEX bindings |
| XDFStreamer | C++ / Qt5 | Dynamic | Drop-in | Unverified (source-verified) | Links LSL::lsl via CMake |
EEG Device Apps
| App | Language | Linking | Migration | Status | Notes |
|---|---|---|---|---|---|
| BrainProducts (RDA) | C++ / Qt5 | Dynamic | Drop-in | Unverified (source-verified) | Links LSL::lsl via CMake |
| BrainAmpSeries | C++ / Qt5 | Dynamic | Drop-in | Unverified (source-verified) | Links LSL::lsl via CMake |
| BioSemi | C++ / Qt5 | Dynamic | Drop-in | Unverified (source-verified) | Links LSL::lsl via CMake |
| emotiv | C++ / Qt | Dynamic | Drop-in | Unverified (source-verified) | Links LSL::lsl via CMake |
| Cognionics | C++ | Dynamic | Drop-in | Unverified (source-verified) | Links LSL::lsl via CMake |
| EGIAmpServer | C++ / Qt5 | Dynamic | Drop-in | Unverified (source-verified) | Links LSL::lsl via CMake |
| eegoSports | C++ | Dynamic | Drop-in | Unverified (source-verified) | Links LSL::lsl via CMake |
Eye Tracker Apps
| App | Language | Linking | Migration | Status | Notes |
|---|---|---|---|---|---|
| EyeLink | Python | Dynamic | Drop-in | Unverified (source-verified) | Uses pylsl |
| PupilLabs | Python | Dynamic | Drop-in | Unverified (source-verified) | Pupil Capture plugin using pylsl |
| TobiiPro | C++ / Qt5 | Dynamic | Drop-in | Unverified (source-verified) | Links LSL::lsl via CMake |
| TobiiStreamEngine | C++ / Qt5 | Dynamic | Drop-in | Unverified (source-verified) | Links LSL::lsl via CMake |
| SMIEyetracker | C++ | Dynamic | Drop-in | Unverified (source-verified) | Links liblsl shared library |
Input and Motion Capture Apps
| App | Language | Linking | Migration | Status | Notes |
|---|---|---|---|---|---|
| Input (Keyboard/Mouse) | C++ | Dynamic | Drop-in | Unverified (source-verified) | Links LSL::lsl via CMake |
| GameController | C++ / Qt4 | Dynamic | Drop-in | Unverified (source-verified) | Legacy VS project; links liblsl DLL |
| Gamepad | C++ / Qt5 | Dynamic | Drop-in | Unverified (source-verified) | Links LSL::lsl via CMake |
| OptiTrack | C++ | Dynamic | Drop-in | Unverified (source-verified) | Links LSL::lsl via CMake |
Other Apps
| App | Language | Linking | Migration | Status | Notes |
|---|---|---|---|---|---|
| AudioCapture | C++ | Dynamic | Drop-in | Unverified (source-verified) | Links LSL::lsl via CMake |
| OpenVR | C++ / Qt5 | Dynamic | Drop-in | Unverified (source-verified) | Links LSL::lsl via CMake |
| MQTT | C++ | Dynamic | Drop-in | Unverified (source-verified) | Links LSL::lsl via CMake |
| SerialPort | C++ | Dynamic | Drop-in | Unverified (source-verified) | Links LSL::lsl via CMake |
| Zephyr | Python | Dynamic | Drop-in | Unverified (source-verified) | Uses pylsl |
| RippleTrellis | Python | Dynamic | Drop-in | Unverified (source-verified) | Uses pylsl |
Game Engine Plugins
| Plugin | Framework | Linking | Migration | Status | Notes |
|---|---|---|---|---|---|
| plugin-UE4 | Unreal Engine 4 | Dynamic | Drop-in | Unverified (source-verified) | Delay-loaded DLL; replace pre-built binary in ThirdParty/ |
| OpenEphysLSL-Inlet | C++ | Dynamic | Drop-in | Unverified (source-verified) | Open Ephys plugin; links LSL::lsl |
Summary
Out of 35+ apps and bindings in the LSL ecosystem, only liblsl-rust and liblsl-Android use static linking and require a rebuild. Every other app loads liblsl as a shared library at runtime and works by simply replacing the binary with liblsl-secure. Three apps (pylsl, LabRecorder, SigVisualizer) have been verified on Mac Mini M4 Pro and Raspberry Pi 5. See the Drop-In Testing Protocol to verify additional apps or platforms.
Migration Steps
Step 1: Install Secure liblsl (2 minutes)
Step 2: Generate and Distribute Keys (5 minutes)
All devices in your lab must share the same keypair. Generate on one device, then distribute to all others.
On the first device (key generator):
# Generate and export a shared keypair (prompts for a passphrase)
./lsl-keygen --export lab_shared
# Creates: lab_shared.pub and lab_shared.key.enc
# Securely transfer lab_shared.key.enc to each other device
# (e.g., via scp, USB, or your lab's file sharing)
On every device (including the one that generated the key):
Do Not Generate Keys Independently on Each Device
Running ./lsl-keygen on each device creates a different keypair. Devices with different keys reject each other with "security mismatch" errors. Always use --export / --import to share one keypair across all lab devices.
Protect Your Private Key
The private key in ~/.lsl_api/lsl_api.cfg authorizes access to the lab's encrypted streams.
Delete lab_shared.key.enc after distributing it, or store securely.
Never commit the key file to version control.
Step 3: Update Configuration (1 minute)
The lsl-keygen command automatically creates or updates your lsl_api.cfg.
Verify it contains a [security] section with enabled = true and a key field:
[security]
enabled = true
encrypted_private_key = <base64-encoded-encrypted-key> ; default (with passphrase)
; or:
; private_key = <base64-encoded-key> ; if generated with --insecure
Configuration file locations:
- macOS/Linux:
~/.lsl_api/lsl_api.cfg - Windows:
%USERPROFILE%\.lsl_api\lsl_api.cfg - Or: Same directory as your application
Step 4: Verify Setup (30 seconds)
./lsl-config --check
# Expected output:
# LSL Security Configuration Status
# ==================================
#
# Security subsystem: initialized
# Security enabled: YES
# Config file: /Users/you/.lsl_api/lsl_api.cfg
# Key fingerprint: BLAKE2b:70:14:e1:b5:...
# Key created: 2025-12-05T19:00:00Z
# Session lifetime: 3600 seconds
# Device token: not set
#
# [OK] Configuration valid
Step 5: Run Your Existing Code
Dynamically linked applications work without code changes:
# No changes needed!
import pylsl
# Create outlet (automatically encrypted)
info = pylsl.StreamInfo("EEG", "EEG", 8, 250, pylsl.cf_float32, "mydevice")
outlet = pylsl.StreamOutlet(info)
# Push samples (encrypted transparently)
outlet.push_sample([1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0])
Per-Binding Instructions
Python (pylsl)
Point pylsl to the secure library:
# Option 1: Environment variable
export PYLSL_LIB=/path/to/liblsl-secure.dylib
# Option 2: In Python before importing
import os
os.environ['PYLSL_LIB'] = '/path/to/liblsl-secure.dylib'
import pylsl # Must import AFTER setting env
Verify you're using secure LSL:
import pylsl
info = pylsl.library_info()
if 'security' not in info:
print("WARNING: Not using secure LSL!")
else:
print(f"Using: {info}")
MATLAB
Update the library path in your MATLAB code:
% Load the secure library using liblsl-Matlab's lsl_loadlib wrapper
% (set LIBLSL path to point to liblsl-secure before starting MATLAB)
lib = lsl_loadlib();
Or set the library path before starting MATLAB:
C++
Relink your application with the secure library:
Or update your CMakeLists.txt:
# The target name is still 'lsl' in CMake
find_package(LSL REQUIRED)
target_link_libraries(myapp PRIVATE LSL::lsl)
C
Update the DllImport attribute:
Verifying Security is Active
Runtime Check
import lsl_security_helper # must come before import pylsl; adds security methods
import pylsl
# Check library info
info = pylsl.library_info()
print(info)
# Should contain: security:X.X.X
# Check stream security (requires lsl_security_helper imported above)
streams = pylsl.resolve_streams(wait_time=2.0)
if streams:
stream_info = streams[0]
print(f"Security enabled: {stream_info.security_enabled()}")
Stream Discovery
Secure streams advertise their security status. You can see this in the stream metadata:
streams = pylsl.resolve_streams()
for s in streams:
print(f"Stream: {s.name()}")
# Security info is in the stream's XML description
Visual Indicators
LabRecorder and SigVisualizer (with security patches) show:
- Lock icon for encrypted streams
- Security status banner in main window
- Warning for mixed secure/insecure environments
Troubleshooting
"Using wrong library" Error
Symptom: Your application loads regular liblsl instead of liblsl-secure.
Solution:
-
Check which library is loaded:
-
Set the correct path:
-
Verify no conflicting libraries:
Security Mismatch Errors
Symptom: "Security mismatch: secure inlet cannot connect to insecure outlet"
Cause: One device has security enabled, another doesn't.
Solution: Enable security on all devices in your lab with the same shared key:
- Generate and export a key on one device:
lsl-keygen --export lab_shared - Import on every device (including the generator):
lsl-keygen --import lab_shared.key.enc - Verify fingerprints match:
lsl-config --show-public - Restart all LSL applications
Key File Permissions
Symptom: "Cannot read private key" or permission denied errors.
Solution:
Missing libsodium
Symptom: Build fails with "libsodium not found"
Solution:
# macOS
brew install libsodium
# Ubuntu/Debian
sudo apt install libsodium-dev
# Windows (vcpkg)
vcpkg install libsodium
Multi-Device Lab Setup
For labs with multiple devices:
- Install secure LSL on each device
- Generate a shared keypair on one device and export it (
lsl-keygen --export lab_shared) - Import the shared key on every device, including the generator (
lsl-keygen --import lab_shared.key.enc) - Verify fingerprints match on each device with
lsl-config --show-public - Test connectivity between devices before experiments
# On Device A (outlet)
./cpp_secure_outlet
# On Device B (inlet)
./cpp_secure_inlet
# Should connect and show encrypted data transfer
Rollback to Regular LSL
If you need to temporarily disable security:
-
Edit
lsl_api.cfg: -
Or use regular liblsl binary:
Security Implications
Disabling security means your data is transmitted in plaintext. Only do this for debugging or compatibility testing.