Skip to content

Commit bb216a5

Browse files
Violet2314claude
andcommitted
fix(safety): cache tool name lookup and lock custom rule registry
Avoid importing the full tools package on every filter scan, and make the global custom-rule registry thread-safe with RLock snapshots. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 parent ad09672 commit bb216a5

2 files changed

Lines changed: 35 additions & 12 deletions

File tree

trpc_agent_sdk/safety/_filter.py

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,14 @@ def __init__(
4949
self.audit = AuditLogger(audit_path)
5050
self._configured_tool_name = tool_name
5151
self._block_on_review = (policy.block_on_review if block_on_review is None else block_on_review)
52+
# Prefer lightweight context-var import; never pull trpc_agent_sdk.tools
53+
# package (heavy optional deps like anthropic) on every scan.
54+
self._get_tool_var = None
55+
try:
56+
from trpc_agent_sdk.tools._context_var import get_tool_var
57+
self._get_tool_var = get_tool_var
58+
except Exception: # pylint: disable=broad-except
59+
self._get_tool_var = None
5260

5361
async def _before(self, ctx: Any, req: Any, rsp: FilterResult) -> None:
5462
"""Scan the tool args; block execution when decision is DENY."""
@@ -98,8 +106,10 @@ async def _before(self, ctx: Any, req: Any, rsp: FilterResult) -> None:
98106
f"rules={','.join(report.rule_ids)}")
99107

100108
def _resolve_tool_name(self, ctx: Any) -> str:
109+
get_tool_var = self._get_tool_var
110+
if get_tool_var is None:
111+
return self._configured_tool_name
101112
try:
102-
from trpc_agent_sdk.tools import get_tool_var
103113
tool = get_tool_var()
104114
if tool is not None and getattr(tool, "name", None):
105115
return tool.name

trpc_agent_sdk/safety/_scanner.py

Lines changed: 24 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -24,20 +24,25 @@
2424
from ._types import ScanInput
2525
from ._types import max_risk_level
2626

27+
import threading
28+
2729
SCANNER_VERSION = "1.2.0"
2830

2931
_custom_rules: list[SafetyRule] = []
32+
_custom_rules_lock = threading.RLock()
3033

3134

3235
def register_custom_rule(rule: SafetyRule) -> None:
3336
"""Register a custom rule included in new scanners by default.
3437
3538
Existing SafetyScanner instances are unaffected. Registration is
36-
idempotent by rule_id.
39+
idempotent by rule_id. Prefer passing ``rules=`` to SafetyScanner for
40+
process-local isolation; the global registry is a convenience for plugins.
3741
"""
38-
global _custom_rules
39-
_custom_rules = [r for r in _custom_rules if r.rule_id != rule.rule_id]
40-
_custom_rules.append(rule)
42+
with _custom_rules_lock:
43+
global _custom_rules
44+
_custom_rules = [r for r in _custom_rules if r.rule_id != rule.rule_id]
45+
_custom_rules.append(rule)
4146

4247

4348
def register_rule(cls: type | None = None):
@@ -63,16 +68,23 @@ def _decorate(rule_cls: type) -> type:
6368

6469
def unregister_custom_rule(rule_id: str) -> bool:
6570
"""Remove a previously registered custom rule. Returns True if removed."""
66-
global _custom_rules
67-
before = len(_custom_rules)
68-
_custom_rules = [r for r in _custom_rules if r.rule_id != rule_id]
69-
return len(_custom_rules) < before
71+
with _custom_rules_lock:
72+
global _custom_rules
73+
before = len(_custom_rules)
74+
_custom_rules = [r for r in _custom_rules if r.rule_id != rule_id]
75+
return len(_custom_rules) < before
7076

7177

7278
def clear_custom_rules() -> None:
7379
"""Remove all custom rules from the registry."""
74-
global _custom_rules
75-
_custom_rules = []
80+
with _custom_rules_lock:
81+
global _custom_rules
82+
_custom_rules = []
83+
84+
85+
def _snapshot_custom_rules() -> list[SafetyRule]:
86+
with _custom_rules_lock:
87+
return list(_custom_rules)
7688

7789

7890
class SafetyScanner:
@@ -84,7 +96,8 @@ def __init__(
8496
rules: Optional[list[SafetyRule]] = None,
8597
):
8698
self.policy = policy
87-
self.rules = rules if rules is not None else default_rules() + list(_custom_rules)
99+
# Prefer explicit rules= for isolation; global registry is a snapshot.
100+
self.rules = rules if rules is not None else default_rules() + _snapshot_custom_rules()
88101

89102
def scan(self, scan_input: ScanInput) -> SafetyReport:
90103
"""Scan *scan_input* and return a structured SafetyReport."""

0 commit comments

Comments
 (0)