This guide helps existing LSL users transition to Secure LSL with minimal friction.
| 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) |
- All API functions (C, C++, Python, MATLAB)
- Stream discovery mechanism
- XDF file format (data is decrypted before recording)
- Channel formats and timestamps
!!! note "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](#c) below for details.
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.
!!! note "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.
| 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 |
| 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 |
| 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 |
| 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 |
| 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 |
| 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 |
| 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 |
!!! tip "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.
=== "macOS"
```bash
# Option A: Build from source
brew install libsodium cmake
cd secureLSL/liblsl
mkdir build && cd build
cmake -DLSL_SECURITY=ON ..
cmake --build . --parallel
# Option B: Download release (when available)
# brew install sccn/tap/liblsl-secure
```
=== "Linux"
```bash
# Install dependencies
sudo apt install libsodium-dev cmake build-essential
# Build
cd secureLSL/liblsl
mkdir build && cd build
cmake -DLSL_SECURITY=ON ..
cmake --build . --parallel
sudo make install
```
=== "Windows"
```powershell
# Install libsodium via vcpkg
vcpkg install libsodium
# Build
cd secureLSL\liblsl
mkdir build && cd build
cmake -DLSL_SECURITY=ON -DCMAKE_TOOLCHAIN_FILE=[vcpkg-root]/scripts/buildsystems/vcpkg.cmake ..
cmake --build . --config Release
```
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):
# Import the shared keypair (enter the same passphrase)
./lsl-keygen --import lab_shared.key.enc!!! warning "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.
!!! warning "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.
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 --insecureConfiguration file locations:
- macOS/Linux:
~/.lsl_api/lsl_api.cfg - Windows:
%USERPROFILE%\.lsl_api\lsl_api.cfg - Or: Same directory as your application
./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 validDynamically 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])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 envVerify 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}")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:
# macOS/Linux
export DYLD_LIBRARY_PATH=/path/to/secure/lib:$DYLD_LIBRARY_PATH
matlabRelink your application with the secure library:
# Change from:
g++ myapp.cpp -llsl -o myapp
# To:
g++ myapp.cpp -llsl-secure -o myappOr update your CMakeLists.txt:
# The target name is still 'lsl' in CMake
find_package(LSL REQUIRED)
target_link_libraries(myapp PRIVATE LSL::lsl)Update the DllImport attribute:
// Change from:
[DllImport("lsl")]
// To:
[DllImport("lsl-secure")]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()}")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 descriptionLabRecorder and SigVisualizer (with security patches) show:
- Lock icon for encrypted streams
- Security status banner in main window
- Warning for mixed secure/insecure environments
Symptom: Your application loads regular liblsl instead of liblsl-secure.
Solution:
-
Check which library is loaded:
import pylsl print(pylsl.library_info())
-
Set the correct path:
export PYLSL_LIB=/path/to/liblsl-secure.dylib -
Verify no conflicting libraries:
# macOS ls /usr/local/lib/liblsl* # Remove or rename regular liblsl if needed
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
Symptom: "Cannot read private key" or permission denied errors.
Solution:
# Ensure correct permissions
chmod 600 ~/.lsl_api/lsl_api.cfg
chmod 700 ~/.lsl_api/Symptom: Build fails with "libsodium not found"
Solution:
# macOS
brew install libsodium
# Ubuntu/Debian
sudo apt install libsodium-dev
# Windows (vcpkg)
vcpkg install libsodiumFor 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 transferIf you need to temporarily disable security:
-
Edit
lsl_api.cfg:[security] enabled = false
-
Or use regular liblsl binary:
export PYLSL_LIB=/path/to/liblsl.dylib # regular version
!!! warning "Security Implications" Disabling security means your data is transmitted in plaintext. Only do this for debugging or compatibility testing.