server-management-lib 0.2.0__tar.gz → 0.2.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {server_management_lib-0.2.0 → server_management_lib-0.2.1}/PKG-INFO +1 -1
- {server_management_lib-0.2.0 → server_management_lib-0.2.1}/pyproject.toml +1 -1
- {server_management_lib-0.2.0 → server_management_lib-0.2.1}/src/server_management_lib/__init__.py +1 -1
- {server_management_lib-0.2.0 → server_management_lib-0.2.1}/src/server_management_lib/redaction.py +45 -11
- {server_management_lib-0.2.0 → server_management_lib-0.2.1}/src/server_management_lib.egg-info/PKG-INFO +1 -1
- {server_management_lib-0.2.0 → server_management_lib-0.2.1}/tests/test_redaction.py +66 -0
- {server_management_lib-0.2.0 → server_management_lib-0.2.1}/README.md +0 -0
- {server_management_lib-0.2.0 → server_management_lib-0.2.1}/setup.cfg +0 -0
- {server_management_lib-0.2.0 → server_management_lib-0.2.1}/src/server_management_lib/audit.py +0 -0
- {server_management_lib-0.2.0 → server_management_lib-0.2.1}/src/server_management_lib/base.py +0 -0
- {server_management_lib-0.2.0 → server_management_lib-0.2.1}/src/server_management_lib/config.py +0 -0
- {server_management_lib-0.2.0 → server_management_lib-0.2.1}/src/server_management_lib/executor.py +0 -0
- {server_management_lib-0.2.0 → server_management_lib-0.2.1}/src/server_management_lib/http_clients.py +0 -0
- {server_management_lib-0.2.0 → server_management_lib-0.2.1}/src/server_management_lib/local_executor.py +0 -0
- {server_management_lib-0.2.0 → server_management_lib-0.2.1}/src/server_management_lib/operations.py +0 -0
- {server_management_lib-0.2.0 → server_management_lib-0.2.1}/src/server_management_lib/output.py +0 -0
- {server_management_lib-0.2.0 → server_management_lib-0.2.1}/src/server_management_lib/py.typed +0 -0
- {server_management_lib-0.2.0 → server_management_lib-0.2.1}/src/server_management_lib/rate_limit.py +0 -0
- {server_management_lib-0.2.0 → server_management_lib-0.2.1}/src/server_management_lib/responses.py +0 -0
- {server_management_lib-0.2.0 → server_management_lib-0.2.1}/src/server_management_lib/security.py +0 -0
- {server_management_lib-0.2.0 → server_management_lib-0.2.1}/src/server_management_lib/ssh_manager.py +0 -0
- {server_management_lib-0.2.0 → server_management_lib-0.2.1}/src/server_management_lib/transport.py +0 -0
- {server_management_lib-0.2.0 → server_management_lib-0.2.1}/src/server_management_lib/tunnel.py +0 -0
- {server_management_lib-0.2.0 → server_management_lib-0.2.1}/src/server_management_lib.egg-info/SOURCES.txt +0 -0
- {server_management_lib-0.2.0 → server_management_lib-0.2.1}/src/server_management_lib.egg-info/dependency_links.txt +0 -0
- {server_management_lib-0.2.0 → server_management_lib-0.2.1}/src/server_management_lib.egg-info/requires.txt +0 -0
- {server_management_lib-0.2.0 → server_management_lib-0.2.1}/src/server_management_lib.egg-info/top_level.txt +0 -0
- {server_management_lib-0.2.0 → server_management_lib-0.2.1}/tests/test_audit.py +0 -0
- {server_management_lib-0.2.0 → server_management_lib-0.2.1}/tests/test_config.py +0 -0
- {server_management_lib-0.2.0 → server_management_lib-0.2.1}/tests/test_http_clients.py +0 -0
- {server_management_lib-0.2.0 → server_management_lib-0.2.1}/tests/test_local_executor.py +0 -0
- {server_management_lib-0.2.0 → server_management_lib-0.2.1}/tests/test_operations.py +0 -0
- {server_management_lib-0.2.0 → server_management_lib-0.2.1}/tests/test_output.py +0 -0
- {server_management_lib-0.2.0 → server_management_lib-0.2.1}/tests/test_security.py +0 -0
- {server_management_lib-0.2.0 → server_management_lib-0.2.1}/tests/test_ssh_manager.py +0 -0
- {server_management_lib-0.2.0 → server_management_lib-0.2.1}/tests/test_tooling.py +0 -0
- {server_management_lib-0.2.0 → server_management_lib-0.2.1}/tests/test_tunnel.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: server-management-lib
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.1
|
|
4
4
|
Summary: Shared security, SSH/local execution, configuration and HTTP clients for the Server MCP suite.
|
|
5
5
|
Author-email: Alessio Elmi <alessio.elmi@gmail.com>
|
|
6
6
|
License-Expression: MIT
|
{server_management_lib-0.2.0 → server_management_lib-0.2.1}/src/server_management_lib/redaction.py
RENAMED
|
@@ -63,16 +63,37 @@ def redact_env_content(text: str) -> str:
|
|
|
63
63
|
# General secret redaction (service config files and host status/log output)
|
|
64
64
|
# ============================================================================
|
|
65
65
|
|
|
66
|
-
# Key names whose values are secrets.
|
|
67
|
-
# ``
|
|
68
|
-
#
|
|
69
|
-
# ``
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
66
|
+
# Key names whose values are secrets. Keys are split into words on separators
|
|
67
|
+
# (``_``, ``-``, ``.``, ...) and each word is checked, so ``POSTGRES_PASSWORD``,
|
|
68
|
+
# ``GITHUB_TOKEN``, ``api_key``, ``db-pass`` and a bare INI ``pass`` are covered
|
|
69
|
+
# without treating unrelated ``*.key`` file names as secrets (that policy lives
|
|
70
|
+
# in ``security.BLOCKED_PATH_*``).
|
|
71
|
+
#
|
|
72
|
+
# Short generic names are matched as word suffixes rather than substrings:
|
|
73
|
+
# ``dbpass``/``smtpPass`` are credentials, while ``tokenizer`` and ``keyboard``
|
|
74
|
+
# are not. The trade-off is deliberate: ``bypass`` is masked too, because a
|
|
75
|
+
# spurious mask is harmless and a missed credential is not.
|
|
76
|
+
_SENSITIVE_KEY_WORDS: tuple[str, ...] = (
|
|
77
|
+
"auth",
|
|
78
|
+
"authorization",
|
|
79
|
+
"credential",
|
|
80
|
+
"credentials",
|
|
81
|
+
"key",
|
|
82
|
+
"pass",
|
|
83
|
+
"passphrase",
|
|
84
|
+
"passwd",
|
|
85
|
+
"password",
|
|
86
|
+
"pw",
|
|
87
|
+
"pwd",
|
|
88
|
+
"secret",
|
|
89
|
+
"secrets",
|
|
90
|
+
"token",
|
|
91
|
+
"tokens",
|
|
74
92
|
)
|
|
75
93
|
|
|
94
|
+
# Word separators used when normalizing a key (``db_pass`` -> ``db``, ``pass``).
|
|
95
|
+
_KEY_SEPARATOR = re.compile(r"[^A-Za-z0-9]+")
|
|
96
|
+
|
|
76
97
|
# JSON object member: "key": "value" or "key": value
|
|
77
98
|
_JSON_MEMBER = re.compile(
|
|
78
99
|
r'(?P<prefix>"(?P<key>[^"\n]+)"\s*:\s*)'
|
|
@@ -108,10 +129,23 @@ _URL_PASSWORD = re.compile(
|
|
|
108
129
|
)
|
|
109
130
|
|
|
110
131
|
|
|
132
|
+
def _key_words(key: str) -> list[str]:
|
|
133
|
+
"""Split a normalized key into lowercase words on non-alphanumerics."""
|
|
134
|
+
return [word.lower() for word in _KEY_SEPARATOR.split(key) if word]
|
|
135
|
+
|
|
136
|
+
|
|
111
137
|
def _is_sensitive_key(key: str) -> bool:
|
|
112
|
-
"""Return True when an assignment key names a credential.
|
|
113
|
-
|
|
114
|
-
|
|
138
|
+
"""Return True when an assignment key names a credential.
|
|
139
|
+
|
|
140
|
+
The key is normalized (quotes and leading CLI dashes removed) and split
|
|
141
|
+
into words. A word matches when it equals a sensitive name or ends with
|
|
142
|
+
one, so separators and concatenation (``api_key``, ``apikey``,
|
|
143
|
+
``db-pass``, ``dbpass``) are all recognized.
|
|
144
|
+
"""
|
|
145
|
+
cleaned = key.strip().strip("\"'").lstrip("-").strip()
|
|
146
|
+
if not cleaned:
|
|
147
|
+
return False
|
|
148
|
+
return any(word.endswith(_SENSITIVE_KEY_WORDS) for word in _key_words(cleaned))
|
|
115
149
|
|
|
116
150
|
|
|
117
151
|
def _redact_line(line: str) -> str:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: server-management-lib
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.1
|
|
4
4
|
Summary: Shared security, SSH/local execution, configuration and HTTP clients for the Server MCP suite.
|
|
5
5
|
Author-email: Alessio Elmi <alessio.elmi@gmail.com>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
"""Tests for the file-content redaction helpers."""
|
|
2
2
|
|
|
3
|
+
import pytest
|
|
4
|
+
|
|
3
5
|
from server_management_lib.redaction import REDACTED_VALUE, redact_env_content
|
|
4
6
|
|
|
5
7
|
|
|
@@ -130,3 +132,67 @@ class TestRedactSecrets:
|
|
|
130
132
|
text = "# token=comment\nmonkey.txt is readable\n"
|
|
131
133
|
|
|
132
134
|
assert redact_secrets(text) == text
|
|
135
|
+
|
|
136
|
+
def test_ini_short_password_key_is_masked(self):
|
|
137
|
+
from server_management_lib.redaction import redact_secrets
|
|
138
|
+
|
|
139
|
+
text = "[mail]\npass = hunter2\nuser = bob\n"
|
|
140
|
+
|
|
141
|
+
result = redact_secrets(text)
|
|
142
|
+
|
|
143
|
+
assert "hunter2" not in result
|
|
144
|
+
assert "[mail]" in result
|
|
145
|
+
assert "user = bob" in result
|
|
146
|
+
assert result.splitlines()[1] == f"pass = {REDACTED_VALUE}"
|
|
147
|
+
|
|
148
|
+
@pytest.mark.parametrize(
|
|
149
|
+
"key",
|
|
150
|
+
[
|
|
151
|
+
"pass",
|
|
152
|
+
"PASS",
|
|
153
|
+
"Pass",
|
|
154
|
+
"pw",
|
|
155
|
+
"pwd",
|
|
156
|
+
"passphrase",
|
|
157
|
+
"passwd",
|
|
158
|
+
"db_pass",
|
|
159
|
+
"db-pass",
|
|
160
|
+
"dbpass",
|
|
161
|
+
"smtpPass",
|
|
162
|
+
"api_key",
|
|
163
|
+
"apikey",
|
|
164
|
+
"apiKey",
|
|
165
|
+
"auth_token",
|
|
166
|
+
"authToken",
|
|
167
|
+
"secret_key",
|
|
168
|
+
"client_secret",
|
|
169
|
+
"credentials",
|
|
170
|
+
],
|
|
171
|
+
)
|
|
172
|
+
def test_credential_key_spellings_are_masked(self, key):
|
|
173
|
+
from server_management_lib.redaction import redact_secrets
|
|
174
|
+
|
|
175
|
+
result = redact_secrets(f"{key} = hunter2")
|
|
176
|
+
|
|
177
|
+
assert "hunter2" not in result, f"leaked value for key {key!r}: {result}"
|
|
178
|
+
assert result == f"{key} = {REDACTED_VALUE}"
|
|
179
|
+
|
|
180
|
+
@pytest.mark.parametrize(
|
|
181
|
+
"key",
|
|
182
|
+
["user", "username", "host", "port", "tokenizer", "keyboard", "mode"],
|
|
183
|
+
)
|
|
184
|
+
def test_non_credential_keys_stay_readable(self, key):
|
|
185
|
+
from server_management_lib.redaction import redact_secrets
|
|
186
|
+
|
|
187
|
+
assert redact_secrets(f"{key} = visible") == f"{key} = visible"
|
|
188
|
+
|
|
189
|
+
def test_short_password_key_is_masked_in_yaml_and_json(self):
|
|
190
|
+
from server_management_lib.redaction import redact_secrets
|
|
191
|
+
|
|
192
|
+
yaml_result = redact_secrets(" pass: hunter2\n")
|
|
193
|
+
json_result = redact_secrets('{"pass": "hunter2"}')
|
|
194
|
+
|
|
195
|
+
assert "hunter2" not in yaml_result
|
|
196
|
+
assert f"pass: {REDACTED_VALUE}" in yaml_result
|
|
197
|
+
assert "hunter2" not in json_result
|
|
198
|
+
assert f'"pass": "{REDACTED_VALUE}"' in json_result
|
|
File without changes
|
|
File without changes
|
{server_management_lib-0.2.0 → server_management_lib-0.2.1}/src/server_management_lib/audit.py
RENAMED
|
File without changes
|
{server_management_lib-0.2.0 → server_management_lib-0.2.1}/src/server_management_lib/base.py
RENAMED
|
File without changes
|
{server_management_lib-0.2.0 → server_management_lib-0.2.1}/src/server_management_lib/config.py
RENAMED
|
File without changes
|
{server_management_lib-0.2.0 → server_management_lib-0.2.1}/src/server_management_lib/executor.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{server_management_lib-0.2.0 → server_management_lib-0.2.1}/src/server_management_lib/operations.py
RENAMED
|
File without changes
|
{server_management_lib-0.2.0 → server_management_lib-0.2.1}/src/server_management_lib/output.py
RENAMED
|
File without changes
|
{server_management_lib-0.2.0 → server_management_lib-0.2.1}/src/server_management_lib/py.typed
RENAMED
|
File without changes
|
{server_management_lib-0.2.0 → server_management_lib-0.2.1}/src/server_management_lib/rate_limit.py
RENAMED
|
File without changes
|
{server_management_lib-0.2.0 → server_management_lib-0.2.1}/src/server_management_lib/responses.py
RENAMED
|
File without changes
|
{server_management_lib-0.2.0 → server_management_lib-0.2.1}/src/server_management_lib/security.py
RENAMED
|
File without changes
|
{server_management_lib-0.2.0 → server_management_lib-0.2.1}/src/server_management_lib/ssh_manager.py
RENAMED
|
File without changes
|
{server_management_lib-0.2.0 → server_management_lib-0.2.1}/src/server_management_lib/transport.py
RENAMED
|
File without changes
|
{server_management_lib-0.2.0 → server_management_lib-0.2.1}/src/server_management_lib/tunnel.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|