Skip to content

Commit 4fa8c89

Browse files
committed
perf: tighten rust masking parity and hot path
Signed-off-by: lucarlig <luca.carlig@ibm.com>
1 parent 7d3a3f4 commit 4fa8c89

4 files changed

Lines changed: 64 additions & 19 deletions

File tree

.secrets.baseline

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
"files": null,
44
"lines": null
55
},
6-
"generated_at": "2026-04-03T14:47:08Z",
6+
"generated_at": "2026-04-03T15:25:49Z",
77
"plugins_used": [
88
{
99
"name": "AWSKeyDetector"
@@ -22032,7 +22032,7 @@
2203222032
"hashed_secret": "4ea8d2335b430796cf3f500368c5b0f5b1dc90f5",
2203322033
"is_secret": false,
2203422034
"is_verified": false,
22035-
"line_number": 207,
22035+
"line_number": 221,
2203622036
"type": "Secret Keyword",
2203722037
"verified_result": null
2203822038
}

tests/performance/test_request_logging_masking_sidecar_benchmark.py

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -76,9 +76,9 @@ def _measure_once(label: str, fn: Callable[[Any], Any], payload: Any) -> float:
7676
return elapsed_ms
7777

7878

79-
def _run_with_rust_masking_enabled(enabled: bool, fn: Callable[[Any], Any], payload: Any) -> Any:
79+
def _run_with_rust_masking_enabled(enabled: bool, fn: Callable[..., Any], *args: Any) -> Any:
8080
with _rust_masking_enabled(enabled):
81-
return fn(payload)
81+
return fn(*args)
8282

8383

8484
def _measure_with_rust_masking_enabled(enabled: bool, label: str, fn: Callable[[Any], Any], payload: Any, iterations: int) -> tuple[float, float]:
@@ -94,6 +94,14 @@ def _assert_parity(python_fn: Callable[[Any], Any], rust_fn: Callable[[Any], Any
9494
raise AssertionError(f"Parity mismatch for payload {payload!r}: python={python_result!r} rust={rust_result!r}")
9595

9696

97+
def _assert_depth_parity(python_fn: Callable[[Any, int], Any], rust_fn: Callable[[Any, int], Any], cases: list[tuple[Any, int]]) -> None:
98+
for payload, max_depth in cases:
99+
python_result = python_fn(payload, max_depth)
100+
rust_result = rust_fn(payload, max_depth)
101+
if python_result != rust_result:
102+
raise AssertionError(f"Depth parity mismatch for payload {payload!r} at max_depth={max_depth}: " f"python={python_result!r} rust={rust_result!r}")
103+
104+
97105
def main() -> None:
98106
sidecar = _ensure_sidecar_installed()
99107
request_logging_middleware._RUST_REQUEST_LOGGING_MODULE = sidecar
@@ -104,6 +112,12 @@ def python_data(payload: Any) -> Any:
104112
def rust_data(payload: Any) -> Any:
105113
return mask_sensitive_data(payload, 12)
106114

115+
def python_data_with_depth(payload: Any, max_depth: int) -> Any:
116+
return mask_sensitive_data(payload, max_depth)
117+
118+
def rust_data_with_depth(payload: Any, max_depth: int) -> Any:
119+
return mask_sensitive_data(payload, max_depth)
120+
107121
def python_headers(payload: Any) -> Any:
108122
return mask_sensitive_headers(payload)
109123

@@ -121,6 +135,17 @@ def rust_headers(payload: Any) -> Any:
121135
{"password": "secret", "nested": {"authToken": "abc", "ok": "value"}},
122136
{"token_count": 3, "tokenizer": "ok", "privateKey": "secret"},
123137
[{"jwt_token": "abc"}, {"normal": "value"}],
138+
{"outer": [{1: "numeric-key", "authToken": "abc"}, {"nested": {("tuple", 2): "tuple-key", "clientSecret": "hidden"}}]}, # pragma: allowlist secret
139+
{"outer": [{"leaf": "value"}, {"nested": ["safe", {"secret": "x"}]}]},
140+
],
141+
)
142+
_assert_depth_parity(
143+
lambda payload, max_depth: _run_with_rust_masking_enabled(False, python_data_with_depth, payload, max_depth),
144+
lambda payload, max_depth: _run_with_rust_masking_enabled(True, rust_data_with_depth, payload, max_depth),
145+
[
146+
({"level": "x"}, 1),
147+
([{"leaf": "x"}], 1),
148+
({"outer": [{"leaf": "value"}, {"nested": ["safe", {"secret": "x"}]}]}, 2),
124149
],
125150
)
126151
_assert_parity(

tests/unit/mcpgateway/middleware/test_request_logging_middleware.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,20 @@ def test_mask_sensitive_data_ignores_empty_normalized_keys():
125125
assert masked["password"] == "******"
126126

127127

128+
def test_mask_sensitive_data_preserves_non_string_keys_in_nested_structures():
129+
data = {
130+
"outer": [
131+
{1: "numeric-key", "authToken": "secret"},
132+
{"nested": {("tuple", 2): "tuple-key", "clientSecret": "hidden"}}, # pragma: allowlist secret
133+
]
134+
}
135+
masked = mask_sensitive_data(data)
136+
assert masked["outer"][0][1] == "numeric-key"
137+
assert masked["outer"][0]["authToken"] == "******"
138+
assert masked["outer"][1]["nested"][("tuple", 2)] == "tuple-key"
139+
assert masked["outer"][1]["nested"]["clientSecret"] == "******"
140+
141+
128142
def test_mask_sensitive_data_uses_rust_sidecar_when_enabled(monkeypatch):
129143
rust_module = MagicMock()
130144
rust_module.mask_sensitive_data.return_value = {"password": "******", "username": "user"} # pragma: allowlist secret

tools_rust/request_logging_masking_sidecar/src/lib.rs

Lines changed: 21 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,15 @@ fn is_sensitive_key_cached(key: &str, cache: &mut HashMap<String, bool>) -> bool
121121
result
122122
}
123123

124+
fn py_key_is_sensitive(
125+
key: &Bound<'_, PyAny>,
126+
cache: &mut HashMap<String, bool>,
127+
) -> PyResult<bool> {
128+
let key_string = key.str()?;
129+
let key_text = key_string.to_string_lossy();
130+
Ok(is_sensitive_key_cached(key_text.as_ref(), cache))
131+
}
132+
124133
fn mask_cookie_header(cookie_header: &str) -> String {
125134
let mut masked = Vec::new();
126135

@@ -159,8 +168,7 @@ fn mask_sensitive_data_inner(
159168
if let Ok(dict) = data.cast::<PyDict>() {
160169
let masked = PyDict::new(py);
161170
for (key, value) in dict.iter() {
162-
let key_string = key.str()?.to_string_lossy().into_owned();
163-
if is_sensitive_key_cached(&key_string, key_cache) {
171+
if py_key_is_sensitive(&key, key_cache)? {
164172
masked.set_item(key, MASKED_VALUE)?;
165173
} else {
166174
masked.set_item(
@@ -173,15 +181,11 @@ fn mask_sensitive_data_inner(
173181
}
174182

175183
if let Ok(list) = data.cast::<PyList>() {
176-
let masked = PyList::empty(py);
177-
for item in list.iter() {
178-
masked.append(mask_sensitive_data_inner(
179-
py,
180-
&item,
181-
max_depth - 1,
182-
key_cache,
183-
)?)?;
184-
}
184+
let items = list
185+
.iter()
186+
.map(|item| mask_sensitive_data_inner(py, &item, max_depth - 1, key_cache))
187+
.collect::<PyResult<Vec<_>>>()?;
188+
let masked = PyList::new(py, items)?;
185189
return Ok(masked.into_any().unbind());
186190
}
187191

@@ -194,7 +198,7 @@ fn mask_sensitive_data(
194198
data: &Bound<'_, PyAny>,
195199
max_depth: Option<i32>,
196200
) -> PyResult<Py<PyAny>> {
197-
let mut key_cache = HashMap::new();
201+
let mut key_cache = HashMap::with_capacity(32);
198202
mask_sensitive_data_inner(py, data, max_depth.unwrap_or(10), &mut key_cache)
199203
}
200204

@@ -205,13 +209,15 @@ fn mask_sensitive_headers(py: Python<'_>, headers: &Bound<'_, PyAny>) -> PyResul
205209
let mut key_cache = HashMap::with_capacity(source.len());
206210

207211
for (key, value) in source.iter() {
208-
let key_string = key.str()?.to_string_lossy().into_owned();
209-
if is_sensitive_key_cached(&key_string, &mut key_cache) {
212+
let key_string = key.str()?;
213+
let key_text = key_string.to_string_lossy();
214+
215+
if is_sensitive_key_cached(key_text.as_ref(), &mut key_cache) {
210216
masked.set_item(key, MASKED_VALUE)?;
211217
continue;
212218
}
213219

214-
if key_string.eq_ignore_ascii_case("cookie") && value.is_instance_of::<PyString>() {
220+
if key_text.eq_ignore_ascii_case("cookie") && value.is_instance_of::<PyString>() {
215221
let cookie_value = value.cast::<PyString>()?.to_str()?;
216222
masked.set_item(key, mask_cookie_header(cookie_value))?;
217223
continue;

0 commit comments

Comments
 (0)