cisco-ai-skill-scanner 1.0.0__py3-none-any.whl → 1.0.2__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 → cisco_ai_skill_scanner-1.0.2.dist-info}/METADATA +28 -13
- cisco_ai_skill_scanner-1.0.2.dist-info/RECORD +102 -0
- cisco_ai_skill_scanner-1.0.2.dist-info/entry_points.txt +4 -0
- {skillanalyzer → skill_scanner}/__init__.py +8 -4
- {skillanalyzer → skill_scanner}/_version.py +2 -2
- {skillanalyzer → skill_scanner}/api/__init__.py +1 -1
- {skillanalyzer → skill_scanner}/api/api.py +4 -4
- {skillanalyzer → skill_scanner}/api/api_cli.py +8 -8
- {skillanalyzer → skill_scanner}/api/api_server.py +7 -7
- {skillanalyzer → skill_scanner}/api/router.py +3 -3
- {skillanalyzer → skill_scanner}/cli/__init__.py +1 -1
- {skillanalyzer → skill_scanner}/cli/cli.py +71 -13
- {skillanalyzer → skill_scanner}/config/__init__.py +3 -3
- {skillanalyzer → skill_scanner}/config/config.py +2 -2
- {skillanalyzer → skill_scanner}/config/config_parser.py +9 -9
- {skillanalyzer → skill_scanner}/config/constants.py +2 -2
- skill_scanner/config/yara_modes.py +314 -0
- {skillanalyzer → skill_scanner}/core/__init__.py +1 -1
- {skillanalyzer → skill_scanner}/core/analyzers/__init__.py +3 -3
- {skillanalyzer → skill_scanner}/core/analyzers/aidefense_analyzer.py +3 -3
- {skillanalyzer → skill_scanner}/core/analyzers/behavioral/__init__.py +1 -1
- {skillanalyzer → skill_scanner}/core/analyzers/behavioral/alignment/alignment_llm_client.py +1 -1
- {skillanalyzer → skill_scanner}/core/analyzers/behavioral/alignment/alignment_prompt_builder.py +2 -2
- {skillanalyzer → skill_scanner}/core/analyzers/behavioral_analyzer.py +1 -1
- skillanalyzer/core/analyzers/cross_skill_analyzer.py → skill_scanner/core/analyzers/cross_skill_scanner.py +5 -5
- {skillanalyzer → skill_scanner}/core/analyzers/llm_analyzer.py +4 -4
- {skillanalyzer → skill_scanner}/core/analyzers/llm_prompt_builder.py +2 -2
- {skillanalyzer → skill_scanner}/core/analyzers/meta_analyzer.py +52 -20
- {skillanalyzer → skill_scanner}/core/analyzers/static.py +185 -35
- {skillanalyzer → skill_scanner}/core/analyzers/trigger_analyzer.py +2 -2
- {skillanalyzer → skill_scanner}/core/exceptions.py +10 -10
- {skillanalyzer → skill_scanner}/core/loader.py +4 -4
- {skillanalyzer → skill_scanner}/core/models.py +7 -6
- {skillanalyzer → skill_scanner}/core/reporters/markdown_reporter.py +11 -5
- {skillanalyzer → skill_scanner}/core/reporters/sarif_reporter.py +2 -2
- {skillanalyzer → skill_scanner}/core/reporters/table_reporter.py +2 -2
- {skillanalyzer → skill_scanner}/core/rules/yara_scanner.py +1 -1
- {skillanalyzer → skill_scanner}/core/scanner.py +2 -2
- {skillanalyzer → skill_scanner}/core/static_analysis/context_extractor.py +88 -14
- {skillanalyzer → skill_scanner}/core/static_analysis/dataflow/__init__.py +1 -1
- {skillanalyzer → skill_scanner}/core/static_analysis/interprocedural/call_graph_analyzer.py +2 -2
- {skillanalyzer → skill_scanner}/core/static_analysis/parser/python_parser.py +5 -5
- {skillanalyzer → skill_scanner}/data/__init__.py +1 -1
- {skillanalyzer → skill_scanner}/data/prompts/boilerplate_protection_rule_prompt.md +5 -5
- {skillanalyzer → skill_scanner}/data/prompts/code_alignment_threat_analysis_prompt.md +128 -53
- {skillanalyzer → skill_scanner}/data/prompts/llm_response_schema.json +3 -3
- {skillanalyzer → skill_scanner}/data/prompts/skill_meta_analysis_prompt.md +16 -15
- {skillanalyzer → skill_scanner}/data/prompts/skill_threat_analysis_prompt.md +53 -17
- {skillanalyzer → skill_scanner}/data/prompts/unified_response_schema.md +1 -1
- {skillanalyzer → skill_scanner}/data/rules/signatures.yaml +143 -37
- skill_scanner/data/yara_rules/autonomy_abuse_generic.yara +66 -0
- skillanalyzer/data/yara_rules/skill_discovery_abuse.yara → skill_scanner/data/yara_rules/capability_inflation_generic.yara +7 -4
- skill_scanner/data/yara_rules/code_execution_generic.yara +76 -0
- skillanalyzer/data/yara_rules/coercive_injection.yara → skill_scanner/data/yara_rules/coercive_injection_generic.yara +2 -2
- skill_scanner/data/yara_rules/command_injection_generic.yara +77 -0
- skillanalyzer/data/yara_rules/credential_harvesting.yara → skill_scanner/data/yara_rules/credential_harvesting_generic.yara +25 -4
- skillanalyzer/data/yara_rules/transitive_trust_abuse.yara → skill_scanner/data/yara_rules/indirect_prompt_injection_generic.yara +8 -5
- skillanalyzer/data/yara_rules/prompt_injection.yara → skill_scanner/data/yara_rules/prompt_injection_generic.yara +2 -2
- skillanalyzer/data/yara_rules/unicode_steganography.yara → skill_scanner/data/yara_rules/prompt_injection_unicode_steganography.yara +23 -17
- skill_scanner/data/yara_rules/script_injection_generic.yara +82 -0
- skillanalyzer/data/yara_rules/sql_injection.yara → skill_scanner/data/yara_rules/sql_injection_generic.yara +22 -8
- skill_scanner/data/yara_rules/system_manipulation_generic.yara +79 -0
- skill_scanner/data/yara_rules/tool_chaining_abuse_generic.yara +72 -0
- {skillanalyzer → skill_scanner}/hooks/__init__.py +1 -1
- {skillanalyzer → skill_scanner}/hooks/pre_commit.py +16 -16
- {skillanalyzer → skill_scanner}/threats/__init__.py +25 -3
- skill_scanner/threats/cisco_ai_taxonomy.py +274 -0
- {skillanalyzer → skill_scanner}/threats/threats.py +28 -99
- {skillanalyzer → skill_scanner}/utils/__init__.py +1 -1
- {skillanalyzer → skill_scanner}/utils/command_utils.py +1 -1
- {skillanalyzer → skill_scanner}/utils/di_container.py +1 -1
- {skillanalyzer → skill_scanner}/utils/logging_config.py +7 -7
- cisco_ai_skill_scanner-1.0.0.dist-info/RECORD +0 -100
- cisco_ai_skill_scanner-1.0.0.dist-info/entry_points.txt +0 -4
- skillanalyzer/data/yara_rules/autonomy_abuse.yara +0 -66
- skillanalyzer/data/yara_rules/code_execution.yara +0 -61
- skillanalyzer/data/yara_rules/command_injection.yara +0 -54
- skillanalyzer/data/yara_rules/script_injection.yara +0 -83
- skillanalyzer/data/yara_rules/system_manipulation.yara +0 -65
- skillanalyzer/data/yara_rules/tool_chaining_abuse.yara +0 -60
- {cisco_ai_skill_scanner-1.0.0.dist-info → cisco_ai_skill_scanner-1.0.2.dist-info}/WHEEL +0 -0
- {cisco_ai_skill_scanner-1.0.0.dist-info → cisco_ai_skill_scanner-1.0.2.dist-info}/licenses/LICENSE +0 -0
- {skillanalyzer → skill_scanner}/core/analyzers/base.py +0 -0
- {skillanalyzer → skill_scanner}/core/analyzers/behavioral/alignment/__init__.py +0 -0
- {skillanalyzer → skill_scanner}/core/analyzers/behavioral/alignment/alignment_orchestrator.py +0 -0
- {skillanalyzer → skill_scanner}/core/analyzers/behavioral/alignment/alignment_response_validator.py +0 -0
- {skillanalyzer → skill_scanner}/core/analyzers/behavioral/alignment/threat_vulnerability_classifier.py +0 -0
- {skillanalyzer → skill_scanner}/core/analyzers/llm_provider_config.py +0 -0
- {skillanalyzer → skill_scanner}/core/analyzers/llm_request_handler.py +0 -0
- {skillanalyzer → skill_scanner}/core/analyzers/llm_response_parser.py +0 -0
- {skillanalyzer → skill_scanner}/core/analyzers/virustotal_analyzer.py +0 -0
- {skillanalyzer → skill_scanner}/core/reporters/__init__.py +0 -0
- {skillanalyzer → skill_scanner}/core/reporters/json_reporter.py +0 -0
- {skillanalyzer → skill_scanner}/core/rules/__init__.py +0 -0
- {skillanalyzer → skill_scanner}/core/rules/patterns.py +0 -0
- {skillanalyzer → skill_scanner}/core/static_analysis/__init__.py +0 -0
- {skillanalyzer → skill_scanner}/core/static_analysis/cfg/__init__.py +0 -0
- {skillanalyzer → skill_scanner}/core/static_analysis/cfg/builder.py +0 -0
- {skillanalyzer → skill_scanner}/core/static_analysis/dataflow/forward_analysis.py +0 -0
- {skillanalyzer → skill_scanner}/core/static_analysis/interprocedural/__init__.py +0 -0
- {skillanalyzer → skill_scanner}/core/static_analysis/interprocedural/cross_file_analyzer.py +0 -0
- {skillanalyzer → skill_scanner}/core/static_analysis/parser/__init__.py +0 -0
- {skillanalyzer → skill_scanner}/core/static_analysis/semantic/__init__.py +0 -0
- {skillanalyzer → skill_scanner}/core/static_analysis/semantic/name_resolver.py +0 -0
- {skillanalyzer → skill_scanner}/core/static_analysis/semantic/type_analyzer.py +0 -0
- {skillanalyzer → skill_scanner}/core/static_analysis/taint/__init__.py +0 -0
- {skillanalyzer → skill_scanner}/core/static_analysis/taint/tracker.py +0 -0
- {skillanalyzer → skill_scanner}/core/static_analysis/types/__init__.py +0 -0
- {skillanalyzer → skill_scanner}/utils/file_utils.py +0 -0
- {skillanalyzer → skill_scanner}/utils/logging_utils.py +0 -0
|
@@ -14,10 +14,10 @@
|
|
|
14
14
|
#
|
|
15
15
|
# SPDX-License-Identifier: Apache-2.0
|
|
16
16
|
|
|
17
|
-
"""Configuration parser for Skill
|
|
17
|
+
"""Configuration parser for Skill Scanner.
|
|
18
18
|
|
|
19
19
|
This module provides functionality to parse configuration files
|
|
20
|
-
and environment variables for Skill
|
|
20
|
+
and environment variables for Skill Scanner.
|
|
21
21
|
"""
|
|
22
22
|
|
|
23
23
|
import os
|
|
@@ -25,7 +25,7 @@ from pathlib import Path
|
|
|
25
25
|
|
|
26
26
|
from ..utils.logging_config import get_logger
|
|
27
27
|
from .config import Config
|
|
28
|
-
from .constants import
|
|
28
|
+
from .constants import SkillScannerConstants
|
|
29
29
|
|
|
30
30
|
logger = get_logger(__name__)
|
|
31
31
|
|
|
@@ -74,10 +74,10 @@ def parse_config_file(config_path: str | None = None) -> Config:
|
|
|
74
74
|
if not config_path:
|
|
75
75
|
# Try to find default config file
|
|
76
76
|
default_paths = [
|
|
77
|
-
Path.home() / ".
|
|
78
|
-
Path.home() / ".
|
|
79
|
-
Path.cwd() / ".
|
|
80
|
-
Path.cwd() / ".
|
|
77
|
+
Path.home() / ".skill_scanner" / "config.yaml",
|
|
78
|
+
Path.home() / ".skill_scanner" / "config.json",
|
|
79
|
+
Path.cwd() / ".skill_scanner.yaml",
|
|
80
|
+
Path.cwd() / ".skill_scanner.json",
|
|
81
81
|
]
|
|
82
82
|
|
|
83
83
|
for path in default_paths:
|
|
@@ -96,11 +96,11 @@ def parse_config_file(config_path: str | None = None) -> Config:
|
|
|
96
96
|
|
|
97
97
|
|
|
98
98
|
class ConfigParser:
|
|
99
|
-
"""Parser for Skill
|
|
99
|
+
"""Parser for Skill Scanner configuration files."""
|
|
100
100
|
|
|
101
101
|
def __init__(self):
|
|
102
102
|
"""Initialize the config parser."""
|
|
103
|
-
self.constants =
|
|
103
|
+
self.constants = SkillScannerConstants
|
|
104
104
|
|
|
105
105
|
def parse(self, config_path: str | None = None) -> Config:
|
|
106
106
|
"""Parse configuration from file and environment.
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
# SPDX-License-Identifier: Apache-2.0
|
|
16
16
|
|
|
17
17
|
"""
|
|
18
|
-
Constants for
|
|
18
|
+
Constants for Skill Scanner.
|
|
19
19
|
|
|
20
20
|
Mirrors MCP Scanner's constants structure.
|
|
21
21
|
"""
|
|
@@ -23,7 +23,7 @@ Mirrors MCP Scanner's constants structure.
|
|
|
23
23
|
from pathlib import Path
|
|
24
24
|
|
|
25
25
|
|
|
26
|
-
class
|
|
26
|
+
class SkillScannerConstants:
|
|
27
27
|
"""Constants used throughout the analyzer."""
|
|
28
28
|
|
|
29
29
|
# Version
|
|
@@ -0,0 +1,314 @@
|
|
|
1
|
+
# Copyright 2026 Cisco Systems, Inc. and its affiliates
|
|
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
|
+
YARA Mode Configuration System
|
|
19
|
+
|
|
20
|
+
Provides configurable detection modes to balance false positives vs true positives:
|
|
21
|
+
- STRICT: Maximum security, higher false positives acceptable
|
|
22
|
+
- BALANCED: Default mode, good tradeoff between FP and TP
|
|
23
|
+
- PERMISSIVE: Minimize false positives, may miss some threats
|
|
24
|
+
- CUSTOM: User-defined thresholds
|
|
25
|
+
|
|
26
|
+
Each mode configures:
|
|
27
|
+
- Rule-specific thresholds
|
|
28
|
+
- Post-processing filters
|
|
29
|
+
- Enabled/disabled rule sets
|
|
30
|
+
"""
|
|
31
|
+
|
|
32
|
+
from dataclasses import dataclass, field
|
|
33
|
+
from enum import Enum
|
|
34
|
+
from typing import Optional
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class YaraMode(Enum):
|
|
38
|
+
"""YARA detection mode presets."""
|
|
39
|
+
|
|
40
|
+
STRICT = "strict"
|
|
41
|
+
BALANCED = "balanced"
|
|
42
|
+
PERMISSIVE = "permissive"
|
|
43
|
+
CUSTOM = "custom"
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
@dataclass
|
|
47
|
+
class UnicodeStegConfig:
|
|
48
|
+
"""Configuration for unicode steganography detection."""
|
|
49
|
+
|
|
50
|
+
# Zero-width character thresholds
|
|
51
|
+
zerowidth_threshold_with_decode: int = 50 # When dangerous code is present
|
|
52
|
+
zerowidth_threshold_alone: int = 200 # Without dangerous code context
|
|
53
|
+
|
|
54
|
+
# Enable/disable specific patterns
|
|
55
|
+
detect_rtl_override: bool = True
|
|
56
|
+
detect_ltl_override: bool = True
|
|
57
|
+
detect_line_separators: bool = True
|
|
58
|
+
detect_unicode_tags: bool = True
|
|
59
|
+
detect_variation_selectors: bool = True
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
@dataclass
|
|
63
|
+
class CredentialHarvestingConfig:
|
|
64
|
+
"""Configuration for credential harvesting detection."""
|
|
65
|
+
|
|
66
|
+
# Placeholder filtering
|
|
67
|
+
filter_placeholder_patterns: bool = True
|
|
68
|
+
|
|
69
|
+
# Specific API key patterns
|
|
70
|
+
detect_ai_api_keys: bool = True
|
|
71
|
+
detect_aws_keys: bool = True
|
|
72
|
+
detect_ssh_keys: bool = True
|
|
73
|
+
detect_env_exfiltration: bool = True
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
@dataclass
|
|
77
|
+
class ToolChainingConfig:
|
|
78
|
+
"""Configuration for tool chaining abuse detection."""
|
|
79
|
+
|
|
80
|
+
# Post-filter settings
|
|
81
|
+
filter_api_documentation: bool = True
|
|
82
|
+
filter_generic_http_verbs: bool = True
|
|
83
|
+
filter_email_field_mentions: bool = True
|
|
84
|
+
|
|
85
|
+
# Pattern detection
|
|
86
|
+
detect_read_send: bool = True
|
|
87
|
+
detect_collect_exfil: bool = True
|
|
88
|
+
detect_env_network: bool = True
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
@dataclass
|
|
92
|
+
class YaraModeConfig:
|
|
93
|
+
"""Complete YARA mode configuration."""
|
|
94
|
+
|
|
95
|
+
mode: YaraMode = YaraMode.BALANCED
|
|
96
|
+
description: str = ""
|
|
97
|
+
|
|
98
|
+
# Rule-specific configs
|
|
99
|
+
unicode_steg: UnicodeStegConfig = field(default_factory=UnicodeStegConfig)
|
|
100
|
+
credential_harvesting: CredentialHarvestingConfig = field(default_factory=CredentialHarvestingConfig)
|
|
101
|
+
tool_chaining: ToolChainingConfig = field(default_factory=ToolChainingConfig)
|
|
102
|
+
|
|
103
|
+
# Global settings
|
|
104
|
+
enabled_rules: set[str] = field(default_factory=set) # Empty = all enabled
|
|
105
|
+
disabled_rules: set[str] = field(default_factory=set)
|
|
106
|
+
|
|
107
|
+
@classmethod
|
|
108
|
+
def strict(cls) -> "YaraModeConfig":
|
|
109
|
+
"""
|
|
110
|
+
STRICT mode: Maximum security, accept higher FP rate.
|
|
111
|
+
|
|
112
|
+
Use when:
|
|
113
|
+
- Scanning untrusted/external skills
|
|
114
|
+
- Security audit requirements
|
|
115
|
+
- Compliance scanning
|
|
116
|
+
"""
|
|
117
|
+
return cls(
|
|
118
|
+
mode=YaraMode.STRICT,
|
|
119
|
+
description="Maximum security - flags more potential threats",
|
|
120
|
+
unicode_steg=UnicodeStegConfig(
|
|
121
|
+
zerowidth_threshold_with_decode=20, # Lower threshold
|
|
122
|
+
zerowidth_threshold_alone=100,
|
|
123
|
+
),
|
|
124
|
+
credential_harvesting=CredentialHarvestingConfig(
|
|
125
|
+
filter_placeholder_patterns=False, # Don't filter - flag for review
|
|
126
|
+
),
|
|
127
|
+
tool_chaining=ToolChainingConfig(
|
|
128
|
+
filter_api_documentation=False,
|
|
129
|
+
filter_generic_http_verbs=False,
|
|
130
|
+
),
|
|
131
|
+
)
|
|
132
|
+
|
|
133
|
+
@classmethod
|
|
134
|
+
def balanced(cls) -> "YaraModeConfig":
|
|
135
|
+
"""
|
|
136
|
+
BALANCED mode: Default - good tradeoff between FP and TP.
|
|
137
|
+
|
|
138
|
+
Use when:
|
|
139
|
+
- Regular skill scanning
|
|
140
|
+
- CI/CD pipeline integration
|
|
141
|
+
- Development workflow
|
|
142
|
+
"""
|
|
143
|
+
return cls(
|
|
144
|
+
mode=YaraMode.BALANCED,
|
|
145
|
+
description="Balanced detection - default mode",
|
|
146
|
+
unicode_steg=UnicodeStegConfig(
|
|
147
|
+
zerowidth_threshold_with_decode=50,
|
|
148
|
+
zerowidth_threshold_alone=200,
|
|
149
|
+
),
|
|
150
|
+
credential_harvesting=CredentialHarvestingConfig(
|
|
151
|
+
filter_placeholder_patterns=True,
|
|
152
|
+
),
|
|
153
|
+
tool_chaining=ToolChainingConfig(
|
|
154
|
+
filter_api_documentation=True,
|
|
155
|
+
filter_generic_http_verbs=True,
|
|
156
|
+
filter_email_field_mentions=True,
|
|
157
|
+
),
|
|
158
|
+
)
|
|
159
|
+
|
|
160
|
+
@classmethod
|
|
161
|
+
def permissive(cls) -> "YaraModeConfig":
|
|
162
|
+
"""
|
|
163
|
+
PERMISSIVE mode: Minimize false positives.
|
|
164
|
+
|
|
165
|
+
Use when:
|
|
166
|
+
- Scanning trusted/internal skills
|
|
167
|
+
- High FP rate is disrupting workflow
|
|
168
|
+
- Focus on critical threats only
|
|
169
|
+
"""
|
|
170
|
+
return cls(
|
|
171
|
+
mode=YaraMode.PERMISSIVE,
|
|
172
|
+
description="Minimal false positives - may miss some threats",
|
|
173
|
+
unicode_steg=UnicodeStegConfig(
|
|
174
|
+
zerowidth_threshold_with_decode=100, # Higher threshold
|
|
175
|
+
zerowidth_threshold_alone=500,
|
|
176
|
+
detect_line_separators=False, # Common in some content
|
|
177
|
+
),
|
|
178
|
+
credential_harvesting=CredentialHarvestingConfig(
|
|
179
|
+
filter_placeholder_patterns=True,
|
|
180
|
+
),
|
|
181
|
+
tool_chaining=ToolChainingConfig(
|
|
182
|
+
filter_api_documentation=True,
|
|
183
|
+
filter_generic_http_verbs=True,
|
|
184
|
+
filter_email_field_mentions=True,
|
|
185
|
+
),
|
|
186
|
+
# Disable noisier rules
|
|
187
|
+
disabled_rules={
|
|
188
|
+
"capability_inflation_generic",
|
|
189
|
+
"indirect_prompt_injection_generic",
|
|
190
|
+
},
|
|
191
|
+
)
|
|
192
|
+
|
|
193
|
+
@classmethod
|
|
194
|
+
def custom(
|
|
195
|
+
cls,
|
|
196
|
+
unicode_steg: UnicodeStegConfig | None = None,
|
|
197
|
+
credential_harvesting: CredentialHarvestingConfig | None = None,
|
|
198
|
+
tool_chaining: ToolChainingConfig | None = None,
|
|
199
|
+
enabled_rules: set[str] | None = None,
|
|
200
|
+
disabled_rules: set[str] | None = None,
|
|
201
|
+
) -> "YaraModeConfig":
|
|
202
|
+
"""
|
|
203
|
+
CUSTOM mode: User-defined configuration.
|
|
204
|
+
|
|
205
|
+
Args:
|
|
206
|
+
unicode_steg: Unicode steganography config
|
|
207
|
+
credential_harvesting: Credential harvesting config
|
|
208
|
+
tool_chaining: Tool chaining config
|
|
209
|
+
enabled_rules: Set of rule names to enable (empty = all)
|
|
210
|
+
disabled_rules: Set of rule names to disable
|
|
211
|
+
"""
|
|
212
|
+
return cls(
|
|
213
|
+
mode=YaraMode.CUSTOM,
|
|
214
|
+
description="Custom user-defined configuration",
|
|
215
|
+
unicode_steg=unicode_steg or UnicodeStegConfig(),
|
|
216
|
+
credential_harvesting=credential_harvesting or CredentialHarvestingConfig(),
|
|
217
|
+
tool_chaining=tool_chaining or ToolChainingConfig(),
|
|
218
|
+
enabled_rules=enabled_rules or set(),
|
|
219
|
+
disabled_rules=disabled_rules or set(),
|
|
220
|
+
)
|
|
221
|
+
|
|
222
|
+
@classmethod
|
|
223
|
+
def from_mode_name(cls, mode_name: str) -> "YaraModeConfig":
|
|
224
|
+
"""Create config from mode name string."""
|
|
225
|
+
mode_map = {
|
|
226
|
+
"strict": cls.strict,
|
|
227
|
+
"balanced": cls.balanced,
|
|
228
|
+
"permissive": cls.permissive,
|
|
229
|
+
}
|
|
230
|
+
if mode_name.lower() in mode_map:
|
|
231
|
+
return mode_map[mode_name.lower()]()
|
|
232
|
+
raise ValueError(f"Unknown mode: {mode_name}. Use: strict, balanced, permissive, or custom")
|
|
233
|
+
|
|
234
|
+
def is_rule_enabled(self, rule_name: str) -> bool:
|
|
235
|
+
"""Check if a specific rule is enabled in this mode."""
|
|
236
|
+
# If enabled_rules is specified, only those are allowed
|
|
237
|
+
if self.enabled_rules and rule_name not in self.enabled_rules:
|
|
238
|
+
return False
|
|
239
|
+
# Check if explicitly disabled
|
|
240
|
+
if rule_name in self.disabled_rules:
|
|
241
|
+
return False
|
|
242
|
+
return True
|
|
243
|
+
|
|
244
|
+
def to_dict(self) -> dict:
|
|
245
|
+
"""Convert config to dictionary for serialization."""
|
|
246
|
+
return {
|
|
247
|
+
"mode": self.mode.value,
|
|
248
|
+
"description": self.description,
|
|
249
|
+
"unicode_steg": {
|
|
250
|
+
"zerowidth_threshold_with_decode": self.unicode_steg.zerowidth_threshold_with_decode,
|
|
251
|
+
"zerowidth_threshold_alone": self.unicode_steg.zerowidth_threshold_alone,
|
|
252
|
+
"detect_rtl_override": self.unicode_steg.detect_rtl_override,
|
|
253
|
+
"detect_ltl_override": self.unicode_steg.detect_ltl_override,
|
|
254
|
+
"detect_line_separators": self.unicode_steg.detect_line_separators,
|
|
255
|
+
"detect_unicode_tags": self.unicode_steg.detect_unicode_tags,
|
|
256
|
+
"detect_variation_selectors": self.unicode_steg.detect_variation_selectors,
|
|
257
|
+
},
|
|
258
|
+
"credential_harvesting": {
|
|
259
|
+
"filter_placeholder_patterns": self.credential_harvesting.filter_placeholder_patterns,
|
|
260
|
+
"detect_ai_api_keys": self.credential_harvesting.detect_ai_api_keys,
|
|
261
|
+
"detect_aws_keys": self.credential_harvesting.detect_aws_keys,
|
|
262
|
+
"detect_ssh_keys": self.credential_harvesting.detect_ssh_keys,
|
|
263
|
+
"detect_env_exfiltration": self.credential_harvesting.detect_env_exfiltration,
|
|
264
|
+
},
|
|
265
|
+
"tool_chaining": {
|
|
266
|
+
"filter_api_documentation": self.tool_chaining.filter_api_documentation,
|
|
267
|
+
"filter_generic_http_verbs": self.tool_chaining.filter_generic_http_verbs,
|
|
268
|
+
"filter_email_field_mentions": self.tool_chaining.filter_email_field_mentions,
|
|
269
|
+
"detect_read_send": self.tool_chaining.detect_read_send,
|
|
270
|
+
"detect_collect_exfil": self.tool_chaining.detect_collect_exfil,
|
|
271
|
+
"detect_env_network": self.tool_chaining.detect_env_network,
|
|
272
|
+
},
|
|
273
|
+
"enabled_rules": list(self.enabled_rules),
|
|
274
|
+
"disabled_rules": list(self.disabled_rules),
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
|
|
278
|
+
# Default mode
|
|
279
|
+
DEFAULT_YARA_MODE = YaraModeConfig.balanced()
|
|
280
|
+
|
|
281
|
+
|
|
282
|
+
# Mode descriptions for CLI/API documentation
|
|
283
|
+
MODE_DESCRIPTIONS = {
|
|
284
|
+
"strict": """
|
|
285
|
+
STRICT Mode - Maximum Security
|
|
286
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
287
|
+
• Lower detection thresholds
|
|
288
|
+
• Minimal post-processing filters
|
|
289
|
+
• Flags more potential threats
|
|
290
|
+
• Higher false positive rate acceptable
|
|
291
|
+
|
|
292
|
+
Use for: Untrusted skills, security audits, compliance
|
|
293
|
+
""",
|
|
294
|
+
"balanced": """
|
|
295
|
+
BALANCED Mode - Default
|
|
296
|
+
━━━━━━━━━━━━━━━━━━━━━━━
|
|
297
|
+
• Moderate detection thresholds
|
|
298
|
+
• Context-aware post-processing
|
|
299
|
+
• Good tradeoff between FP and TP
|
|
300
|
+
• Suitable for most use cases
|
|
301
|
+
|
|
302
|
+
Use for: Regular scanning, CI/CD, development
|
|
303
|
+
""",
|
|
304
|
+
"permissive": """
|
|
305
|
+
PERMISSIVE Mode - Minimal False Positives
|
|
306
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
307
|
+
• Higher detection thresholds
|
|
308
|
+
• Aggressive filtering
|
|
309
|
+
• Focus on critical threats only
|
|
310
|
+
• May miss some edge-case threats
|
|
311
|
+
|
|
312
|
+
Use for: Trusted skills, high FP disruption
|
|
313
|
+
""",
|
|
314
|
+
}
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
# SPDX-License-Identifier: Apache-2.0
|
|
16
16
|
|
|
17
17
|
"""
|
|
18
|
-
Analyzer modules for detecting security vulnerabilities in
|
|
18
|
+
Analyzer modules for detecting security vulnerabilities in agent skills.
|
|
19
19
|
|
|
20
20
|
Structure mirrors MCP Scanner's analyzer organization.
|
|
21
21
|
"""
|
|
@@ -61,9 +61,9 @@ except (ImportError, ModuleNotFoundError):
|
|
|
61
61
|
pass
|
|
62
62
|
|
|
63
63
|
try:
|
|
64
|
-
from .
|
|
64
|
+
from .cross_skill_scanner import CrossSkillScanner # noqa: F401
|
|
65
65
|
|
|
66
|
-
__all__.append("
|
|
66
|
+
__all__.append("CrossSkillScanner")
|
|
67
67
|
except (ImportError, ModuleNotFoundError):
|
|
68
68
|
pass
|
|
69
69
|
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
# SPDX-License-Identifier: Apache-2.0
|
|
16
16
|
|
|
17
17
|
"""
|
|
18
|
-
AI Defense API analyzer for
|
|
18
|
+
AI Defense API analyzer for agent skills security scanning.
|
|
19
19
|
|
|
20
20
|
Integrates with Cisco AI Defense API (https://api.aidefense.cisco.com) for:
|
|
21
21
|
- Prompt injection detection
|
|
@@ -325,7 +325,7 @@ class AIDefenseAnalyzer(BaseAnalyzer):
|
|
|
325
325
|
}
|
|
326
326
|
]
|
|
327
327
|
metadata = {
|
|
328
|
-
"source": "
|
|
328
|
+
"source": "skill_scanner",
|
|
329
329
|
"skill_name": skill_name,
|
|
330
330
|
"file_path": file_path,
|
|
331
331
|
"content_type": content_type,
|
|
@@ -465,7 +465,7 @@ class AIDefenseAnalyzer(BaseAnalyzer):
|
|
|
465
465
|
{"role": "user", "content": f"# Code Analysis for {file_path}\n```{language}\n{content[:15000]}\n```"}
|
|
466
466
|
]
|
|
467
467
|
metadata = {
|
|
468
|
-
"source": "
|
|
468
|
+
"source": "skill_scanner",
|
|
469
469
|
"skill_name": skill_name,
|
|
470
470
|
"file_path": file_path,
|
|
471
471
|
"language": language,
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
#
|
|
15
15
|
# SPDX-License-Identifier: Apache-2.0
|
|
16
16
|
|
|
17
|
-
"""Behavioral Analysis Package for
|
|
17
|
+
"""Behavioral Analysis Package for Agent Skills.
|
|
18
18
|
|
|
19
19
|
This package provides enhanced behavioral analysis capabilities including:
|
|
20
20
|
- Alignment verification between skill description and code behavior
|
|
@@ -192,7 +192,7 @@ class AlignmentLLMClient:
|
|
|
192
192
|
{
|
|
193
193
|
"role": "system",
|
|
194
194
|
"content": (
|
|
195
|
-
"You are a security expert analyzing
|
|
195
|
+
"You are a security expert analyzing agent skills. "
|
|
196
196
|
"You receive complete dataflow analysis and code context. "
|
|
197
197
|
"Analyze if the skill description accurately describes what the code actually does. "
|
|
198
198
|
"Respond ONLY with valid JSON. Do not include any markdown formatting or code blocks."
|
{skillanalyzer → skill_scanner}/core/analyzers/behavioral/alignment/alignment_prompt_builder.py
RENAMED
|
@@ -378,9 +378,9 @@ Parameter Flow Tracking:
|
|
|
378
378
|
Returns:
|
|
379
379
|
Embedded prompt template
|
|
380
380
|
"""
|
|
381
|
-
return """#
|
|
381
|
+
return """# Agent Skill Alignment Analysis
|
|
382
382
|
|
|
383
|
-
You are a security expert analyzing
|
|
383
|
+
You are a security expert analyzing agent skills for alignment mismatches between their
|
|
384
384
|
described behavior and actual implementation.
|
|
385
385
|
|
|
386
386
|
## Your Task
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
# SPDX-License-Identifier: Apache-2.0
|
|
16
16
|
|
|
17
17
|
"""
|
|
18
|
-
Behavioral analyzer for
|
|
18
|
+
Behavioral analyzer for agent skills using static dataflow analysis.
|
|
19
19
|
|
|
20
20
|
Analyzes skill scripts using AST parsing, dataflow tracking, and description-behavior
|
|
21
21
|
alignment checking. Detects threats through code analysis without execution.
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
# SPDX-License-Identifier: Apache-2.0
|
|
16
16
|
|
|
17
17
|
"""
|
|
18
|
-
Cross-skill
|
|
18
|
+
Cross-skill scanner for detecting coordinated attacks across multiple skills.
|
|
19
19
|
|
|
20
20
|
This analyzer looks for patterns that suggest multiple skills are working together
|
|
21
21
|
to perform malicious activities, such as:
|
|
@@ -30,7 +30,7 @@ from ..models import Finding, Severity, Skill, ThreatCategory
|
|
|
30
30
|
from .base import BaseAnalyzer
|
|
31
31
|
|
|
32
32
|
|
|
33
|
-
class
|
|
33
|
+
class CrossSkillScanner(BaseAnalyzer):
|
|
34
34
|
"""
|
|
35
35
|
Analyzes multiple skills together to detect coordinated attack patterns.
|
|
36
36
|
|
|
@@ -40,13 +40,13 @@ class CrossSkillAnalyzer(BaseAnalyzer):
|
|
|
40
40
|
"""
|
|
41
41
|
|
|
42
42
|
def __init__(self):
|
|
43
|
-
"""Initialize cross-skill
|
|
44
|
-
super().__init__("
|
|
43
|
+
"""Initialize cross-skill scanner."""
|
|
44
|
+
super().__init__("cross_skill_scanner")
|
|
45
45
|
self._skills: list[Skill] = []
|
|
46
46
|
|
|
47
47
|
def analyze(self, skill: Skill) -> list[Finding]:
|
|
48
48
|
"""
|
|
49
|
-
Analyze a single skill (no-op for cross-skill
|
|
49
|
+
Analyze a single skill (no-op for cross-skill scanner).
|
|
50
50
|
|
|
51
51
|
This analyzer only produces findings when analyzing skill sets.
|
|
52
52
|
Call analyze_skill_set() instead.
|
|
@@ -273,16 +273,16 @@ class LLMAnalyzer(BaseAnalyzer):
|
|
|
273
273
|
messages = [
|
|
274
274
|
{
|
|
275
275
|
"role": "system",
|
|
276
|
-
"content": """You are a security expert analyzing
|
|
276
|
+
"content": """You are a security expert analyzing agent skills. Follow the analysis framework provided.
|
|
277
277
|
|
|
278
278
|
When selecting AITech codes for findings, use these mappings:
|
|
279
279
|
- AITech-1.1: Direct prompt injection in SKILL.md (jailbreak, instruction override)
|
|
280
|
-
- AITech-1.2: Indirect prompt injection (
|
|
281
|
-
- AITech-
|
|
280
|
+
- AITech-1.2: Indirect prompt injection - instruction manipulation (embedding malicious instructions in external sources)
|
|
281
|
+
- AITech-4.3: Protocol manipulation - capability inflation (skill discovery abuse, keyword baiting, over-broad claims)
|
|
282
282
|
- AITech-8.2: Data exfiltration/exposure (unauthorized access, credential theft, hardcoded secrets)
|
|
283
283
|
- AITech-9.1: Model/agentic manipulation (command injection, code injection, SQL injection, obfuscation)
|
|
284
284
|
- AITech-12.1: Tool exploitation (tool poisoning, shadowing, unauthorized use)
|
|
285
|
-
- AITech-13.
|
|
285
|
+
- AITech-13.1: Disruption of Availability (resource abuse, DoS, infinite loops) - AISubtech-13.1.1: Compute Exhaustion
|
|
286
286
|
- AITech-15.1: Harmful/misleading content (deceptive content, misinformation)
|
|
287
287
|
|
|
288
288
|
The structured output schema will enforce these exact codes.""",
|
|
@@ -47,7 +47,7 @@ class PromptBuilder:
|
|
|
47
47
|
self.protection_rules = protection_file.read_text(encoding="utf-8")
|
|
48
48
|
else:
|
|
49
49
|
print(f"Warning: Protection rules file not found at {protection_file}")
|
|
50
|
-
self.protection_rules = "You are a security analyst analyzing
|
|
50
|
+
self.protection_rules = "You are a security analyst analyzing agent skills."
|
|
51
51
|
|
|
52
52
|
if threat_file.exists():
|
|
53
53
|
self.threat_analysis_prompt = threat_file.read_text(encoding="utf-8")
|
|
@@ -57,7 +57,7 @@ class PromptBuilder:
|
|
|
57
57
|
|
|
58
58
|
except Exception as e:
|
|
59
59
|
print(f"Warning: Failed to load prompts: {e}")
|
|
60
|
-
self.protection_rules = "You are a security analyst analyzing
|
|
60
|
+
self.protection_rules = "You are a security analyst analyzing agent skills."
|
|
61
61
|
self.threat_analysis_prompt = "Analyze for security threats."
|
|
62
62
|
|
|
63
63
|
def build_threat_analysis_prompt(
|