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.
- agent_audit/cli/commands/scan.py +41 -30
- agent_audit/cli/formatters/json.py +2 -2
- agent_audit/cli/formatters/sarif.py +9 -3
- agent_audit/models/finding.py +19 -7
- agent_audit/models/risk.py +13 -0
- agent_audit/rules/builtin/owasp_agentic.yaml +126 -0
- agent_audit/rules/builtin/owasp_agentic_v2.yaml +832 -0
- agent_audit/rules/engine.py +60 -1
- agent_audit/scanners/base.py +5 -3
- agent_audit/scanners/config_scanner.py +1 -1
- agent_audit/scanners/mcp_config_scanner.py +4 -3
- agent_audit/scanners/mcp_inspector.py +5 -4
- agent_audit/scanners/python_scanner.py +668 -7
- agent_audit/utils/mcp_client.py +1 -0
- agent_audit/version.py +1 -1
- {agent_audit-0.1.0.dist-info → agent_audit-0.2.0.dist-info}/METADATA +49 -35
- {agent_audit-0.1.0.dist-info → agent_audit-0.2.0.dist-info}/RECORD +19 -17
- {agent_audit-0.1.0.dist-info → agent_audit-0.2.0.dist-info}/WHEEL +0 -0
- {agent_audit-0.1.0.dist-info → agent_audit-0.2.0.dist-info}/entry_points.txt +0 -0
agent_audit/rules/engine.py
CHANGED
|
@@ -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=
|
|
540
|
+
owasp_id=owasp_id,
|
|
482
541
|
remediation=remediation,
|
|
483
542
|
confidence=match.get('confidence', 1.0)
|
|
484
543
|
)
|
agent_audit/scanners/base.py
CHANGED
|
@@ -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
|
|
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) ->
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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:
|