agent-audit 0.1.0__py3-none-any.whl → 0.2.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -32,6 +32,50 @@ class RuleEngine:
32
32
  Loads rules from YAML files and applies them to scan results.
33
33
  """
34
34
 
35
+ # Pattern type to Rule ID mapping for OWASP Agentic Top 10
36
+ PATTERN_TYPE_TO_RULE_MAP: Dict[str, str] = {
37
+ # ASI-01: Goal Hijack
38
+ 'prompt_injection_fstring': 'AGENT-010',
39
+ 'prompt_injection_fstring_kwarg': 'AGENT-010',
40
+ 'prompt_injection_format': 'AGENT-010',
41
+ 'system_prompt_fstring': 'AGENT-010',
42
+ 'system_prompt_concat': 'AGENT-010',
43
+ 'system_prompt_format': 'AGENT-010',
44
+ 'agent_without_input_guard': 'AGENT-011',
45
+
46
+ # ASI-03: Identity & Privilege Abuse
47
+ 'hardcoded_credential_in_agent': 'AGENT-013',
48
+ 'excessive_tools': 'AGENT-014',
49
+ 'auto_approval': 'AGENT-014',
50
+
51
+ # ASI-04: Supply Chain
52
+ 'npx_unfixed_version': 'AGENT-015',
53
+ 'unofficial_mcp_source': 'AGENT-015',
54
+ 'unvalidated_rag_ingestion': 'AGENT-016',
55
+
56
+ # ASI-05: RCE
57
+ 'unsandboxed_code_exec_in_tool': 'AGENT-017',
58
+
59
+ # ASI-06: Memory Poisoning
60
+ 'unsanitized_memory_write': 'AGENT-018',
61
+ 'unbounded_memory': 'AGENT-019',
62
+
63
+ # ASI-07: Insecure Communication
64
+ 'multi_agent_no_auth': 'AGENT-020',
65
+ 'agent_comm_no_tls': 'AGENT-020',
66
+
67
+ # ASI-08: Cascading Failures
68
+ 'missing_circuit_breaker': 'AGENT-021',
69
+ 'tool_without_error_handling': 'AGENT-022',
70
+
71
+ # ASI-09: Trust Exploitation
72
+ 'opaque_agent_output': 'AGENT-023',
73
+
74
+ # ASI-10: Rogue Agents
75
+ 'no_kill_switch': 'AGENT-024',
76
+ 'no_observability': 'AGENT-025',
77
+ }
78
+
35
79
  # Pre-compiled regex patterns for common detections
36
80
  CREDENTIAL_PATTERNS = [
37
81
  (re.compile(r'AKIA[0-9A-Z]{16}'), "AWS Access Key"),
@@ -104,6 +148,7 @@ class RuleEngine:
104
148
  for pattern in patterns:
105
149
  pattern_type = pattern.get('type', '')
106
150
 
151
+ # Check original patterns (AGENT-001)
107
152
  if pattern_type == 'shell_true' or pattern_type == 'dangerous_function_call':
108
153
  # Check if this matches AGENT-001 (Command Injection)
109
154
  if self._is_command_injection(pattern):
@@ -115,6 +160,17 @@ class RuleEngine:
115
160
  if finding:
116
161
  findings.append(finding)
117
162
 
163
+ # Check OWASP Agentic patterns
164
+ if pattern_type in self.PATTERN_TYPE_TO_RULE_MAP:
165
+ rule_id = self.PATTERN_TYPE_TO_RULE_MAP[pattern_type]
166
+ finding = self._create_finding_from_pattern(
167
+ rule_id=rule_id,
168
+ pattern=pattern,
169
+ file_path=file_path
170
+ )
171
+ if finding:
172
+ findings.append(finding)
173
+
118
174
  return findings
119
175
 
120
176
  def evaluate_credentials(
@@ -465,6 +521,9 @@ class RuleEngine:
465
521
  if remediation_data.get('references') else None
466
522
  )
467
523
 
524
+ # Support both owasp_id and owasp_agentic_id
525
+ owasp_id = rule.get('owasp_agentic_id') or rule.get('owasp_id')
526
+
468
527
  return Finding(
469
528
  rule_id=rule['id'],
470
529
  title=rule['title'],
@@ -478,7 +537,7 @@ class RuleEngine:
478
537
  snippet=match.get('snippet', '')
479
538
  ),
480
539
  cwe_id=rule.get('cwe_id'),
481
- owasp_id=rule.get('owasp_id'),
540
+ owasp_id=owasp_id,
482
541
  remediation=remediation,
483
542
  confidence=match.get('confidence', 1.0)
484
543
  )
@@ -3,7 +3,9 @@
3
3
  from abc import ABC, abstractmethod
4
4
  from dataclasses import dataclass, field
5
5
  from pathlib import Path
6
- from typing import List, Any, Dict
6
+ from typing import Any, Dict, Sequence, TypeVar
7
+
8
+ T = TypeVar("T", bound="ScanResult", covariant=True)
7
9
 
8
10
 
9
11
  @dataclass
@@ -19,7 +21,7 @@ class BaseScanner(ABC):
19
21
  name: str = "BaseScanner"
20
22
 
21
23
  @abstractmethod
22
- def scan(self, path: Path) -> List[ScanResult]:
24
+ def scan(self, path: Path) -> Sequence[ScanResult]:
23
25
  """
24
26
  Scan the given path and return results.
25
27
 
@@ -27,6 +29,6 @@ class BaseScanner(ABC):
27
29
  path: Path to scan (file or directory)
28
30
 
29
31
  Returns:
30
- List of scan results
32
+ Sequence of scan results
31
33
  """
32
34
  pass
@@ -45,7 +45,7 @@ class ConfigScanner(BaseScanner):
45
45
  name = "Config Scanner"
46
46
 
47
47
  # Configuration patterns to check
48
- DANGEROUS_PATTERNS = {
48
+ DANGEROUS_PATTERNS: Dict[str, Dict[str, Any]] = {
49
49
  # Debug/development settings that shouldn't be in production
50
50
  'debug': {
51
51
  'dangerous_values': [True, 'true', 'True', '1', 'yes'],
@@ -299,10 +299,11 @@ class MCPConfigScanner(BaseScanner):
299
299
 
300
300
  Returns analysis of filesystem access configuration.
301
301
  """
302
- result = {
302
+ accessible_paths: List[str] = []
303
+ result: Dict[str, Any] = {
303
304
  'has_root_access': False,
304
305
  'has_home_access': False,
305
- 'accessible_paths': [],
306
+ 'accessible_paths': accessible_paths,
306
307
  'risk_level': 'low'
307
308
  }
308
309
 
@@ -316,6 +317,6 @@ class MCPConfigScanner(BaseScanner):
316
317
  result['has_home_access'] = True
317
318
  result['risk_level'] = 'high' if result['risk_level'] != 'critical' else 'critical'
318
319
  elif arg.startswith('/'):
319
- result['accessible_paths'].append(arg)
320
+ accessible_paths.append(arg)
320
321
 
321
322
  return result
@@ -261,9 +261,10 @@ class MCPInspector:
261
261
 
262
262
  def _analyze_input_schema(self, schema: Dict[str, Any]) -> Dict[str, Any]:
263
263
  """Analyze input schema for security properties."""
264
- result = {
264
+ unconstrained_strings: List[str] = []
265
+ result: Dict[str, Any] = {
265
266
  "has_validation": False,
266
- "unconstrained_strings": [],
267
+ "unconstrained_strings": unconstrained_strings,
267
268
  "has_enum": False,
268
269
  "has_pattern": False,
269
270
  }
@@ -283,7 +284,7 @@ class MCPInspector:
283
284
  elif "maxLength" in param_def or "minLength" in param_def:
284
285
  result["has_validation"] = True
285
286
  else:
286
- result["unconstrained_strings"].append(param_name)
287
+ unconstrained_strings.append(param_name)
287
288
 
288
289
  elif param_type in ("integer", "number"):
289
290
  if "minimum" in param_def or "maximum" in param_def:
@@ -361,7 +362,7 @@ class MCPInspector:
361
362
 
362
363
  def _generate_findings(self, result: MCPInspectionResult) -> List[Dict[str, Any]]:
363
364
  """Generate security findings from inspection."""
364
- findings = []
365
+ findings: List[Dict[str, Any]] = []
365
366
 
366
367
  # Check for high-risk tools
367
368
  for tool in result.tools: