Three known limits in the effective_config guards and redactor added by #264. None is a live defect today; all are drift risk, and each is written down here rather than left implicit.
1. Guard 1 has two blind spots
no_production_code_reads_the_environment_outside_the_recorder is a substring scan. Two shapes evade it:
- Braced import.
use std::env::{var}; produces no use std::env::var substring, and the call site is then a bare var("X") with no env::var( prefix. Invisible.
env_var(&expr). Guard 2 counts non-literal env_var sites to pin the one legitimate dynamic caller, but excludes anything starting with & — so env_var(&name.to_uppercase()) is hidden from the dynamic count by the & exclusion and from the literal-name list by being non-literal. Invisible to both directions. The & exclusion has no current user and can probably just be dropped.
The real fix for both is to stop matching text: walk the token stream (or a syn parse) instead.
2. is_secret over-matches, and changes a JSON type
scrub_value replaces any leaf under a credential-named key with the string <redacted:set>, so a number becomes a string — session_pool_size: 8 → "<redacted:set>". A consumer that expects a number gets one only when the key happens not to match.
The marker list is contains over 13 substrings, so plausible-but-benign keys match: tokenizer (a real ES/OS analyzer key), password_policy, session_pool_size. And strip_userinfo's conservative fallback rewrites http://reporting.internal:8080/dashboards/team@ops → http://<redacted:set>@ops, destroying host, port and path.
Measured today: zero false positives across all 31 shipped configs and all 104 recorded knob names — every match is a genuine credential. The bias towards over-redaction is deliberate (a redacted knob costs provenance; a published one costs a rotation). But the type change deserves a decision: emit a typed placeholder, or keep the value's type and blank only strings.
3. Recording proves presence, not identity
effective_config::Recording is move-only and consumed by run_single_experiment, which makes a commented-out, conditional, or hoisted begin_experiment a compile error. It does not prove the token belongs to the experiment being run: handing run_single_experiment a token minted for a different (config, dataset) pair compiles and passes.
Closing that means carrying the config name and dataset in the token and checking it at the far end. The ordering half ("the recording begins before the engine is built") is covered by a source-scanning guard instead.
Three known limits in the
effective_configguards and redactor added by #264. None is a live defect today; all are drift risk, and each is written down here rather than left implicit.1. Guard 1 has two blind spots
no_production_code_reads_the_environment_outside_the_recorderis a substring scan. Two shapes evade it:use std::env::{var};produces nouse std::env::varsubstring, and the call site is then a barevar("X")with noenv::var(prefix. Invisible.env_var(&expr). Guard 2 counts non-literalenv_varsites to pin the one legitimate dynamic caller, but excludes anything starting with&— soenv_var(&name.to_uppercase())is hidden from the dynamic count by the&exclusion and from the literal-name list by being non-literal. Invisible to both directions. The&exclusion has no current user and can probably just be dropped.The real fix for both is to stop matching text: walk the token stream (or a
synparse) instead.2.
is_secretover-matches, and changes a JSON typescrub_valuereplaces any leaf under a credential-named key with the string<redacted:set>, so a number becomes a string —session_pool_size: 8→"<redacted:set>". A consumer that expects a number gets one only when the key happens not to match.The marker list is
containsover 13 substrings, so plausible-but-benign keys match:tokenizer(a real ES/OS analyzer key),password_policy,session_pool_size. Andstrip_userinfo's conservative fallback rewriteshttp://reporting.internal:8080/dashboards/team@ops→http://<redacted:set>@ops, destroying host, port and path.Measured today: zero false positives across all 31 shipped configs and all 104 recorded knob names — every match is a genuine credential. The bias towards over-redaction is deliberate (a redacted knob costs provenance; a published one costs a rotation). But the type change deserves a decision: emit a typed placeholder, or keep the value's type and blank only strings.
3.
Recordingproves presence, not identityeffective_config::Recordingis move-only and consumed byrun_single_experiment, which makes a commented-out, conditional, or hoistedbegin_experimenta compile error. It does not prove the token belongs to the experiment being run: handingrun_single_experimenta token minted for a different (config, dataset) pair compiles and passes.Closing that means carrying the config name and dataset in the token and checking it at the far end. The ordering half ("the recording begins before the engine is built") is covered by a source-scanning guard instead.