Skip to content

Commit d8b9cc7

Browse files
committed
fix: address CI lint and review feedback on safety module
- Remove unused typing.Any import in _wrapper.py (F401) - _rules.py: parenthesize or/and precedence in decode-exec check - _rules.py: simplify isinstance(...) if ... else False patterns - _rules.py: extract is_process_call variable for readability - _rules.py: use getattr chain instead of hasattr for ast.Subscript.slice - _filter.py: drop redundant isinstance(rsp.rsp, dict) after the if not isinstance - _policy.py: validate placeholder byte caps regardless of strict mode - _wrapper.py: clarify SafeCodeExecutor alias rationale - Wrap several >120-char lines to satisfy flake8 --max-line-length=120 flake8: clean. pytest tests/tool_safety/: 104 passed, 3 skipped
1 parent 50a6ec0 commit d8b9cc7

5 files changed

Lines changed: 44 additions & 24 deletions

File tree

trpc_agent_sdk/safety/_filter.py

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -76,8 +76,10 @@ async def _before(self, ctx: Any, req: Any, rsp: FilterResult) -> None:
7676
)
7777
report = self.scanner.scan(scan_input)
7878

79-
should_block = report.decision == Decision.DENY or (report.decision == Decision.NEEDS_HUMAN_REVIEW
80-
and self._block_on_review)
79+
should_block = (
80+
report.decision == Decision.DENY
81+
or (report.decision == Decision.NEEDS_HUMAN_REVIEW and self._block_on_review)
82+
)
8183
self.audit.log(report, intercepted=should_block)
8284

8385
if should_block:
@@ -110,10 +112,9 @@ async def _before(self, ctx: Any, req: Any, rsp: FilterResult) -> None:
110112
# remain in audit / OTel.
111113
if not isinstance(rsp.rsp, dict):
112114
rsp.rsp = {}
113-
if isinstance(rsp.rsp, dict):
114-
rsp.rsp["safety_warning"] = "TOOL_SAFETY_NEEDS_REVIEW"
115-
rsp.rsp["safety_risk_level"] = report.risk_level.value
116-
rsp.rsp["safety_rule_ids"] = list(report.rule_ids)
115+
rsp.rsp["safety_warning"] = "TOOL_SAFETY_NEEDS_REVIEW"
116+
rsp.rsp["safety_risk_level"] = report.risk_level.value
117+
rsp.rsp["safety_rule_ids"] = list(report.rule_ids)
117118

118119
def _resolve_tool_name(self, ctx: Any) -> str:
119120
get_tool_var = self._get_tool_var

trpc_agent_sdk/safety/_policy.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -74,9 +74,12 @@ def from_dict(cls, data: dict[str, Any]) -> "PolicyConfig":
7474
unknown = set(data.keys()) - cls._KNOWN_KEYS
7575
if unknown:
7676
raise ValueError(f"unknown policy keys: {sorted(unknown)}")
77-
for key in ("max_timeout_seconds", "max_output_bytes", "max_file_write_bytes"):
78-
if key in data and data[key] is not None and int(data[key]) < 0:
79-
raise ValueError(f"{key} must be non-negative")
77+
# max_output_bytes / max_file_write_bytes are runtime placeholders
78+
# (static scanning cannot enforce byte caps). Validate as non-negative
79+
# ints regardless of strict mode so malformed YAML fails fast.
80+
for key in ("max_timeout_seconds", "max_output_bytes", "max_file_write_bytes"):
81+
if key in data and data[key] is not None and int(data[key]) < 0:
82+
raise ValueError(f"{key} must be non-negative")
8083

8184
deny_lvl = _parse_risk_level(data.get("deny_risk_level"), RiskLevel.HIGH)
8285
review_lvl = _parse_risk_level(data.get("review_risk_level"), RiskLevel.MEDIUM)

trpc_agent_sdk/safety/_rules.py

Lines changed: 22 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -188,7 +188,7 @@ def _check_python(self, scan_input: ScanInput, policy: PolicyConfig) -> list[Saf
188188

189189
if lname in {"open", "builtins.open"} or lname.endswith(".open"):
190190
target = _first_str_or_path(node) or "<dynamic>"
191-
if isinstance(node.args[0], ast.Name) if node.args else False:
191+
if node.args and isinstance(node.args[0], ast.Name):
192192
var = node.args[0].id
193193
if var in path_vars:
194194
target = path_vars[var]
@@ -366,8 +366,8 @@ def _collect_sensitive_path_vars(
366366
text = ""
367367
if isinstance(value, ast.Call):
368368
cname = resolve_name(value.func, aliases).lower()
369-
if cname in join_names or cname.endswith(".join") or cname.endswith(".joinpath") or cname.endswith(
370-
".expanduser"):
369+
if (cname in join_names or cname.endswith(".join")
370+
or cname.endswith(".joinpath") or cname.endswith(".expanduser")):
371371
text = path_expr_text(value)
372372
else:
373373
text = path_expr_text(value)
@@ -736,10 +736,15 @@ def _check_python(self, scan_input: ScanInput, policy: PolicyConfig) -> list[Saf
736736

737737
for node, name in iter_python_calls(tree, aliases):
738738
lname = name.lower()
739-
if lname in _PY_PROCESS_CALLS or any(
739+
is_process_call = (
740+
lname in _PY_PROCESS_CALLS
741+
or lname in {c.lower() for c in _PY_PROCESS_CALLS}
742+
or any(
740743
lname.endswith("." + c.split(".")[-1]) and c.split(".")[0] in lname
741-
for c in _PY_PROCESS_CALLS) or lname in {c.lower()
742-
for c in _PY_PROCESS_CALLS}:
744+
for c in _PY_PROCESS_CALLS
745+
)
746+
)
747+
if is_process_call:
743748
shell_true = _has_shell_true(node)
744749
findings.append(
745750
self._finding(
@@ -892,8 +897,9 @@ def _check_bash(self, scan_input: ScanInput, policy: PolicyConfig) -> list[Safet
892897
level=RiskLevel.CRITICAL,
893898
message="Use of eval in bash",
894899
))
895-
if _DECODE_EXEC_BASH.search(line) or re.search(r"\|\s*(sh|bash|zsh)\b", line) and re.search(
896-
r"base64|xxd|openssl", line, re.IGNORECASE):
900+
if (_DECODE_EXEC_BASH.search(line)
901+
or (re.search(r"\|\s*(sh|bash|zsh)\b", line)
902+
and re.search(r"base64|xxd|openssl", line, re.IGNORECASE))):
897903
findings.append(
898904
self._finding(
899905
line,
@@ -1266,8 +1272,9 @@ def _const_int(node: ast.AST | None) -> int | None:
12661272
"httpx.post",
12671273
}
12681274

1269-
_ENV_SECRET_KEYS = re.compile(r"(?i)(API[_-]?KEY|TOKEN|SECRET|PASSWORD|PASSWD|PRIVATE[_-]?KEY|ACCESS[_-]?KEY|"
1270-
r"OPENAI|ANTHROPIC|AWS_SECRET|GITHUB_TOKEN|GH_TOKEN)")
1275+
_ENV_SECRET_KEYS = re.compile(
1276+
r"(?i)(API[_-]?KEY|TOKEN|SECRET|PASSWORD|PASSWD|PRIVATE[_-]?KEY|"
1277+
r"ACCESS[_-]?KEY|OPENAI|ANTHROPIC|AWS_SECRET|GITHUB_TOKEN|GH_TOKEN)")
12711278

12721279

12731280
class SecretLeakRule(SafetyRule):
@@ -1369,8 +1376,11 @@ def _check_python(self, scan_input: ScanInput, policy: PolicyConfig) -> list[Saf
13691376
# Direct print(os.environ["X"])
13701377
for node in ast.walk(tree):
13711378
if isinstance(node, ast.Subscript) and _is_environ_subscript(node, aliases):
1372-
key = get_string_literal(node.slice) if hasattr(node, "slice") else None
1373-
if key is None or _ENV_SECRET_KEYS.search(str(key) if key else "SECRET"):
1379+
# ast.Subscript always has .slice in py>=3.9; fall back to None
1380+
# for older interpreters or unusual AST shapes.
1381+
key = getattr(getattr(node, "slice", None), "value", None)
1382+
key_str = get_string_literal(key) if key is not None else None
1383+
if key_str is None or _ENV_SECRET_KEYS.search(str(key_str if key_str else "SECRET")):
13741384
# Flag any environ subscript used as expression; severity HIGH
13751385
findings.append(
13761386
self._finding(

trpc_agent_sdk/safety/_scanner.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -161,7 +161,10 @@ def scan(self, scan_input: ScanInput) -> SafetyReport:
161161

162162
# blocked means "would be intercepted under this policy": DENY always,
163163
# and NEEDS_HUMAN_REVIEW when policy.block_on_review is enabled.
164-
blocked = decision == Decision.DENY or (decision == Decision.NEEDS_HUMAN_REVIEW and self.policy.block_on_review)
164+
blocked = (
165+
decision == Decision.DENY
166+
or (decision == Decision.NEEDS_HUMAN_REVIEW and self.policy.block_on_review)
167+
)
165168
return SafetyReport(
166169
decision=decision,
167170
risk_level=agg_level,

trpc_agent_sdk/safety/_wrapper.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@
88

99
import asyncio
1010
import functools
11-
from typing import Any
1211
from typing import Optional
1312

1413
from ._audit import AuditLogger
@@ -166,7 +165,11 @@ async def execute_code(self, invocation_context, input_data):
166165
return _SafeCodeExecutor()
167166

168167

169-
# Backwards-compatible factory alias (returns an object with execute_code).
168+
# Backwards-compatible alias. SafeCodeExecutor is exposed as a "class-like"
169+
# callable that returns an object with execute_code; it is intentionally a
170+
# factory function (not a class) so we can avoid importing the optional
171+
# code_executors package. Existing code that writes ``SafeCodeExecutor(inner,
172+
# policy)`` continues to work.
170173
SafeCodeExecutor = safe_code_executor
171174

172175

0 commit comments

Comments
 (0)