Skip to content

Log environment variables sorted by key while redacting values of unsafe ones #3543

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions docs/changelog/3542.bugfix.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Improves logging of environment variables by sorting them by key and redacting
the values for the ones that are likely to contain secrets.
9 changes: 9 additions & 0 deletions docs/user_guide.rst
Original file line number Diff line number Diff line change
Expand Up @@ -441,6 +441,15 @@ CLI
have a stale Python environment; e.g. ``tox run -e py310 -r`` would clean the run environment and recreate it from
scratch.

Logging
~~~~~~~

Tox logs its activity inside ``.tox/<env_name>/log`` which can prove to be a good source of information when debugging
its behavior. It should be noted that some of the environment variables with names containing one of the words
``access``, ``api``, ``auth``, ``client``, ``cred``, ``key``, ``passwd``, ``password``, ``private``, ``pwd``,
``secret`` and ``token`` will be logged with their values redacted with ``*`` to prevent accidental secret leaking when
tox is used in CI/CD environments (as log collection is common).

Config files
~~~~~~~~~~~~

Expand Down
31 changes: 29 additions & 2 deletions src/tox/tox_env/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,30 @@
from tox.tox_env.installer import Installer

LOGGER = logging.getLogger(__name__)
# Based on original gitleaks rule named generic-api-key
# See: https://github.com/gitleaks/gitleaks/blob/master/config/gitleaks.toml#L587
SECRET_KEYWORDS = [
"access",
"api",
"auth",
"client",
"cred",
"key",
"passwd",
"password",
"private",
"pwd",
"secret",
"token",
]
SECRET_ENV_VAR_REGEX = re.compile(".*(" + "|".join(SECRET_KEYWORDS) + ").*", re.IGNORECASE)


def redact_value(name: str, value: str) -> str:
"""Returns a redacted text if the key name looks like a secret."""
if SECRET_ENV_VAR_REGEX.match(name):
return "*" * len(value)
return value


class ToxEnvCreateArgs(NamedTuple):
Expand Down Expand Up @@ -461,8 +485,11 @@ def _write_execute_log(env_name: str, log_file: Path, request: ExecuteRequest, s
with log_file.open("wt", encoding="utf-8") as file:
file.write(f"name: {env_name}\n")
file.write(f"run_id: {request.run_id}\n")
for env_key, env_value in request.env.items():
file.write(f"env {env_key}: {env_value}\n")
msg = ""
for env_key, env_value in sorted(request.env.items()):
redacted_value = redact_value(name=env_key, value=env_value)
msg += f"env {env_key}: {redacted_value}\n"
file.write(msg)
for meta_key, meta_value in status.metadata.items():
file.write(f"metadata {meta_key}: {meta_value}\n")
file.write(f"cwd: {request.cwd}\n")
Expand Down
32 changes: 32 additions & 0 deletions tests/tox_env/test_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@

from typing import TYPE_CHECKING

import pytest

from tox.tox_env.api import redact_value

if TYPE_CHECKING:
from pathlib import Path

Expand Down Expand Up @@ -32,3 +36,31 @@ def test_setenv_section_substitution(tox_project: ToxProjectCreator) -> None:
project = tox_project({"tox.ini": ini})
result = project.run()
result.assert_success()


@pytest.mark.parametrize(
("key", "do_redact"),
[
pytest.param("SOME_KEY", True, id="key"),
pytest.param("API_FOO", True, id="api"),
pytest.param("AUTH", True, id="auth"),
pytest.param("CLIENT", True, id="client"),
pytest.param("DB_PASSWORD", True, id="password"),
pytest.param("FOO", False, id="foo"),
pytest.param("GITHUB_TOKEN", True, id="token"),
pytest.param("NORMAL_VAR", False, id="other"),
pytest.param("S_PASSWD", True, id="passwd"),
pytest.param("SECRET", True, id="secret"),
pytest.param("SOME_ACCESS", True, id="access"),
pytest.param("MY_CRED", True, id="cred"),
pytest.param("MY_PRIVATE", True, id="private"),
pytest.param("MY_PWD", True, id="pwd"),
],
)
def test_redact(key: str, do_redact: bool) -> None:
"""Ensures that redact_value works as expected."""
result = redact_value(key, "foo")
if do_redact:
assert result == "***"
else:
assert result == "foo"
Loading