Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 34 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
name: CI

on:
push:
branches: [main]
pull_request:
branches: [main]

jobs:
test:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
python-version: ["3.11", "3.13"]
steps:
- uses: actions/checkout@v4

- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}

- name: Install test dependencies
# The test suite loads sparkplug-fuzzer.py via importlib and exercises
# only the pure-Python paths (topic helpers, logger, arg/path
# resolution, payload loader), so it needs neither paho/protobuf nor
# the Tahu `--setup` step. Keep this in sync with requirements-dev.txt.
run: |
python -m pip install --upgrade pip
pip install -r requirements-dev.txt

- name: Run tests
run: python -m pytest -q
6 changes: 3 additions & 3 deletions NOTICE
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,13 @@ from the Eclipse Tahu project and copied into the working directory:

Eclipse Tahu
Copyright (c) 2012, 2018 Cirrus Link Solutions and others
Licensed under the Apache License, Version 2.0 (the "License");
https://www.apache.org/licenses/LICENSE-2.0
Licensed under the Eclipse Public License 2.0 (the "License");
https://www.eclipse.org/legal/epl-2.0/
https://github.com/eclipse/tahu

The compiled Protocol Buffers bindings (`sparkplug_b_pb2.py`) are
generated locally from `sparkplug_b.proto`, also distributed by Eclipse
Tahu under the Apache License, Version 2.0.
Tahu under the Eclipse Public License 2.0.

Neither the Eclipse Tahu source files nor the compiled bindings are
redistributed in this repository.
10 changes: 6 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ The Sparkplug B specification defines a topic namespace and payload format built

## Prerequisites

- **Python 3.8+**
- **Python 3.11+**
- **MQTT Broker** — the target system under test (e.g., Mosquitto, HiveMQ, EMQX, or any Sparkplug B aware broker)
- **Authorization** — this tool is intended for authorized security testing only

Expand Down Expand Up @@ -132,8 +132,10 @@ python3 sparkplug-fuzzer.py [OPTIONS]
| `--discovery-time` | `10` | Seconds to passively listen for network discovery |
| `--delay` | `0.1` | Delay in seconds between fuzz messages |
| `--probe-anon-write` | off | During discovery, send one QoS=1 publish to confirm whether the broker accepts unauthenticated PUBLISH |
| `--request-rebirth` | off | After the passive listen window, publish an NCMD `Rebirth` to each discovered node so it re-sends its (non-retained) NBIRTH/DBIRTH — recovers full node/device metric definitions missed when the fuzzer joins after a birth. Active step. |
| `-l`, `--log` | `sparkplug_fuzz.jsonl` | Output log file name (relative paths land inside `--output-dir`; absolute paths are honored as-is) |
| `--output-dir` | `./sparkplug-runs/<UTC-ts>_<host>/` | Per-run output directory. Created if absent. |
| `--topology-diagram` | off | After the run, write a [Mermaid](https://mermaid.js.org/) diagram of the discovered topology (broker → group → node → device), marking fuzzed entities. Bare flag writes `topology.mmd` in the output dir; pass a path to override (relative paths land in the output dir). |
| `-v`, `--verbose` | 0 | Increase console verbosity (`-v` = info, `-vv` = debug). `-vv` also surfaces fuzz-generator skips, and the throttled `paho.mqtt` logger bumps to INFO/DEBUG with verbosity. |
| `--setup` | — | Install all dependencies and exit |
| `--tahu-path` | — | Path to a local clone of [eclipse/tahu](https://github.com/eclipse/tahu) (or its `python/core` directory). Used by `--setup` in air-gapped environments instead of `git clone`. |
Expand Down Expand Up @@ -314,10 +316,10 @@ When the fuzzer connects without `-u/-P` (and `MQTT_USERNAME`/`MQTT_PASSWORD` ar
|---|---|---|
| `anon_connect_accepted` | Broker accepted CONNECT without credentials | The fuzzer's own CONNECT succeeded |
| `anon_subscribe_accepted` | Broker forwards `spBv1.0/#` / `STATE/#` to anonymous clients | At least one RX message arrived during the listen window |
| `anon_publish_accepted` | Broker accepts PUBLISH from anonymous clients | Set only if `--probe-anon-write` is passed; QoS=1 probe + PUBACK wait |
| `anon_publish_accepted` | Broker accepts *and forwards* a PUBLISH from an anonymous client | Set only if `--probe-anon-write` is passed; QoS=1 probe confirmed by redelivery to our own subscription |
| `unauth_endpoints` | Nodes / devices / host applications observable without auth | Every entity in the discovered network map (auth was never produced) |

The QoS=1 probe is opt-in because it crosses from passive into active. With QoS=0 the broker silently drops messages it would deny, so confirming write-accept requires reading a PUBACK.
The QoS=1 probe is opt-in because it crosses from passive into active. A PUBACK alone is **not** proof of acceptance — most brokers PUBACK a QoS=1 message even when an ACL denies it (the PUBACK acknowledges receipt, not authorization). So the probe instead publishes to a topic already covered by our `spBv1.0/#` subscription and waits for the broker to **redeliver** the message back to us; only an accepted-and-forwarded publish confirms the finding.

MQTT/Sparkplug have no per-endpoint auth — auth is a broker-level concern. So "endpoints observable without authentication" is reported as a list of *targets reachable at zero cost* rather than as a property of the endpoints themselves.

Expand Down Expand Up @@ -543,4 +545,4 @@ This project is licensed under the MIT License — see [LICENSE](LICENSE) for th
- `array_packer.py` — Array packing helper
- `sparkplug_b.proto` — Protocol Buffer definition (used to generate `sparkplug_b_pb2.py`)

Eclipse Tahu is distributed under the [Apache License, Version 2.0](https://www.apache.org/licenses/LICENSE-2.0). None of the Tahu source files are redistributed in this repository. See [NOTICE](NOTICE) for the full attribution.
Eclipse Tahu is distributed under the [Eclipse Public License 2.0](https://www.eclipse.org/legal/epl-2.0/). None of the Tahu source files are redistributed in this repository. See [NOTICE](NOTICE) for the full attribution.
Loading
Loading