Skip to content

fix(logging): drop the account e-mail and device names from unattended records - #1370

Merged
jleinenbach merged 6 commits into
mainfrom
fix/log-personal-data
Oct 17, 2026
Merged

jleinenbach merged 6 commits into
mainfrom
fix/log-personal-data

Conversation

@jleinenbach

Copy link
Copy Markdown
Owner

Summary

Drop the account e-mail from every log record in NovaApi/nova_request.py, and keep device names and Google Home labels out of INFO records and the UpdateFailed text on unattended paths (they now name the device by its id; the name moves to a DEBUG companion record). Three DEBUG format strings get neutral wording so Semgrep's python-logger-credential-disclosure rule no longer matches them.

Motivation

AGENTS.md section 1 ("Log hygiene for scanners") and section 5 ("Never log") forbid the account e-mail on every log level and forbid logging a cache key name, because per-account keys embed the address. Section 5 (b) allows a device name above DEBUG only on paths a user triggers, not on unattended ones. Seven records in nova_request.py still carried the address (directly or as part of a key), and three records on the poll path still named the device at INFO or in the UpdateFailed text that Home Assistant logs at ERROR.


Change Log (human-readable)

Added

  • tests/test_log_hygiene_nova_account.py: static and runtime checks that no logger call in NovaApi/nova_request.py passes the account name or a key built from it (arguments, keyword values such as extra, and computed format arguments), that both namespaced-mirror records in _get_initial_token_async log only the exception type, and that the AAS lifetime records and the cache read failure name no account at runtime.
  • One new test each in tests/test_coordinator_semantic_mappings.py, tests/test_coordinator_status.py (both UpdateFailed slots) and tests/test_location_request_r6_name_sweep.py.

Changed

  • NovaApi/nova_request.py: the four AAS lifetime records (three INFO, one WARNING) no longer pass self.username; the cache read failure in async_on_401 and the two namespaced-mirror records log type(err).__name__ instead of the key, the exception text, extra={"cache_key": ...} and exc_info. Levels are unchanged.
  • coordinator/polling.py: the Google Home substitution record names the device by dev_id at INFO; name, id and label go to a new DEBUG record. Both poll-timeout UpdateFailed texts read Location request timed out for device <dev_id>.
  • NovaApi/ExecuteAction/LocateTracker/location_request.py: a cancelled locate request names the device by its canonical id at INFO, the name goes to DEBUG.
  • Neutral wording for three DEBUG format strings: No push registration for %s; skipping this locate, FCM registration ready for %s (length %d), Nova API: using the refreshed authorization for the request to %s.
  • tests/test_log_hygiene_coordinator_nova.py now also covers both Nova modules.
  • AGENTS.md names the new test and the two modules added to the existing one.
  • tests/test_coordinator_semantic_mappings.py gets its path header, and its entry leaves LEGACY_ALLOWLIST in tests/test_guard_path_header.py.

Fixed

  • Account e-mail in AAS lifetime, cache read and namespaced-mirror records.
  • Device name and Google Home label at INFO / in UpdateFailed on unattended paths.

Removed

  • Nothing.

Performance

  • No change.

Security/Privacy

  • Personal data (account e-mail, device names, Google Home labels) no longer reaches INFO or higher on unattended paths in the touched records. The account is dropped without a replacement label; with several accounts, the AAS records can no longer be attributed to one of them (deliberate trade-off).
  • Correction to the message of commit 032a4d22: it says a cancellation follows every outer poll timeout; it can follow one, but a timeout during the cache reads before the handler's try does not reach this record. The code comment says so since 79c4d0c1.

Compatibility

  • No breaking change, no version bump, no change to manifest.json. Anyone grepping the old log texts needs the new wording.

Tests (MUST)

  • ☑ pytest -q passes locally: 8796 passed, 19 skipped (908 s), total coverage 82.50 % (the threshold in pyproject.toml is 80 %).
  • ☑ New/updated unit tests cover the change; diff-cover against 61a04838: 100 % of the executable changed lines.
  • ☑ Regression tests fail without the fix: against 61a04838, 4 of the 5 tests in the new hygiene module fail (the vacuum guard passes), both UpdateFailed slot cases fail, the Google Home and cancellation tests fail.
  • Mutants (each run separately): putting self.username back, WARNING→INFO, key instead of the type name, exc_info=err back, extra back each fail at least one test; each UpdateFailed slot mutant fails exactly its own case; each new branch of the widened account predicate fails exactly its own form case.
  • Coverage targets: ☐ config_flow.py not touched by this PR. ☐ Total ≥ 95 % is not met (82.50 %), unchanged by this PR.

Token Cache Safety (MUST)

  • No change to cache keys, reads or writes; only log records and their tests change.

Docs & i18n

  • ☑ No hard-coded UI strings added.
  • ☐ No translation changes needed (log texts only; UpdateFailed has no translation_key anywhere in the package, not changed here).
  • AGENTS.md updated as described above.

Deprecation Check (concise)


Risk & Rollback

  • Risk level: ☑ low ☐ medium ☐ high
  • Rollback plan: revert the 6 commits of this branch (61a04838..79c4d0c1); no data, entity IDs or cache keys change.

Local Verification (run before pushing)

  • Project preflight (nine blocks, including ruff format --check): rc 0; 178 guard tests, Semgrep mirror and alias mutants green.
  • ruff format --check . and ruff check .: clean. mypy --strict over custom_components/googlefindmy and tests: 557 files, no issues.
  • Semgrep 1.180.0 on the three touched modules: python-logger-credential-disclosure 0 results (3 before).
  • pre-commit run --all-files: the same hooks fail on 61a04838 and on this branch, with the same bandit (3) and codespell (582) findings; the failing hooks are environment issues here (hook environments without pytest/mypy, legacy ruff alias). Nothing new from this diff.
  • script/local_verify.py --all --skip-pytest: no stage failed.
  • hassfest was not run locally (no Docker); CI runs it.

Reviewer Notes (optional)

  • Edge cases to double-check: the two namespaced-mirror records in _get_initial_token_async sit in # pragma: no cover branches; they are pinned statically, not at runtime.
  • Follow-up tasks: google_home_filter.py still logs the Google Home label at INFO and WARNING; eight older poll-path records above DEBUG still carry the device name (documented exception in AGENTS.md section 5 (b)).

🤖 Generated with Claude Code

jleinenbach and others added 6 commits October 16, 2026 20:16
AGENTS.md forbids the account e-mail on every log level and forbids logging
a cache key name, because per-account keys embed the address. Seven records
in NovaApi/nova_request.py carried it:

- the four AAS lifetime records of AsyncTTLPolicy (three INFO, one WARNING)
  passed self.username;
- the DEBUG record for a failed cache read in async_on_401 passed the key
  (adm_token_<email>) and the exception, whose text can repeat the key;
- the two DEBUG records for the namespaced mirror in _get_initial_token_async
  passed the key in `extra` and the exception as exc_info.

The account is dropped without replacement; levels are unchanged. The cache
read and mirror records now log only type(err).__name__. The new wording
avoids credential words in front of a %s.

tests/test_log_hygiene_nova_account.py pins this statically (no logger
argument or keyword value carries the account or a key built from it; both
mirror records pass no exc_info/extra) and at runtime (the AAS records and
the cache read failure name no account). Against the base, 4 of its 5 tests
fail; the vacuum guard passes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ecord

The Google Home filter on the unattended poll path logged the device name and
the Google Home label at INFO when it substituted Home coordinates. AGENTS.md
section 5 (b) allows a device name only at DEBUG on unattended paths.

The INFO record now names the device by its id; a DEBUG companion carries the
name, the id and the label. The sibling branch that preserves precise
coordinates already logs at DEBUG and is unchanged.

The new test in tests/test_coordinator_semantic_mappings.py fails against the
base (the INFO record carries the name, not the id). The file gets its path
header, and its entry leaves LEGACY_ALLOWLIST in tests/test_guard_path_header.py.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
When the push transport is down, a poll timeout raises
UpdateFailed("Location request timed out for <name>"), and Home Assistant
logs that text at ERROR through async_set_update_error. AGENTS.md section
5 (b) allows a device name only at DEBUG on unattended paths.

Both slots (last_exception and independent_error) now read
"Location request timed out for device <id>", the same wording as the INFO
record next to them. The slot logic is unchanged.

test_timeout_report_names_the_device_by_id covers both slots; against the
base both cases fail. Two docstrings that described the old text are brought
up to date; the measured statement in one of them is kept as it was.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A locate request is cancelled after every outer poll timeout, so the INFO
record "Location request cancelled for <name>" recurred on an unattended path
(three hits in one live log since 2026-10-15). AGENTS.md section 5 (b) allows
a device name only at DEBUG there.

The INFO record now names the device by its canonical id; a DEBUG companion
carries the name and the id. The re-raise is unchanged.

The new test in tests/test_location_request_r6_name_sweep.py injects
asyncio.CancelledError into the Nova request and fails against the base. The
helper _wire_nova_raises now accepts a BaseException for that purpose.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Semgrep's python-logger-credential-disclosure rule reports a format string
that carries "token" in front of a %s on its first source line, whatever the
argument is. Three DEBUG records matched it:

- location_request.py: "No FCM token for %s; skipping this locate" and
  "FCM token obtained for %s (len=%d)";
- nova_request.py: "Nova API: using refreshed token for request to %s.".

They now read "No push registration for %s; ...", "FCM registration ready for
%s (length %d)" and "Nova API: using the refreshed authorization for the
request to %s.". Levels and arguments are unchanged. Semgrep 1.180.0 reports
0 results on the three touched modules (3 before).

tests/test_log_hygiene_coordinator_nova.py now also covers both Nova modules;
its Semgrep mirror reports exactly these three lines against the previous
commit. AGENTS.md lists the two modules for that test.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Review follow-up for the log hygiene changes in this branch.

- tests/test_log_hygiene_nova_account.py: the static check now also looks
  at a format argument that is not a plain string literal (f-string, `%`
  expression), at per-account key properties (`self.k_*`) and at calls of
  `_key_variants`. A parametrised test pins that each claimed form is
  detected; dropping any of the three new branches fails its own case.
  A local alias stays out of reach and is listed in the docstring.
- AGENTS.md describes the check with these forms.
- polling.py: the comment now speaks about this record only; the Google Home
  filter module has its own records.
- location_request.py and the sweep docstring: an outer poll timeout can
  cancel the request while it waits in this handler, not on every timeout
  (a timeout during the cache reads before the `try` does not reach it).
- test_coordinator_status.py: "since this change" had no anchor after the
  merge; the docstring now says "now reads".

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@jleinenbach

Copy link
Copy Markdown
Owner Author

@codex review

@jleinenbach

Copy link
Copy Markdown
Owner Author

@codex security review

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 16, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-10-16T21:16:14.567078Z 79c4d0c Manual request
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. 🚀

Reviewed commit: 79c4d0c1c4

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@codecov

codecov Bot commented Oct 16, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@jleinenbach
jleinenbach merged commit 0257a3c into main Oct 17, 2026
28 checks passed
@jleinenbach
jleinenbach deleted the fix/log-personal-data branch October 17, 2026 12:07

This branch was successfully deployed

1 active deployment
ci-freigabe — 79c4d0c1 Deployed Oct 16, 2026 by jleinenbach via CI approval gate #3185
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant