Skip to content

fix(channel): carry config_secrets into the generated DRIVER header - #107

Draft
frahlg wants to merge 2 commits into
mainfrom
claude/hopeful-fermi-vsbatx
Draft

fix(channel): carry config_secrets into the generated DRIVER header#107
frahlg wants to merge 2 commits into
mainfrom
claude/hopeful-fermi-vsbatx

Conversation

@frahlg

@frahlg frahlg commented Sep 4, 2026

Copy link
Copy Markdown
Member

Summary

tools/ftw_repository.py prepends a generated DRIVER = { … } header ahead of a driver's own source block. FTW core parses only the first DRIVER block it finds (extractDriverBlock), so that generated header is the one that actually reaches a running box. It already copies id, version, capabilities, tested_models and a few other fields — but never config_secrets, the field that tells a host which config keys to mask in API responses and logs.

Confirmed live on myuplink (drivers-stable, driver-myuplink-v1.2.0-45cc4ad15760e4e8.lua): the generated header omits config_secrets, and GET /api/drivers/catalog / GET /api/config on a box running that channel build returned client_secret and refresh_token in clear text, even though the source's own DRIVER block further down (line 112 of that artifact) still correctly declares config_secrets = { "client_secret", "refresh_token" }.

The fix: read config_secrets from the driver's own DRIVER table (via the existing _string_list_field helper, same as tested_models) and carry it into the generated header when present. Four drivers declare the field today — myuplink, nibe_local, sonnen, tibber — and all four now get it in their published artifact.

Scope note: the issue (#106) also mentions write_capabilities and controls as possibly-missing fields. controls is already carried through the signed identity via the preserve_controls mechanism (see #85), and no driver declares write_capabilities yet, so there's nothing there to fix today — this PR stays scoped to the confirmed, currently-live config_secrets gap.

Fixes #106.

Driver evidence

Not applicable — this is a channel-build tooling fix (tools/ftw_repository.py), not a driver change. No Lua source, register map or manifest is touched.

Safety

  • Not a driver change; no read/write behavior on any device is affected.
  • No credentials, serial numbers, private addresses or site data are included (the affected secret namesclient_secret, refresh_token, etc. — are already public in the drivers' own source; no secret values appear anywhere in this diff).

Package or promotion evidence

Not applicable — no package version, target or control contract changes.

Checks

  • Commits include Signed-off-by.
  • uv run --frozen --extra package --extra dev python -m pytest tests/test_ftw_repository.py -q — 44 passed, 3 skipped (new regression test test_config_secrets_reach_the_generated_header included).
  • uv run --frozen --extra package --extra dev pytest -q drivers/tests tests — 4035 passed, 1309 skipped. (One pre-existing, unrelated failure — test_history_never_rewrites_a_published_version — reproduces identically on main with no changes in this session's shallow-clone sandbox; it needs full git history that a shallow clone doesn't have, and is not caused by this diff.)
  • make check — same shallow-clone-only failure as above (generate_history.py --check); every other step in the target (manifest sync/validate, index/devices/support-status generation, FTW-baseline check, host-API check, sandbox check) passes clean.

🤖 Generated with Claude Code

https://claude.ai/code/session_01UBX7xb1rd9ndEhCYUmZMjp


Generated by Claude Code

FTW core parses only the generated header tools/ftw_repository.py
prepends, never the source's own DRIVER block further down. That
header never copied config_secrets, so myuplink, nibe_local, sonnen
and tibber all published catalog entries with nothing declared to
mask -- confirmed live: GET /api/config on a box running the channel
build returned myuplink's client_secret and refresh_token in clear
text.

Fixes #106.

Signed-off-by: Fredrik Ahlgren <fredrik@sourceful-labs.com>
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UBX7xb1rd9ndEhCYUmZMjp
The config_secrets fix changes these four drivers' published artifact
bytes -- the signed channel now emits their config_secrets list in the
generated header -- so the channel's own version rule (checked in CI
as "signed channel accepts this tree") requires a new version before
it will publish the changed bytes. No Lua source changed beyond the
version field.

Signed-off-by: Fredrik Ahlgren <fredrik@sourceful-labs.com>
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UBX7xb1rd9ndEhCYUmZMjp

frahlg commented Sep 4, 2026

Copy link
Copy Markdown
Member Author

The signed channel accepts this tree check failed because this was expected, once I read the rule closely: config_secrets reaching the generated header is exactly the kind of metadata change _load_channel copies into the signed artifact, so the four drivers that declare it (myuplink, nibe_local, sonnen, tibber) all get changed published bytes — and the channel's own version rule refuses to republish changed bytes under an already-published version.

Pushed 3aa2151: patch bump for all four (myuplink 1.2.2, nibe_local 1.1.4, sonnen 2.0.2, tibber 1.1.1), CHANGELOG entry added, no Lua source touched beyond the version field. Reproduced the exact CI command locally against the published drivers-beta manifest — tools/ftw_repository.py check-versions now exits 0 and reports only those four as changed, which is what this fix is supposed to do. Full test suite green (4035 passed; the one unrelated failure is this sandbox's shallow git clone hitting generate_history.py --check, reproduces identically on unmodified main).


Generated by Claude Code

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.

Channel build prepends a DRIVER header that drops config_secrets, so the box masks nothing

2 participants