Skip to content

Commit 645d41d

Browse files
committed
fix(base.py, txt.py): fix security issues and CONTRIBUTING compliance
- base.py: get_state() no longer calls coe()/sys.exit() on malformed range specs, returns STATE_UNKNOWN instead - base.py: oao() and cu() now escape HTML <> characters in messages - base.py: get_perfdata() sanitizes labels (strips quotes, replaces =) - txt.py: sanitize_sensitive_data() now also redacts JSON-style fields and HTTP Authorization headers
1 parent f5ca9ab commit 645d41d

3 files changed

Lines changed: 43 additions & 9 deletions

File tree

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1818

1919
### Changed
2020

21+
* base.py: `get_perfdata()` now sanitizes labels by stripping single quotes and replacing `=` with `_`
2122
* base.py: deduplicate `get_state()` operator logic using `operator` module
2223
* base.py: deduplicate `sum_dict()` by delegating to `sum_lod()`
2324
* base.py: improve `get_table()` performance for large tables
@@ -38,10 +39,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
3839

3940
### Fixed
4041

42+
* base.py: `cu()` now also escapes HTML characters in the error message, not just in the traceback
43+
* base.py: `get_state()` no longer calls `sys.exit()` on malformed range specs, returns UNKNOWN instead
44+
* base.py: `oao()` now escapes HTML characters in the output message to prevent injection in web UIs
4145
* base.py: fix `get_table()` using wrong separator for the second data row when called without a header
4246
* human.py: fix incorrect `seconds2human()` docstring example for sub-second values
4347
* powershell.py: fix outdated shebang line
4448
* txt.py: fix `exception2text()` missing `traceback` import (fallback path was dead code)
49+
* txt.py: `sanitize_sensitive_data()` now also redacts JSON-style fields and HTTP Authorization headers
4550
* txt.py: fix `sanitize_sensitive_data()` replacing the key name instead of the secret value
4651
* winrm.py: pass parameters correctly in `run_cmd()` when using pypsrp
4752

base.py

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
"""
1313

1414
__author__ = 'Linuxfabrik GmbH, Zurich/Switzerland'
15-
__version__ = '2026030301'
15+
__version__ = '2026032101'
1616

1717
import numbers
1818
import operator
@@ -120,7 +120,7 @@ def cu(msg=None):
120120
has_traceback = tb and 'NoneType: None' not in tb
121121

122122
if msg is not None:
123-
msg = txt.sanitize_sensitive_data(msg).strip()
123+
msg = txt.sanitize_sensitive_data(msg).strip().replace('<', "'").replace('>', "'")
124124
print(msg, end='')
125125
print(' (Traceback for debugging purposes attached)\n' if has_traceback else '\n')
126126

@@ -151,6 +151,7 @@ def get_perfdata(label, value, uom=None, warn=None, crit=None, _min=None, _max=N
151151
>>> get_perfdata('load1', 0.42, '', 1.0, 5.0, 0, 10)
152152
"'load1'=0.42;1.0;5.0;0;10 "
153153
"""
154+
label = str(label).replace("'", '').replace('=', '_')
154155
msg = f"'{label}'={value}{uom or ''};"
155156
msg += f'{warn};' if warn is not None else ';'
156157
msg += f'{crit};' if crit is not None else ';'
@@ -191,10 +192,18 @@ def get_state(value, warn, crit, _operator='ge'):
191192
value = float(value)
192193

193194
if _operator == 'range':
194-
if crit is not None and not coe(match_range(value, crit)):
195-
return STATE_CRIT
196-
if warn is not None and not coe(match_range(value, warn)):
197-
return STATE_WARN
195+
if crit is not None:
196+
success, result = match_range(value, crit)
197+
if not success:
198+
return STATE_UNKNOWN
199+
if not result:
200+
return STATE_CRIT
201+
if warn is not None:
202+
success, result = match_range(value, warn)
203+
if not success:
204+
return STATE_UNKNOWN
205+
if not result:
206+
return STATE_WARN
198207
return STATE_OK
199208

200209
op = _OPS.get(_operator)
@@ -602,7 +611,7 @@ def oao(msg, state=STATE_OK, perfdata='', always_ok=False):
602611
(and exits with code 2)
603612
604613
"""
605-
msg = txt.sanitize_sensitive_data(msg.strip()).replace('|', '!')
614+
msg = txt.sanitize_sensitive_data(msg.strip()).replace('|', '!').replace('<', "'").replace('>', "'")
606615
if always_ok and msg:
607616
# Instead of splitlines(), we just split('\n', 1), so only first line is touched.
608617
parts = msg.split('\n', 1)

txt.py

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
"""
1717

1818
__author__ = 'Linuxfabrik GmbH, Zurich/Switzerland'
19-
__version__ = '2026030301'
19+
__version__ = '2026032101'
2020

2121
import operator
2222
import re
@@ -51,6 +51,23 @@
5151
# ) # └ End of capture group 1
5252
# [^\s&]+ # The secret value (not captured, will be replaced)
5353

54+
SENSITIVE_JSON_PATTERN = re.compile(
55+
r'(?i)("(?:password|pass|token|key|secret|api[_-]?key|access[_-]?token)"\s*:\s*")[^"]*(")'
56+
)
57+
# Matches JSON-style sensitive fields like:
58+
# "password": "secret123"
59+
# "api_key" : "abc"
60+
# Captures the prefix (group 1) and the closing quote (group 2),
61+
# replacing only the secret value between them.
62+
63+
SENSITIVE_AUTH_PATTERN = re.compile(
64+
r'(?i)(Authorization:\s*(?:Bearer|Basic|Digest|Token)\s+)\S+'
65+
)
66+
# Matches HTTP Authorization headers like:
67+
# Authorization: Bearer abc123
68+
# Authorization: Basic dXNlcjpwYXNz
69+
# Captures the prefix (group 1), replacing the credential value.
70+
5471

5572
def compile_regex(regex, key=''):
5673
"""
@@ -425,7 +442,10 @@ def sanitize_sensitive_data(msg, replacement='******'):
425442
"""
426443
if not isinstance(msg, str):
427444
return msg
428-
return SENSITIVE_FIELDS_PATTERN.sub(rf'\1{replacement}', msg)
445+
msg = SENSITIVE_FIELDS_PATTERN.sub(rf'\1{replacement}', msg)
446+
msg = SENSITIVE_JSON_PATTERN.sub(rf'\1{replacement}\2', msg)
447+
msg = SENSITIVE_AUTH_PATTERN.sub(rf'\1{replacement}', msg)
448+
return msg
429449

430450

431451
def to_bytes(obj, encoding='utf-8', errors=None,

0 commit comments

Comments
 (0)