Skip to content

Commit 4908771

Browse files
committed
value handling policy change: allow,deny changes to allow,redact,drop
1 parent 2afa519 commit 4908771

File tree

12 files changed

+221
-111
lines changed

12 files changed

+221
-111
lines changed

codetracer-python-recorder/README.md

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ or activated virtual environments behave identically to `python script.py`.
5656
- Supported selector domains: `pkg`, `file`, `obj` for scopes; `local`, `global`, `arg`, `ret`, `attr` for value policies. Match types default to `glob` and also accept `regex` or `literal` (e.g. `local:regex:^(metric|masked)_\w+$`).
5757
- Default discovery: `.codetracer/trace-filter.toml` next to the traced script. Chain additional files via CLI (`--trace-filter path_a --trace-filter path_b`), environment variable (`CODETRACER_TRACE_FILTER=path_a::path_b`), or Python helpers (`trace(..., trace_filter=[path_a, path_b])`). Later entries override earlier ones when selectors overlap.
5858
- A built-in `builtin-default` filter is always prepended. It skips CPython standard-library frames (e.g. `asyncio`, `threading`, `importlib`) while re-enabling third-party packages under `site-packages` (except helpers such as `_virtualenv.py`), and redacts common secrets (`password`, `token`, API keys, etc.) across locals/globals/args/returns/attributes. Project filters can loosen or tighten these defaults as required.
59-
- Runtime metadata captures the active chain under `trace_metadata.json -> trace_filter`, including per-kind redaction counters. See `docs/onboarding/trace-filters.md` for the full DSL reference and examples.
59+
- Runtime metadata captures the active chain under `trace_metadata.json -> trace_filter`, including per-kind redaction and drop counters. See `docs/onboarding/trace-filters.md` for the full DSL reference and examples.
6060

6161
Example snippet:
6262
```toml
@@ -70,13 +70,16 @@ default_value_action = "allow"
7070

7171
[[scope.rules]]
7272
selector = "pkg:my_app.services.*"
73-
value_default = "deny"
73+
value_default = "redact"
7474
[[scope.rules.value_patterns]]
7575
selector = "local:glob:public_*"
7676
action = "allow"
7777
[[scope.rules.value_patterns]]
7878
selector = 'local:regex:^(metric|masked)_\w+$'
7979
action = "allow"
80+
[[scope.rules.value_patterns]]
81+
selector = "arg:literal:debug_payload"
82+
action = "drop"
8083
```
8184

8285
## Packaging expectations
@@ -93,4 +96,4 @@ decisions without re-running the trace.
9396
## Development benchmarks
9497
- Rust microbench: `cargo bench --bench trace_filter --no-default-features` exercises baseline, glob-heavy, and regex-heavy selector chains.
9598
- Python smoke benchmark: `pytest codetracer-python-recorder/tests/python/perf/test_trace_filter_perf.py -q` when the environment variable `CODETRACER_TRACE_FILTER_PERF=1` is set.
96-
- Run both together with `just bench`. The helper seeds a virtualenv, runs Criterion, then executes the Python smoke test while writing `target/perf/trace_filter_py.json` (per-scenario durations and redaction statistics).
99+
- Run both together with `just bench`. The helper seeds a virtualenv, runs Criterion, then executes the Python smoke test while writing `target/perf/trace_filter_py.json` (per-scenario durations plus redaction/drop statistics).

codetracer-python-recorder/benches/trace_filter.rs

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -332,7 +332,7 @@ default_value_action = "allow"
332332
333333
[[scope.rules]]
334334
selector = "pkg:bench_pkg.services.api.*"
335-
value_default = "deny"
335+
value_default = "redact"
336336
reason = "Redact service locals except approved public fields"
337337
[[scope.rules.value_patterns]]
338338
selector = "local:glob:public_*"
@@ -342,16 +342,16 @@ selector = "local:glob:metric_*"
342342
action = "allow"
343343
[[scope.rules.value_patterns]]
344344
selector = "local:glob:secret_*"
345-
action = "deny"
345+
action = "redact"
346346
[[scope.rules.value_patterns]]
347347
selector = "local:glob:token_*"
348-
action = "deny"
348+
action = "redact"
349349
[[scope.rules.value_patterns]]
350350
selector = "local:glob:masked_*"
351351
action = "allow"
352352
[[scope.rules.value_patterns]]
353353
selector = "local:glob:password_*"
354-
action = "deny"
354+
action = "redact"
355355
356356
[[scope.rules]]
357357
selector = "file:glob:bench_pkg/jobs/worker/module_*.py"
@@ -360,7 +360,7 @@ reason = "Disable redundant worker instrumentation"
360360
361361
[[scope.rules]]
362362
selector = "pkg:bench_pkg.external.integration_*"
363-
value_default = "deny"
363+
value_default = "redact"
364364
[[scope.rules.value_patterns]]
365365
selector = "local:glob:metric_*"
366366
action = "allow"
@@ -385,17 +385,17 @@ default_value_action = "allow"
385385
386386
[[scope.rules]]
387387
selector = 'pkg:regex:^bench_pkg\.services\.api\.module_\d+$'
388-
value_default = "deny"
388+
value_default = "redact"
389389
reason = "Regex match on service modules"
390390
[[scope.rules.value_patterns]]
391391
selector = 'local:regex:^(public|metric)_\w+$'
392392
action = "allow"
393393
[[scope.rules.value_patterns]]
394394
selector = 'local:regex:^(secret|token)_\w+$'
395-
action = "deny"
395+
action = "redact"
396396
[[scope.rules.value_patterns]]
397397
selector = 'local:regex:^(password|api|credit|session)_.*$'
398-
action = "deny"
398+
action = "redact"
399399
400400
[[scope.rules]]
401401
selector = 'file:regex:^bench_pkg/jobs/worker/module_\d+\.py$'
@@ -404,7 +404,7 @@ reason = "Regex skip for worker modules"
404404
405405
[[scope.rules]]
406406
selector = 'obj:regex:^bench_pkg\.external\.integration_\d+\.integration_op_\d+$'
407-
value_default = "deny"
407+
value_default = "redact"
408408
[[scope.rules.value_patterns]]
409409
selector = 'local:regex:^masked_.*$'
410410
action = "allow"

codetracer-python-recorder/resources/trace_filters/builtin_default.toml

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
[meta]
22
name = "builtin-default"
33
version = 1
4-
description = "Skip CPython stdlib internals and redact common sensitive identifiers."
4+
description = "Skip CPython stdlib internals, redact sensitive identifiers, and drop nothing by default."
55
labels = ["builtin", "default"]
66

77
[scope]
@@ -39,25 +39,25 @@ value_default = "allow"
3939

4040
[[scope.rules.value_patterns]]
4141
selector = 'local:regex:(?i).*(pass(word)?|passwd|pwd|secret|token|session|cookie|auth|credential|creds|bearer|ssn|credit|card|iban|cvv|cvc|pan|api[_-]?key|private[_-]?key|secret[_-]?key|ssh[_-]?key|jwt|refresh[_-]?token|access[_-]?token).*'
42-
action = "deny"
42+
action = "redact"
4343
reason = "Redact sensitive locals"
4444

4545
[[scope.rules.value_patterns]]
4646
selector = 'global:regex:(?i).*(pass(word)?|passwd|pwd|secret|token|session|cookie|auth|credential|creds|bearer|ssn|credit|card|iban|cvv|cvc|pan|api[_-]?key|private[_-]?key|secret[_-]?key|ssh[_-]?key|jwt|refresh[_-]?token|access[_-]?token).*'
47-
action = "deny"
47+
action = "redact"
4848
reason = "Redact sensitive globals"
4949

5050
[[scope.rules.value_patterns]]
5151
selector = 'arg:regex:(?i).*(pass(word)?|passwd|pwd|secret|token|session|cookie|auth|credential|creds|bearer|ssn|credit|card|iban|cvv|cvc|pan|api[_-]?key|private[_-]?key|secret[_-]?key|ssh[_-]?key|jwt|refresh[_-]?token|access[_-]?token).*'
52-
action = "deny"
52+
action = "redact"
5353
reason = "Redact sensitive arguments"
5454

5555
[[scope.rules.value_patterns]]
5656
selector = 'ret:regex:(?i).*(pass(word)?|passwd|pwd|secret|token|session|cookie|auth|credential|creds|bearer|ssn|credit|card|iban|cvv|cvc|pan|api[_-]?key|private[_-]?key|secret[_-]?key|ssh[_-]?key|jwt|refresh[_-]?token|access[_-]?token).*'
57-
action = "deny"
57+
action = "redact"
5858
reason = "Redact sensitive return values"
5959

6060
[[scope.rules.value_patterns]]
6161
selector = 'attr:regex:(?i).*(pass(word)?|passwd|pwd|secret|token|session|cookie|auth|credential|creds|bearer|ssn|credit|card|iban|cvv|cvc|pan|api[_-]?key|private[_-]?key|secret[_-]?key|ssh[_-]?key|jwt|refresh[_-]?token|access[_-]?token).*'
62-
action = "deny"
62+
action = "redact"
6363
reason = "Redact sensitive attributes"

codetracer-python-recorder/src/runtime/mod.rs

Lines changed: 59 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ use activation::ActivationController;
1616
use frame_inspector::capture_frame;
1717
use logging::log_event;
1818
use value_capture::{
19-
capture_call_arguments, record_return_value, record_visible_scope, ValueRedactionStats,
19+
capture_call_arguments, record_return_value, record_visible_scope, ValueFilterStats,
2020
};
2121

2222
use std::collections::{hash_map::Entry, HashMap, HashSet};
@@ -137,31 +137,40 @@ impl FailureStage {
137137
#[derive(Debug, Default)]
138138
struct FilterStats {
139139
skipped_scopes: u64,
140-
redactions: ValueRedactionStats,
140+
values: ValueFilterStats,
141141
}
142142

143143
impl FilterStats {
144144
fn record_skip(&mut self) {
145145
self.skipped_scopes += 1;
146146
}
147147

148-
fn redactions_mut(&mut self) -> &mut ValueRedactionStats {
149-
&mut self.redactions
148+
fn values_mut(&mut self) -> &mut ValueFilterStats {
149+
&mut self.values
150150
}
151151

152152
fn reset(&mut self) {
153153
self.skipped_scopes = 0;
154-
self.redactions = ValueRedactionStats::default();
154+
self.values = ValueFilterStats::default();
155155
}
156156

157157
fn summary_json(&self) -> serde_json::Value {
158158
let mut redactions = serde_json::Map::new();
159+
let mut drops = serde_json::Map::new();
159160
for kind in ValueKind::ALL {
160-
redactions.insert(kind.label().to_string(), json!(self.redactions.count(kind)));
161+
redactions.insert(
162+
kind.label().to_string(),
163+
json!(self.values.redacted_count(kind)),
164+
);
165+
drops.insert(
166+
kind.label().to_string(),
167+
json!(self.values.dropped_count(kind)),
168+
);
161169
}
162170
json!({
163171
"scopes_skipped": self.skipped_scopes,
164172
"value_redactions": serde_json::Value::Object(redactions),
173+
"value_drops": serde_json::Value::Object(drops),
165174
})
166175
}
167176
}
@@ -725,7 +734,7 @@ impl Tracer for RuntimeTracer {
725734

726735
if let Ok(fid) = self.ensure_function_id(py, code) {
727736
let mut telemetry_holder = if wants_telemetry {
728-
Some(self.filter_stats.redactions_mut())
737+
Some(self.filter_stats.values_mut())
729738
} else {
730739
None
731740
};
@@ -805,7 +814,7 @@ impl Tracer for RuntimeTracer {
805814

806815
let mut recorded: HashSet<String> = HashSet::new();
807816
let mut telemetry_holder = if wants_telemetry {
808-
Some(self.filter_stats.redactions_mut())
817+
Some(self.filter_stats.values_mut())
809818
} else {
810819
None
811820
};
@@ -850,7 +859,7 @@ impl Tracer for RuntimeTracer {
850859
let object_name = scope_resolution.as_ref().and_then(|res| res.object_name());
851860

852861
let mut telemetry_holder = if wants_telemetry {
853-
Some(self.filter_stats.redactions_mut())
862+
Some(self.filter_stats.values_mut())
854863
} else {
855864
None
856865
};
@@ -1718,23 +1727,27 @@ def emit_return(value):
17181727
17191728
[[scope.rules.value_patterns]]
17201729
selector = "arg:password"
1721-
action = "deny"
1730+
action = "redact"
17221731
17231732
[[scope.rules.value_patterns]]
17241733
selector = "local:password"
1725-
action = "deny"
1734+
action = "redact"
17261735
17271736
[[scope.rules.value_patterns]]
17281737
selector = "local:secret"
1729-
action = "deny"
1738+
action = "redact"
17301739
17311740
[[scope.rules.value_patterns]]
17321741
selector = "global:shared_secret"
1733-
action = "deny"
1742+
action = "redact"
17341743
17351744
[[scope.rules.value_patterns]]
17361745
selector = "ret:literal:app.sec.sensitive"
1737-
action = "deny"
1746+
action = "redact"
1747+
1748+
[[scope.rules.value_patterns]]
1749+
selector = "local:internal"
1750+
action = "drop"
17381751
"#,
17391752
);
17401753
let config = TraceFilterConfig::from_paths(&[filter_path]).expect("load filter");
@@ -1748,6 +1761,7 @@ shared_secret = "initial"
17481761
17491762
def sensitive(password):
17501763
secret = "token"
1764+
internal = "hidden"
17511765
public = "visible"
17521766
globals()['shared_secret'] = password
17531767
snapshot()
@@ -1786,6 +1800,10 @@ sensitive("s3cr3t")
17861800
variable_names.push(name.clone());
17871801
}
17881802
}
1803+
assert!(
1804+
!variable_names.iter().any(|name| name == "internal"),
1805+
"internal variable should not be recorded"
1806+
);
17891807

17901808
let password_index = variable_names
17911809
.iter()
@@ -1832,6 +1850,7 @@ sensitive("s3cr3t")
18321850
"password",
18331851
SimpleValue::Raw("<redacted>".to_string()),
18341852
);
1853+
assert_no_variable(&snapshots, "internal");
18351854

18361855
let return_record = tracer
18371856
.writer
@@ -1879,23 +1898,27 @@ sensitive("s3cr3t")
18791898
18801899
[[scope.rules.value_patterns]]
18811900
selector = "arg:password"
1882-
action = "deny"
1901+
action = "redact"
18831902
18841903
[[scope.rules.value_patterns]]
18851904
selector = "local:password"
1886-
action = "deny"
1905+
action = "redact"
18871906
18881907
[[scope.rules.value_patterns]]
18891908
selector = "local:secret"
1890-
action = "deny"
1909+
action = "redact"
18911910
18921911
[[scope.rules.value_patterns]]
18931912
selector = "global:shared_secret"
1894-
action = "deny"
1913+
action = "redact"
18951914
18961915
[[scope.rules.value_patterns]]
18971916
selector = "ret:literal:app.sec.sensitive"
1898-
action = "deny"
1917+
action = "redact"
1918+
1919+
[[scope.rules.value_patterns]]
1920+
selector = "local:internal"
1921+
action = "drop"
18991922
"#,
19001923
);
19011924
let config = TraceFilterConfig::from_paths(&[filter_path]).expect("load filter");
@@ -1909,6 +1932,8 @@ shared_secret = "initial"
19091932
19101933
def sensitive(password):
19111934
secret = "token"
1935+
internal = "hidden"
1936+
public = "visible"
19121937
globals()['shared_secret'] = password
19131938
snapshot()
19141939
emit_return(password)
@@ -1999,6 +2024,21 @@ sensitive("s3cr3t")
19992024
value_redactions.get("attribute").and_then(|v| v.as_u64()),
20002025
Some(0)
20012026
);
2027+
let value_drops = stats
2028+
.get("value_drops")
2029+
.and_then(|value| value.as_object())
2030+
.expect("value_drops object");
2031+
assert_eq!(
2032+
value_drops.get("argument").and_then(|v| v.as_u64()),
2033+
Some(0)
2034+
);
2035+
assert_eq!(value_drops.get("local").and_then(|v| v.as_u64()), Some(1));
2036+
assert_eq!(value_drops.get("global").and_then(|v| v.as_u64()), Some(0));
2037+
assert_eq!(value_drops.get("return").and_then(|v| v.as_u64()), Some(0));
2038+
assert_eq!(
2039+
value_drops.get("attribute").and_then(|v| v.as_u64()),
2040+
Some(0)
2041+
);
20022042
});
20032043
}
20042044

0 commit comments

Comments
 (0)