feat(plugins): per-source pattern attribution + explicit pre-screen rebuild proof
Two review items raised on #65449 (thanks @hansai-art): 1. Explicit test that post-module-load registration REBUILDS the _PREFIX_SUBSTRINGS pre-screen tuple — plugin patterns flow through the same fast path as built-ins, never around it. This was covered implicitly by the masking tests; now it is asserted directly. 2. Plugin patterns are now stored keyed by registration source, giving the #64229 lifecycle/ownership-ledger work a clean seam to drop one plugin's patterns on unload. No public removal API is added — additive-only stands; unload remains a host-owned lifecycle concern. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PnMCvi2vXqfs996AjVeF2F
This commit is contained in:
committed by
Teknium
parent
50f12e6ad8
commit
22002b1d3e
@@ -1274,10 +1274,19 @@ def _has_known_prefix_substring(text: str) -> bool:
|
||||
# (``security.redact_secrets: false`` / HERMES_REDACT_SECRETS) applies to
|
||||
# plugin patterns exactly as it does to built-ins.
|
||||
|
||||
_PLUGIN_PREFIX_PATTERNS: list = []
|
||||
# Keyed by registration source (e.g. "plugin:my-plugin") so the plugin
|
||||
# lifecycle/ownership-ledger work (#64229) has a clean seam to drop ONE
|
||||
# plugin's patterns on unload. There is deliberately no public removal
|
||||
# API — additive-only stands; unload is a host-owned lifecycle concern.
|
||||
_PLUGIN_PREFIX_PATTERNS: dict = {}
|
||||
_registry_lock = threading.Lock()
|
||||
|
||||
|
||||
def _plugin_patterns() -> list:
|
||||
"""All plugin-registered patterns in registration order."""
|
||||
return [p for patterns in _PLUGIN_PREFIX_PATTERNS.values() for p in patterns]
|
||||
|
||||
|
||||
def _rebuild_prefix_matcher() -> None:
|
||||
"""Recompile the prefix alternation and pre-screen substrings.
|
||||
|
||||
@@ -1286,7 +1295,7 @@ def _rebuild_prefix_matcher() -> None:
|
||||
under the GIL) propagates immediately to every caller.
|
||||
"""
|
||||
global _PREFIX_RE, _PREFIX_SUBSTRINGS
|
||||
combined = _PREFIX_PATTERNS + _PLUGIN_PREFIX_PATTERNS
|
||||
combined = _PREFIX_PATTERNS + _plugin_patterns()
|
||||
_PREFIX_RE = re.compile(
|
||||
r"(?<![A-Za-z0-9_-])(" + "|".join(combined) + r")(?![A-Za-z0-9_-])"
|
||||
)
|
||||
@@ -1365,14 +1374,14 @@ def register_redaction_patterns(patterns, source: str = "plugin") -> int:
|
||||
source, pattern,
|
||||
)
|
||||
continue
|
||||
if pattern in _PREFIX_PATTERNS or pattern in _PLUGIN_PREFIX_PATTERNS or pattern in accepted:
|
||||
if pattern in _PREFIX_PATTERNS or pattern in _plugin_patterns() or pattern in accepted:
|
||||
logger.debug("%s: redaction pattern %r already registered", source, pattern)
|
||||
continue
|
||||
accepted.append(pattern)
|
||||
|
||||
if accepted:
|
||||
with _registry_lock:
|
||||
_PLUGIN_PREFIX_PATTERNS.extend(accepted)
|
||||
_PLUGIN_PREFIX_PATTERNS.setdefault(source, []).extend(accepted)
|
||||
_rebuild_prefix_matcher()
|
||||
logger.info(
|
||||
"%s: registered %d redaction pattern(s)", source, len(accepted)
|
||||
|
||||
@@ -55,6 +55,28 @@ def test_registered_pattern_masks_token():
|
||||
assert "nvapi-" in out and "..." in out
|
||||
|
||||
|
||||
def test_prescreen_tuple_rebuilt_not_bypassed():
|
||||
# The cheap pre-screen gate (_has_known_prefix_substring) consults
|
||||
# _PREFIX_SUBSTRINGS. Registration after module load must REBUILD that
|
||||
# tuple so plugin patterns flow through the same fast path as built-ins
|
||||
# — never around it.
|
||||
assert "nvapi-" not in redact_mod._PREFIX_SUBSTRINGS
|
||||
assert not redact_mod._has_known_prefix_substring(f"x {NVAPI_KEY}")
|
||||
register_redaction_patterns([NVAPI_PATTERN], source="test")
|
||||
assert "nvapi-" in redact_mod._PREFIX_SUBSTRINGS
|
||||
assert redact_mod._has_known_prefix_substring(f"x {NVAPI_KEY}")
|
||||
|
||||
|
||||
def test_patterns_attributed_per_source():
|
||||
# Patterns are stored keyed by registration source — the seam the
|
||||
# #64229 lifecycle/ledger path needs to drop one plugin's patterns on
|
||||
# unload. No public removal API exists; additive-only stands.
|
||||
register_redaction_patterns([NVAPI_PATTERN], source="plugin:alpha")
|
||||
register_redaction_patterns([r"zk-[A-Za-z0-9]{24,}"], source="plugin:beta")
|
||||
assert redact_mod._PLUGIN_PREFIX_PATTERNS["plugin:alpha"] == [NVAPI_PATTERN]
|
||||
assert redact_mod._PLUGIN_PREFIX_PATTERNS["plugin:beta"] == [r"zk-[A-Za-z0-9]{24,}"]
|
||||
|
||||
|
||||
def test_builtins_unaffected_by_registration():
|
||||
register_redaction_patterns([NVAPI_PATTERN], source="test")
|
||||
sk = "sk-proj-AbCdEf1234567890GhIjKl"
|
||||
|
||||
Reference in New Issue
Block a user