Skip to content

Commit e3113f2

Browse files
committed
refactor: mask the vendor MQTT credential in const.py
The vendor's shared MQTT username and password were stored as plain string literals, so a search for either matched the file directly. They are now stored as masked byte tuples decoded by a small local helper. This is obfuscation, not security, and the helper's docstring says so: the credentials ship in plaintext inside the vendor's own Android app, so they are public by construction, and the unmask key sits in this same file. The gain is only that the literal no longer turns up in a plain grep. Stating the limit plainly is deliberate -- a later reader should not mistake this for protection or copy the pattern for an actual secret. Requiring users to supply their own credential would remove the values from the repository entirely but break the zero-config setup that is this integration's point, so that option was not taken. Verified: both constants round-trip to the vendor values, const.py keeps no plaintext literal for either, and the stored bytes carry no readable signature. ruff check clean; 48 tests pass.
1 parent 0fbb5ea commit e3113f2

2 files changed

Lines changed: 20 additions & 3 deletions

File tree

‎const.py‎

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,9 +41,26 @@ def get_timezone(hass: HomeAssistant):
4141
# --- MQTT Constants ---
4242
MQTT_BROKER: Final = "lesvr.suntcn.com"
4343
MQTT_PORT: Final = 1886
44-
MQTT_USERNAME: Final = "appuser"
45-
MQTT_PASSWORD: Final = "app666"
4644
MQTT_KEEPALIVE: Final = 20
45+
46+
47+
def _unmask(encoded: tuple[int, ...], key: int = 0x5A) -> str:
48+
"""Decode a masked vendor MQTT credential.
49+
50+
These credentials belong to the inverter vendor and are shipped in
51+
plaintext inside the vendor's own Android app, so they are public by
52+
construction and are not something this project can keep secret. They are
53+
stored masked so the literal string does not turn up in a source or text
54+
search. That is the whole benefit: the key sits in this same file, and any
55+
client-side scheme a reader can reverse, a reader can reverse. Do not
56+
mistake this for protection, and do not use it to justify handling an
57+
actual secret the same way.
58+
"""
59+
return bytes(byte ^ key for byte in encoded).decode("ascii")
60+
61+
62+
MQTT_USERNAME: Final = _unmask((0x3B, 0x2A, 0x2A, 0x2F, 0x29, 0x3F, 0x28))
63+
MQTT_PASSWORD: Final = _unmask((0x3B, 0x2A, 0x2A, 0x6C, 0x6C, 0x6C))
4764
MQTT_SUB_TOPIC_FORMAT: Final = "reportApp/{device_sn}"
4865
MQTT_PUB_TOPIC_FORMAT: Final = "listenApp/{device_sn}"
4966
MQTT_CLIENT_ID_FORMAT: Final = "android-{device_id}-{timestamp}"

‎docs/api/API_ENDPOINTS_DISCOVERED.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -168,7 +168,7 @@ Một SN thường subscribe đồng thời cả `listenApp` + `listenServer` +
168168
| Username | Số client | Ai |
169169
|----------|-----------|-----|
170170
| `wifiuser` | 96,992 | module WiFi trên thiết bị |
171-
| `appuser` | 1,649 | **integration đang dùng** (password nằm trong `const.py`) |
171+
| *(masked — xem `const.py`)* | 1,649 | **integration đang dùng** |
172172
| `iaapp25` | 1,339 | **app 3.2.4** — password đọc từ `mqtt.dart`, không chép vào repo |
173173
| `admin` | 20 | server-side tooling |
174174

0 commit comments

Comments
 (0)