Skip to content

Bug: applyEnvironmentVariables() in write() overwrites !secret.yaml references with plain text #31140

@ilancas

Description

@ilancas

What happened?

Description

When using !secret.yaml references in configuration.yaml (as documented on the MQTT configuration page), Z2M's write() function in lib/util/settings.ts correctly detects the reference and preserves it — but then immediately calls applyEnvironmentVariables(toWrite) after the secret-reference preservation logic. This stamps any matching ZIGBEE2MQTT_CONFIG_* environment variable value (as plain text) over the !secret reference before writing the file.

The result is that !secret.yaml references are silently overwritten with plain text values on every write cycle (device join, settings change, restart, etc.), making the secret reference feature effectively non-functional when env vars are in play.

Root cause

In lib/util/settings.ts, the write() function:

  1. Correctly detects !secret.yaml references in the existing file and routes values to secret.yaml, preserving the reference in toWrite
  2. Then calls applyEnvironmentVariables(toWrite) which overwrites those same keys with plain text from env vars
  3. Then writes toWrite to configuration.yaml — with the plain text value, not the secret reference
// Step 1: secret reference preservation (correct)
for (const [ns, key] of [["mqtt", "server"], ["mqtt", "user"], ["mqtt", "password"], ...]) {
    if (actual[ns]?.[key]) {
        const ref = parseValueRef(actual[ns][key]);
        if (ref) {
            yaml.updateIfChanged(data.joinPath(ref.filename), ref.key, toWrite[ns][key]);
            toWrite[ns][key] = actual[ns][key]; // preserves !secret reference
        }
    }
}

// Step 2: env vars applied AFTER, clobbering the reference (bug)
applyEnvironmentVariables(toWrite);

// Step 3: plain text password written to configuration.yaml
yaml.writeIfChanged(CONFIG_FILE_PATH, toWrite);

Suggested fix

applyEnvironmentVariables() should be called before the secret-reference preservation loop, not after. That way the resolved value gets routed to secret.yaml and the reference is preserved in configuration.yaml. Alternatively, the secret-reference preservation loop could be moved to run after applyEnvironmentVariables().

How to reproduce

This is particularly evident when using the Home Assistant addon, where docker-entrypoint.sh automatically injects ZIGBEE2MQTT_CONFIG_MQTT_PASSWORD (sourced from the Mosquitto broker service). Even with a correctly configured !secret.yaml mqtt_password reference in configuration.yaml, Z2M overwrites it with the plain text password on every startup.

Impact

Users cannot use !secret.yaml to keep credentials out of configuration.yaml when any matching ZIGBEE2MQTT_CONFIG_* env var is set, defeating the documented secret management feature.

What did you expect to happen?

I expected that !secret.yaml references in configuration.yaml would be preserved on write, even when ZIGBEE2MQTT_CONFIG_* environment variables are set. The secret reference should survive restart cycles without the plain text value being written back to configuration.yaml.

How to reproduce it (minimal and precise)

  1. Using the Home Assistant addon with Mosquitto broker also installed
  2. Leave the addon UI config mqtt section empty (server/user/password all blank)
  3. Add password: '!secret.yaml mqtt_password' to configuration.yaml
  4. Add mqtt_password: yourpassword to secret.yaml
  5. Start the addon
  6. Observe that configuration.yaml has the plain text password written back in place of the !secret.yaml reference

Zigbee2MQTT version

latest (bug is present in current master — see lib/util/settings.ts)

Adapter firmware version

N/A - this is a code/configuration bug, not hardware-specific

Adapter

N/A - this is a code/configuration bug, not hardware-specific

Setup

Home Assistant addon (hassio-zigbee2mqtt) with Mosquitto broker addon

Device database.db entry

No response

Debug log

No response

Notes

No response

Metadata

Metadata

Assignees

No one assigned

    Labels

    problemSomething isn't working

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions