Built as a hobby project to proof-of-concept defensive cyber capabilities for drone ground control stations. Mavlink security gateway is a configurable message filter for defending drones against malicious, spoofed, or unauthorised MAVLink traffic. Includes a little vibe coded, web-based schema builder UI for creating security policies without coding. The idea is to expose only the commands or telemetry required for a mission and policy enforce this at the network layer, abstracting mission/strategic policy from the operator. See the blogpost at samblack.co.
This filter acts as a security gateway between MAVLink sources (radio links, network bridges, companion computers) and your GCS or autopilot. It inspects every MAVLink message and applies configurable allow/block rules based on:
- Message type and ID
- Source and target system/component IDs
- Command codes (for COMMAND_LONG, COMMAND_INT, MISSION_ITEM)
- Parameter names (for PARAM_SET, PARAM_VALUE)
- Arbitrary payload field values with comparison operators
- Sequence numbers for replay/reorder detection
| Threat | Description | Mitigation |
|---|---|---|
| Command Injection | Attacker sends unauthorised commands (arm, disarm, mode change, RTL override) | Block unauthorised command IDs and sources |
| Parameter Tampering | Malicious PARAM_SET to disable failsafes, geofences, or alter flight characteristics | Allowlist specific safe parameters |
| Data Exfiltration | Non-mavlink traffic using the uplink as a bearer of opportunity Application layer attacks against the GCS/from the GCS to the battlespace management system | Comprehensive security filtering on trusted network |
| Replay Attacks | Re-transmitting captured legitimate commands | Sequence tracking with replay detection |
| Privilege Escalation | GCS operator exceeding authorised actions | Role-based schema selection |
Deployment pattern one, defend the GCS from remote attack.
Deployment pattern two, defend the battlespace management program from remote attack, release of sensitive information.
pip install pymavlinkFor YAML schema support (optional):
pip install pyyamlCopy the filter module to your project:
cp mavlink-filter.py /your/project/-
Start a web server:
python3 -m http.server 8000
-
Open
http://localhost:8000/schema-ui.htmlin your browser -
Select a template or build a custom schema
-
Configure rules using the guided interface
-
Download your schema as JSON or YAML
-
Use with the gateway or in your code
# Filter UDP traffic
python3 mavlink-gateway.py \
--schema my-schema.json \
--input udp:127.0.0.1:14550 \
--output udp:127.0.0.1:14551
# Filter serial traffic
python3 mavlink-gateway.py \
--schema my-schema.json \
--input serial:/dev/ttyUSB0:57600 \
--output udp:127.0.0.1:14550from mavlink_filter import MavlinkFilter
# Load from file
filter = MavlinkFilter("my-schema.json")
# Or use dict directly
schema = {"mode": "blocklist", "allow": [...], "block": [...]}
filter = MavlinkFilter(schema)
# Check if message is allowed
if filter.allowed(message):
# Process message
passA user-friendly web interface for creating MAVLink security filter schemas without writing code.
- Visual Interface - No coding required
- 295 MAVLink Message Types - Complete catalog with search
- Templates - Pre-configured security policies
- Guided Configuration - Step-by-step rule creation with explanations
- Command Catalog - Browse commands with descriptions and risk levels
- Context-Aware Options - Only shows relevant configuration options
- Real-time Preview - See schema as you build
- Export - Download JSON or YAML schemas
- Import - Load existing schemas for editing
-
Select a Template (optional)
- Permissive: Blocklist mode, blocks only dangerous commands
- Secure Gateway: Allowlist mode with sequence tracking
- Read-Only Observer: Telemetry only, no commands or writes
- Emergency Override: Always allows safety-critical commands
- Custom: Start from scratch
-
Configure Settings
- Filter Mode:
- Blocklist (allow by default) - More permissive, easier to configure
- Allowlist (block by default) - More secure, requires explicit allow rules
- Default Behaviour: What happens to messages that don't match any rules
- Sequence Tracking: Enable replay attack detection
- Filter Mode:
-
Add Rules
- Click "+ Add Allow Rule" or "+ Add Block Rule"
- The editor opens immediately
- Step 1: Select message types from the searchable list
- Step 2: Use quick presets (optional) for common patterns
- Advanced Options: Toggle to show commands, parameters, filtering, etc.
-
Configure Advanced Options (when needed)
- Commands: Select from catalog or enter command IDs
- Parameter Names: Filter by parameter with wildcard support
- Source/Target Filtering: Restrict by system/component IDs
- Field Conditions: Match based on message field values
- Sequence Tracking: Detect replays and out-of-order messages
-
Export Schema
- Click "Download JSON" or "Download YAML"
- Use with
mavlink-gateway.pyor in your code
Message Types (like HEARTBEAT, COMMAND_LONG, PARAM_SET) are the structure/format of messages. They are defined in the MAVLink message definitions.
Command Enum Values (like 400, 512, 176) are MAV_CMD constants that specify what action a command performs. These appear inside certain message types like COMMAND_LONG.
For example:
COMMAND_LONGis a message type400(ARM_DISARM) is a command value that can appear in aCOMMAND_LONGmessage
When you configure a rule with {"names": ["COMMAND_LONG"], "commands": [400]}, you're saying: "Match COMMAND_LONG messages where the command field equals 400 (ARM_DISARM)".
| ID | Name | Risk | Description |
|---|---|---|---|
| 400 | ARM_DISARM | Critical | Arm or disarm the vehicle (motors on/off) |
| 176 | DO_SET_MODE | High | Change flight mode |
| 20 | NAV_RETURN_TO_LAUNCH | Medium | Return to launch position |
| 21 | NAV_LAND | Medium | Land at current location |
| 22 | NAV_TAKEOFF | High | Take off to specified altitude |
| 92 | NAV_GUIDED_ENABLE | High | Enable external control |
| 183 | DO_SET_SERVO | High | Direct actuator control |
| 184 | DO_REPEAT_SERVO | High | Direct actuator control (repeat) |
| 185 | DO_FLIGHTTERMINATION | Critical | Kill switch - immediately stop vehicle |
| 252 | PREFLIGHT_REBOOT | Critical | Reboot autopilot |
| 511 | SET_MESSAGE_INTERVAL | Low | Set message stream rate |
| 512 | REQUEST_MESSAGE | Low | Request a specific message stream |
| 520 | REQUEST_AUTOPILOT_CAPABILITIES | Low | Request autopilot capabilities |
The mavlink-gateway.py tool provides a standalone command-line interface for filtering MAVLink traffic using schema files.
python3 mavlink-gateway.py \
--schema <schema-file> \
--input <input-spec> \
--output <output-spec> \
[--audit] \
[--stats]--schema: Path to schema file (JSON or YAML)--input: Input specification in formattype:address:portortype:device:baud- udp:
udp:127.0.0.1:14550 - serial:
serial:/dev/ttyUSB0:57600
- udp:
--output: Output specification (same format as input)--audit: Enable audit logging (prints all decisions)--stats: Print statistics periodically
# UDP proxy with filtering
python3 mavlink-gateway.py \
--schema schemas/01-secure-command-gateway.json \
--input udp:127.0.0.1:14550 \
--output udp:127.0.0.1:14551
# Serial to UDP gateway
python3 mavlink-gateway.py \
--schema schemas/02-parameter-protection.json \
--input serial:/dev/ttyUSB0:57600 \
--output udp:127.0.0.1:14550 \
--audit
# Bidirectional filtering (requires two instances)
# Terminal 1: Vehicle → GCS
python3 mavlink-gateway.py \
--schema telemetry-schema.json \
--input serial:/dev/ttyUSB0:57600 \
--output udp:127.0.0.1:14550
# Terminal 2: GCS → Vehicle
python3 mavlink-gateway.py \
--schema command-schema.json \
--input udp:127.0.0.1:14551 \
--output serial:/dev/ttyUSB0:57600{
"mode": "allowlist" | "blocklist",
"default": "allow" | "block",
"allow": [rule, ...],
"block": [rule, ...],
"seq_tracking": {
"enabled": true,
"max_reorder": 10,
"update_on": "allowed"
}
}Allowlist Mode (deny by default):
- Messages that don't match any rules: BLOCKED
- If both allow and block rules match: BLOCK wins
- Security model: Deny everything except what's explicitly allowed
- More secure but requires explicit allow rules for all needed messages
Blocklist Mode (allow by default):
- Messages that don't match any rules: Use default (usually ALLOW)
- If both allow and block rules match: ALLOW wins (allow overrides block)
- Security model: Allow everything except what's explicitly blocked
- More permissive but easier to configure
{
"names": ["HEARTBEAT", {"glob": "GPS_*"}, {"regex": "^COMMAND_"}],
"msgids": [0, 76, 77],
"src_sysids": [255],
"src_compids": [190],
"target_sysids": [1],
"target_compids": [1],
"commands": [400, 176, 21],
"param_ids": ["SYSID_THISMAV", {"glob": "FENCE_*"}],
"fields": {
"param1": {"gt": 0, "lt": 100},
"confirmation": {"ne": 21}
},
"seq": {
"replay": true,
"out_of_order": true,
"max_gap": 50
}
}| Predicate | Example | Description |
|---|---|---|
eq |
{"eq": 5} |
Equals |
ne |
{"ne": 0} |
Not equals |
gt, gte |
{"gt": 100} |
Greater than (or equal) |
lt, lte |
{"lt": 50} |
Less than (or equal) |
in |
{"in": [1,2,3]} |
Value in set |
nin |
{"nin": [0]} |
Value not in set |
between |
{"between": [-10, 10]} |
Inclusive range |
abs_gt |
{"abs_gt": 1000} |
Absolute value greater than |
{
"fields": {
"any": [
{"field": "param1", "pred": {"gt": 100}},
{"field": "param2", "pred": {"gt": 100}}
]
}
}{
"fields": {
"all": [
{"field": "param1", "pred": {"gt": 100}},
{"field": "confirmation", "pred": {"ne": 21}}
]
}
}{
"fields": {
"not": {"field": "confirmation", "pred": {"eq": 21}}
}
}Sequence tracking detects replay attacks by monitoring message sequence numbers:
- replay: Block messages with the same sequence number we've seen before
- out_of_order: Block messages that arrive out of order (beyond reorder window)
- max_gap: Block if sequence number jumps more than N steps
The update_on setting controls when sequence state is updated:
"seen": Update on all messages (less secure)"allowed": Update only on allowed messages (recommended for security)"matched": Update only on messages that match rules
The schemas/ directory contains seven pre-configured example schemas:
- 01-secure-command-gateway.json - High-security allowlist with comprehensive telemetry
- 02-parameter-protection.json - Parameter tampering protection
- 03-mission-protection.json - Mission upload protection
- 04-anti-replay.json - Replay attack mitigation
- 05-emergency-override.json - Emergency safety commands
- 06-read-only-observer.json - Monitoring station configuration
- 07-permissive-blocklist.json - Permissive policy for development
See the schemas/ directory for detailed descriptions of each schema.
mavlink-filter.py- Core filtering librarymavlink-gateway.py- Command-line gateway toolschema-ui.html- Web-based schema buildermavlink_message_catalog.json- Complete message type catalogschemas/- Example schema filesexample-schema.json- Basic example schemaexample-schema.yaml- Basic example schema (YAML)
The project includes some test suites to validate filter functionality, more need to be built to encompas all of the current catalogue for now we have:
test_security_configs.py - Unit tests using mock MAVLink messages
- Tests all 7 security configurations
- 42+ tests covering command blocking, parameter protection, sequence tracking, etc.
- Run:
python3 test_security_configs.py
test_pymavlink_integration.py - Integration tests using real pymavlink messages
- Uses actual MAVLink message objects from pymavlink
- Validates filter works with real MAVLink protocol structures
- Tests byte stream filtering with MavlinkStreamFilter
- Run:
python3 test_pymavlink_integration.py
test_schema_files.py - Tests schema file loading and gateway functionality
- Validates JSON and YAML schema loading
- Tests command-line gateway with various configurations
- Run:
python3 test_schema_files.py
- Message types: HEARTBEAT, COMMAND_LONG, PARAM_SET, MISSION_ITEM, GPS_RAW_INT, and more
- Security scenarios: Command blocking, parameter protection, source/target filtering, sequence tracking, field predicates
- Modes: Allowlist and blocklist modes
- Edge cases: First messages, replay detection, out-of-order handling
- No significant load, throughput or ramp up testing performed
MIT, see LICENSE. Use at your own risk. This software is designed as a security tool but does not guarantee any protection against any attack vectors. Always implement defence in depth.