From 3364a794201242ddc23ed0ee9430502778d42f28 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 4 Sep 2026 07:09:52 +0000 Subject: [PATCH 1/2] fix(channel): carry config_secrets into the generated DRIVER header 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 Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_01UBX7xb1rd9ndEhCYUmZMjp --- CHANGELOG.md | 4 ++++ tests/test_ftw_repository.py | 15 +++++++++++++++ tools/ftw_repository.py | 10 ++++++++++ 3 files changed, 29 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9205608..130dbab 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,10 @@ Driver versions follow [Semantic Versioning](https://semver.org/spec/v2.0.0.html ## [Unreleased] +### Fixed + +- **The channel build now carries `config_secrets` into the generated header, so a box no longer masks nothing.** `tools/ftw_repository.py` prepends a generated `DRIVER = { … }` block ahead of the source's own, and FTW core parses only the first block it finds (`extractDriverBlock`). The generated block never copied `config_secrets`, so any driver declaring it — `myuplink`, `nibe_local`, `sonnen`, `tibber` — published a catalog entry with nothing to mask: `GET /api/config` on a box running the channel build returned `myuplink`'s `client_secret` and `refresh_token` in clear text, confirmed on a live installation. The source block further down still declared the field correctly, which is exactly why nothing caught this in a source-level review. `test_config_secrets_reach_the_generated_header` holds the generated header to it. Fixes #106. + ### Changed - **easee_cloud** 1.2.0 — emit `request_active`: false only when the vehicle diff --git a/tests/test_ftw_repository.py b/tests/test_ftw_repository.py index 9199915..f88455b 100644 --- a/tests/test_ftw_repository.py +++ b/tests/test_ftw_repository.py @@ -731,6 +731,21 @@ def test_nested_or_commented_controls_do_not_change_a_control_artifact( assert "__sourceful_ftw_controls" not in entry["raw"].decode("utf-8") +def test_config_secrets_reach_the_generated_header(tmp_path: Path) -> None: + # FTW core parses only the generated header this tool prepends, never the + # source's own DRIVER block further down (srcfl/device-drivers#106) -- a + # secret name missing from the header is a name nothing on the host ever + # masks, and myuplink's client_secret/refresh_token shipped in the clear + # because of exactly that gap. + repo, config_path = single_driver_repo(tmp_path, "myuplink") + + entry = _load_channel(config_path, repo)[0] + + assert entry["metadata"]["config_secrets"] == ["client_secret", "refresh_token"] + header = entry["raw"].decode("utf-8").split("DRIVER = __sourceful_ftw_metadata", 1)[0] + assert 'config_secrets = { "client_secret", "refresh_token" },' in header + + def test_driver_locator_skips_non_module_decoys() -> None: source = '''-- DRIVER = { id = "commented" } --[=[ DRIVER = { id = "long-commented" } ]=] diff --git a/tools/ftw_repository.py b/tools/ftw_repository.py index b8474ae..75bdc24 100644 --- a/tools/ftw_repository.py +++ b/tools/ftw_repository.py @@ -414,6 +414,7 @@ def _ftw_artifact( protocols = metadata.get("protocols", []) capabilities = metadata.get("capabilities", []) tested_models = metadata.get("tested_models", []) + config_secrets = metadata.get("config_secrets", []) fields = [ f" id = {_lua_string(metadata['id'])},", f" name = {_lua_string(metadata['name'])},", @@ -437,6 +438,8 @@ def _ftw_artifact( fields.append(f" {name} = {_lua_string(value)},") if tested_models: fields.append(f" tested_models = {_lua_string_list(tested_models)},") + if config_secrets: + fields.append(f" config_secrets = {_lua_string_list(config_secrets)},") # A driver that declares itself read-only keeps the guards: those are # meters and telemetry gateways stating what they are. A driver the catalog # marks control: true keeps the control path it was ported with. @@ -714,6 +717,13 @@ def _load_channel(config_path: Path, repo_root: Path) -> list[dict[str, Any]]: value = _string_field(body, output_name) if value: metadata[output_name] = value + # config_secrets names which config keys a host must mask (API + # responses, logs). FTW core parses only the generated header + # below, never the source's own DRIVER block, so a name missing + # here is a name nothing on the host ever masks. + config_secrets = _string_list_field(body, "config_secrets") + if config_secrets: + metadata["config_secrets"] = config_secrets if auth_post_path and not controls: metadata["auth_post_path"] = auth_post_path From 3aa21518dbfaf1c0355a68bab22172d4139a5782 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 4 Sep 2026 07:12:57 +0000 Subject: [PATCH 2/2] chore: bump myuplink, nibe_local, sonnen, tibber for config_secrets fix 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 Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_01UBX7xb1rd9ndEhCYUmZMjp --- CHANGELOG.md | 1 + SUPPORT_STATUS.md | 16 ++++++++-------- devices.yaml | 8 ++++---- drivers/lua/myuplink.lua | 2 +- drivers/lua/nibe_local.lua | 2 +- index.yaml | 12 ++++++------ manifests/myuplink.yaml | 4 ++-- manifests/nibe_local.yaml | 4 ++-- manifests/sonnen.yaml | 2 +- manifests/tibber.yaml | 2 +- support-status.json | 8 ++++---- 11 files changed, 31 insertions(+), 30 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 130dbab..2f78fd4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ Driver versions follow [Semantic Versioning](https://semver.org/spec/v2.0.0.html ### Fixed - **The channel build now carries `config_secrets` into the generated header, so a box no longer masks nothing.** `tools/ftw_repository.py` prepends a generated `DRIVER = { … }` block ahead of the source's own, and FTW core parses only the first block it finds (`extractDriverBlock`). The generated block never copied `config_secrets`, so any driver declaring it — `myuplink`, `nibe_local`, `sonnen`, `tibber` — published a catalog entry with nothing to mask: `GET /api/config` on a box running the channel build returned `myuplink`'s `client_secret` and `refresh_token` in clear text, confirmed on a live installation. The source block further down still declared the field correctly, which is exactly why nothing caught this in a source-level review. `test_config_secrets_reach_the_generated_header` holds the generated header to it. Fixes #106. +- **myuplink** 1.2.2, **nibe_local** 1.1.4, **sonnen** 2.0.2, **tibber** 1.1.1 — patch bump only. The `config_secrets` fix above changes these four drivers' published artifact bytes (the signed channel now emits their `config_secrets` list in the generated header), so the signed channel's own version rule requires a new version to publish it under. No Lua source changed. ### Changed diff --git a/SUPPORT_STATUS.md b/SUPPORT_STATUS.md index 531ced2..189d28e 100644 --- a/SUPPORT_STATUS.md +++ b/SUPPORT_STATUS.md @@ -96,10 +96,10 @@ Catalog source is not proof that a target can install or run a driver. | kstar | 1.1.1 | blixt-l1 | not_assessed | — | — | not_recorded | — | not_assessed | no | | mennekes | 1.0.3 | ftw-core | not_assessed | — | — | not_recorded | — | not_assessed | no | | mennekes | 1.0.3 | blixt-l1 | not_assessed | — | — | not_recorded | — | not_assessed | no | -| myuplink | 1.2.1 | ftw-core | not_assessed | — | — | not_recorded | — | not_assessed | no | -| myuplink | 1.2.1 | blixt-l1 | not_assessed | — | — | not_recorded | — | not_assessed | no | -| nibe_local | 1.1.3 | ftw-core | not_assessed | — | — | not_recorded | — | not_assessed | no | -| nibe_local | 1.1.3 | blixt-l1 | not_assessed | — | — | not_recorded | — | not_assessed | no | +| myuplink | 1.2.2 | ftw-core | not_assessed | — | — | not_recorded | — | not_assessed | no | +| myuplink | 1.2.2 | blixt-l1 | not_assessed | — | — | not_recorded | — | not_assessed | no | +| nibe_local | 1.1.4 | ftw-core | not_assessed | — | — | not_recorded | — | not_assessed | no | +| nibe_local | 1.1.4 | blixt-l1 | not_assessed | — | — | not_recorded | — | not_assessed | no | | opendtu | 1.0.2 | ftw-core | not_assessed | — | — | not_recorded | — | not_assessed | no | | opendtu | 1.0.2 | blixt-l1 | not_assessed | — | — | not_recorded | — | not_assessed | no | | opendtu_mqtt | 1.0.3 | ftw-core | not_assessed | — | — | not_recorded | — | not_assessed | no | @@ -154,14 +154,14 @@ Catalog source is not proof that a target can install or run a driver. | solis | 2.1.1 | blixt-l1 | not_assessed | — | — | not_recorded | — | not_assessed | no | | solis_string | 1.1.1 | ftw-core | not_assessed | — | — | not_recorded | — | not_assessed | no | | solis_string | 1.1.1 | blixt-l1 | not_assessed | — | — | not_recorded | — | not_assessed | no | -| sonnen | 2.0.1 | ftw-core | not_assessed | — | — | not_recorded | — | not_assessed | no | -| sonnen | 2.0.1 | blixt-l1 | not_assessed | — | — | not_recorded | — | not_assessed | no | +| sonnen | 2.0.2 | ftw-core | not_assessed | — | — | not_recorded | — | not_assessed | no | +| sonnen | 2.0.2 | blixt-l1 | not_assessed | — | — | not_recorded | — | not_assessed | no | | sungrow | 1.5.8 | ftw-core | observe_only_profile_blocked | 1.3.3 | 1.3.0 | exact_model_firmware_required | — | not_assessed | no | | sungrow | 1.5.8 | blixt-l1 | not_assessed | — | — | not_recorded | — | not_assessed | no | | tesla_vehicle | 0.2.1 | ftw-core | not_assessed | — | — | not_recorded | — | not_assessed | no | | tesla_vehicle | 0.2.1 | blixt-l1 | not_assessed | — | — | not_recorded | — | not_assessed | no | -| tibber | 1.1.0 | ftw-core | not_assessed | — | — | not_recorded | — | not_assessed | no | -| tibber | 1.1.0 | blixt-l1 | not_assessed | — | — | not_recorded | — | not_assessed | no | +| tibber | 1.1.1 | ftw-core | not_assessed | — | — | not_recorded | — | not_assessed | no | +| tibber | 1.1.1 | blixt-l1 | not_assessed | — | — | not_recorded | — | not_assessed | no | | varta | 1.1.1 | ftw-core | not_assessed | — | — | not_recorded | — | not_assessed | no | | varta | 1.1.1 | blixt-l1 | not_assessed | — | — | not_recorded | — | not_assessed | no | | victron | 2.1.1 | ftw-core | not_assessed | — | — | not_recorded | — | not_assessed | no | diff --git a/devices.yaml b/devices.yaml index 6780733..b12e82f 100644 --- a/devices.yaml +++ b/devices.yaml @@ -967,7 +967,7 @@ manufacturers: protocols: - protocol: http driver: "myuplink" - version: "1.2.1" + version: "1.2.2" ders: [heatpump] control: false firmware_versions: "" @@ -993,7 +993,7 @@ manufacturers: protocols: - protocol: http driver: "nibe_local" - version: "1.1.3" + version: "1.1.4" ders: [heatpump] control: false firmware_versions: "" @@ -1817,7 +1817,7 @@ manufacturers: protocols: - protocol: http driver: "tibber" - version: "1.1.0" + version: "1.1.1" ders: [meter] control: false firmware_versions: "" @@ -2092,7 +2092,7 @@ manufacturers: protocols: - protocol: http driver: "sonnen" - version: "2.0.1" + version: "2.0.2" ders: [battery, meter] control: false firmware_versions: "" diff --git a/drivers/lua/myuplink.lua b/drivers/lua/myuplink.lua index 8cccdbd..2a6f1f7 100644 --- a/drivers/lua/myuplink.lua +++ b/drivers/lua/myuplink.lua @@ -48,7 +48,7 @@ DRIVER = { id = "myuplink", name = "MyUplink Heat Pump (telemetry)", manufacturer = "MyUplink (NIBE, Bosch, Atlantic, Daikin, ...)", - version = "1.2.1", + version = "1.2.2", protocols = { "http" }, capabilities = { "apicreds" }, -- Says what the header, the description and driver_command have always diff --git a/drivers/lua/nibe_local.lua b/drivers/lua/nibe_local.lua index 5dee250..f5f12cc 100644 --- a/drivers/lua/nibe_local.lua +++ b/drivers/lua/nibe_local.lua @@ -57,7 +57,7 @@ DRIVER = { id = "nibe-local", name = "NIBE REST API S-series", manufacturer = "NIBE", - version = "1.1.3", + version = "1.1.4", protocols = { "http" }, capabilities = { "apicreds" }, -- Without this the channel infers control from driver_command and diff --git a/index.yaml b/index.yaml index 4b56577..33c32d3 100644 --- a/index.yaml +++ b/index.yaml @@ -423,7 +423,7 @@ drivers: size_bytes: 3766 sha256: "5f985b8917aea7b08fba13c02506b45e40232a763393dc6af7c6eeb6ab5853af" - name: "myuplink" - version: "1.2.1" + version: "1.2.2" tier: core protocol: http connectivity: cloud @@ -431,9 +431,9 @@ drivers: ders: [heatpump] control: false size_bytes: 17549 - sha256: "6ac47f27388ba8c89e5aafc1b764f598a0fe8cb89e12bcabdae855419f8d5e42" + sha256: "0af1e129899c4c014b2e825d3efe13887dbc7f041cde6dbcb5496cc712e67bf9" - name: "nibe_local" - version: "1.1.3" + version: "1.1.4" tier: core protocol: http connectivity: local @@ -441,7 +441,7 @@ drivers: ders: [heatpump] control: false size_bytes: 19446 - sha256: "7f020f70eb4109c58f1af1097e37597280447fcbc95246b30e9638413592039e" + sha256: "2886420e43a42e225b02ff07bbf9166787727e89758b48ec6f4c356990a3bcd7" - name: "opendtu" version: "1.0.2" tier: community @@ -689,7 +689,7 @@ drivers: size_bytes: 5784 sha256: "3cfcf53653680dede7caf6173cee251efa3cf2c9ec46fdc16a89b9b526dceb7e" - name: "sonnen" - version: "2.0.1" + version: "2.0.2" tier: community protocol: http connectivity: local @@ -718,7 +718,7 @@ drivers: size_bytes: 23154 sha256: "a708681364facbd09fb94a813050f6889d952a0178b392e51a64eddcc43fbf0d" - name: "tibber" - version: "1.1.0" + version: "1.1.1" tier: core protocol: http connectivity: cloud diff --git a/manifests/myuplink.yaml b/manifests/myuplink.yaml index 972f368..d813ab5 100644 --- a/manifests/myuplink.yaml +++ b/manifests/myuplink.yaml @@ -1,5 +1,5 @@ name: "myuplink" -version: "1.2.1" +version: "1.2.2" tier: core author: "Sourceful Labs AB" protocol: http @@ -23,7 +23,7 @@ upstream_docs: min_host_version: "2.0.0" size_bytes: 17549 dkb_id: "myuplink" -sha256: "6ac47f27388ba8c89e5aafc1b764f598a0fe8cb89e12bcabdae855419f8d5e42" +sha256: "0af1e129899c4c014b2e825d3efe13887dbc7f041cde6dbcb5496cc712e67bf9" signature: "" bytecode_sha256: "" diff --git a/manifests/nibe_local.yaml b/manifests/nibe_local.yaml index edc23a8..96c5ebc 100644 --- a/manifests/nibe_local.yaml +++ b/manifests/nibe_local.yaml @@ -1,5 +1,5 @@ name: "nibe_local" -version: "1.1.3" +version: "1.1.4" tier: core author: "HuggeK with the help of Claude Code" protocol: http @@ -23,7 +23,7 @@ upstream_docs: min_host_version: "2.0.0" size_bytes: 19446 dkb_id: "nibe_local" -sha256: "7f020f70eb4109c58f1af1097e37597280447fcbc95246b30e9638413592039e" +sha256: "2886420e43a42e225b02ff07bbf9166787727e89758b48ec6f4c356990a3bcd7" signature: "" bytecode_sha256: "" diff --git a/manifests/sonnen.yaml b/manifests/sonnen.yaml index 5ecbfd3..68c6199 100644 --- a/manifests/sonnen.yaml +++ b/manifests/sonnen.yaml @@ -1,5 +1,5 @@ name: "sonnen" -version: "2.0.1" +version: "2.0.2" tier: community author: "Sourceful Labs AB" protocol: http diff --git a/manifests/tibber.yaml b/manifests/tibber.yaml index ecd2136..6617f0b 100644 --- a/manifests/tibber.yaml +++ b/manifests/tibber.yaml @@ -1,5 +1,5 @@ name: "tibber" -version: "1.1.0" +version: "1.1.1" tier: core author: "Sourceful Labs AB" protocol: http diff --git a/support-status.json b/support-status.json index 948a218..26baff7 100644 --- a/support-status.json +++ b/support-status.json @@ -1290,7 +1290,7 @@ }, { "catalog_source": true, - "catalog_version": "1.2.1", + "catalog_version": "1.2.2", "driver_id": "myuplink", "package_id": null, "targets": { @@ -1318,7 +1318,7 @@ }, { "catalog_source": true, - "catalog_version": "1.1.3", + "catalog_version": "1.1.4", "driver_id": "nibe_local", "package_id": null, "targets": { @@ -2102,7 +2102,7 @@ }, { "catalog_source": true, - "catalog_version": "2.0.1", + "catalog_version": "2.0.2", "driver_id": "sonnen", "package_id": null, "targets": { @@ -2186,7 +2186,7 @@ }, { "catalog_source": true, - "catalog_version": "1.1.0", + "catalog_version": "1.1.1", "driver_id": "tibber", "package_id": null, "targets": {