The integration leaks the Blynk auth token (the credential entered at setup) into Home Assistant logs and persistent storage through four distinct paths. Found while doing log forensics on a live install (v1.0.6, current master matches).
1. Token logged in cleartext twice per poll at DEBUG — and DEBUG is force-enabled
blynk_service.py builds the request URL with the token as a query parameter and logs it verbatim:
url = self._get_request_url(f'external/api/get', params) + f"&{pin}" # params = {'token': self.token}
_LOGGER.debug(f"Request URL: {url}") # line 39, and again at line 66
Because blynk_service.py, climate.py, and entity.py all call _LOGGER.setLevel(logging.DEBUG) at module import (line 7 here), these lines are emitted on every install regardless of the user's logger: configuration defaults. With the coordinator's 60s poll interval, the token lands in the log ring buffer ~2,880 times/day, and from there into every log export, ha core logs capture, and diagnostics bundle. (Side effect: the DEBUG volume — ~30 lines/poll — can flush the entire HAOS log buffer, wiping unrelated log history.)
2. Token leaks at ERROR level when the Blynk endpoint is unreachable
blynk_service.py calls bare requests.get(url) with no exception handling. When dashboard.windmillair.com is unreachable (DNS failure, outage, offline LAN), requests.exceptions.ConnectionError's message embeds the full URL — token included:
HTTPConnectionPool(host='...'): Max retries exceeded with url: /external/api/get?token=<TOKEN>&V1 (...)
That propagates to coordinator.py:37-38:
_LOGGER.error(f"Error fetching data: {err}")
raise UpdateFailed(f"Error fetching data: {err}")
So even a user who has suppressed the DEBUG flood with logger: overrides still gets the token at ERROR level once per poll for as long as the endpoint is down (verified empirically on 1.0.6). The UpdateFailed message also surfaces in the config-entry "not ready" INFO lines.
3. Token persisted in unique_id and device identifiers
entity.py:
self._attr_unique_id = f"{DOMAIN}_{coordinator.blynk_service.token}_{entity_description.key}"
identifiers={(DOMAIN, self.unique_id)},
The raw token is written into core.entity_registry and core.device_registry, so it ships inside every HA backup and registry/diagnostics export. HA core also prints unique_ids in some of its own log lines (e.g. the duplicate-unique-ID error), which the integration cannot redact.
4. (Minor) requests.get has no timeout=
Not a leak, but a hung connection blocks the executor thread indefinitely. requests never times out by default.
Suggested fixes
-
Remove the three module-level _LOGGER.setLevel(logging.DEBUG) calls — let users opt into debug via logger:.
-
Never log the full request URL; log the endpoint/pin only, or redact the token (note the URL carries the percent-encoded token via urlencode, so redaction should cover both token and quote_plus(token)).
-
Wrap the requests.get calls in try/except requests.exceptions.RequestException and re-raise with the token redacted from the message (raise ... from None so the chained traceback doesn't reintroduce it). This is what I've patched locally and it cleanly closes path 2:
try:
response = requests.get(url, timeout=10)
except requests.exceptions.RequestException as err:
raise Exception(f"Request failed getting pin {pin}: {redact(err)}") from None
-
Derive unique_id from something non-secret — e.g. a hash of the token or the config entry id (entry.entry_id) — with a registry migration for existing installs.
-
Users affected by 1–3 should regenerate their token in the Windmill dashboard after upgrading, since existing logs/backups already contain it.
Happy to open a PR for any subset of this if useful.
The integration leaks the Blynk auth token (the credential entered at setup) into Home Assistant logs and persistent storage through four distinct paths. Found while doing log forensics on a live install (v1.0.6, current
mastermatches).1. Token logged in cleartext twice per poll at DEBUG — and DEBUG is force-enabled
blynk_service.pybuilds the request URL with the token as a query parameter and logs it verbatim:Because
blynk_service.py,climate.py, andentity.pyall call_LOGGER.setLevel(logging.DEBUG)at module import (line 7 here), these lines are emitted on every install regardless of the user'slogger:configuration defaults. With the coordinator's 60s poll interval, the token lands in the log ring buffer ~2,880 times/day, and from there into every log export,ha core logscapture, and diagnostics bundle. (Side effect: the DEBUG volume — ~30 lines/poll — can flush the entire HAOS log buffer, wiping unrelated log history.)2. Token leaks at ERROR level when the Blynk endpoint is unreachable
blynk_service.pycalls barerequests.get(url)with no exception handling. Whendashboard.windmillair.comis unreachable (DNS failure, outage, offline LAN),requests.exceptions.ConnectionError's message embeds the full URL — token included:That propagates to
coordinator.py:37-38:So even a user who has suppressed the DEBUG flood with
logger:overrides still gets the token at ERROR level once per poll for as long as the endpoint is down (verified empirically on 1.0.6). TheUpdateFailedmessage also surfaces in the config-entry "not ready" INFO lines.3. Token persisted in
unique_idand device identifiersentity.py:The raw token is written into
core.entity_registryandcore.device_registry, so it ships inside every HA backup and registry/diagnostics export. HA core also prints unique_ids in some of its own log lines (e.g. the duplicate-unique-ID error), which the integration cannot redact.4. (Minor)
requests.gethas notimeout=Not a leak, but a hung connection blocks the executor thread indefinitely.
requestsnever times out by default.Suggested fixes
Remove the three module-level
_LOGGER.setLevel(logging.DEBUG)calls — let users opt into debug vialogger:.Never log the full request URL; log the endpoint/pin only, or redact the token (note the URL carries the percent-encoded token via
urlencode, so redaction should cover bothtokenandquote_plus(token)).Wrap the
requests.getcalls intry/except requests.exceptions.RequestExceptionand re-raise with the token redacted from the message (raise ... from Noneso the chained traceback doesn't reintroduce it). This is what I've patched locally and it cleanly closes path 2:Derive
unique_idfrom something non-secret — e.g. a hash of the token or the config entry id (entry.entry_id) — with a registry migration for existing installs.Users affected by 1–3 should regenerate their token in the Windmill dashboard after upgrading, since existing logs/backups already contain it.
Happy to open a PR for any subset of this if useful.