Skip to content

Commit 2e52028

Browse files
committed
fix(safety): resolve CI lint failures (yapf + flake8)
CI lint job (YAPF + flake8) failed on the safety package. Fixes: - Remove unused Decision/RiskLevel imports and unused `norm` local - Reformat the package with yapf (column_limit=120, split_before_logical_operator) to match CI style exactly - Wrap an over-long ValueError message in _policy._decision flake8 + yapf now clean on all changed trpc_agent_sdk/*.py; 73 tests pass.
1 parent a834834 commit 2e52028

8 files changed

Lines changed: 69 additions & 77 deletions

File tree

trpc_agent_sdk/tools/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -217,4 +217,5 @@ def __getattr__(name):
217217
def __dir__():
218218
return sorted(set(list(globals()) + list(_LAZY_REEXPORTS)))
219219

220+
220221
from trpc_agent_sdk.tools import safety # noqa: F401

trpc_agent_sdk/tools/safety/_audit.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,8 @@ def build_audit_record(
8181
language=language,
8282
decision=report.decision.name,
8383
risk_level=report.risk_level.name,
84-
rule_ids=sorted({f.rule_id for f in report.findings}),
84+
rule_ids=sorted({f.rule_id
85+
for f in report.findings}),
8586
scan_duration_ms=report.scan_duration_ms,
8687
sanitized=bool(report.sanitized),
8788
intercepted=bool(intercepted),

trpc_agent_sdk/tools/safety/_bash_scanner.py

Lines changed: 17 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -35,42 +35,39 @@ def scan_bash(policy: Policy, script: str) -> list[Finding]:
3535

3636
def add(rule_id: str, evidence: str, rec: str) -> None:
3737
meta = rule_meta[rule_id]
38-
findings.append(Finding(
39-
rule_id=rule_id,
40-
risk_level=meta.risk_level,
41-
rule_decision=meta.decision,
42-
evidence=evidence[:max_ev],
43-
recommendation=rec,
44-
language="bash",
45-
))
38+
findings.append(
39+
Finding(
40+
rule_id=rule_id,
41+
risk_level=meta.risk_level,
42+
rule_decision=meta.decision,
43+
evidence=evidence[:max_ev],
44+
recommendation=rec,
45+
language="bash",
46+
))
4647

4748
joined = script
4849

4950
# Recursive delete
50-
if re.search(r"\brm\b[^;\n]*-r[f]?", joined) and ("/" == _rm_target(joined) or
51-
re.search(r"rm\s+-[rf]+\s+/", joined)):
52-
add(R_FS_RECURSIVE_DELETE, "rm -rf against root/system path",
53-
"Refuse recursive delete of system paths.")
51+
if re.search(r"\brm\b[^;\n]*-r[f]?", joined) and ("/" == _rm_target(joined)
52+
or re.search(r"rm\s+-[rf]+\s+/", joined)):
53+
add(R_FS_RECURSIVE_DELETE, "rm -rf against root/system path", "Refuse recursive delete of system paths.")
5454

5555
# Fork bomb
5656
if _FORK_BOMB_RE.search(joined):
5757
add(R_RES_FORK_BOMB, "fork bomb pattern", "Refuse fork bomb.")
5858

5959
# Dependency install
6060
if re.search(r"\b(pip|pip3|npm|yarn|apt|apt-get|yum|brew)\s+install\b", joined):
61-
add(R_PKG_INSTALL, "dependency install command",
62-
"Installing deps changes the runtime environment; review.")
61+
add(R_PKG_INSTALL, "dependency install command", "Installing deps changes the runtime environment; review.")
6362

6463
# Privilege escalation
6564
if re.search(r"\b(sudo|su|doas)\b", joined):
66-
add(R_PROC_PRIVILEGE_ESCALATION, "privilege escalation command",
67-
"Privilege escalation requires review.")
65+
add(R_PROC_PRIVILEGE_ESCALATION, "privilege escalation command", "Privilege escalation requires review.")
6866

6967
# Long sleep (>= policy.max_timeout_seconds)
7068
for m in _SLEEP_RE.finditer(joined):
7169
if int(m.group(1)) >= policy.max_timeout_seconds:
72-
add(R_RES_LONG_SLEEP, f"sleep {m.group(1)}",
73-
f"sleep >= {policy.max_timeout_seconds}s is suspicious.")
70+
add(R_RES_LONG_SLEEP, f"sleep {m.group(1)}", f"sleep >= {policy.max_timeout_seconds}s is suspicious.")
7471
break
7572

7673
# Large write: dd/truncate with GB+ size, or head -c with a huge byte count.
@@ -79,8 +76,7 @@ def add(rule_id: str, evidence: str, rec: str) -> None:
7976
"Very large file generation; possible disk exhaustion. Review.")
8077
for m in _HEAD_C_RE.finditer(joined):
8178
if int(m.group(1)) >= _HUGE_BYTES:
82-
add(R_RES_LARGE_WRITE, f"head -c {m.group(1)}",
83-
"Very large write; possible disk exhaustion. Review.")
79+
add(R_RES_LARGE_WRITE, f"head -c {m.group(1)}", "Very large write; possible disk exhaustion. Review.")
8480
break
8581

8682
# Shell pipe / bypass
@@ -100,8 +96,7 @@ def add(rule_id: str, evidence: str, rec: str) -> None:
10096
host = m.group(1).lower()
10197
root_domain = ".".join(host.split(".")[-2:]) if len(host.split(".")) >= 2 else host
10298
if root_domain not in policy.whitelisted_domains and host not in policy.whitelisted_domains:
103-
add(R_NET_HTTP, f"network egress to {host}",
104-
f"{host} is not whitelisted; review or allowlist.")
99+
add(R_NET_HTTP, f"network egress to {host}", f"{host} is not whitelisted; review or allowlist.")
105100

106101
return findings
107102

trpc_agent_sdk/tools/safety/_code_executor_guard.py

Lines changed: 5 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -49,22 +49,18 @@ def _ensure_policy(self) -> Policy:
4949
return self._policy
5050

5151
@override
52-
async def execute_code(self,
53-
invocation_context: InvocationContext,
52+
async def execute_code(self, invocation_context: InvocationContext,
5453
input_data: CodeExecutionInput) -> CodeExecutionResult:
5554
if not input_data.code_blocks and input_data.code:
5655
input_data.code_blocks = [CodeBlock(code=input_data.code, language="python")]
5756

5857
kept: list[CodeBlock] = []
5958
blocked_msgs: list[str] = []
6059
for block in input_data.code_blocks:
61-
report = scan(self._ensure_policy(), block.code,
62-
language=block.language or "auto")
60+
report = scan(self._ensure_policy(), block.code, language=block.language or "auto")
6361
decision = report.decision
64-
block_allowed = (
65-
decision == Decision.ALLOW
66-
or (decision == Decision.NEEDS_REVIEW and not self.block_on_review)
67-
)
62+
block_allowed = (decision == Decision.ALLOW
63+
or (decision == Decision.NEEDS_REVIEW and not self.block_on_review))
6864
# Audit each scanned block (issue #90): tool name, decision, risk,
6965
# rule ids, duration, sanitized, intercepted.
7066
record_safety_decision(
@@ -77,8 +73,7 @@ async def execute_code(self,
7773
kept.append(block)
7874
else:
7975
ids = ",".join(sorted({f.rule_id for f in report.findings}))
80-
blocked_msgs.append(
81-
f"TOOL_SAFETY_BLOCKED [{block.language}] {decision.name} ({ids})")
76+
blocked_msgs.append(f"TOOL_SAFETY_BLOCKED [{block.language}] {decision.name} ({ids})")
8277

8378
out_parts: list[str] = []
8479
err_parts: list[str] = []

trpc_agent_sdk/tools/safety/_policy.py

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -78,9 +78,17 @@ def _policy_from_dict(raw: dict[str, Any]) -> Policy:
7878

7979

8080
_ALLOWED_TOP_LEVEL = {
81-
"name", "description", "whitelisted_domains", "allowed_commands",
82-
"denied_paths", "max_timeout_seconds", "max_output_bytes", "max_evidence_chars",
83-
"deny_risk_level", "review_risk_level", "rule_overrides",
81+
"name",
82+
"description",
83+
"whitelisted_domains",
84+
"allowed_commands",
85+
"denied_paths",
86+
"max_timeout_seconds",
87+
"max_output_bytes",
88+
"max_evidence_chars",
89+
"deny_risk_level",
90+
"review_risk_level",
91+
"rule_overrides",
8492
}
8593

8694

@@ -130,15 +138,17 @@ def _string_list(raw: dict[str, Any], key: str) -> list[str]:
130138
def _risk(raw: dict[str, Any], key: str, default: RiskLevel) -> RiskLevel:
131139
name = raw.get(key, default.name)
132140
if not isinstance(name, str):
133-
raise ValueError(f"{key} must be a string enum name (one of {sorted(_VALID_RISK)}), got {type(name).__name__}: {name!r}")
141+
raise ValueError(
142+
f"{key} must be a string enum name (one of {sorted(_VALID_RISK)}), got {type(name).__name__}: {name!r}")
134143
if name not in _VALID_RISK:
135144
raise ValueError(f"{key} must be one of {sorted(_VALID_RISK)}, got {name!r}")
136145
return RiskLevel[name]
137146

138147

139148
def _decision(name: str) -> Decision:
140149
if not isinstance(name, str):
141-
raise ValueError(f"decision must be a string enum name (one of {sorted(_VALID_DECISION)}), got {type(name).__name__}: {name!r}")
150+
raise ValueError(f"decision must be a string enum name (one of {sorted(_VALID_DECISION)}), "
151+
f"got {type(name).__name__}: {name!r}")
142152
if name not in _VALID_DECISION:
143153
raise ValueError(f"decision must be one of {sorted(_VALID_DECISION)}, got {name!r}")
144154
return Decision[name]

trpc_agent_sdk/tools/safety/_python_scanner.py

Lines changed: 25 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -24,17 +24,14 @@
2424
from trpc_agent_sdk.tools.safety._rules import R_RES_LARGE_WRITE
2525
from trpc_agent_sdk.tools.safety._rules import R_SECRET_LOGGING
2626
from trpc_agent_sdk.tools.safety._rules import R_SECRET_PRIVATE_KEY
27-
from trpc_agent_sdk.tools.safety._types import Decision
2827
from trpc_agent_sdk.tools.safety._types import Finding
29-
from trpc_agent_sdk.tools.safety._types import RiskLevel
3028

3129
_CRED_PATH_RE = re.compile(r"(\.ssh|\.env|\.aws/credentials|id_rsa|id_ed25519|credentials)", re.I)
3230
_URL_RE = re.compile(r"https?://([^/\s'\"']+)", re.I)
3331
_SECRET_NAME_RE = re.compile(r"(api[_-]?key|secret|token|password|passwd|private[_-]?key)", re.I)
3432
_PRIVATE_KEY_RE = re.compile(r"-----BEGIN [A-Z ]*PRIVATE KEY-----")
3533
# System directories: writing here can brick the runtime (issue risk class #1).
36-
_SYSTEM_DIRS = ("/etc/", "/usr/", "/bin/", "/sbin/", "/boot/", "/sys/",
37-
"/proc/", "/lib/", "/lib64/", "/var/", "/dev/")
34+
_SYSTEM_DIRS = ("/etc/", "/usr/", "/bin/", "/sbin/", "/boot/", "/sys/", "/proc/", "/lib/", "/lib64/", "/var/", "/dev/")
3835

3936
# attribute path -> rule fired when called/used
4037
_DANGEROUS_ATTR = {
@@ -73,9 +70,13 @@ def scan_python(policy: Policy, script: str) -> list[Finding]:
7370

7471
def add(rule_id: str, evidence: str, rec: str) -> None:
7572
meta = policy.rules[rule_id]
76-
findings.append(Finding(
77-
rule_id=rule_id, risk_level=meta.risk_level, rule_decision=meta.decision,
78-
evidence=evidence[:max_ev], recommendation=rec, language="python"))
73+
findings.append(
74+
Finding(rule_id=rule_id,
75+
risk_level=meta.risk_level,
76+
rule_decision=meta.decision,
77+
evidence=evidence[:max_ev],
78+
recommendation=rec,
79+
language="python"))
7980

8081
def resolve_attr(node: ast.AST) -> str:
8182
"""Resolve `x.system` or `system` to 'module.attr' using alias tables."""
@@ -116,31 +117,28 @@ def resolve_attr(node: ast.AST) -> str:
116117
add(R_NET_HTTP, f"{fname}({url})", f"{url} not whitelisted.")
117118
# Socket handled at module level to catch ALL socket.* calls (not a per-attribute list).
118119
if mod == "socket":
119-
add(R_NET_SOCKET, f"{fname}()",
120-
"raw socket use bypasses HTTP allowlist; review egress.")
120+
add(R_NET_SOCKET, f"{fname}()", "raw socket use bypasses HTTP allowlist; review egress.")
121121
# Resource abuse: very large writes and concurrency floods.
122122
if _is_write_call(fname) and _has_huge_size(node):
123123
add(R_RES_LARGE_WRITE, f"{fname}(huge payload)",
124124
"Very large write; possible disk/resource exhaustion. Review.")
125125
if _is_pool_call(fname) and _max_workers_too_large(node):
126126
add(R_RES_CONCURRENT_FLOOD, f"{fname}(max_workers>>)",
127127
"Very large worker pool; possible resource exhaustion. Review.")
128-
if mod in ("open",) or fname.endswith(".open"):
128+
if mod in ("open", ) or fname.endswith(".open"):
129129
_check_open_path(node, policy, add)
130130
# infinite loop
131-
if isinstance(node, (ast.While,)) and _is_truthy(node.test):
131+
if isinstance(node, (ast.While, )) and _is_truthy(node.test):
132132
add(R_RES_INFINITE_LOOP, "while True:", "infinite loop; review.")
133133
# secret logging: assignment to a secret-named variable.
134134
if isinstance(node, ast.Assign):
135135
for t in node.targets:
136136
if isinstance(t, ast.Name) and _SECRET_NAME_RE.search(t.id):
137-
add(R_SECRET_LOGGING, f"secret assigned to {t.id}",
138-
"secret-like variable; avoid logging.")
137+
add(R_SECRET_LOGGING, f"secret assigned to {t.id}", "secret-like variable; avoid logging.")
139138
# private-key literal embedded anywhere (independent of variable name).
140139
if isinstance(node, ast.Constant) and isinstance(node.value, str) \
141140
and _PRIVATE_KEY_RE.search(node.value):
142-
add(R_SECRET_PRIVATE_KEY, "private key literal",
143-
"embedded private key; refuse.")
141+
add(R_SECRET_PRIVATE_KEY, "private key literal", "embedded private key; refuse.")
144142

145143
return findings
146144

@@ -169,23 +167,19 @@ def _check_open_path(call: ast.Call, policy: Policy, add) -> None:
169167
path = "".join(v.value for v in arg.values if isinstance(v, ast.Constant))
170168
mode = _open_mode(call)
171169
if path and _is_write_mode(mode) and path.startswith(_SYSTEM_DIRS):
172-
add(R_FS_SYSTEM_DIR, f"open('{path}','{mode}')",
173-
f"writing to system directory {path}; refuse.")
170+
add(R_FS_SYSTEM_DIR, f"open('{path}','{mode}')", f"writing to system directory {path}; refuse.")
174171
return
175172
if path and _CRED_PATH_RE.search(path):
176-
add(R_FS_READ_CREDENTIALS, f"open('{path}')",
177-
f"reading credential path {path}; review.")
173+
add(R_FS_READ_CREDENTIALS, f"open('{path}')", f"reading credential path {path}; review.")
178174
return
179175
for denied in policy.denied_paths:
180-
norm = denied.replace("~", "/root") # crude home expansion for matching
181176
if denied in path:
182-
add(R_FS_READ_CREDENTIALS, f"open('{path}')",
183-
f"path matches denied path {denied}.")
177+
add(R_FS_READ_CREDENTIALS, f"open('{path}')", f"path matches denied path {denied}.")
184178
return
185179

186180

187181
_HUGE_BYTES = 10_000_000 # ~10 MB heuristic threshold for "large write"
188-
_MAX_WORKERS = 100 # heuristic threshold for concurrency flood
182+
_MAX_WORKERS = 100 # heuristic threshold for concurrency flood
189183

190184
_WRITE_METHODS = ("write", "write_bytes", "write_text")
191185
_POOL_CLASSES = ("ThreadPoolExecutor", "ProcessPoolExecutor")
@@ -209,9 +203,7 @@ def _huge_value(node: ast.AST) -> bool:
209203

210204

211205
def _has_huge_size(call: ast.Call) -> bool:
212-
return any(_huge_value(a) for a in call.args) or any(
213-
_huge_value(kw.value) for kw in call.keywords
214-
)
206+
return any(_huge_value(a) for a in call.args) or any(_huge_value(kw.value) for kw in call.keywords)
215207

216208

217209
def _max_workers_too_large(call: ast.Call) -> bool:
@@ -254,9 +246,13 @@ def _heuristic_fallback(policy: Policy, script: str) -> list[Finding]:
254246

255247
def add(rule_id: str, evidence: str, rec: str) -> None:
256248
meta = policy.rules[rule_id]
257-
findings.append(Finding(
258-
rule_id=rule_id, risk_level=meta.risk_level, rule_decision=meta.decision,
259-
evidence=evidence[:max_ev], recommendation=rec, language="python"))
249+
findings.append(
250+
Finding(rule_id=rule_id,
251+
risk_level=meta.risk_level,
252+
rule_decision=meta.decision,
253+
evidence=evidence[:max_ev],
254+
recommendation=rec,
255+
language="python"))
260256

261257
if re.search(r"\beval\s*\(", script):
262258
add(R_CODE_UNSAFE_EVAL, "eval(", "eval executes arbitrary code.")

trpc_agent_sdk/tools/safety/_safety_filter.py

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -69,14 +69,11 @@ async def run(self, ctx: AgentContext, req: Any, handle: FilterHandleType) -> Fi
6969

7070
script, language = extracted
7171
tool_name = getattr(ctx, "tool_name", None) or "tool_safety_filter"
72-
report = scan(self._ensure_policy(), script, language=language,
73-
meta={"tool_name": tool_name})
72+
report = scan(self._ensure_policy(), script, language=language, meta={"tool_name": tool_name})
7473
# Audit every decision (issue #90): allowed scripts get a summary,
7574
# denied scripts get a reason + an intercepted flag.
7675
intercepted = report.decision != Decision.ALLOW
77-
record_safety_decision(
78-
report, tool_name=tool_name, language=language, intercepted=intercepted
79-
)
76+
record_safety_decision(report, tool_name=tool_name, language=language, intercepted=intercepted)
8077
if report.decision == Decision.ALLOW:
8178
return await handle()
8279

trpc_agent_sdk/tools/safety/_scanner.py

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -25,10 +25,7 @@ def detect_language(script: str) -> str:
2525
return "python" # default
2626

2727

28-
def scan(policy: Policy,
29-
script: str,
30-
language: str = "auto",
31-
meta: Optional[dict] = None) -> SafetyReport:
28+
def scan(policy: Policy, script: str, language: str = "auto", meta: Optional[dict] = None) -> SafetyReport:
3229
"""Scan one script; return an aggregated SafetyReport.
3330
3431
Args:
@@ -46,4 +43,4 @@ def scan(policy: Policy,
4643
elapsed_ms = int((time.perf_counter() - start) * 1000)
4744
report = aggregate(findings, policy)
4845
report.scan_duration_ms = elapsed_ms
49-
return report
46+
return report

0 commit comments

Comments
 (0)