Skip to content

Commit

Permalink
fix: hide sensitive environment variables with asterisks. #1628
Browse files Browse the repository at this point in the history
  • Loading branch information
nedbat committed May 18, 2023
1 parent 48126d4 commit 3fb8da5
Show file tree
Hide file tree
Showing 4 changed files with 66 additions and 13 deletions.
5 changes: 5 additions & 0 deletions CHANGES.rst
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,12 @@ Unreleased
as invisible functions and coverage.py would warn you if they weren't
completely executed. This no longer happens under Python 3.12.

- Fix: the ``coverage debug sys`` command includes some environment variables
in its output. This could have included sensitive data. Those values are
now hidden with asterisks, closing `issue 1628`_.

.. _issue 1553: https://github.com/nedbat/coveragepy/issues/1553
.. _issue 1628: https://github.com/nedbat/coveragepy/issues/1628


.. scriv-start-here
Expand Down
15 changes: 5 additions & 10 deletions coverage/control.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,15 +29,17 @@
from coverage.config import CoverageConfig, read_coverage_config
from coverage.context import should_start_context_test_function, combine_context_switchers
from coverage.data import CoverageData, combine_parallel_data
from coverage.debug import DebugControl, NoDebugging, short_stack, write_formatted_info
from coverage.debug import (
DebugControl, NoDebugging, short_stack, write_formatted_info, relevant_environment_display
)
from coverage.disposition import disposition_debug_msg
from coverage.exceptions import ConfigError, CoverageException, CoverageWarning, PluginError
from coverage.files import PathAliases, abs_file, relative_filename, set_relative_directory
from coverage.html import HtmlReporter
from coverage.inorout import InOrOut
from coverage.jsonreport import JsonReporter
from coverage.lcovreport import LcovReporter
from coverage.misc import bool_or_none, join_regex, human_sorted
from coverage.misc import bool_or_none, join_regex
from coverage.misc import DefaultValue, ensure_dir_for_file, isolate_module
from coverage.multiproc import patch_multiprocessing
from coverage.plugin import FileReporter
Expand Down Expand Up @@ -1298,14 +1300,7 @@ def plugin_info(plugins: List[Any]) -> List[str]:
("pid", os.getpid()),
("cwd", os.getcwd()),
("path", sys.path),
("environment", human_sorted(
f"{k} = {v}"
for k, v in os.environ.items()
if (
any(slug in k for slug in ("COV", "PY")) or
(k in ("HOME", "TEMP", "TMP"))
)
)),
("environment", [f"{k} = {v}" for k, v in relevant_environment_display(os.environ)]),
("command_line", " ".join(getattr(sys, "argv", ["-none-"]))),
]

Expand Down
37 changes: 35 additions & 2 deletions coverage/debug.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,16 +12,18 @@
import itertools
import os
import pprint
import re
import reprlib
import sys
import types
import _thread

from typing import (
Any, Callable, IO, Iterable, Iterator, Optional, List, Tuple, cast,
cast,
Any, Callable, IO, Iterable, Iterator, Mapping, Optional, List, Tuple,
)

from coverage.misc import isolate_module
from coverage.misc import human_sorted_items, isolate_module
from coverage.types import TWritable

os = isolate_module(os)
Expand Down Expand Up @@ -489,3 +491,34 @@ def _clean_stack_line(s: str) -> str: # pragma: debugging
s = s.replace(os.path.dirname(os.__file__) + "/", "")
s = s.replace(sys.prefix + "/", "")
return s


def relevant_environment_display(env: Mapping[str, str]) -> List[Tuple[str, str]]:
"""Filter environment variables for a debug display.
Select variables to display (with COV or PY in the name, or HOME, TEMP, or
TMP), and also cloak sensitive values with asterisks.
Arguments:
env: a dict of environment variable names and values.
Returns:
A list of pairs (name, value) to show.
"""
slugs = {"COV", "PY"}
include = {"HOME", "TEMP", "TMP"}
cloak = {"API", "TOKEN", "KEY", "SECRET", "PASS", "SIGNATURE"}

to_show = []
for name, val in env.items():
keep = False
if name in include:
keep = True
elif any(slug in name for slug in slugs):
keep = True
if keep:
if any(slug in name for slug in cloak):
val = re.sub(r"\w", "*", val)
to_show.append((name, val))
return human_sorted_items(to_show)
22 changes: 21 additions & 1 deletion tests/test_debug.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@
from coverage import env
from coverage.debug import (
DebugOutputFile,
clipped_repr, filter_text, info_formatter, info_header, short_id, short_stack,
clipped_repr, filter_text, info_formatter, info_header, relevant_environment_display,
short_id, short_stack,
)

from tests.coveragetest import CoverageTest
Expand Down Expand Up @@ -297,3 +298,22 @@ def test_short_stack_limit(self) -> None:
def test_short_stack_skip(self) -> None:
stack = f_one(skip=1).splitlines()
assert "f_two" in stack[-1]


def test_relevant_environment_display() -> None:
env_vars = {
"HOME": "my home",
"HOME_DIR": "other place",
"XYZ_NEVER_MIND": "doesn't matter",
"SOME_PYOTHER": "xyz123",
"COVERAGE_THING": "abcd",
"MY_PYPI_TOKEN": "secret.something",
"TMP": "temporary",
}
assert relevant_environment_display(env_vars) == [
("COVERAGE_THING", "abcd"),
("HOME", "my home"),
("MY_PYPI_TOKEN", "******.*********"),
("SOME_PYOTHER", "xyz123"),
("TMP", "temporary"),
]

0 comments on commit 3fb8da5

Please sign in to comment.