homebridge-bluos 2.0.2 → 2.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +7 -0
- package/README.md +1 -1
- package/SECURITY.md +2 -2
- package/dist/api/discovery.d.ts +6 -4
- package/dist/api/discovery.js +14 -4
- package/dist/utils/validators.d.ts +11 -0
- package/dist/utils/validators.js +24 -0
- package/docs/FEATURES.md +1 -1
- package/docs/PROTOCOL.md +1 -1
- package/docs/README-DETAILED.md +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [2.0.3](https://github.com/tbaur/homebridge-bluos/compare/v2.0.2...v2.0.3) (2026-09-13)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Bug Fixes
|
|
7
|
+
|
|
8
|
+
* skip public and loopback addresses in mDNS discovery ([#46](https://github.com/tbaur/homebridge-bluos/issues/46)) ([9010b86](https://github.com/tbaur/homebridge-bluos/commit/9010b861a95f8d11e450c7c3d877f1f243c2f01d))
|
|
9
|
+
|
|
3
10
|
## [2.0.2](https://github.com/tbaur/homebridge-bluos/compare/v2.0.1...v2.0.2) (2026-09-10)
|
|
4
11
|
|
|
5
12
|
|
package/README.md
CHANGED
|
@@ -56,7 +56,7 @@ Nothing needs enabling on the player, because the BluOS LAN API is always on. A
|
|
|
56
56
|
|
|
57
57
|
### 3. Configure
|
|
58
58
|
|
|
59
|
-
**Homebridge UI** (recommended): open the plugin settings and press **Discover Players**. Every zone that answers is listed. Tick the players and accessories you want, then press the Homebridge Save button.
|
|
59
|
+
**Homebridge UI** (recommended): open the plugin settings and press **Discover Players**. Every zone that answers on a local IPv4 address is listed. Public and loopback advertisements are skipped. Tick the players and accessories you want, then press the Homebridge Save button.
|
|
60
60
|
|
|
61
61
|
Or in `config.json`:
|
|
62
62
|
|
package/SECURITY.md
CHANGED
|
@@ -16,8 +16,8 @@ Do not open a public issue. Use GitHub's [private vulnerability reporting](https
|
|
|
16
16
|
- **Input validation:** Config is checked at startup. A missing or non-list `devices` value disables the platform without unregistering accessories. A bad player or preset is skipped. Hosts, ports and timeouts are rejected or clamped.
|
|
17
17
|
- **Log safety:** Values written to logs have control characters stripped and are length-limited.
|
|
18
18
|
- **Bounded I/O:** Connect and total timeouts on every request, responses capped at 128 KiB, and XML parsed with size, depth and element caps.
|
|
19
|
-
- **Discovery:** mDNS errors are caught so they cannot take Homebridge down. Browse results are capped.
|
|
20
|
-
- **Settings probe:** Accepts only a private or local address and a documented BluOS port.
|
|
19
|
+
- **Discovery:** mDNS errors are caught so they cannot take Homebridge down. Browse results are capped. Advertised addresses are verified only when they are local (RFC 1918, CGNAT/Tailscale, link-local). Public IPv4 and loopback are skipped, so an advertisement cannot make Homebridge open `/SyncStatus` on itself or on the public internet. Players already in configuration are unchanged.
|
|
20
|
+
- **Settings probe:** Accepts only a private or local address and a documented BluOS port. Loopback is allowed here because an administrator asked for that probe.
|
|
21
21
|
- **Dependencies:** CI runs `npm audit` on the runtime tree and OSV-Scanner on the full tree.
|
|
22
22
|
|
|
23
23
|
## Best Practices for Users
|
package/dist/api/discovery.d.ts
CHANGED
|
@@ -112,10 +112,12 @@ export declare class BluOSDiscovery {
|
|
|
112
112
|
/**
|
|
113
113
|
* Turn service instances into addressable endpoints.
|
|
114
114
|
*
|
|
115
|
-
* A zone is only usable once its SRV record (for the port) and
|
|
116
|
-
* are both known
|
|
117
|
-
*
|
|
118
|
-
*
|
|
115
|
+
* A zone is only usable once its SRV record (for the port) and a local IPv4
|
|
116
|
+
* address are both known (RFC 1918, CGNAT / Tailscale, link-local). Public
|
|
117
|
+
* IPv4 and loopback are ignored, so an advertisement cannot point verify at
|
|
118
|
+
* Homebridge or at an off-network host. The address comes from an A record
|
|
119
|
+
* when one was offered, and otherwise from the responder's own source
|
|
120
|
+
* address, which for a player advertising its own service is the same machine.
|
|
119
121
|
*/
|
|
120
122
|
private toEndpoints;
|
|
121
123
|
/** Collect mDNS records for the configured window. */
|
package/dist/api/discovery.js
CHANGED
|
@@ -35,6 +35,7 @@ const node_os_1 = __importDefault(require("node:os"));
|
|
|
35
35
|
const settings_1 = require("../settings");
|
|
36
36
|
const errors_1 = require("../utils/errors");
|
|
37
37
|
const timing_1 = require("../utils/timing");
|
|
38
|
+
const validators_1 = require("../utils/validators");
|
|
38
39
|
const identity_1 = require("./identity");
|
|
39
40
|
/** Re-query schedule, in milliseconds, to survive dropped multicast packets. */
|
|
40
41
|
const QUERY_SCHEDULE_MS = [0, 400, 1_200];
|
|
@@ -202,10 +203,12 @@ class BluOSDiscovery {
|
|
|
202
203
|
/**
|
|
203
204
|
* Turn service instances into addressable endpoints.
|
|
204
205
|
*
|
|
205
|
-
* A zone is only usable once its SRV record (for the port) and
|
|
206
|
-
* are both known
|
|
207
|
-
*
|
|
208
|
-
*
|
|
206
|
+
* A zone is only usable once its SRV record (for the port) and a local IPv4
|
|
207
|
+
* address are both known (RFC 1918, CGNAT / Tailscale, link-local). Public
|
|
208
|
+
* IPv4 and loopback are ignored, so an advertisement cannot point verify at
|
|
209
|
+
* Homebridge or at an off-network host. The address comes from an A record
|
|
210
|
+
* when one was offered, and otherwise from the responder's own source
|
|
211
|
+
* address, which for a player advertising its own service is the same machine.
|
|
209
212
|
*/
|
|
210
213
|
toEndpoints(candidates) {
|
|
211
214
|
const seen = new Set();
|
|
@@ -217,6 +220,13 @@ class BluOSDiscovery {
|
|
|
217
220
|
this.log.debug(`discovery: no IPv4 address for ${candidate.instance} (target ${candidate.target})`);
|
|
218
221
|
continue;
|
|
219
222
|
}
|
|
223
|
+
if (!(0, validators_1.isDiscoveryHost)(host)) {
|
|
224
|
+
const reason = (0, validators_1.isLoopbackIpv4)(host)
|
|
225
|
+
? 'loopback is not accepted from mDNS'
|
|
226
|
+
: 'not a local address';
|
|
227
|
+
this.log.debug(`discovery: skipping ${host} (${reason})`);
|
|
228
|
+
continue;
|
|
229
|
+
}
|
|
220
230
|
const key = (0, identity_1.formatEndpoint)(host, candidate.port);
|
|
221
231
|
if (seen.has(key)) {
|
|
222
232
|
continue;
|
|
@@ -85,6 +85,17 @@ export declare function isNonPrivateIpv4(value: string): boolean;
|
|
|
85
85
|
* hostnames are accepted.
|
|
86
86
|
*/
|
|
87
87
|
export declare function isProbeableHost(value: unknown): value is string;
|
|
88
|
+
/**
|
|
89
|
+
* True for an address discovery may dial after an unsolicited mDNS advertisement.
|
|
90
|
+
*
|
|
91
|
+
* Narrower than {@link isProbeableHost}. The settings-page probe is an
|
|
92
|
+
* administrator asking this host to open a connection, so loopback is allowed
|
|
93
|
+
* there. An advertisement on the LAN is not a request, and a packet that names
|
|
94
|
+
* 127.0.0.1 would make Homebridge talk to itself.
|
|
95
|
+
*/
|
|
96
|
+
export declare function isDiscoveryHost(value: unknown): value is string;
|
|
97
|
+
/** True for IPv4 loopback (first octet 127). `isProbeableHost` treats this as local; discovery does not. */
|
|
98
|
+
export declare function isLoopbackIpv4(value: string): boolean;
|
|
88
99
|
/** Clamp the discovery window into the supported range. */
|
|
89
100
|
export declare function resolveDiscoveryTimeoutSec(value: unknown, warnings?: string[]): number;
|
|
90
101
|
/**
|
package/dist/utils/validators.js
CHANGED
|
@@ -25,6 +25,8 @@ exports.isIpv4 = isIpv4;
|
|
|
25
25
|
exports.isValidHost = isValidHost;
|
|
26
26
|
exports.isNonPrivateIpv4 = isNonPrivateIpv4;
|
|
27
27
|
exports.isProbeableHost = isProbeableHost;
|
|
28
|
+
exports.isDiscoveryHost = isDiscoveryHost;
|
|
29
|
+
exports.isLoopbackIpv4 = isLoopbackIpv4;
|
|
28
30
|
exports.resolveDiscoveryTimeoutSec = resolveDiscoveryTimeoutSec;
|
|
29
31
|
exports.resolveSliderService = resolveSliderService;
|
|
30
32
|
exports.validateConfig = validateConfig;
|
|
@@ -169,6 +171,28 @@ function isProbeableHost(value) {
|
|
|
169
171
|
}
|
|
170
172
|
return PROBE_LOCAL_SUFFIXES.some((suffix) => lower.endsWith(suffix) && lower.length > suffix.length);
|
|
171
173
|
}
|
|
174
|
+
/**
|
|
175
|
+
* True for an address discovery may dial after an unsolicited mDNS advertisement.
|
|
176
|
+
*
|
|
177
|
+
* Narrower than {@link isProbeableHost}. The settings-page probe is an
|
|
178
|
+
* administrator asking this host to open a connection, so loopback is allowed
|
|
179
|
+
* there. An advertisement on the LAN is not a request, and a packet that names
|
|
180
|
+
* 127.0.0.1 would make Homebridge talk to itself.
|
|
181
|
+
*/
|
|
182
|
+
function isDiscoveryHost(value) {
|
|
183
|
+
if (!isProbeableHost(value)) {
|
|
184
|
+
return false;
|
|
185
|
+
}
|
|
186
|
+
return !isLoopbackIpv4(value.trim());
|
|
187
|
+
}
|
|
188
|
+
/** True for IPv4 loopback (first octet 127). `isProbeableHost` treats this as local; discovery does not. */
|
|
189
|
+
function isLoopbackIpv4(value) {
|
|
190
|
+
if (!isIpv4(value)) {
|
|
191
|
+
return false;
|
|
192
|
+
}
|
|
193
|
+
const [firstOctet = 0] = value.split('.').map(Number);
|
|
194
|
+
return firstOctet === 127;
|
|
195
|
+
}
|
|
172
196
|
/** Clamp the discovery window into the supported range. */
|
|
173
197
|
function resolveDiscoveryTimeoutSec(value, warnings) {
|
|
174
198
|
if (value === undefined) {
|
package/docs/FEATURES.md
CHANGED
|
@@ -42,7 +42,7 @@ A checklist of what is built. The plugin aims to cover everything about a BluOS
|
|
|
42
42
|
- ✅ An unusable configuration disables the platform and keeps every accessory registered. Nothing is deleted
|
|
43
43
|
- ✅ Per-device validation: one bad entry is skipped with a warning instead of stopping the rest
|
|
44
44
|
- ✅ Size-, depth-, element- and attribute-capped XML parsing, sanitised log output, and a length cap on any identity a player reports for itself
|
|
45
|
-
- ✅ Bounded discovery: the records kept from a browse, the candidates verified from it and the verifications in flight are all capped
|
|
45
|
+
- ✅ Bounded discovery: the records kept from a browse, the candidates verified from it and the verifications in flight are all capped. Public IPv4 and loopback advertisements are not verified
|
|
46
46
|
- ✅ Clean shutdown: poll loops, backoff delays and mDNS browses are cancelled, not left to run out
|
|
47
47
|
- ✅ A cached accessory that the plugin cannot drive reports No Response and says what to do about it. It never shows a stale value forever
|
|
48
48
|
- ✅ Custom Homebridge UI settings page, plus a plain `config.schema.json` form
|
package/docs/PROTOCOL.md
CHANGED
|
@@ -259,7 +259,7 @@ mDNS, browsing two service types:
|
|
|
259
259
|
| `_musc._tcp.local` | Primary players |
|
|
260
260
|
| `_musp._tcp.local` | Secondary zones of a multi-zone chassis (spec appendix §13.1, LSDP class `0x0003`) |
|
|
261
261
|
|
|
262
|
-
A zone is usable once its `SRV` record (for the port) and
|
|
262
|
+
A zone is usable once its `SRV` record (for the port) and a **local** IPv4 address are both known; the address comes from an `A` record when one is offered and otherwise from the responder's own source address. Public IPv4 and loopback advertisements are ignored, so a packet on the LAN cannot steer verify at Homebridge itself or at an off-network host. `TXT` records carry `model`, `version`, `mac` and `zs`, but secondary zones **omit `mac`**. That is why identity is always confirmed by reading `/SyncStatus`, and never trusted from the advertisement alone.
|
|
263
263
|
|
|
264
264
|
**LSDP** (UDP 11430) is documented as an alternative and was tried first: it failed repeatedly against this fleet, while mDNS answered reliably. mDNS is therefore the only discovery path, with manual address entry as the fallback for networks that filter multicast.
|
|
265
265
|
|
package/docs/README-DETAILED.md
CHANGED
|
@@ -60,7 +60,7 @@ Name it with `options.rebootAllName`. It is the one accessory with no room of it
|
|
|
60
60
|
|
|
61
61
|
## Reliability in detail
|
|
62
62
|
|
|
63
|
-
**Discovery in the settings page.** Finds zones over mDNS and writes the configuration for you, including the stable identity the platform will look for. Manual address entry covers networks where multicast is filtered.
|
|
63
|
+
**Discovery in the settings page.** Finds zones over mDNS and writes the configuration for you, including the stable identity the platform will look for. Only local IPv4 advertisements are verified (private, CGNAT/Tailscale, link-local). Public and loopback addresses are skipped. Manual address entry covers networks where multicast is filtered; the settings probe still allows loopback because you typed it.
|
|
64
64
|
|
|
65
65
|
**Long-polling, not polling.** `/SyncStatus?timeout=100` with an `etag`, so a change made on the front panel, the remote or the BluOS app reaches HomeKit in about a second, without hammering the player. One poll loop per zone, because the etag is per zone and not per chassis.
|
|
66
66
|
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "homebridge-bluos",
|
|
3
3
|
"displayName": "Homebridge BluOS",
|
|
4
|
-
"version": "2.0.
|
|
4
|
+
"version": "2.0.3",
|
|
5
5
|
"description": "Homebridge plugin for BluOS players — per-zone volume, mute, volume-preset and battery accessories over the LAN Custom Integration API. Verified on NAD and Bluesound hardware",
|
|
6
6
|
"main": "dist/index.js",
|
|
7
7
|
"types": "dist/index.d.ts",
|