Repository: sloria/environs

Issue:
All environment variable names and associated values logged on unhandled env attribute error 
We had a surprise recently in our code base when an attribute error was raised on access to an `Env` instance. All environment variables and their associated values were logged to stdout which was subsequently scraped and shipped to our log analytics stack. The culprit seems to be that the `Env.__repr__` magic method deliberately stringifies the `self._values` dictionary and includes the results in the string representation of any given `Env` instance.

Was this a deliberate design decision? Environment variables are often used to handle secrets in many systems. The choice to spit all of those values out without user input seems unwise and doesn't appear to provide any immediate value to the library itself. 

There are obviously failings on our end in this. Specifically, our test suite didn't catch the issue because this particular section of the code doesn't have great coverage admittedly. Be that as it may, it doesn't seem like a sound design choice to arbitrarily leak information that is generally assumed to be sensitive, if not downright privileged, in nature.

My workaround (aside from fixing the typo in the method name) was to simply monkeypatch the `Env.__repr__` method with something that was safer and wouldn't divulge information that we don't want to appear in plain text logs in case there are any other "oopsies" in the future. If y'all are opposed to removing the stringification of the `self._values` dictionary altogether, then would y'all consider a `safe: bool = False` constructor parameter? If that variable is set to `True`, then `Env` wouldn't haphazardly report on every value within its scope. 

Discussion:

Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 9692767..37487c8 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,12 @@
 # Changelog
 
+## 10.3.0 (unreleased)
+
+Features:
+
+- `Env.__repr__` and `Env.__str__` no longer display environment variables, to prevent accidental exfiltration of sensitive data ([#292](https://github.com/sloria/environs/issues/292)).
+  Thanks [mro-rhansen2](https://github.com/mro-rhansen2) for the suggestion.
+
 ## 10.2.0 (2024-01-09)
 
 Features:
diff --git a/environs/__init__.py b/environs/__init__.py
index 8ba8fd2..b52774d 100644
--- a/environs/__init__.py
+++ b/environs/__init__.py
@@ -387,7 +387,7 @@ class Env:
         self.__custom_parsers__: typing.Dict[_StrType, ParserMethod] = {}
 
     def __repr__(self) -> _StrType:
-        return f"<{self.__class__.__name__} {self._values}>"
+        return f"<{self.__class__.__name__}(eager={self.eager}, expand_vars={self.expand_vars})>"
 
     @staticmethod
     def read_env(


Test patch:
diff --git a/tests/test_environs.py b/tests/test_environs.py
index 2430e66..95484e9 100644
--- a/tests/test_environs.py
+++ b/tests/test_environs.py
@@ -487,15 +487,17 @@ class TestDumping:
 
 
 def test_repr(set_env, env):
+    env = environs.Env(eager=True, expand_vars=True)
     set_env({"FOO": "foo", "BAR": "42"})
     env.str("FOO")
-    assert repr(env) == "<Env {}>".format({"FOO": "foo"})
+    assert repr(env) == "<Env(eager=True, expand_vars=True)>"
 
 
 def test_str(set_env, env):
+    env = environs.Env(eager=True, expand_vars=True)
     set_env({"FOO": "foo", "BAR": "42"})
     env.str("FOO")
-    assert repr(env) == "<Env {}>".format({"FOO": "foo"})
+    assert str(env) == "<Env(eager=True, expand_vars=True)>"
 
 
 def test_env_isolation(set_env):