Skip to content

Redact credentials from logs across Python and bash#528

Merged
tomquist merged 4 commits into
developfrom
claude/sleepy-gauss-d5olmu
Jun 25, 2026
Merged

Redact credentials from logs across Python and bash#528
tomquist merged 4 commits into
developfrom
claude/sleepy-gauss-d5olmu

Conversation

@tomquist

@tomquist tomquist commented Jun 25, 2026

Copy link
Copy Markdown
Owner

Summary

Add comprehensive credential redaction to prevent passwords, tokens, and other secrets from appearing in logs, regardless of how the application is launched (Home Assistant add-on, Docker, CLI, etc.). Redaction covers both Python logging output and bash/supervisor API responses.

Changes

  • Python logging: Introduce redact_secrets() function and _RedactingFormatter class that mask credentials in fully-rendered log lines (including tracebacks). Patterns cover:

    • URI userinfo (e.g., mqtt://user:pass@hostmqtt://***:***@host)
    • JSON-ish payloads with sensitive keys (password, token, secret, api_key, username, mailbox)
    • Inline key=value / key: value formats with sensitive key names
  • Integration: Install the redacting formatter on all root handlers when setLogLevel() is called, ensuring all logging passes through credential masking.

  • Bash redaction: Add a Python-based line filter in ha_addon/run.sh that applies the same redaction patterns to stdout/stderr before they reach the log. This covers supervisor API responses (MQTT broker password, add-on config values) that bashio echoes at debug level.

  • Tests: Add comprehensive test coverage for redact_secrets() with both sensitive and benign message examples, plus end-to-end tests verifying redaction works through the root logger and in traceback text.

Implementation Details

  • Redaction happens on the fully-rendered log line (message + traceback), so secrets cannot slip through via exception reprs.
  • The bash filter uses the same regex patterns as Python for consistency across all output sources.
  • The ASTRAMETER_NO_LOG_REDACT environment variable allows disabling bash redaction if needed (set by the script itself to avoid recursive filtering).
  • All patterns use case-insensitive matching for key names to catch variations like PASSWORD, password, marstek_password, etc.

https://claude.ai/code/session_01BfVkp21s7kFFcCEmMJh1JV

Summary by CodeRabbit

  • Bug Fixes
    • Sensitive values in logs are now automatically masked, including passwords, tokens, API keys, usernames, mailbox data, and credentials embedded in URLs.
    • Redaction also applies to exception and traceback output, reducing the chance of secrets appearing in debug logs.
    • Logging behavior is updated so redaction is applied consistently across output from the app and child processes.

tomquist and others added 3 commits June 25, 2026 06:46
bashio echoes full supervisor API responses at debug level, leaking the
MQTT broker password (GET /services/mqtt) and the add-on's own option
values such as the Marstek account email/password (GET /addons/self/info)
into the add-on log — visible to anyone the user shares a debug log with
(discussion #520).

Route all add-on output (and the inherited stdout of the Python app)
through a line-buffered Python filter that masks passwords, tokens,
secrets, usernames and mailbox values in JSON responses, plus credentials
embedded in scheme://user:pass@host URIs. The existing config-dump
redaction (print_redacted_config) is unaffected.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BfVkp21s7kFFcCEmMJh1JV
The run.sh stream filter only covers the Home Assistant add-on path;
standalone deployments (the root Dockerfile's `CMD ["astrameter"]` and
docker-compose) launch the app directly and bypassed it entirely.

Add an in-app redacting log formatter in config/logger.py so credentials
are masked on every deployment. It operates on the fully-rendered line,
so a secret can't slip through a message, a structured arg, or an
exception traceback. Masks URI userinfo (scheme://user:pass@host) and
password/token/secret/api-key/username/mailbox values in both JSON and
inline key=value / key: value forms.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BfVkp21s7kFFcCEmMJh1JV
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BfVkp21s7kFFcCEmMJh1JV
@coderabbitai

coderabbitai Bot commented Jun 25, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@tomquist, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 47 minutes and 28 seconds. Learn how PR review limits work.

Your organization has used up its prepaid credits, and credit purchases are no longer available. Enable the review add-on in the billing tab to keep reviews running — you're only billed for reviews past your plan's rate limits ($0.25/file).

⌛ How to resolve this issue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based credits.

🚦 How do rate limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: eb27b18b-15a7-4034-801b-0ed716c3922d

📥 Commits

Reviewing files that changed from the base of the PR and between f8c5955 and 518adf1.

📒 Files selected for processing (1)
  • ha_addon/run.sh

Walkthrough

This PR adds secret redaction to addon startup output and Python logging. It masks credential-like values in rendered log lines, traceback text, and streamed stdout/stderr, and adds tests for helper behavior and handler installation.

Changes

Secret redaction flow

Layer / File(s) Summary
Redaction helpers
src/astrameter/config/logger.py, src/astrameter/config/logger_test.py
redact_secrets() masks URI userinfo, key/value pairs, and JSON-like payloads, with unit tests for matching and non-matching inputs.
Handler formatting
src/astrameter/config/logger.py, src/astrameter/config/logger_test.py
setLogLevel() installs _RedactingFormatter on root handlers, and tests cover emitted log output and traceback text.
Addon stream filter
ha_addon/run.sh
run.sh routes stdout and stderr through a Python redaction filter when python3 is available and ASTRAMETER_NO_LOG_REDACT is unset.

Sequence Diagram

sequenceDiagram
  participant setLogLevel as setLogLevel()
  participant handlers as root logger handlers
  participant fmt as _RedactingFormatter
  participant redact as redact_secrets()
  setLogLevel->>handlers: install _RedactingFormatter
  handlers->>fmt: format LogRecord
  fmt->>redact: redact rendered line
  redact-->>fmt: masked output
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 33.33% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main change: credential redaction was added to both Python logging and the bash add-on script.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch claude/sleepy-gauss-d5olmu

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@tomquist tomquist marked this pull request as ready for review June 25, 2026 22:05
@github-actions

github-actions Bot commented Jun 25, 2026

Copy link
Copy Markdown
Contributor

Steering evaluation (base vs head)

Overall: 0 improved, 0 regressed, 15 unchanged across 15 metrics — mean 0% (unchanged).

Priority: priority-weighted 0% (unchanged) — ✅ no do-no-harm guardrail regressions.

Lower is better for every metric. See src/astrameter/simulator/evaluation.py for definitions.

Metrics are the per-scenario mean of 5 seeds.

Aggregate — mean across 31 scenarios

Metric Base Head Δ
settle_mean_s 36.3 36.3 =
settle_p95_s 63.5 63.5 =
unsettled_events 0.5 0.5 =
overshoot_mean_w 45.4 45.4 =
overshoot_max_w 97.7 97.7 =
band_crossings_per_h 306.8 306.8 =
grid_p2p_w 263.9 263.9 =
grid_rms_w 246.3 246.3 =
steady_rms_w 114.3 114.3 =
mean_abs_grid_w 97.8 97.8 =
share_imbalance_w 62.7 62.7 =
avoidable_import_wh 36.2 36.2 =
avoidable_export_wh 26.2 26.2 =
cost_regret_ct 0.85 0.85 =
battery_travel_w_per_h 23435.9 23435.9 =

📊 Interactive grid-power charts (zoom / hover / toggle series) are in the self-contained steering-eval-report.html report — see the link below (it opens directly in the browser).

What do these metrics mean?
Metric Meaning
settle_mean_s Mean seconds after a load/PV step for grid power to return inside the ±25 W settle band and hold for 10 s (reaction speed).
settle_p95_s 95th-percentile settle time — the slow tail of reactions.
unsettled_events Number of disturbance events that never settled within the 10-minute measurement window.
overshoot_mean_w Mean overshoot (W): how far grid power swings past zero to the opposite sign after an event.
overshoot_max_w Worst-case overshoot (W) across all events.
band_crossings_per_h Sign flips per hour across the ±20 W hysteresis band — oscillation / hunting frequency.
grid_p2p_w Sustained peak-to-peak grid swing (95th - 5th percentile) over the whole run — oscillation amplitude. Non-zero whenever the loop keeps hunting, including continuous oscillation the step-response metrics (settle/overshoot) miss.
grid_rms_w RMS grid power (W) over the whole run, transients included — the L2 tracking error: how cleanly the loop held zero, penalising big excursions (overshoot, swings) far harder than a small steady offset. Pairs with battery_travel_w_per_h as the control-effort term.
steady_rms_w RMS grid power (W) during steady state (excluding the 120 s after each event) — residual jitter when nothing is changing.
mean_abs_grid_w Mean absolute grid power (W) over the whole run — overall tracking accuracy.
share_imbalance_w Time-weighted watts misallocated between batteries sharing a phase (sum of each battery's deviation from the even fair share) — 0 when the pool splits load evenly, higher when one battery is left lopsided (issue #523). 0 for scenarios with at most one battery per phase.
avoidable_import_wh Energy imported from the grid (Wh) the battery could have supplied (it had charge and discharge headroom) — missed self-consumption.
avoidable_export_wh Energy exported to the grid (Wh) an AC-chargeable battery could have absorbed (it had room and charge headroom) — missed charging.
cost_regret_ct Money north-star: electricity bill (eurocents, import @ 30 ct/kWh, export @ 8 ct/kWh) over what a perfect-foresight optimal battery would have paid on the same load. Ungameable (both grid directions cost); 0 = matched the optimum. The single number that says how much the controller left on the table.
battery_travel_w_per_h Total absolute change in battery setpoints per hour (W/h) — control effort / actuator wear; lower is smoother.
Per-scenario tables (31 scenarios)
mixed_cadence/eff — settle 50.8→50.8s, overshoot 164.5→164.5W, RMS 21.9→21.9W
Metric Base Head Δ
settle_mean_s 50.8 50.8 =
settle_p95_s 72.4 72.4 =
unsettled_events 0.0 0.0 =
overshoot_mean_w 64.0 64.0 =
overshoot_max_w 164.5 164.5 =
band_crossings_per_h 24.4 24.4 =
grid_p2p_w 160.6 160.6 =
grid_rms_w 255.3 255.3 =
steady_rms_w 21.9 21.9 =
mean_abs_grid_w 64.1 64.1 =
share_imbalance_w 223.0 223.0 =
avoidable_import_wh 38.5 38.5 =
avoidable_export_wh 25.6 25.6 =
cost_regret_ct 0.95 0.95 =
battery_travel_w_per_h 31306.0 31306.0 =
mixed_cadence/fair — settle 47.7→47.7s, overshoot 70.4→70.4W, RMS 12.9→12.9W
Metric Base Head Δ
settle_mean_s 47.7 47.7 =
settle_p95_s 67.4 67.4 =
unsettled_events 0.0 0.0 =
overshoot_mean_w 30.7 30.7 =
overshoot_max_w 70.4 70.4 =
band_crossings_per_h 21.6 21.6 =
grid_p2p_w 131.9 131.9 =
grid_rms_w 246.0 246.0 =
steady_rms_w 12.9 12.9 =
mean_abs_grid_w 58.6 58.6 =
share_imbalance_w 101.3 101.3 =
avoidable_import_wh 33.7 33.7 =
avoidable_export_wh 24.9 24.9 =
cost_regret_ct 0.81 0.81 =
battery_travel_w_per_h 28457.4 28457.4 =
mixed_cadence_solar/eff — settle 52.5→52.5s, overshoot 384.7→384.7W, RMS 28.6→28.6W
Metric Base Head Δ
settle_mean_s 52.5 52.5 =
settle_p95_s 95.6 95.6 =
unsettled_events 2.0 2.0 =
overshoot_mean_w 62.4 62.4 =
overshoot_max_w 384.7 384.7 =
band_crossings_per_h 37.3 37.3 =
grid_p2p_w 192.3 192.3 =
grid_rms_w 268.4 268.4 =
steady_rms_w 28.6 28.6 =
mean_abs_grid_w 76.4 76.4 =
share_imbalance_w 148.1 148.1 =
avoidable_import_wh 60.5 60.5 =
avoidable_export_wh 54.1 54.1 =
cost_regret_ct 1.38 1.38 =
battery_travel_w_per_h 37320.0 37320.0 =
mixed_cadence_solar/fair — settle 56.0→56.0s, overshoot 75.4→75.4W, RMS 23.7→23.7W
Metric Base Head Δ
settle_mean_s 56.0 56.0 =
settle_p95_s 119.5 119.5 =
unsettled_events 1.8 1.8 =
overshoot_mean_w 28.7 28.7 =
overshoot_max_w 75.4 75.4 =
band_crossings_per_h 28.3 28.3 =
grid_p2p_w 147.8 147.8 =
grid_rms_w 261.6 261.6 =
steady_rms_w 23.7 23.7 =
mean_abs_grid_w 72.2 72.2 =
share_imbalance_w 119.0 119.0 =
avoidable_import_wh 56.0 56.0 =
avoidable_export_wh 52.4 52.4 =
cost_regret_ct 1.26 1.26 =
battery_travel_w_per_h 32435.8 32435.8 =
mixed_venus_b2500/eff — settle 81.2→81.2s, overshoot 221.9→221.9W, RMS 18.6→18.6W
Metric Base Head Δ
settle_mean_s 81.2 81.2 =
settle_p95_s 217.1 217.1 =
unsettled_events 2.4 2.4 =
overshoot_mean_w 100.2 100.2 =
overshoot_max_w 221.9 221.9 =
band_crossings_per_h 49.2 49.2 =
grid_p2p_w 67.3 67.3 =
grid_rms_w 197.5 197.5 =
steady_rms_w 18.6 18.6 =
mean_abs_grid_w 43.2 43.2 =
share_imbalance_w 263.0 263.0 =
avoidable_import_wh 44.2 44.2 =
avoidable_export_wh 20.5 20.5 =
cost_regret_ct 1.16 1.16 =
battery_travel_w_per_h 39704.6 39704.6 =
mixed_venus_b2500/fair — settle 75.4→75.4s, overshoot 231.1→231.1W, RMS 22.3→22.3W
Metric Base Head Δ
settle_mean_s 75.4 75.4 =
settle_p95_s 191.0 191.0 =
unsettled_events 1.4 1.4 =
overshoot_mean_w 110.5 110.5 =
overshoot_max_w 231.1 231.1 =
band_crossings_per_h 53.3 53.3 =
grid_p2p_w 67.1 67.1 =
grid_rms_w 189.9 189.9 =
steady_rms_w 22.3 22.3 =
mean_abs_grid_w 44.5 44.5 =
share_imbalance_w 100.7 100.7 =
avoidable_import_wh 47.4 47.4 =
avoidable_export_wh 19.4 19.4 =
cost_regret_ct 1.27 1.27 =
battery_travel_w_per_h 43475.4 43475.4 =
phase_imbalance — settle 53.4→53.4s, overshoot 145.2→145.2W, RMS 30.3→30.3W
Metric Base Head Δ
settle_mean_s 53.4 53.4 =
settle_p95_s 123.6 123.6 =
unsettled_events 0.0 0.0 =
overshoot_mean_w 64.9 64.9 =
overshoot_max_w 145.2 145.2 =
band_crossings_per_h 87.6 87.6 =
grid_p2p_w 42.4 42.4 =
grid_rms_w 199.6 199.6 =
steady_rms_w 30.3 30.3 =
mean_abs_grid_w 40.4 40.4 =
share_imbalance_w 0.0 0.0 =
avoidable_import_wh 25.6 25.6 =
avoidable_export_wh 14.8 14.8 =
cost_regret_ct 0.65 0.65 =
battery_travel_w_per_h 18274.2 18274.2 =
single_venus_d_solar — settle 24.2→24.2s, overshoot 94.4→94.4W, RMS 15.9→15.9W
Metric Base Head Δ
settle_mean_s 24.2 24.2 =
settle_p95_s 27.1 27.1 =
unsettled_events 0.0 0.0 =
overshoot_mean_w 76.8 76.8 =
overshoot_max_w 94.4 94.4 =
band_crossings_per_h 10.7 10.7 =
grid_p2p_w 29.9 29.9 =
grid_rms_w 104.3 104.3 =
steady_rms_w 15.9 15.9 =
mean_abs_grid_w 18.5 18.5 =
share_imbalance_w 0.0 0.0 =
avoidable_import_wh 17.9 17.9 =
avoidable_export_wh 9.9 9.9 =
cost_regret_ct 0.46 0.46 =
battery_travel_w_per_h 9425.4 9425.4 =
single_venus_d_steps — settle 26.3→26.3s, overshoot 90.3→90.3W, RMS 15.5→15.5W
Metric Base Head Δ
settle_mean_s 26.3 26.3 =
settle_p95_s 33.8 33.8 =
unsettled_events 0.0 0.0 =
overshoot_mean_w 73.3 73.3 =
overshoot_max_w 90.3 90.3 =
band_crossings_per_h 25.8 25.8 =
grid_p2p_w 30.9 30.9 =
grid_rms_w 256.4 256.4 =
steady_rms_w 15.5 15.5 =
mean_abs_grid_w 58.6 58.6 =
share_imbalance_w 0.0 0.0 =
avoidable_import_wh 32.3 32.3 =
avoidable_export_wh 26.2 26.2 =
cost_regret_ct 0.76 0.76 =
battery_travel_w_per_h 21425.2 21425.2 =
single_venus_d_washer — settle 0.0→0.0s, overshoot 0.0→0.0W, RMS 61.0→61.0W
Metric Base Head Δ
settle_mean_s 0.0 0.0 =
settle_p95_s 0.0 0.0 =
unsettled_events 0.0 0.0 =
overshoot_mean_w 0.0 0.0 =
overshoot_max_w 0.0 0.0 =
band_crossings_per_h 322.4 322.4 =
grid_p2p_w 192.7 192.7 =
grid_rms_w 61.0 61.0 =
steady_rms_w 61.0 61.0 =
mean_abs_grid_w 42.8 42.8 =
share_imbalance_w 0.0 0.0 =
avoidable_import_wh 13.3 13.3 =
avoidable_export_wh 8.1 8.1 =
cost_regret_ct 0.33 0.33 =
battery_travel_w_per_h 24204.4 24204.4 =
single_venus_drain — settle 0.0→0.0s, overshoot 0.0→0.0W, RMS 907.3→907.3W
Metric Base Head Δ
settle_mean_s 0.0 0.0 =
settle_p95_s 0.0 0.0 =
unsettled_events 0.0 0.0 =
overshoot_mean_w 0.0 0.0 =
overshoot_max_w 0.0 0.0 =
band_crossings_per_h 73.2 73.2 =
grid_p2p_w 1598.9 1598.9 =
grid_rms_w 907.3 907.3 =
steady_rms_w 907.3 907.3 =
mean_abs_grid_w 645.3 645.3 =
share_imbalance_w 0.0 0.0 =
avoidable_import_wh 14.4 14.4 =
avoidable_export_wh 10.2 10.2 =
cost_regret_ct 0.21 0.21 =
battery_travel_w_per_h 4062.0 4062.0 =
single_venus_fill — settle 360.0→360.0s, overshoot 0.0→0.0W, RMS 953.6→953.6W
Metric Base Head Δ
settle_mean_s 360.0 360.0 =
settle_p95_s 600.0 600.0 =
unsettled_events 4.0 4.0 =
overshoot_mean_w 0.0 0.0 =
overshoot_max_w 0.0 0.0 =
band_crossings_per_h 10.1 10.1 =
grid_p2p_w 1713.1 1713.1 =
grid_rms_w 978.6 978.6 =
steady_rms_w 953.6 953.6 =
mean_abs_grid_w 662.8 662.8 =
share_imbalance_w 0.0 0.0 =
avoidable_import_wh 4.8 4.8 =
avoidable_export_wh 6.7 6.7 =
cost_regret_ct 0.14 0.14 =
battery_travel_w_per_h 3173.0 3173.0 =
single_venus_noisy — settle 0.0→0.0s, overshoot 0.0→0.0W, RMS 94.3→94.3W
Metric Base Head Δ
settle_mean_s 0.0 0.0 =
settle_p95_s 0.0 0.0 =
unsettled_events 0.0 0.0 =
overshoot_mean_w 0.0 0.0 =
overshoot_max_w 0.0 0.0 =
band_crossings_per_h 1452.6 1452.6 =
grid_p2p_w 296.7 296.7 =
grid_rms_w 94.3 94.3 =
steady_rms_w 94.3 94.3 =
mean_abs_grid_w 79.1 79.1 =
share_imbalance_w 0.0 0.0 =
avoidable_import_wh 42.0 42.0 =
avoidable_export_wh 37.0 37.0 =
cost_regret_ct 0.96 0.96 =
battery_travel_w_per_h 22758.8 22758.8 =
single_venus_pv — settle 0.0→0.0s, overshoot 0.0→0.0W, RMS 60.8→60.8W
Metric Base Head Δ
settle_mean_s 0.0 0.0 =
settle_p95_s 0.0 0.0 =
unsettled_events 0.0 0.0 =
overshoot_mean_w 0.0 0.0 =
overshoot_max_w 0.0 0.0 =
band_crossings_per_h 38.5 38.5 =
grid_p2p_w 42.9 42.9 =
grid_rms_w 60.8 60.8 =
steady_rms_w 60.8 60.8 =
mean_abs_grid_w 17.3 17.3 =
share_imbalance_w 0.0 0.0 =
avoidable_import_wh 9.9 9.9 =
avoidable_export_wh 16.1 16.1 =
cost_regret_ct 0.17 0.17 =
battery_travel_w_per_h 6782.0 6782.0 =
single_venus_solar — settle 26.8→26.8s, overshoot 80.3→80.3W, RMS 17.8→17.8W
Metric Base Head Δ
settle_mean_s 26.8 26.8 =
settle_p95_s 32.6 32.6 =
unsettled_events 0.0 0.0 =
overshoot_mean_w 67.1 67.1 =
overshoot_max_w 80.3 80.3 =
band_crossings_per_h 26.3 26.3 =
grid_p2p_w 41.9 41.9 =
grid_rms_w 108.7 108.7 =
steady_rms_w 17.8 17.8 =
mean_abs_grid_w 21.2 21.2 =
share_imbalance_w 0.0 0.0 =
avoidable_import_wh 15.5 15.5 =
avoidable_export_wh 16.3 16.3 =
cost_regret_ct 0.34 0.34 =
battery_travel_w_per_h 7331.6 7331.6 =
single_venus_solar_slow — settle 33.9→33.9s, overshoot 68.3→68.3W, RMS 22.8→22.8W
Metric Base Head Δ
settle_mean_s 33.9 33.9 =
settle_p95_s 39.9 39.9 =
unsettled_events 0.0 0.0 =
overshoot_mean_w 32.1 32.1 =
overshoot_max_w 68.3 68.3 =
band_crossings_per_h 5.6 5.6 =
grid_p2p_w 60.6 60.6 =
grid_rms_w 131.7 131.7 =
steady_rms_w 22.8 22.8 =
mean_abs_grid_w 31.6 31.6 =
share_imbalance_w 0.0 0.0 =
avoidable_import_wh 21.3 21.3 =
avoidable_export_wh 26.1 26.1 =
cost_regret_ct 0.43 0.43 =
battery_travel_w_per_h 6486.0 6486.0 =
single_venus_steps — settle 26.0→26.0s, overshoot 88.0→88.0W, RMS 14.7→14.7W
Metric Base Head Δ
settle_mean_s 26.0 26.0 =
settle_p95_s 32.7 32.7 =
unsettled_events 0.0 0.0 =
overshoot_mean_w 69.7 69.7 =
overshoot_max_w 88.0 88.0 =
band_crossings_per_h 23.8 23.8 =
grid_p2p_w 27.1 27.1 =
grid_rms_w 266.9 266.9 =
steady_rms_w 14.7 14.7 =
mean_abs_grid_w 61.1 61.1 =
share_imbalance_w 0.0 0.0 =
avoidable_import_wh 34.1 34.1 =
avoidable_export_wh 27.0 27.0 =
cost_regret_ct 0.8 0.8 =
battery_travel_w_per_h 19543.6 19543.6 =
single_venus_steps_slow — settle 40.5→40.5s, overshoot 98.5→98.5W, RMS 14.8→14.8W
Metric Base Head Δ
settle_mean_s 40.5 40.5 =
settle_p95_s 56.2 56.2 =
unsettled_events 0.0 0.0 =
overshoot_mean_w 37.1 37.1 =
overshoot_max_w 98.5 98.5 =
band_crossings_per_h 9.4 9.4 =
grid_p2p_w 78.2 78.2 =
grid_rms_w 331.7 331.7 =
steady_rms_w 14.8 14.8 =
mean_abs_grid_w 88.2 88.2 =
share_imbalance_w 0.0 0.0 =
avoidable_import_wh 47.5 47.5 =
avoidable_export_wh 40.7 40.7 =
cost_regret_ct 1.1 1.1 =
battery_travel_w_per_h 17699.8 17699.8 =
single_venus_trace — settle 0.0→0.0s, overshoot 0.0→0.0W, RMS 278.9→278.9W
Metric Base Head Δ
settle_mean_s 0.0 0.0 =
settle_p95_s 0.0 0.0 =
unsettled_events 0.0 0.0 =
overshoot_mean_w 0.0 0.0 =
overshoot_max_w 0.0 0.0 =
band_crossings_per_h 284.8 284.8 =
grid_p2p_w 689.3 689.3 =
grid_rms_w 278.5 278.5 =
steady_rms_w 278.9 278.9 =
mean_abs_grid_w 117.0 117.0 =
share_imbalance_w 0.0 0.0 =
avoidable_import_wh 81.4 81.4 =
avoidable_export_wh 35.5 35.5 =
cost_regret_ct 1.36 1.36 =
battery_travel_w_per_h 33179.4 33179.4 =
single_venus_washer — settle 0.0→0.0s, overshoot 0.0→0.0W, RMS 61.0→61.0W
Metric Base Head Δ
settle_mean_s 0.0 0.0 =
settle_p95_s 0.0 0.0 =
unsettled_events 0.0 0.0 =
overshoot_mean_w 0.0 0.0 =
overshoot_max_w 0.0 0.0 =
band_crossings_per_h 320.4 320.4 =
grid_p2p_w 196.1 196.1 =
grid_rms_w 61.1 61.1 =
steady_rms_w 61.0 61.0 =
mean_abs_grid_w 40.6 40.6 =
share_imbalance_w 0.0 0.0 =
avoidable_import_wh 11.3 11.3 =
avoidable_export_wh 9.0 9.0 =
cost_regret_ct 0.27 0.27 =
battery_travel_w_per_h 24290.4 24290.4 =
two_venus/eff — settle 18.1→18.1s, overshoot 126.1→126.1W, RMS 14.0→14.0W
Metric Base Head Δ
settle_mean_s 18.1 18.1 =
settle_p95_s 23.0 23.0 =
unsettled_events 0.0 0.0 =
overshoot_mean_w 70.7 70.7 =
overshoot_max_w 126.1 126.1 =
band_crossings_per_h 33.6 33.6 =
grid_p2p_w 26.2 26.2 =
grid_rms_w 221.6 221.6 =
steady_rms_w 14.0 14.0 =
mean_abs_grid_w 43.1 43.1 =
share_imbalance_w 166.4 166.4 =
avoidable_import_wh 25.7 25.7 =
avoidable_export_wh 17.3 17.3 =
cost_regret_ct 0.64 0.64 =
battery_travel_w_per_h 22029.2 22029.2 =
two_venus/fair — settle 18.4→18.4s, overshoot 116.7→116.7W, RMS 13.8→13.8W
Metric Base Head Δ
settle_mean_s 18.4 18.4 =
settle_p95_s 24.4 24.4 =
unsettled_events 0.0 0.0 =
overshoot_mean_w 94.4 94.4 =
overshoot_max_w 116.7 116.7 =
band_crossings_per_h 21.6 21.6 =
grid_p2p_w 23.1 23.1 =
grid_rms_w 217.6 217.6 =
steady_rms_w 13.8 13.8 =
mean_abs_grid_w 41.4 41.4 =
share_imbalance_w 18.8 18.8 =
avoidable_import_wh 24.2 24.2 =
avoidable_export_wh 17.1 17.1 =
cost_regret_ct 0.59 0.59 =
battery_travel_w_per_h 19385.4 19385.4 =
two_venus_noisy/eff — settle 0.0→0.0s, overshoot 0.0→0.0W, RMS 94.3→94.3W
Metric Base Head Δ
settle_mean_s 0.0 0.0 =
settle_p95_s 0.0 0.0 =
unsettled_events 0.0 0.0 =
overshoot_mean_w 0.0 0.0 =
overshoot_max_w 0.0 0.0 =
band_crossings_per_h 2898.0 2898.0 =
grid_p2p_w 294.5 294.5 =
grid_rms_w 94.3 94.3 =
steady_rms_w 94.3 94.3 =
mean_abs_grid_w 79.4 79.4 =
share_imbalance_w 26.9 26.9 =
avoidable_import_wh 43.6 43.6 =
avoidable_export_wh 35.8 35.8 =
cost_regret_ct 1.02 1.02 =
battery_travel_w_per_h 23402.6 23402.6 =
two_venus_noisy/fair — settle 0.0→0.0s, overshoot 0.0→0.0W, RMS 94.2→94.2W
Metric Base Head Δ
settle_mean_s 0.0 0.0 =
settle_p95_s 0.0 0.0 =
unsettled_events 0.0 0.0 =
overshoot_mean_w 0.0 0.0 =
overshoot_max_w 0.0 0.0 =
band_crossings_per_h 2893.6 2893.6 =
grid_p2p_w 294.1 294.1 =
grid_rms_w 94.2 94.2 =
steady_rms_w 94.2 94.2 =
mean_abs_grid_w 79.4 79.4 =
share_imbalance_w 26.4 26.4 =
avoidable_import_wh 43.6 43.6 =
avoidable_export_wh 35.8 35.8 =
cost_regret_ct 1.02 1.02 =
battery_travel_w_per_h 22778.8 22778.8 =
two_venus_slow/fair — settle 41.8→41.8s, overshoot 174.5→174.5W, RMS 14.0→14.0W
Metric Base Head Δ
settle_mean_s 41.8 41.8 =
settle_p95_s 52.8 52.8 =
unsettled_events 0.0 0.0 =
overshoot_mean_w 56.6 56.6 =
overshoot_max_w 174.5 174.5 =
band_crossings_per_h 10.4 10.4 =
grid_p2p_w 81.8 81.8 =
grid_rms_w 303.7 303.7 =
steady_rms_w 14.0 14.0 =
mean_abs_grid_w 76.9 76.9 =
share_imbalance_w 24.4 24.4 =
avoidable_import_wh 41.5 41.5 =
avoidable_export_wh 35.3 35.3 =
cost_regret_ct 0.96 0.96 =
battery_travel_w_per_h 18563.6 18563.6 =
two_venus_solar/eff — settle 26.0→26.0s, overshoot 396.6→396.6W, RMS 20.4→20.4W
Metric Base Head Δ
settle_mean_s 26.0 26.0 =
settle_p95_s 44.3 44.3 =
unsettled_events 1.6 1.6 =
overshoot_mean_w 100.5 100.5 =
overshoot_max_w 396.6 396.6 =
band_crossings_per_h 40.4 40.4 =
grid_p2p_w 60.2 60.2 =
grid_rms_w 228.7 228.7 =
steady_rms_w 20.4 20.4 =
mean_abs_grid_w 51.9 51.9 =
share_imbalance_w 69.3 69.3 =
avoidable_import_wh 42.4 42.4 =
avoidable_export_wh 35.4 35.4 =
cost_regret_ct 0.99 0.99 =
battery_travel_w_per_h 26336.0 26336.0 =
two_venus_solar/fair — settle 25.9→25.9s, overshoot 151.4→151.4W, RMS 20.4→20.4W
Metric Base Head Δ
settle_mean_s 25.9 25.9 =
settle_p95_s 52.1 52.1 =
unsettled_events 1.4 1.4 =
overshoot_mean_w 98.1 98.1 =
overshoot_max_w 151.4 151.4 =
band_crossings_per_h 30.7 30.7 =
grid_p2p_w 59.8 59.8 =
grid_rms_w 225.6 225.6 =
steady_rms_w 20.4 20.4 =
mean_abs_grid_w 51.1 51.1 =
share_imbalance_w 31.3 31.3 =
avoidable_import_wh 40.1 40.1 =
avoidable_export_wh 36.5 36.5 =
cost_regret_ct 0.91 0.91 =
battery_travel_w_per_h 24206.8 24206.8 =
two_venus_trace/eff — settle 0.0→0.0s, overshoot 0.0→0.0W, RMS 283.1→283.1W
Metric Base Head Δ
settle_mean_s 0.0 0.0 =
settle_p95_s 0.0 0.0 =
unsettled_events 0.0 0.0 =
overshoot_mean_w 0.0 0.0 =
overshoot_max_w 0.0 0.0 =
band_crossings_per_h 307.6 307.6 =
grid_p2p_w 744.9 744.9 =
grid_rms_w 282.1 282.1 =
steady_rms_w 283.1 283.1 =
mean_abs_grid_w 122.0 122.0 =
share_imbalance_w 398.3 398.3 =
avoidable_import_wh 80.0 80.0 =
avoidable_export_wh 42.0 42.0 =
cost_regret_ct 2.06 2.06 =
battery_travel_w_per_h 47999.0 47999.0 =
two_venus_trace/fair — settle 0.0→0.0s, overshoot 0.0→0.0W, RMS 282.1→282.1W
Metric Base Head Δ
settle_mean_s 0.0 0.0 =
settle_p95_s 0.0 0.0 =
unsettled_events 0.0 0.0 =
overshoot_mean_w 0.0 0.0 =
overshoot_max_w 0.0 0.0 =
band_crossings_per_h 310.0 310.0 =
grid_p2p_w 732.2 732.2 =
grid_rms_w 281.1 281.1 =
steady_rms_w 282.1 282.1 =
mean_abs_grid_w 121.1 121.1 =
share_imbalance_w 30.3 30.3 =
avoidable_import_wh 79.0 79.0 =
avoidable_export_wh 42.0 42.0 =
cost_regret_ct 2.04 2.04 =
battery_travel_w_per_h 46054.4 46054.4 =
venus_d_plus_c/eff — settle 20.1→20.1s, overshoot 128.9→128.9W, RMS 14.7→14.7W
Metric Base Head Δ
settle_mean_s 20.1 20.1 =
settle_p95_s 31.9 31.9 =
unsettled_events 0.0 0.0 =
overshoot_mean_w 78.9 78.9 =
overshoot_max_w 128.9 128.9 =
band_crossings_per_h 35.2 35.2 =
grid_p2p_w 28.0 28.0 =
grid_rms_w 214.7 214.7 =
steady_rms_w 14.7 14.7 =
mean_abs_grid_w 41.6 41.6 =
share_imbalance_w 167.3 167.3 =
avoidable_import_wh 24.9 24.9 =
avoidable_export_wh 16.7 16.7 =
cost_regret_ct 0.61 0.61 =
battery_travel_w_per_h 23208.0 23208.0 =
venus_d_plus_c/fair — settle 21.6→21.6s, overshoot 121.0→121.0W, RMS 14.6→14.6W
Metric Base Head Δ
settle_mean_s 21.6 21.6 =
settle_p95_s 32.5 32.5 =
unsettled_events 0.0 0.0 =
overshoot_mean_w 90.6 90.6 =
overshoot_max_w 121.0 121.0 =
band_crossings_per_h 24.8 24.8 =
grid_p2p_w 27.4 27.4 =
grid_rms_w 211.5 211.5 =
steady_rms_w 14.6 14.6 =
mean_abs_grid_w 40.8 40.8 =
share_imbalance_w 30.2 30.2 =
avoidable_import_wh 24.1 24.1 =
avoidable_export_wh 16.7 16.7 =
cost_regret_ct 0.59 0.59 =
battery_travel_w_per_h 21212.8 21212.8 =

📊 Open the interactive reportsteering-eval-report.html, a single self-contained file (opens in-browser; download it if your browser blocks inline scripts).

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
ha_addon/run.sh (1)

25-29: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Filter can crash on a single bad byte and take down all logging (and possibly the addon).

sys.stdin is opened in text mode with the locale encoding. A non-UTF-8 byte anywhere in the merged stream raises UnicodeDecodeError, the filter exits, and the upstream writers (this script and the inherited Python app at line 34) then hit a broken pipe / SIGPIPE — losing all subsequent output and potentially terminating the process. For a wrapper whose whole job is to stay in the log path, make it decode-tolerant and resilient.

🛡️ Make the stream tolerant of bad bytes
 import re, sys
+
+try:
+    sys.stdin.reconfigure(errors="replace")
+    sys.stdout.reconfigure(errors="replace")
+except AttributeError:
+    pass
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@ha_addon/run.sh` around lines 25 - 29, The stream filter in the
stdin-to-stdout loop is too fragile because `sys.stdin` text decoding can raise
on a bad byte and kill the logging path. Update the `run.sh` Python filter logic
around the `for line in iter(sys.stdin.readline, '')` loop so it tolerates
undecodable input and keeps forwarding output, using a resilient decode strategy
and safe handling in the `PATTERNS` substitution/write path. Make sure the
wrapper continues processing subsequent lines instead of exiting on a single
malformed byte.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@ha_addon/run.sh`:
- Around line 14-23: The config redaction patterns in print_redacted_config() do
not cover MQTT USERNAME, so it can still appear in the shared logs. Update the
PATTERNS list in run.sh to redact USERNAME alongside the existing MAILBOX,
PASSWORD, ACCESSTOKEN, TOKEN, and SECRET matches, ensuring the config dump masks
any USERNAME=... entries before logging.

---

Nitpick comments:
In `@ha_addon/run.sh`:
- Around line 25-29: The stream filter in the stdin-to-stdout loop is too
fragile because `sys.stdin` text decoding can raise on a bad byte and kill the
logging path. Update the `run.sh` Python filter logic around the `for line in
iter(sys.stdin.readline, '')` loop so it tolerates undecodable input and keeps
forwarding output, using a resilient decode strategy and safe handling in the
`PATTERNS` substitution/write path. Make sure the wrapper continues processing
subsequent lines instead of exiting on a single malformed byte.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: c1ecb448-b272-4b9d-8828-835f9cc79c59

📥 Commits

Reviewing files that changed from the base of the PR and between ba42b9c and f8c5955.

📒 Files selected for processing (3)
  • ha_addon/run.sh
  • src/astrameter/config/logger.py
  • src/astrameter/config/logger_test.py

Comment thread ha_addon/run.sh
Address CodeRabbit review on #528:
- The stdout/stderr redaction filter read stdin in the locale encoding, so
  a non-UTF-8 byte (likely under a C/POSIX locale) would raise
  UnicodeDecodeError, kill the filter and break the log path. Reconfigure
  stdin/stdout as UTF-8 with errors="replace" so one bad byte can't take
  logging down.
- print_redacted_config() masked MAILBOX/PASSWORD/ACCESSTOKEN/TOKEN/SECRET
  but not USERNAME, so the MQTT USERNAME= line in the config dump still
  reached the log. Add USERNAME to the redaction set.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BfVkp21s7kFFcCEmMJh1JV
@tomquist tomquist merged commit 54e0c9d into develop Jun 25, 2026
9 checks passed
@tomquist tomquist deleted the claude/sleepy-gauss-d5olmu branch June 25, 2026 22:17
tomquist added a commit that referenced this pull request Jun 27, 2026
* Redact secrets from Home Assistant add-on logs

bashio echoes full supervisor API responses at debug level, leaking the
MQTT broker password (GET /services/mqtt) and the add-on's own option
values such as the Marstek account email/password (GET /addons/self/info)
into the add-on log — visible to anyone the user shares a debug log with
(discussion #520).

Route all add-on output (and the inherited stdout of the Python app)
through a line-buffered Python filter that masks passwords, tokens,
secrets, usernames and mailbox values in JSON responses, plus credentials
embedded in scheme://user:pass@host URIs. The existing config-dump
redaction (print_redacted_config) is unaffected.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BfVkp21s7kFFcCEmMJh1JV

* Redact secrets in the app's own logging, not just the add-on

The run.sh stream filter only covers the Home Assistant add-on path;
standalone deployments (the root Dockerfile's `CMD ["astrameter"]` and
docker-compose) launch the app directly and bypassed it entirely.

Add an in-app redacting log formatter in config/logger.py so credentials
are masked on every deployment. It operates on the fully-rendered line,
so a secret can't slip through a message, a structured arg, or an
exception traceback. Masks URI userinfo (scheme://user:pass@host) and
password/token/secret/api-key/username/mailbox values in both JSON and
inline key=value / key: value forms.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BfVkp21s7kFFcCEmMJh1JV

* Drop changelog entry for log redaction

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BfVkp21s7kFFcCEmMJh1JV

* Harden add-on log redaction: decode-tolerant filter, mask USERNAME

Address CodeRabbit review on #528:
- The stdout/stderr redaction filter read stdin in the locale encoding, so
  a non-UTF-8 byte (likely under a C/POSIX locale) would raise
  UnicodeDecodeError, kill the filter and break the log path. Reconfigure
  stdin/stdout as UTF-8 with errors="replace" so one bad byte can't take
  logging down.
- print_redacted_config() masked MAILBOX/PASSWORD/ACCESSTOKEN/TOKEN/SECRET
  but not USERNAME, so the MQTT USERNAME= line in the config dump still
  reached the log. Add USERNAME to the redaction set.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BfVkp21s7kFFcCEmMJh1JV

---------

Co-authored-by: tomquist <528585+tomquist@users.noreply.github.com>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
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