2424from trpc_agent_sdk .tools .safety ._rules import R_RES_LARGE_WRITE
2525from trpc_agent_sdk .tools .safety ._rules import R_SECRET_LOGGING
2626from trpc_agent_sdk .tools .safety ._rules import R_SECRET_PRIVATE_KEY
27- from trpc_agent_sdk .tools .safety ._types import Decision
2827from 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
211205def _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
217209def _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." )
0 commit comments