cisco-ai-skill-scanner 1.0.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.
- cisco_ai_skill_scanner-1.0.0.dist-info/METADATA +253 -0
- cisco_ai_skill_scanner-1.0.0.dist-info/RECORD +100 -0
- cisco_ai_skill_scanner-1.0.0.dist-info/WHEEL +4 -0
- cisco_ai_skill_scanner-1.0.0.dist-info/entry_points.txt +4 -0
- cisco_ai_skill_scanner-1.0.0.dist-info/licenses/LICENSE +17 -0
- skillanalyzer/__init__.py +45 -0
- skillanalyzer/_version.py +34 -0
- skillanalyzer/api/__init__.py +25 -0
- skillanalyzer/api/api.py +34 -0
- skillanalyzer/api/api_cli.py +78 -0
- skillanalyzer/api/api_server.py +634 -0
- skillanalyzer/api/router.py +527 -0
- skillanalyzer/cli/__init__.py +25 -0
- skillanalyzer/cli/cli.py +816 -0
- skillanalyzer/config/__init__.py +26 -0
- skillanalyzer/config/config.py +149 -0
- skillanalyzer/config/config_parser.py +122 -0
- skillanalyzer/config/constants.py +85 -0
- skillanalyzer/core/__init__.py +24 -0
- skillanalyzer/core/analyzers/__init__.py +75 -0
- skillanalyzer/core/analyzers/aidefense_analyzer.py +872 -0
- skillanalyzer/core/analyzers/base.py +53 -0
- skillanalyzer/core/analyzers/behavioral/__init__.py +30 -0
- skillanalyzer/core/analyzers/behavioral/alignment/__init__.py +45 -0
- skillanalyzer/core/analyzers/behavioral/alignment/alignment_llm_client.py +240 -0
- skillanalyzer/core/analyzers/behavioral/alignment/alignment_orchestrator.py +216 -0
- skillanalyzer/core/analyzers/behavioral/alignment/alignment_prompt_builder.py +422 -0
- skillanalyzer/core/analyzers/behavioral/alignment/alignment_response_validator.py +136 -0
- skillanalyzer/core/analyzers/behavioral/alignment/threat_vulnerability_classifier.py +198 -0
- skillanalyzer/core/analyzers/behavioral_analyzer.py +453 -0
- skillanalyzer/core/analyzers/cross_skill_analyzer.py +490 -0
- skillanalyzer/core/analyzers/llm_analyzer.py +440 -0
- skillanalyzer/core/analyzers/llm_prompt_builder.py +270 -0
- skillanalyzer/core/analyzers/llm_provider_config.py +215 -0
- skillanalyzer/core/analyzers/llm_request_handler.py +284 -0
- skillanalyzer/core/analyzers/llm_response_parser.py +81 -0
- skillanalyzer/core/analyzers/meta_analyzer.py +845 -0
- skillanalyzer/core/analyzers/static.py +1105 -0
- skillanalyzer/core/analyzers/trigger_analyzer.py +341 -0
- skillanalyzer/core/analyzers/virustotal_analyzer.py +463 -0
- skillanalyzer/core/exceptions.py +77 -0
- skillanalyzer/core/loader.py +377 -0
- skillanalyzer/core/models.py +300 -0
- skillanalyzer/core/reporters/__init__.py +26 -0
- skillanalyzer/core/reporters/json_reporter.py +65 -0
- skillanalyzer/core/reporters/markdown_reporter.py +209 -0
- skillanalyzer/core/reporters/sarif_reporter.py +246 -0
- skillanalyzer/core/reporters/table_reporter.py +195 -0
- skillanalyzer/core/rules/__init__.py +19 -0
- skillanalyzer/core/rules/patterns.py +165 -0
- skillanalyzer/core/rules/yara_scanner.py +157 -0
- skillanalyzer/core/scanner.py +437 -0
- skillanalyzer/core/static_analysis/__init__.py +27 -0
- skillanalyzer/core/static_analysis/cfg/__init__.py +21 -0
- skillanalyzer/core/static_analysis/cfg/builder.py +439 -0
- skillanalyzer/core/static_analysis/context_extractor.py +742 -0
- skillanalyzer/core/static_analysis/dataflow/__init__.py +25 -0
- skillanalyzer/core/static_analysis/dataflow/forward_analysis.py +715 -0
- skillanalyzer/core/static_analysis/interprocedural/__init__.py +21 -0
- skillanalyzer/core/static_analysis/interprocedural/call_graph_analyzer.py +406 -0
- skillanalyzer/core/static_analysis/interprocedural/cross_file_analyzer.py +190 -0
- skillanalyzer/core/static_analysis/parser/__init__.py +21 -0
- skillanalyzer/core/static_analysis/parser/python_parser.py +380 -0
- skillanalyzer/core/static_analysis/semantic/__init__.py +28 -0
- skillanalyzer/core/static_analysis/semantic/name_resolver.py +206 -0
- skillanalyzer/core/static_analysis/semantic/type_analyzer.py +200 -0
- skillanalyzer/core/static_analysis/taint/__init__.py +21 -0
- skillanalyzer/core/static_analysis/taint/tracker.py +252 -0
- skillanalyzer/core/static_analysis/types/__init__.py +36 -0
- skillanalyzer/data/__init__.py +30 -0
- skillanalyzer/data/prompts/boilerplate_protection_rule_prompt.md +26 -0
- skillanalyzer/data/prompts/code_alignment_threat_analysis_prompt.md +901 -0
- skillanalyzer/data/prompts/llm_response_schema.json +71 -0
- skillanalyzer/data/prompts/skill_meta_analysis_prompt.md +303 -0
- skillanalyzer/data/prompts/skill_threat_analysis_prompt.md +263 -0
- skillanalyzer/data/prompts/unified_response_schema.md +97 -0
- skillanalyzer/data/rules/signatures.yaml +440 -0
- skillanalyzer/data/yara_rules/autonomy_abuse.yara +66 -0
- skillanalyzer/data/yara_rules/code_execution.yara +61 -0
- skillanalyzer/data/yara_rules/coercive_injection.yara +115 -0
- skillanalyzer/data/yara_rules/command_injection.yara +54 -0
- skillanalyzer/data/yara_rules/credential_harvesting.yara +115 -0
- skillanalyzer/data/yara_rules/prompt_injection.yara +71 -0
- skillanalyzer/data/yara_rules/script_injection.yara +83 -0
- skillanalyzer/data/yara_rules/skill_discovery_abuse.yara +57 -0
- skillanalyzer/data/yara_rules/sql_injection.yara +73 -0
- skillanalyzer/data/yara_rules/system_manipulation.yara +65 -0
- skillanalyzer/data/yara_rules/tool_chaining_abuse.yara +60 -0
- skillanalyzer/data/yara_rules/transitive_trust_abuse.yara +73 -0
- skillanalyzer/data/yara_rules/unicode_steganography.yara +65 -0
- skillanalyzer/hooks/__init__.py +21 -0
- skillanalyzer/hooks/pre_commit.py +450 -0
- skillanalyzer/threats/__init__.py +25 -0
- skillanalyzer/threats/threats.py +480 -0
- skillanalyzer/utils/__init__.py +28 -0
- skillanalyzer/utils/command_utils.py +129 -0
- skillanalyzer/utils/di_container.py +154 -0
- skillanalyzer/utils/file_utils.py +86 -0
- skillanalyzer/utils/logging_config.py +96 -0
- skillanalyzer/utils/logging_utils.py +71 -0
|
@@ -0,0 +1,872 @@
|
|
|
1
|
+
# Copyright 2026 Cisco Systems, Inc.
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
#
|
|
15
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
16
|
+
|
|
17
|
+
"""
|
|
18
|
+
AI Defense API analyzer for Claude Skills security scanning.
|
|
19
|
+
|
|
20
|
+
Integrates with Cisco AI Defense API (https://api.aidefense.cisco.com) for:
|
|
21
|
+
- Prompt injection detection
|
|
22
|
+
- Tool poisoning detection
|
|
23
|
+
- Data exfiltration detection
|
|
24
|
+
- Malicious content analysis
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
import asyncio
|
|
28
|
+
import hashlib
|
|
29
|
+
import json
|
|
30
|
+
import os
|
|
31
|
+
from typing import Any
|
|
32
|
+
|
|
33
|
+
try:
|
|
34
|
+
import httpx
|
|
35
|
+
|
|
36
|
+
HTTPX_AVAILABLE = True
|
|
37
|
+
except ImportError:
|
|
38
|
+
HTTPX_AVAILABLE = False
|
|
39
|
+
|
|
40
|
+
from ...core.models import Finding, Severity, Skill, ThreatCategory
|
|
41
|
+
from ...threats.threats import ThreatMapping
|
|
42
|
+
from .base import BaseAnalyzer
|
|
43
|
+
|
|
44
|
+
# AI Defense API endpoint - Cisco AI Defense Inspect API
|
|
45
|
+
AI_DEFENSE_API_URL = "https://us.api.inspect.aidefense.security.cisco.com/api/v1"
|
|
46
|
+
|
|
47
|
+
# Default enabled rules for AI Defense API
|
|
48
|
+
DEFAULT_ENABLED_RULES = [
|
|
49
|
+
{"rule_name": "Prompt Injection"},
|
|
50
|
+
{"rule_name": "Harassment"},
|
|
51
|
+
{"rule_name": "Hate Speech"},
|
|
52
|
+
{"rule_name": "Profanity"},
|
|
53
|
+
{"rule_name": "Sexual Content & Exploitation"},
|
|
54
|
+
{"rule_name": "Social Division & Polarization"},
|
|
55
|
+
{"rule_name": "Violence & Public Safety Threats"},
|
|
56
|
+
{"rule_name": "Code Detection"},
|
|
57
|
+
]
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
class AIDefenseAnalyzer(BaseAnalyzer):
|
|
61
|
+
"""
|
|
62
|
+
Analyzer that uses Cisco AI Defense API for threat detection.
|
|
63
|
+
|
|
64
|
+
Sends skill content (prompts, markdown, code) to AI Defense API
|
|
65
|
+
for comprehensive security analysis.
|
|
66
|
+
|
|
67
|
+
**Important**: The "Code Detection" rule is automatically excluded when
|
|
68
|
+
analyzing actual code files (Python scripts) to avoid false positives,
|
|
69
|
+
since skills legitimately contain code. Code Detection is still used for
|
|
70
|
+
prompts, markdown, and manifest content where malicious code injection
|
|
71
|
+
would be a security concern.
|
|
72
|
+
|
|
73
|
+
Example:
|
|
74
|
+
>>> # Basic usage
|
|
75
|
+
>>> analyzer = AIDefenseAnalyzer(api_key="your-api-key")
|
|
76
|
+
>>> findings = analyzer.analyze(skill)
|
|
77
|
+
|
|
78
|
+
>>> # Custom rules configuration
|
|
79
|
+
>>> custom_rules = [
|
|
80
|
+
... {"rule_name": "Prompt Injection"},
|
|
81
|
+
... {"rule_name": "Code Detection"}, # Will be excluded for code files
|
|
82
|
+
... ]
|
|
83
|
+
>>> analyzer = AIDefenseAnalyzer(
|
|
84
|
+
... api_key="your-api-key",
|
|
85
|
+
... enabled_rules=custom_rules,
|
|
86
|
+
... include_rules=True
|
|
87
|
+
... )
|
|
88
|
+
|
|
89
|
+
>>> # For API keys with pre-configured rules
|
|
90
|
+
>>> analyzer = AIDefenseAnalyzer(
|
|
91
|
+
... api_key="your-api-key",
|
|
92
|
+
... include_rules=False # Don't send rules config
|
|
93
|
+
... )
|
|
94
|
+
"""
|
|
95
|
+
|
|
96
|
+
def __init__(
|
|
97
|
+
self,
|
|
98
|
+
api_key: str | None = None,
|
|
99
|
+
api_url: str | None = None,
|
|
100
|
+
timeout: int = 60,
|
|
101
|
+
max_retries: int = 3,
|
|
102
|
+
enabled_rules: list[dict[str, str]] | None = None,
|
|
103
|
+
include_rules: bool = True,
|
|
104
|
+
):
|
|
105
|
+
"""
|
|
106
|
+
Initialize AI Defense API analyzer.
|
|
107
|
+
|
|
108
|
+
Args:
|
|
109
|
+
api_key: AI Defense API key (or set AI_DEFENSE_API_KEY env var)
|
|
110
|
+
api_url: Custom API URL (defaults to https://api.aidefense.cisco.com/api/v1)
|
|
111
|
+
timeout: Request timeout in seconds
|
|
112
|
+
max_retries: Maximum retry attempts on failure
|
|
113
|
+
enabled_rules: List of rules to enable (defaults to DEFAULT_ENABLED_RULES).
|
|
114
|
+
Format: [{"rule_name": "Prompt Injection"}, ...]
|
|
115
|
+
include_rules: Whether to include enabled_rules in API payload.
|
|
116
|
+
Set to False if API key has pre-configured rules.
|
|
117
|
+
"""
|
|
118
|
+
super().__init__("aidefense_analyzer")
|
|
119
|
+
|
|
120
|
+
if not HTTPX_AVAILABLE:
|
|
121
|
+
raise ImportError("httpx is required for AI Defense analyzer. Install with: pip install httpx")
|
|
122
|
+
|
|
123
|
+
# Get API key from parameter or environment
|
|
124
|
+
self.api_key = api_key or os.getenv("AI_DEFENSE_API_KEY")
|
|
125
|
+
if not self.api_key:
|
|
126
|
+
raise ValueError(
|
|
127
|
+
"AI Defense API key required. Set AI_DEFENSE_API_KEY environment variable or pass api_key parameter."
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
self.api_url = api_url or os.getenv("AI_DEFENSE_API_URL", AI_DEFENSE_API_URL)
|
|
131
|
+
self.timeout = timeout
|
|
132
|
+
self.max_retries = max_retries
|
|
133
|
+
|
|
134
|
+
# Rules configuration
|
|
135
|
+
self.enabled_rules = enabled_rules or DEFAULT_ENABLED_RULES
|
|
136
|
+
self.include_rules = include_rules
|
|
137
|
+
|
|
138
|
+
# Initialize async client
|
|
139
|
+
self._client = None
|
|
140
|
+
|
|
141
|
+
def _get_client(self) -> httpx.AsyncClient:
|
|
142
|
+
"""Get or create HTTP client."""
|
|
143
|
+
if self._client is None:
|
|
144
|
+
self._client = httpx.AsyncClient(
|
|
145
|
+
timeout=httpx.Timeout(self.timeout),
|
|
146
|
+
headers={
|
|
147
|
+
"X-Cisco-AI-Defense-API-Key": self.api_key,
|
|
148
|
+
"Content-Type": "application/json",
|
|
149
|
+
"Accept": "application/json",
|
|
150
|
+
},
|
|
151
|
+
)
|
|
152
|
+
return self._client
|
|
153
|
+
|
|
154
|
+
async def _close_client(self):
|
|
155
|
+
"""Close HTTP client."""
|
|
156
|
+
if self._client is not None:
|
|
157
|
+
await self._client.aclose()
|
|
158
|
+
self._client = None
|
|
159
|
+
|
|
160
|
+
def _get_payload(
|
|
161
|
+
self,
|
|
162
|
+
messages: list[dict[str, str]],
|
|
163
|
+
metadata: dict[str, Any] | None = None,
|
|
164
|
+
include_rules: bool | None = None,
|
|
165
|
+
rules_override: list[dict[str, str]] | None = None,
|
|
166
|
+
) -> dict[str, Any]:
|
|
167
|
+
"""
|
|
168
|
+
Build API request payload with optional rules configuration.
|
|
169
|
+
|
|
170
|
+
Args:
|
|
171
|
+
messages: List of message dicts with "role" and "content"
|
|
172
|
+
metadata: Optional metadata dict
|
|
173
|
+
include_rules: Whether to include enabled_rules in config.
|
|
174
|
+
If None, uses self.include_rules
|
|
175
|
+
rules_override: Optional list of rules to use instead of self.enabled_rules.
|
|
176
|
+
Useful for excluding certain rules (e.g., Code Detection for code files)
|
|
177
|
+
|
|
178
|
+
Returns:
|
|
179
|
+
Complete payload dict
|
|
180
|
+
"""
|
|
181
|
+
payload = {
|
|
182
|
+
"messages": messages,
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
if metadata:
|
|
186
|
+
payload["metadata"] = metadata
|
|
187
|
+
|
|
188
|
+
# Include rules config if requested
|
|
189
|
+
if include_rules is None:
|
|
190
|
+
include_rules = self.include_rules
|
|
191
|
+
|
|
192
|
+
if include_rules:
|
|
193
|
+
# Use override rules if provided, otherwise use instance rules
|
|
194
|
+
rules_to_use = rules_override if rules_override is not None else self.enabled_rules
|
|
195
|
+
if rules_to_use:
|
|
196
|
+
payload["config"] = {"enabled_rules": rules_to_use}
|
|
197
|
+
|
|
198
|
+
return payload
|
|
199
|
+
|
|
200
|
+
def _get_rules_for_content_type(self, content_type: str) -> list[dict[str, str]]:
|
|
201
|
+
"""
|
|
202
|
+
Get appropriate rules for a given content type.
|
|
203
|
+
|
|
204
|
+
Excludes "Code Detection" for actual code files since they contain
|
|
205
|
+
legitimate code. Code Detection is useful for detecting malicious
|
|
206
|
+
code in prompts/markdown, but not for analyzing actual code files.
|
|
207
|
+
|
|
208
|
+
Args:
|
|
209
|
+
content_type: Type of content being analyzed
|
|
210
|
+
("skill_instructions", "skill_manifest", "markdown_content", "code")
|
|
211
|
+
|
|
212
|
+
Returns:
|
|
213
|
+
List of rule dicts appropriate for the content type
|
|
214
|
+
"""
|
|
215
|
+
if content_type == "code":
|
|
216
|
+
# Exclude Code Detection for actual code files
|
|
217
|
+
return [rule for rule in self.enabled_rules if rule.get("rule_name") != "Code Detection"]
|
|
218
|
+
else:
|
|
219
|
+
# Use all rules for prompts/markdown/manifest
|
|
220
|
+
return self.enabled_rules
|
|
221
|
+
|
|
222
|
+
def analyze(self, skill: Skill) -> list[Finding]:
|
|
223
|
+
"""
|
|
224
|
+
Analyze skill using AI Defense API (sync wrapper).
|
|
225
|
+
|
|
226
|
+
Args:
|
|
227
|
+
skill: Skill to analyze
|
|
228
|
+
|
|
229
|
+
Returns:
|
|
230
|
+
List of security findings
|
|
231
|
+
"""
|
|
232
|
+
try:
|
|
233
|
+
loop = asyncio.get_event_loop()
|
|
234
|
+
except RuntimeError:
|
|
235
|
+
loop = asyncio.new_event_loop()
|
|
236
|
+
asyncio.set_event_loop(loop)
|
|
237
|
+
|
|
238
|
+
try:
|
|
239
|
+
return loop.run_until_complete(self.analyze_async(skill))
|
|
240
|
+
finally:
|
|
241
|
+
loop.run_until_complete(self._close_client())
|
|
242
|
+
|
|
243
|
+
async def analyze_async(self, skill: Skill) -> list[Finding]:
|
|
244
|
+
"""
|
|
245
|
+
Analyze skill using AI Defense API (async).
|
|
246
|
+
|
|
247
|
+
Args:
|
|
248
|
+
skill: Skill to analyze
|
|
249
|
+
|
|
250
|
+
Returns:
|
|
251
|
+
List of security findings
|
|
252
|
+
"""
|
|
253
|
+
findings = []
|
|
254
|
+
|
|
255
|
+
try:
|
|
256
|
+
# 1. Analyze SKILL.md content (prompts/instructions)
|
|
257
|
+
skill_md_findings = await self._analyze_prompt_content(
|
|
258
|
+
skill.instruction_body, skill.name, "SKILL.md", "skill_instructions"
|
|
259
|
+
)
|
|
260
|
+
findings.extend(skill_md_findings)
|
|
261
|
+
|
|
262
|
+
# 2. Analyze manifest/description
|
|
263
|
+
manifest_findings = await self._analyze_prompt_content(
|
|
264
|
+
f"Name: {skill.manifest.name}\nDescription: {skill.manifest.description}",
|
|
265
|
+
skill.name,
|
|
266
|
+
"manifest",
|
|
267
|
+
"skill_manifest",
|
|
268
|
+
)
|
|
269
|
+
findings.extend(manifest_findings)
|
|
270
|
+
|
|
271
|
+
# 3. Analyze each markdown file
|
|
272
|
+
for md_file in skill.get_markdown_files():
|
|
273
|
+
content = md_file.read_content()
|
|
274
|
+
if content:
|
|
275
|
+
md_findings = await self._analyze_prompt_content(
|
|
276
|
+
content, skill.name, md_file.relative_path, "markdown_content"
|
|
277
|
+
)
|
|
278
|
+
findings.extend(md_findings)
|
|
279
|
+
|
|
280
|
+
# 4. Analyze code files
|
|
281
|
+
for script_file in skill.get_scripts():
|
|
282
|
+
content = script_file.read_content()
|
|
283
|
+
if content:
|
|
284
|
+
code_findings = await self._analyze_code_content(
|
|
285
|
+
content, skill.name, script_file.relative_path, script_file.file_type
|
|
286
|
+
)
|
|
287
|
+
findings.extend(code_findings)
|
|
288
|
+
|
|
289
|
+
except Exception as e:
|
|
290
|
+
print(f"AI Defense API analysis failed for {skill.name}: {e}")
|
|
291
|
+
# Return partial findings - don't fail completely
|
|
292
|
+
|
|
293
|
+
return findings
|
|
294
|
+
|
|
295
|
+
async def _analyze_prompt_content(
|
|
296
|
+
self,
|
|
297
|
+
content: str,
|
|
298
|
+
skill_name: str,
|
|
299
|
+
file_path: str,
|
|
300
|
+
content_type: str,
|
|
301
|
+
) -> list[Finding]:
|
|
302
|
+
"""
|
|
303
|
+
Analyze prompt/instruction content via AI Defense API.
|
|
304
|
+
|
|
305
|
+
Args:
|
|
306
|
+
content: Text content to analyze
|
|
307
|
+
skill_name: Name of the skill
|
|
308
|
+
file_path: Source file path
|
|
309
|
+
content_type: Type of content being analyzed
|
|
310
|
+
|
|
311
|
+
Returns:
|
|
312
|
+
List of findings
|
|
313
|
+
"""
|
|
314
|
+
if not content or not content.strip():
|
|
315
|
+
return []
|
|
316
|
+
|
|
317
|
+
findings = []
|
|
318
|
+
|
|
319
|
+
try:
|
|
320
|
+
# Build request payload for chat inspection (prompt injection detection)
|
|
321
|
+
messages = [
|
|
322
|
+
{
|
|
323
|
+
"role": "user",
|
|
324
|
+
"content": content[:10000], # Limit content size
|
|
325
|
+
}
|
|
326
|
+
]
|
|
327
|
+
metadata = {
|
|
328
|
+
"source": "skill_analyzer",
|
|
329
|
+
"skill_name": skill_name,
|
|
330
|
+
"file_path": file_path,
|
|
331
|
+
"content_type": content_type,
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
# Get appropriate rules for prompt content (includes Code Detection)
|
|
335
|
+
rules_for_prompts = self._get_rules_for_content_type(content_type)
|
|
336
|
+
payload = self._get_payload(messages, metadata, include_rules=True, rules_override=rules_for_prompts)
|
|
337
|
+
|
|
338
|
+
# Call AI Defense API - chat inspection endpoint
|
|
339
|
+
response = await self._make_api_request(endpoint="/inspect/chat", payload=payload)
|
|
340
|
+
|
|
341
|
+
if response:
|
|
342
|
+
# Process AI Defense response format:
|
|
343
|
+
# {
|
|
344
|
+
# "classifications": ["SECURITY_VIOLATION"],
|
|
345
|
+
# "is_safe": false,
|
|
346
|
+
# "rules": [{"rule_name": "Prompt Injection", "classification": "SECURITY_VIOLATION"}],
|
|
347
|
+
# "action": "Block"
|
|
348
|
+
# }
|
|
349
|
+
|
|
350
|
+
is_safe = response.get("is_safe", True)
|
|
351
|
+
classifications = response.get("classifications", [])
|
|
352
|
+
rules = response.get("rules", [])
|
|
353
|
+
action = response.get("action", "").lower()
|
|
354
|
+
|
|
355
|
+
# Process each classification
|
|
356
|
+
for classification in classifications:
|
|
357
|
+
if classification and classification != "NONE_VIOLATION":
|
|
358
|
+
severity = self._map_classification_to_severity(classification)
|
|
359
|
+
findings.append(
|
|
360
|
+
Finding(
|
|
361
|
+
id=self._generate_id(f"AIDEFENSE_{classification}", file_path),
|
|
362
|
+
rule_id=f"AIDEFENSE_{classification}",
|
|
363
|
+
category=self._map_violation_category(classification),
|
|
364
|
+
severity=severity,
|
|
365
|
+
title=f"{classification.replace('_', ' ').title()} detected",
|
|
366
|
+
description=f"AI Defense detected {classification.replace('_', ' ').lower()} in {file_path}",
|
|
367
|
+
file_path=file_path,
|
|
368
|
+
remediation="Review and address the security concern flagged by AI Defense",
|
|
369
|
+
analyzer="aidefense",
|
|
370
|
+
metadata={
|
|
371
|
+
"classification": classification,
|
|
372
|
+
"content_type": content_type,
|
|
373
|
+
"is_safe": is_safe,
|
|
374
|
+
"action": action,
|
|
375
|
+
},
|
|
376
|
+
)
|
|
377
|
+
)
|
|
378
|
+
|
|
379
|
+
# Process triggered rules
|
|
380
|
+
for rule in rules:
|
|
381
|
+
rule_name = rule.get("rule_name", "Unknown")
|
|
382
|
+
rule_classification = rule.get("classification", "")
|
|
383
|
+
|
|
384
|
+
# Skip non-violations
|
|
385
|
+
if rule_classification in ("NONE_VIOLATION", ""):
|
|
386
|
+
continue
|
|
387
|
+
|
|
388
|
+
findings.append(
|
|
389
|
+
Finding(
|
|
390
|
+
id=self._generate_id(f"AIDEFENSE_RULE_{rule_name}", file_path),
|
|
391
|
+
rule_id=f"AIDEFENSE_RULE_{rule_name.upper().replace(' ', '_')}",
|
|
392
|
+
category=self._map_violation_category(rule_classification),
|
|
393
|
+
severity=self._map_classification_to_severity(rule_classification),
|
|
394
|
+
title=f"Rule triggered: {rule_name}",
|
|
395
|
+
description=f"AI Defense rule '{rule_name}' detected {rule_classification.replace('_', ' ').lower()}",
|
|
396
|
+
file_path=file_path,
|
|
397
|
+
remediation=f"Address the {rule_name.lower()} issue detected by AI Defense",
|
|
398
|
+
analyzer="aidefense",
|
|
399
|
+
metadata={
|
|
400
|
+
"rule_name": rule_name,
|
|
401
|
+
"rule_id": rule.get("rule_id"),
|
|
402
|
+
"classification": rule_classification,
|
|
403
|
+
"entity_types": rule.get("entity_types", []),
|
|
404
|
+
},
|
|
405
|
+
)
|
|
406
|
+
)
|
|
407
|
+
|
|
408
|
+
# Check overall action
|
|
409
|
+
if action == "block" and not is_safe:
|
|
410
|
+
# Only add if we haven't already added findings for the specific violations
|
|
411
|
+
if not findings:
|
|
412
|
+
findings.append(
|
|
413
|
+
Finding(
|
|
414
|
+
id=self._generate_id("AIDEFENSE_BLOCKED", file_path),
|
|
415
|
+
rule_id="AIDEFENSE_BLOCKED",
|
|
416
|
+
category=ThreatCategory.PROMPT_INJECTION,
|
|
417
|
+
severity=Severity.HIGH,
|
|
418
|
+
title="Content blocked by AI Defense",
|
|
419
|
+
description=f"AI Defense blocked content in {file_path} as potentially malicious",
|
|
420
|
+
file_path=file_path,
|
|
421
|
+
analyzer="aidefense",
|
|
422
|
+
metadata={
|
|
423
|
+
"action": action,
|
|
424
|
+
"content_type": content_type,
|
|
425
|
+
"is_safe": is_safe,
|
|
426
|
+
},
|
|
427
|
+
)
|
|
428
|
+
)
|
|
429
|
+
|
|
430
|
+
except Exception as e:
|
|
431
|
+
print(f"AI Defense prompt analysis failed for {file_path}: {e}")
|
|
432
|
+
|
|
433
|
+
return findings
|
|
434
|
+
|
|
435
|
+
async def _analyze_code_content(
|
|
436
|
+
self,
|
|
437
|
+
content: str,
|
|
438
|
+
skill_name: str,
|
|
439
|
+
file_path: str,
|
|
440
|
+
language: str,
|
|
441
|
+
) -> list[Finding]:
|
|
442
|
+
"""
|
|
443
|
+
Analyze code content via AI Defense API.
|
|
444
|
+
|
|
445
|
+
Uses the HTTP inspection endpoint for code analysis.
|
|
446
|
+
|
|
447
|
+
Args:
|
|
448
|
+
content: Code content to analyze
|
|
449
|
+
skill_name: Name of the skill
|
|
450
|
+
file_path: Source file path
|
|
451
|
+
language: Programming language
|
|
452
|
+
|
|
453
|
+
Returns:
|
|
454
|
+
List of findings
|
|
455
|
+
"""
|
|
456
|
+
if not content or not content.strip():
|
|
457
|
+
return []
|
|
458
|
+
|
|
459
|
+
findings = []
|
|
460
|
+
|
|
461
|
+
try:
|
|
462
|
+
# Build request payload for code analysis using chat inspection
|
|
463
|
+
# Code is analyzed as a message for potential security issues
|
|
464
|
+
messages = [
|
|
465
|
+
{"role": "user", "content": f"# Code Analysis for {file_path}\n```{language}\n{content[:15000]}\n```"}
|
|
466
|
+
]
|
|
467
|
+
metadata = {
|
|
468
|
+
"source": "skill_analyzer",
|
|
469
|
+
"skill_name": skill_name,
|
|
470
|
+
"file_path": file_path,
|
|
471
|
+
"language": language,
|
|
472
|
+
"content_type": "code",
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
# Get appropriate rules for code files (excludes Code Detection)
|
|
476
|
+
rules_for_code = self._get_rules_for_content_type("code")
|
|
477
|
+
payload = self._get_payload(messages, metadata, include_rules=True, rules_override=rules_for_code)
|
|
478
|
+
|
|
479
|
+
# Call AI Defense API - chat inspection endpoint for code
|
|
480
|
+
response = await self._make_api_request(endpoint="/inspect/chat", payload=payload)
|
|
481
|
+
|
|
482
|
+
if response:
|
|
483
|
+
# Process AI Defense response (same format as prompt inspection)
|
|
484
|
+
is_safe = response.get("is_safe", True)
|
|
485
|
+
classifications = response.get("classifications", [])
|
|
486
|
+
rules = response.get("rules", [])
|
|
487
|
+
action = response.get("action", "").lower()
|
|
488
|
+
|
|
489
|
+
# Process classifications
|
|
490
|
+
for classification in classifications:
|
|
491
|
+
if classification and classification != "NONE_VIOLATION":
|
|
492
|
+
severity = self._map_classification_to_severity(classification)
|
|
493
|
+
findings.append(
|
|
494
|
+
Finding(
|
|
495
|
+
id=self._generate_id(f"AIDEFENSE_CODE_{classification}", file_path),
|
|
496
|
+
rule_id=f"AIDEFENSE_CODE_{classification}",
|
|
497
|
+
category=self._map_violation_category(classification),
|
|
498
|
+
severity=severity,
|
|
499
|
+
title=f"Code {classification.replace('_', ' ').lower()} detected",
|
|
500
|
+
description=f"AI Defense detected {classification.replace('_', ' ').lower()} in {language} code",
|
|
501
|
+
file_path=file_path,
|
|
502
|
+
remediation="Review and fix the code issue flagged by AI Defense",
|
|
503
|
+
analyzer="aidefense",
|
|
504
|
+
metadata={
|
|
505
|
+
"classification": classification,
|
|
506
|
+
"language": language,
|
|
507
|
+
"is_safe": is_safe,
|
|
508
|
+
},
|
|
509
|
+
)
|
|
510
|
+
)
|
|
511
|
+
|
|
512
|
+
# Process triggered rules
|
|
513
|
+
for rule in rules:
|
|
514
|
+
rule_name = rule.get("rule_name", "Unknown")
|
|
515
|
+
rule_classification = rule.get("classification", "")
|
|
516
|
+
|
|
517
|
+
if rule_classification in ("NONE_VIOLATION", ""):
|
|
518
|
+
continue
|
|
519
|
+
|
|
520
|
+
findings.append(
|
|
521
|
+
Finding(
|
|
522
|
+
id=self._generate_id(f"AIDEFENSE_CODE_RULE_{rule_name}", file_path),
|
|
523
|
+
rule_id=f"AIDEFENSE_CODE_RULE_{rule_name.upper().replace(' ', '_')}",
|
|
524
|
+
category=self._map_violation_category(rule_classification),
|
|
525
|
+
severity=self._map_classification_to_severity(rule_classification),
|
|
526
|
+
title=f"Code rule triggered: {rule_name}",
|
|
527
|
+
description=f"AI Defense rule '{rule_name}' detected issue in {language} code",
|
|
528
|
+
file_path=file_path,
|
|
529
|
+
remediation=f"Address the {rule_name.lower()} issue in the code",
|
|
530
|
+
analyzer="aidefense",
|
|
531
|
+
metadata={
|
|
532
|
+
"rule_name": rule_name,
|
|
533
|
+
"classification": rule_classification,
|
|
534
|
+
"language": language,
|
|
535
|
+
},
|
|
536
|
+
)
|
|
537
|
+
)
|
|
538
|
+
|
|
539
|
+
# Check action
|
|
540
|
+
if action == "block" and not is_safe and not findings:
|
|
541
|
+
findings.append(
|
|
542
|
+
Finding(
|
|
543
|
+
id=self._generate_id("AIDEFENSE_CODE_BLOCKED", file_path),
|
|
544
|
+
rule_id="AIDEFENSE_CODE_BLOCKED",
|
|
545
|
+
category=ThreatCategory.MALWARE,
|
|
546
|
+
severity=Severity.HIGH,
|
|
547
|
+
title="Code blocked by AI Defense",
|
|
548
|
+
description=f"AI Defense blocked {language} code in {file_path} as potentially malicious",
|
|
549
|
+
file_path=file_path,
|
|
550
|
+
analyzer="aidefense",
|
|
551
|
+
metadata={
|
|
552
|
+
"action": action,
|
|
553
|
+
"language": language,
|
|
554
|
+
"is_safe": is_safe,
|
|
555
|
+
},
|
|
556
|
+
)
|
|
557
|
+
)
|
|
558
|
+
|
|
559
|
+
except Exception as e:
|
|
560
|
+
print(f"AI Defense code analysis failed for {file_path}: {e}")
|
|
561
|
+
|
|
562
|
+
return findings
|
|
563
|
+
|
|
564
|
+
async def _make_api_request(
|
|
565
|
+
self,
|
|
566
|
+
endpoint: str,
|
|
567
|
+
payload: dict[str, Any],
|
|
568
|
+
) -> dict[str, Any] | None:
|
|
569
|
+
"""
|
|
570
|
+
Make request to AI Defense API with retry logic.
|
|
571
|
+
|
|
572
|
+
Handles fallback for pre-configured rules: if API returns 400 with
|
|
573
|
+
"already has rules configured" error, retries without rules config.
|
|
574
|
+
|
|
575
|
+
Args:
|
|
576
|
+
endpoint: API endpoint path
|
|
577
|
+
payload: Request payload (may include config.enabled_rules)
|
|
578
|
+
|
|
579
|
+
Returns:
|
|
580
|
+
API response or None on failure
|
|
581
|
+
"""
|
|
582
|
+
client = self._get_client()
|
|
583
|
+
url = f"{self.api_url}{endpoint}"
|
|
584
|
+
|
|
585
|
+
last_exception = None
|
|
586
|
+
original_payload = payload.copy()
|
|
587
|
+
tried_without_rules = False
|
|
588
|
+
|
|
589
|
+
for attempt in range(self.max_retries):
|
|
590
|
+
try:
|
|
591
|
+
response = await client.post(url, json=payload)
|
|
592
|
+
|
|
593
|
+
if response.status_code == 200:
|
|
594
|
+
return response.json()
|
|
595
|
+
elif response.status_code == 400:
|
|
596
|
+
# Check if this is a pre-configured rules error
|
|
597
|
+
try:
|
|
598
|
+
error_json = response.json()
|
|
599
|
+
error_msg = error_json.get("message", "").lower()
|
|
600
|
+
|
|
601
|
+
# If API key has pre-configured rules, retry without rules config
|
|
602
|
+
if (
|
|
603
|
+
"already has rules configured" in error_msg or "pre-configured" in error_msg
|
|
604
|
+
) and not tried_without_rules:
|
|
605
|
+
# Remove config.enabled_rules and retry
|
|
606
|
+
payload_without_rules = original_payload.copy()
|
|
607
|
+
if "config" in payload_without_rules:
|
|
608
|
+
del payload_without_rules["config"]
|
|
609
|
+
|
|
610
|
+
print(
|
|
611
|
+
"AI Defense API key has pre-configured rules, retrying without enabled_rules config..."
|
|
612
|
+
)
|
|
613
|
+
payload = payload_without_rules
|
|
614
|
+
tried_without_rules = True
|
|
615
|
+
continue
|
|
616
|
+
except (ValueError, KeyError, json.JSONDecodeError):
|
|
617
|
+
# Can't parse error, fall through to generic error handling
|
|
618
|
+
pass
|
|
619
|
+
|
|
620
|
+
# Generic 400 error
|
|
621
|
+
print(f"AI Defense API error: {response.status_code} - {response.text}")
|
|
622
|
+
return None
|
|
623
|
+
elif response.status_code == 429:
|
|
624
|
+
# Rate limited - wait and retry
|
|
625
|
+
delay = (2**attempt) * 1.0
|
|
626
|
+
print(f"AI Defense API rate limited, retrying in {delay}s...")
|
|
627
|
+
await asyncio.sleep(delay)
|
|
628
|
+
continue
|
|
629
|
+
elif response.status_code == 401:
|
|
630
|
+
raise ValueError("Invalid AI Defense API key")
|
|
631
|
+
elif response.status_code == 403:
|
|
632
|
+
raise ValueError("AI Defense API access denied - check permissions")
|
|
633
|
+
else:
|
|
634
|
+
print(f"AI Defense API error: {response.status_code} - {response.text}")
|
|
635
|
+
return None
|
|
636
|
+
|
|
637
|
+
except httpx.TimeoutException:
|
|
638
|
+
last_exception = TimeoutError(f"AI Defense API timeout after {self.timeout}s")
|
|
639
|
+
if attempt < self.max_retries - 1:
|
|
640
|
+
await asyncio.sleep(1.0)
|
|
641
|
+
continue
|
|
642
|
+
except httpx.RequestError as e:
|
|
643
|
+
last_exception = e
|
|
644
|
+
if attempt < self.max_retries - 1:
|
|
645
|
+
await asyncio.sleep(1.0)
|
|
646
|
+
continue
|
|
647
|
+
|
|
648
|
+
if last_exception:
|
|
649
|
+
print(f"AI Defense API request failed after {self.max_retries} attempts: {last_exception}")
|
|
650
|
+
|
|
651
|
+
return None
|
|
652
|
+
|
|
653
|
+
def _convert_api_violation_to_finding(
|
|
654
|
+
self,
|
|
655
|
+
violation: dict[str, Any],
|
|
656
|
+
skill_name: str,
|
|
657
|
+
file_path: str,
|
|
658
|
+
content_type: str,
|
|
659
|
+
) -> Finding | None:
|
|
660
|
+
"""Convert AI Defense API violation to Finding object."""
|
|
661
|
+
try:
|
|
662
|
+
violation_type = violation.get("type", "unknown").upper()
|
|
663
|
+
severity_str = violation.get("severity", "medium").upper()
|
|
664
|
+
|
|
665
|
+
# Map severity
|
|
666
|
+
severity = self._map_violation_severity(severity_str)
|
|
667
|
+
|
|
668
|
+
# Map category based on violation type
|
|
669
|
+
category = self._map_violation_category(violation_type)
|
|
670
|
+
|
|
671
|
+
# Get AITech mapping if available
|
|
672
|
+
try:
|
|
673
|
+
aitech_mapping = ThreatMapping.get_threat_mapping("llm", violation_type.replace("_", " "))
|
|
674
|
+
except (ValueError, KeyError):
|
|
675
|
+
aitech_mapping = {}
|
|
676
|
+
|
|
677
|
+
return Finding(
|
|
678
|
+
id=self._generate_id(f"AIDEFENSE_{violation_type}", file_path),
|
|
679
|
+
rule_id=f"AIDEFENSE_{violation_type}",
|
|
680
|
+
category=category,
|
|
681
|
+
severity=severity,
|
|
682
|
+
title=violation.get("title", f"AI Defense detected: {violation_type.replace('_', ' ').lower()}"),
|
|
683
|
+
description=violation.get("description", f"Violation detected in {content_type}"),
|
|
684
|
+
file_path=file_path,
|
|
685
|
+
line_number=violation.get("line"),
|
|
686
|
+
snippet=violation.get("evidence", violation.get("snippet", "")),
|
|
687
|
+
remediation=violation.get("remediation", "Review and address the security concern"),
|
|
688
|
+
analyzer="aidefense",
|
|
689
|
+
metadata={
|
|
690
|
+
"violation_type": violation_type,
|
|
691
|
+
"confidence": violation.get("confidence"),
|
|
692
|
+
"aitech": aitech_mapping.get("aitech"),
|
|
693
|
+
"aitech_name": aitech_mapping.get("aitech_name"),
|
|
694
|
+
},
|
|
695
|
+
)
|
|
696
|
+
|
|
697
|
+
except Exception as e:
|
|
698
|
+
print(f"Failed to convert AI Defense violation: {e}")
|
|
699
|
+
return None
|
|
700
|
+
|
|
701
|
+
def _map_violation_severity(self, severity_str: str) -> Severity:
|
|
702
|
+
"""Map AI Defense severity string to Severity enum."""
|
|
703
|
+
severity_map = {
|
|
704
|
+
"CRITICAL": Severity.CRITICAL,
|
|
705
|
+
"HIGH": Severity.HIGH,
|
|
706
|
+
"MEDIUM": Severity.MEDIUM,
|
|
707
|
+
"LOW": Severity.LOW,
|
|
708
|
+
"INFO": Severity.INFO,
|
|
709
|
+
"INFORMATIONAL": Severity.INFO,
|
|
710
|
+
"NONE_SEVERITY": Severity.MEDIUM, # Default for AI Defense
|
|
711
|
+
}
|
|
712
|
+
return severity_map.get(severity_str.upper(), Severity.MEDIUM)
|
|
713
|
+
|
|
714
|
+
def _map_classification_to_severity(self, classification: str) -> Severity:
|
|
715
|
+
"""Map AI Defense classification to severity level."""
|
|
716
|
+
classification = classification.upper()
|
|
717
|
+
severity_map = {
|
|
718
|
+
"SECURITY_VIOLATION": Severity.HIGH,
|
|
719
|
+
"PRIVACY_VIOLATION": Severity.HIGH,
|
|
720
|
+
"SAFETY_VIOLATION": Severity.MEDIUM,
|
|
721
|
+
"RELEVANCE_VIOLATION": Severity.LOW,
|
|
722
|
+
"NONE_VIOLATION": Severity.INFO,
|
|
723
|
+
}
|
|
724
|
+
return severity_map.get(classification, Severity.MEDIUM)
|
|
725
|
+
|
|
726
|
+
def _map_violation_category(self, violation_type: str) -> ThreatCategory:
|
|
727
|
+
"""Map AI Defense violation type to ThreatCategory."""
|
|
728
|
+
violation_type = violation_type.upper()
|
|
729
|
+
mapping = {
|
|
730
|
+
"SECURITY_VIOLATION": ThreatCategory.PROMPT_INJECTION,
|
|
731
|
+
"PRIVACY_VIOLATION": ThreatCategory.DATA_EXFILTRATION,
|
|
732
|
+
"SAFETY_VIOLATION": ThreatCategory.SOCIAL_ENGINEERING,
|
|
733
|
+
"RELEVANCE_VIOLATION": ThreatCategory.POLICY_VIOLATION,
|
|
734
|
+
"PROMPT_INJECTION": ThreatCategory.PROMPT_INJECTION,
|
|
735
|
+
"JAILBREAK": ThreatCategory.PROMPT_INJECTION,
|
|
736
|
+
"TOOL_POISONING": ThreatCategory.PROMPT_INJECTION,
|
|
737
|
+
"DATA_EXFILTRATION": ThreatCategory.DATA_EXFILTRATION,
|
|
738
|
+
"DATA_LEAK": ThreatCategory.DATA_EXFILTRATION,
|
|
739
|
+
"COMMAND_INJECTION": ThreatCategory.COMMAND_INJECTION,
|
|
740
|
+
"CODE_INJECTION": ThreatCategory.COMMAND_INJECTION,
|
|
741
|
+
"CREDENTIAL_THEFT": ThreatCategory.HARDCODED_SECRETS,
|
|
742
|
+
"MALWARE": ThreatCategory.MALWARE,
|
|
743
|
+
"SOCIAL_ENGINEERING": ThreatCategory.SOCIAL_ENGINEERING,
|
|
744
|
+
"OBFUSCATION": ThreatCategory.OBFUSCATION,
|
|
745
|
+
}
|
|
746
|
+
return mapping.get(violation_type, ThreatCategory.POLICY_VIOLATION)
|
|
747
|
+
|
|
748
|
+
def _convert_api_threat_to_finding(
|
|
749
|
+
self,
|
|
750
|
+
threat: dict[str, Any],
|
|
751
|
+
skill_name: str,
|
|
752
|
+
file_path: str,
|
|
753
|
+
content_type: str,
|
|
754
|
+
) -> Finding | None:
|
|
755
|
+
"""Convert AI Defense API threat to Finding object (legacy support)."""
|
|
756
|
+
# Delegate to violation converter for consistency
|
|
757
|
+
return self._convert_api_violation_to_finding(threat, skill_name, file_path, content_type)
|
|
758
|
+
|
|
759
|
+
def _convert_api_vulnerability_to_finding(
|
|
760
|
+
self,
|
|
761
|
+
vuln: dict[str, Any],
|
|
762
|
+
skill_name: str,
|
|
763
|
+
file_path: str,
|
|
764
|
+
language: str,
|
|
765
|
+
) -> Finding | None:
|
|
766
|
+
"""Convert AI Defense API vulnerability to Finding object."""
|
|
767
|
+
try:
|
|
768
|
+
vuln_type = vuln.get("type", "unknown").upper()
|
|
769
|
+
severity_str = vuln.get("severity", "MEDIUM").upper()
|
|
770
|
+
|
|
771
|
+
# Map severity
|
|
772
|
+
severity_map = {
|
|
773
|
+
"CRITICAL": Severity.CRITICAL,
|
|
774
|
+
"HIGH": Severity.HIGH,
|
|
775
|
+
"MEDIUM": Severity.MEDIUM,
|
|
776
|
+
"LOW": Severity.LOW,
|
|
777
|
+
"INFO": Severity.INFO,
|
|
778
|
+
}
|
|
779
|
+
severity = severity_map.get(severity_str, Severity.MEDIUM)
|
|
780
|
+
|
|
781
|
+
# Map category
|
|
782
|
+
category = self._map_vuln_type_to_category(vuln_type)
|
|
783
|
+
|
|
784
|
+
return Finding(
|
|
785
|
+
id=self._generate_id(f"AIDEFENSE_VULN_{vuln_type}", f"{file_path}_{vuln.get('line', 0)}"),
|
|
786
|
+
rule_id=f"AIDEFENSE_VULN_{vuln_type}",
|
|
787
|
+
category=category,
|
|
788
|
+
severity=severity,
|
|
789
|
+
title=vuln.get("title", f"Vulnerability: {vuln_type}"),
|
|
790
|
+
description=vuln.get("description", f"Security vulnerability in {language} code"),
|
|
791
|
+
file_path=file_path,
|
|
792
|
+
line_number=vuln.get("line"),
|
|
793
|
+
snippet=vuln.get("snippet", ""),
|
|
794
|
+
remediation=vuln.get("remediation", "Fix the security vulnerability"),
|
|
795
|
+
analyzer="aidefense",
|
|
796
|
+
metadata={
|
|
797
|
+
"vuln_type": vuln_type,
|
|
798
|
+
"cwe": vuln.get("cwe"),
|
|
799
|
+
"language": language,
|
|
800
|
+
},
|
|
801
|
+
)
|
|
802
|
+
|
|
803
|
+
except Exception as e:
|
|
804
|
+
print(f"Failed to convert AI Defense vulnerability: {e}")
|
|
805
|
+
return None
|
|
806
|
+
|
|
807
|
+
def _map_threat_type_to_category(self, threat_type: str) -> ThreatCategory:
|
|
808
|
+
"""Map AI Defense threat type to ThreatCategory."""
|
|
809
|
+
mapping = {
|
|
810
|
+
"PROMPT_INJECTION": ThreatCategory.PROMPT_INJECTION,
|
|
811
|
+
"PROMPT INJECTION": ThreatCategory.PROMPT_INJECTION,
|
|
812
|
+
"JAILBREAK": ThreatCategory.PROMPT_INJECTION,
|
|
813
|
+
"TOOL_POISONING": ThreatCategory.PROMPT_INJECTION,
|
|
814
|
+
"TOOL POISONING": ThreatCategory.PROMPT_INJECTION,
|
|
815
|
+
"DATA_EXFILTRATION": ThreatCategory.DATA_EXFILTRATION,
|
|
816
|
+
"DATA EXFILTRATION": ThreatCategory.DATA_EXFILTRATION,
|
|
817
|
+
"COMMAND_INJECTION": ThreatCategory.COMMAND_INJECTION,
|
|
818
|
+
"COMMAND INJECTION": ThreatCategory.COMMAND_INJECTION,
|
|
819
|
+
"CREDENTIAL_THEFT": ThreatCategory.HARDCODED_SECRETS,
|
|
820
|
+
"MALWARE": ThreatCategory.MALWARE,
|
|
821
|
+
"SOCIAL_ENGINEERING": ThreatCategory.SOCIAL_ENGINEERING,
|
|
822
|
+
"OBFUSCATION": ThreatCategory.OBFUSCATION,
|
|
823
|
+
}
|
|
824
|
+
return mapping.get(threat_type.upper(), ThreatCategory.POLICY_VIOLATION)
|
|
825
|
+
|
|
826
|
+
def _map_vuln_type_to_category(self, vuln_type: str) -> ThreatCategory:
|
|
827
|
+
"""Map vulnerability type to ThreatCategory."""
|
|
828
|
+
mapping = {
|
|
829
|
+
"INJECTION": ThreatCategory.COMMAND_INJECTION,
|
|
830
|
+
"SQL_INJECTION": ThreatCategory.COMMAND_INJECTION,
|
|
831
|
+
"COMMAND_INJECTION": ThreatCategory.COMMAND_INJECTION,
|
|
832
|
+
"XSS": ThreatCategory.COMMAND_INJECTION,
|
|
833
|
+
"PATH_TRAVERSAL": ThreatCategory.DATA_EXFILTRATION,
|
|
834
|
+
"SENSITIVE_DATA": ThreatCategory.HARDCODED_SECRETS,
|
|
835
|
+
"HARDCODED_SECRET": ThreatCategory.HARDCODED_SECRETS,
|
|
836
|
+
"INSECURE_FUNCTION": ThreatCategory.COMMAND_INJECTION,
|
|
837
|
+
}
|
|
838
|
+
return mapping.get(vuln_type.upper(), ThreatCategory.POLICY_VIOLATION)
|
|
839
|
+
|
|
840
|
+
def _map_pattern_to_category(self, pattern_type: str | None) -> ThreatCategory:
|
|
841
|
+
"""Map malicious pattern type to ThreatCategory."""
|
|
842
|
+
if not pattern_type:
|
|
843
|
+
return ThreatCategory.POLICY_VIOLATION
|
|
844
|
+
|
|
845
|
+
pattern_type = pattern_type.upper()
|
|
846
|
+
mapping = {
|
|
847
|
+
"EXFILTRATION": ThreatCategory.DATA_EXFILTRATION,
|
|
848
|
+
"BACKDOOR": ThreatCategory.MALWARE,
|
|
849
|
+
"CREDENTIAL_THEFT": ThreatCategory.HARDCODED_SECRETS,
|
|
850
|
+
"OBFUSCATION": ThreatCategory.OBFUSCATION,
|
|
851
|
+
"INJECTION": ThreatCategory.COMMAND_INJECTION,
|
|
852
|
+
}
|
|
853
|
+
return mapping.get(pattern_type, ThreatCategory.MALWARE)
|
|
854
|
+
|
|
855
|
+
def _map_confidence_to_severity(self, confidence: float) -> Severity:
|
|
856
|
+
"""Map confidence score to severity level."""
|
|
857
|
+
if confidence >= 0.9:
|
|
858
|
+
return Severity.CRITICAL
|
|
859
|
+
elif confidence >= 0.7:
|
|
860
|
+
return Severity.HIGH
|
|
861
|
+
elif confidence >= 0.5:
|
|
862
|
+
return Severity.MEDIUM
|
|
863
|
+
elif confidence >= 0.3:
|
|
864
|
+
return Severity.LOW
|
|
865
|
+
else:
|
|
866
|
+
return Severity.INFO
|
|
867
|
+
|
|
868
|
+
def _generate_id(self, prefix: str, context: str) -> str:
|
|
869
|
+
"""Generate unique finding ID."""
|
|
870
|
+
combined = f"{prefix}:{context}"
|
|
871
|
+
hash_obj = hashlib.sha256(combined.encode())
|
|
872
|
+
return f"{prefix}_{hash_obj.hexdigest()[:10]}"
|