You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
fix(log): keep secrets out of ee.log and make the log readable by root only - #1944
Values passed in $obfuscate are now also masked in the logged output of the command, and EE::obfuscate() masks values in everything logged from then on: command lines, their output, ee.log messages and context, and debug output. Secret flags, secret config values and the secret columns of loaded models (site and auth passwords) are registered automatically, and the logged command line shows secret flags as ******. As a fallback, secret flags, JSON members, mysql -p and openssl pass: values are masked by pattern. What commands print for the user is unchanged.
A new logs directory is created 0700 and ee.log 0600, with the modes set explicitly rather than relying on the umask. A migration restricts existing log files (including rotated ones) and masks secrets that earlier versions wrote to them; each file is rewritten to a checked copy before it replaces the original, and a file that can't be rewritten keeps its content (0600) with a warning, without failing the upgrade. CI reads the logs with sudo.
Tested with PHPUnit (PHP 7.4 and 8.5), a log harness, CI and live on a server with existing logs.
A new logs directory is created with mode 0700 and a new ee.log with 0600, under a 0077 umask, so the file is never readable by other users, not even briefly. Existing modes are left as they are; a migration tightens them once.
Values passed in $obfuscate are now also masked in the logged STDOUT and STDERR of the command, not only in its command line. EE::obfuscate() registers values to mask in everything logged from then on: command lines and output of EE::exec() and EE::launch() (and so docker_compose_exec()), every message and context value written to ee.log, and debug output. What commands print for the user is unchanged.
At startup the values of secret flags (--admin-pass, --dbpass, --pass, --password, --dbrootpass, --dash-auth, --api, and other names ending in pass, password, secret, token or api-key), the value of 'config set <secret-key> <value>' and the values of secret keys in config.yml are registered, and the logged argv line shows them as ******. Logged context arrays have the values of secret keys masked.
As a fallback for values EE doesn't know, such as JSON from another host, '--<secret>=value' flags and '"<secret>": "value"' JSON members are masked by pattern.
A migration sets the logs directory to 0700 and every file in it, including rotated and compressed logs, to 0600. It then rewrites the logs with the secrets masked: the passwords of existing sites and auth users and the secret config values, plus the shapes in which older versions logged secrets (secret flags, JSON members, config set values, the values printed by config get and config list, failed CREATE USER statements, wp config set output, mysql -p and openssl pass: values). Plain logs are rewritten in place, so their inode, owner and mode stay; compressed logs are replaced and keep their modification time. A file that can't be rewritten is left as it is, with a warning, and the migration never fails the upgrade.
…tput
Site, auth and other model rows register their secret columns (and secret options such as the self-signed CA password) with EE::obfuscate() when they are loaded, created or updated, so every command masks them without each caller passing them. The pattern fallback now also covers mysql -p<password>, openssl pass:<password> and numeric JSON values, keeps single-quoted values that start with $ masked, and uses possessive quantifiers so a very long value can't make the regex fail and skip masking. The token part of a secret flag value such as --dash-auth=<id>:<token> is registered too. Masking uses one cached strtr() map instead of sorting the values for every logged line, and objects in log context are masked after conversion to text.
The log migration now writes each masked copy next to the log, checks that every write succeeded and that the copy reads back with the same number of lines, and only then renames it over the log, so a full disk can't leave a truncated log. The running process's handler is closed after ee.log is replaced, so its next lines go to the new file. It also masks the EasyDash token debug lines of older versions, and reads the config through a shared helper.
… umask
A new ee.log and the masked copy that replaces a scrubbed log got 0600 only through umask(0077). A default ACL on the logs path (as on GitHub runners' /opt) overrides the umask, so they came out group/world readable after the scrub (0666 there).
create_log_file() now chmods a log it creates to 0600, and scrub_file() chmods the masked copy to 0600 before renaming it over the log, failing the scrub like any other write error when it can't. The logs dir itself is fine: mkdir()'s 0700 mode still limits a default ACL.
…g config values, and keep logs with read errors
A command's own obfuscate values are also masked in their escapeshellarg() and JSON forms. `config set <secret> <value>` is masked after an @alias and when the value starts with a single dash. The old-log migration keeps a file whose read or decompression fails instead of replacing it with a partial copy.
Closing this process's handler does not protect concurrent EE invocations. Another process that already has ee.log open will continue appending to the old, now-unlinked inode after rename(), and writes made after this scrubber reaches EOF can disappear from the visible log. Coordinate the migration with all log writers or use an active-log rewrite/rotation strategy that preserves the inode (the existing logrotate path uses copytruncate).
Fail closed when secure log file creation or restriction fails
php/EE/Runner.php:645
The privacy guarantee is silently bypassed when touch() or chmod() fails: initialization continues and StreamHandler can write to a file whose inherited ACL/mode is still readable by other users. Since this is the creation path for a new log, fail closed (and restore the umask) unless the file can be created and restricted successfully.
Register secrets from merged effective configuration
php/EE/Runner.php:796
This reads only the global config file, although init_config() also merges inherited and project-level ee.yml/ee.local.yml values into $this->config and $this->extra_config (Runner.php:668-696). A project-level cloudflare-api-key, for example, is therefore never registered and can remain visible when later logged. Register secrets from the already-merged effective configuration instead.
Handle short bare secret values with context-aware masking
php/class-ee.php:577
Discarding every known secret shorter than six characters still permits concrete leaks when the value is logged without its key. For example, a short secret config value printed by config get reaches an INFO message as the bare value, which none of the fallback key/flag patterns can recognize; notably, the migration has dedicated state to scrub this exact historical shape. Preserve the anti-overmasking behavior only with context-aware handling of bare secret outputs rather than dropping these secrets globally.
Collect secret values from nested command configuration
php/utils.php:1522
Command-specific config is nested (for example, $this->extra_config[$name] is passed into commands in Runner.php:376-384), but this collector examines only top-level scalar values. A config entry such as site create: { admin-pass: ... } is not registered, so the automatic masking promised for secret config values does not apply. Recurse into nested config arrays while collecting secret-key values.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Values passed in
$obfuscateare now also masked in the logged output of the command, andEE::obfuscate()masks values in everything logged from then on: command lines, their output,ee.logmessages and context, and debug output. Secret flags, secret config values and the secret columns of loaded models (site and auth passwords) are registered automatically, and the logged command line shows secret flags as******. As a fallback, secret flags, JSON members, mysql-pand opensslpass:values are masked by pattern. What commands print for the user is unchanged.A new logs directory is created 0700 and
ee.log0600, with the modes set explicitly rather than relying on the umask. A migration restricts existing log files (including rotated ones) and masks secrets that earlier versions wrote to them; each file is rewritten to a checked copy before it replaces the original, and a file that can't be rewritten keeps its content (0600) with a warning, without failing the upgrade. CI reads the logs with sudo.Tested with PHPUnit (PHP 7.4 and 8.5), a log harness, CI and live on a server with existing logs.