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.
Files changed (100) hide show
  1. cisco_ai_skill_scanner-1.0.0.dist-info/METADATA +253 -0
  2. cisco_ai_skill_scanner-1.0.0.dist-info/RECORD +100 -0
  3. cisco_ai_skill_scanner-1.0.0.dist-info/WHEEL +4 -0
  4. cisco_ai_skill_scanner-1.0.0.dist-info/entry_points.txt +4 -0
  5. cisco_ai_skill_scanner-1.0.0.dist-info/licenses/LICENSE +17 -0
  6. skillanalyzer/__init__.py +45 -0
  7. skillanalyzer/_version.py +34 -0
  8. skillanalyzer/api/__init__.py +25 -0
  9. skillanalyzer/api/api.py +34 -0
  10. skillanalyzer/api/api_cli.py +78 -0
  11. skillanalyzer/api/api_server.py +634 -0
  12. skillanalyzer/api/router.py +527 -0
  13. skillanalyzer/cli/__init__.py +25 -0
  14. skillanalyzer/cli/cli.py +816 -0
  15. skillanalyzer/config/__init__.py +26 -0
  16. skillanalyzer/config/config.py +149 -0
  17. skillanalyzer/config/config_parser.py +122 -0
  18. skillanalyzer/config/constants.py +85 -0
  19. skillanalyzer/core/__init__.py +24 -0
  20. skillanalyzer/core/analyzers/__init__.py +75 -0
  21. skillanalyzer/core/analyzers/aidefense_analyzer.py +872 -0
  22. skillanalyzer/core/analyzers/base.py +53 -0
  23. skillanalyzer/core/analyzers/behavioral/__init__.py +30 -0
  24. skillanalyzer/core/analyzers/behavioral/alignment/__init__.py +45 -0
  25. skillanalyzer/core/analyzers/behavioral/alignment/alignment_llm_client.py +240 -0
  26. skillanalyzer/core/analyzers/behavioral/alignment/alignment_orchestrator.py +216 -0
  27. skillanalyzer/core/analyzers/behavioral/alignment/alignment_prompt_builder.py +422 -0
  28. skillanalyzer/core/analyzers/behavioral/alignment/alignment_response_validator.py +136 -0
  29. skillanalyzer/core/analyzers/behavioral/alignment/threat_vulnerability_classifier.py +198 -0
  30. skillanalyzer/core/analyzers/behavioral_analyzer.py +453 -0
  31. skillanalyzer/core/analyzers/cross_skill_analyzer.py +490 -0
  32. skillanalyzer/core/analyzers/llm_analyzer.py +440 -0
  33. skillanalyzer/core/analyzers/llm_prompt_builder.py +270 -0
  34. skillanalyzer/core/analyzers/llm_provider_config.py +215 -0
  35. skillanalyzer/core/analyzers/llm_request_handler.py +284 -0
  36. skillanalyzer/core/analyzers/llm_response_parser.py +81 -0
  37. skillanalyzer/core/analyzers/meta_analyzer.py +845 -0
  38. skillanalyzer/core/analyzers/static.py +1105 -0
  39. skillanalyzer/core/analyzers/trigger_analyzer.py +341 -0
  40. skillanalyzer/core/analyzers/virustotal_analyzer.py +463 -0
  41. skillanalyzer/core/exceptions.py +77 -0
  42. skillanalyzer/core/loader.py +377 -0
  43. skillanalyzer/core/models.py +300 -0
  44. skillanalyzer/core/reporters/__init__.py +26 -0
  45. skillanalyzer/core/reporters/json_reporter.py +65 -0
  46. skillanalyzer/core/reporters/markdown_reporter.py +209 -0
  47. skillanalyzer/core/reporters/sarif_reporter.py +246 -0
  48. skillanalyzer/core/reporters/table_reporter.py +195 -0
  49. skillanalyzer/core/rules/__init__.py +19 -0
  50. skillanalyzer/core/rules/patterns.py +165 -0
  51. skillanalyzer/core/rules/yara_scanner.py +157 -0
  52. skillanalyzer/core/scanner.py +437 -0
  53. skillanalyzer/core/static_analysis/__init__.py +27 -0
  54. skillanalyzer/core/static_analysis/cfg/__init__.py +21 -0
  55. skillanalyzer/core/static_analysis/cfg/builder.py +439 -0
  56. skillanalyzer/core/static_analysis/context_extractor.py +742 -0
  57. skillanalyzer/core/static_analysis/dataflow/__init__.py +25 -0
  58. skillanalyzer/core/static_analysis/dataflow/forward_analysis.py +715 -0
  59. skillanalyzer/core/static_analysis/interprocedural/__init__.py +21 -0
  60. skillanalyzer/core/static_analysis/interprocedural/call_graph_analyzer.py +406 -0
  61. skillanalyzer/core/static_analysis/interprocedural/cross_file_analyzer.py +190 -0
  62. skillanalyzer/core/static_analysis/parser/__init__.py +21 -0
  63. skillanalyzer/core/static_analysis/parser/python_parser.py +380 -0
  64. skillanalyzer/core/static_analysis/semantic/__init__.py +28 -0
  65. skillanalyzer/core/static_analysis/semantic/name_resolver.py +206 -0
  66. skillanalyzer/core/static_analysis/semantic/type_analyzer.py +200 -0
  67. skillanalyzer/core/static_analysis/taint/__init__.py +21 -0
  68. skillanalyzer/core/static_analysis/taint/tracker.py +252 -0
  69. skillanalyzer/core/static_analysis/types/__init__.py +36 -0
  70. skillanalyzer/data/__init__.py +30 -0
  71. skillanalyzer/data/prompts/boilerplate_protection_rule_prompt.md +26 -0
  72. skillanalyzer/data/prompts/code_alignment_threat_analysis_prompt.md +901 -0
  73. skillanalyzer/data/prompts/llm_response_schema.json +71 -0
  74. skillanalyzer/data/prompts/skill_meta_analysis_prompt.md +303 -0
  75. skillanalyzer/data/prompts/skill_threat_analysis_prompt.md +263 -0
  76. skillanalyzer/data/prompts/unified_response_schema.md +97 -0
  77. skillanalyzer/data/rules/signatures.yaml +440 -0
  78. skillanalyzer/data/yara_rules/autonomy_abuse.yara +66 -0
  79. skillanalyzer/data/yara_rules/code_execution.yara +61 -0
  80. skillanalyzer/data/yara_rules/coercive_injection.yara +115 -0
  81. skillanalyzer/data/yara_rules/command_injection.yara +54 -0
  82. skillanalyzer/data/yara_rules/credential_harvesting.yara +115 -0
  83. skillanalyzer/data/yara_rules/prompt_injection.yara +71 -0
  84. skillanalyzer/data/yara_rules/script_injection.yara +83 -0
  85. skillanalyzer/data/yara_rules/skill_discovery_abuse.yara +57 -0
  86. skillanalyzer/data/yara_rules/sql_injection.yara +73 -0
  87. skillanalyzer/data/yara_rules/system_manipulation.yara +65 -0
  88. skillanalyzer/data/yara_rules/tool_chaining_abuse.yara +60 -0
  89. skillanalyzer/data/yara_rules/transitive_trust_abuse.yara +73 -0
  90. skillanalyzer/data/yara_rules/unicode_steganography.yara +65 -0
  91. skillanalyzer/hooks/__init__.py +21 -0
  92. skillanalyzer/hooks/pre_commit.py +450 -0
  93. skillanalyzer/threats/__init__.py +25 -0
  94. skillanalyzer/threats/threats.py +480 -0
  95. skillanalyzer/utils/__init__.py +28 -0
  96. skillanalyzer/utils/command_utils.py +129 -0
  97. skillanalyzer/utils/di_container.py +154 -0
  98. skillanalyzer/utils/file_utils.py +86 -0
  99. skillanalyzer/utils/logging_config.py +96 -0
  100. skillanalyzer/utils/logging_utils.py +71 -0
@@ -0,0 +1,154 @@
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
+ Dependency Injection Container for Skill Analyzer.
19
+
20
+ This module provides a simple dependency injection container to improve
21
+ testability and decouple configuration from implementation.
22
+ """
23
+
24
+ from collections.abc import Callable
25
+ from typing import Any, TypeVar
26
+
27
+ from ..config.config import Config
28
+
29
+ T = TypeVar("T")
30
+
31
+
32
+ class DIContainer:
33
+ """Simple dependency injection container."""
34
+
35
+ def __init__(self):
36
+ """Initialize the container."""
37
+ self._services: dict[type, Any] = {}
38
+ self._singletons: dict[type, Any] = {}
39
+
40
+ def register(self, service_type: type[T], instance: T, singleton: bool = True) -> None:
41
+ """Register a service instance.
42
+
43
+ Args:
44
+ service_type: The type/interface to register.
45
+ instance: The instance to register.
46
+ singleton: Whether to treat as singleton (default: True).
47
+ """
48
+ if singleton:
49
+ self._singletons[service_type] = instance
50
+ else:
51
+ self._services[service_type] = instance
52
+
53
+ def register_factory(self, service_type: type[T], factory: Callable[[], T], singleton: bool = True) -> None:
54
+ """Register a factory function for creating instances.
55
+
56
+ Args:
57
+ service_type: The type/interface to register.
58
+ factory: Factory function that creates instances.
59
+ singleton: Whether to treat as singleton (default: True).
60
+ """
61
+ if singleton:
62
+ self._singletons[service_type] = factory()
63
+ else:
64
+ self._services[service_type] = factory
65
+
66
+ def get(self, service_type: type[T]) -> T | None:
67
+ """Get a service instance.
68
+
69
+ Args:
70
+ service_type: The type to retrieve.
71
+
72
+ Returns:
73
+ The service instance or None if not found.
74
+ """
75
+ if service_type in self._singletons:
76
+ return self._singletons[service_type]
77
+
78
+ if service_type in self._services:
79
+ service = self._services[service_type]
80
+ if callable(service):
81
+ return service()
82
+ return service
83
+
84
+ return None
85
+
86
+ def get_or_create(self, service_type: type[T], factory: Callable[[], T] | None = None) -> T:
87
+ """Get a service instance or create with factory if not found.
88
+
89
+ Args:
90
+ service_type: The type to retrieve.
91
+ factory: Optional factory function if service not registered.
92
+
93
+ Returns:
94
+ The service instance.
95
+
96
+ Raises:
97
+ ValueError: If service not found and no factory provided.
98
+ """
99
+ instance = self.get(service_type)
100
+ if instance is not None:
101
+ return instance
102
+
103
+ if factory is not None:
104
+ instance = factory()
105
+ self.register(service_type, instance)
106
+ return instance
107
+
108
+ raise ValueError(f"Service {service_type} not registered and no factory provided")
109
+
110
+ def clear(self) -> None:
111
+ """Clear all registered services."""
112
+ self._services.clear()
113
+ self._singletons.clear()
114
+
115
+
116
+ # Global container instance
117
+ _container = DIContainer()
118
+
119
+
120
+ def get_container() -> DIContainer:
121
+ """Get the global DI container instance.
122
+
123
+ Returns:
124
+ The global container instance.
125
+ """
126
+ return _container
127
+
128
+
129
+ def configure_default_services(config: Config | None = None) -> None:
130
+ """Configure default services in the container.
131
+
132
+ Args:
133
+ config: Optional config instance to register.
134
+ """
135
+ container = get_container()
136
+
137
+ if config:
138
+ container.register(Config, config)
139
+
140
+
141
+ def inject_config() -> Config:
142
+ """Inject Config dependency.
143
+
144
+ Returns:
145
+ Config instance from container.
146
+
147
+ Raises:
148
+ ValueError: If Config not registered in container.
149
+ """
150
+ container = get_container()
151
+ config = container.get(Config)
152
+ if config is None:
153
+ raise ValueError("Config not registered in DI container. Call configure_default_services() first.")
154
+ return config
@@ -0,0 +1,86 @@
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
+ File utility functions.
19
+ """
20
+
21
+ from pathlib import Path
22
+
23
+
24
+ def read_file_safe(file_path: Path, max_size_mb: int = 10) -> str | None:
25
+ """
26
+ Safely read a file with size limit.
27
+
28
+ Args:
29
+ file_path: Path to file
30
+ max_size_mb: Maximum file size in MB
31
+
32
+ Returns:
33
+ File content or None if unreadable
34
+ """
35
+ try:
36
+ size_bytes = file_path.stat().st_size
37
+ max_bytes = max_size_mb * 1024 * 1024
38
+
39
+ if size_bytes > max_bytes:
40
+ return None
41
+
42
+ with open(file_path, encoding="utf-8") as f:
43
+ return f.read()
44
+ except (OSError, UnicodeDecodeError):
45
+ return None
46
+
47
+
48
+ def get_file_type(file_path: Path) -> str:
49
+ """
50
+ Determine file type from extension.
51
+
52
+ Args:
53
+ file_path: Path to file
54
+
55
+ Returns:
56
+ File type string
57
+ """
58
+ suffix = file_path.suffix.lower()
59
+
60
+ type_mapping = {
61
+ ".py": "python",
62
+ ".sh": "bash",
63
+ ".bash": "bash",
64
+ ".md": "markdown",
65
+ ".markdown": "markdown",
66
+ ".exe": "binary",
67
+ ".so": "binary",
68
+ ".dylib": "binary",
69
+ ".dll": "binary",
70
+ ".bin": "binary",
71
+ }
72
+
73
+ return type_mapping.get(suffix, "other")
74
+
75
+
76
+ def is_binary_file(file_path: Path) -> bool:
77
+ """
78
+ Check if file is binary.
79
+
80
+ Args:
81
+ file_path: Path to file
82
+
83
+ Returns:
84
+ True if binary
85
+ """
86
+ return get_file_type(file_path) == "binary"
@@ -0,0 +1,96 @@
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
+ Centralized logging configuration for Skill Analyzer.
19
+
20
+ This module provides consistent logging setup across all components.
21
+ """
22
+
23
+ import logging
24
+ import sys
25
+
26
+
27
+ def setup_logger(name: str, level: str | None = None, format_string: str | None = None) -> logging.Logger:
28
+ """
29
+ Set up a logger with consistent configuration.
30
+
31
+ Args:
32
+ name: Logger name (typically __name__)
33
+ level: Logging level (DEBUG, INFO, WARNING, ERROR, CRITICAL)
34
+ format_string: Custom format string, uses default if None
35
+
36
+ Returns:
37
+ Configured logger instance
38
+ """
39
+ logger = logging.getLogger(name)
40
+
41
+ if logger.handlers:
42
+ return logger
43
+
44
+ skillanalyzer_root = logging.getLogger("skillanalyzer")
45
+ if skillanalyzer_root.level == logging.DEBUG and name.startswith("skillanalyzer"):
46
+ logger.setLevel(logging.DEBUG)
47
+ elif level:
48
+ logger.setLevel(getattr(logging, level.upper()))
49
+ else:
50
+ logger.setLevel(logging.INFO)
51
+
52
+ handler = logging.StreamHandler(sys.stdout)
53
+ handler.setLevel(logger.level)
54
+
55
+ default_format = "%(asctime)s - %(name)s - %(levelname)s - %(message)s"
56
+ formatter = logging.Formatter(format_string or default_format)
57
+ handler.setFormatter(formatter)
58
+
59
+ logger.addHandler(handler)
60
+ logger.propagate = False
61
+
62
+ return logger
63
+
64
+
65
+ def get_logger(name: str, level: str | None = None) -> logging.Logger:
66
+ """
67
+ Get a logger with standard configuration.
68
+
69
+ Args:
70
+ name: Logger name (typically __name__)
71
+ level: Optional logging level override
72
+
73
+ Returns:
74
+ Configured logger instance
75
+ """
76
+ return setup_logger(name, level)
77
+
78
+
79
+ def set_verbose_logging(verbose: bool = False) -> None:
80
+ """
81
+ Enable or disable verbose logging for all skillanalyzer loggers.
82
+
83
+ Args:
84
+ verbose: If True, set all existing skillanalyzer loggers to DEBUG level
85
+ """
86
+ target_level = logging.DEBUG if verbose else logging.INFO
87
+
88
+ root_logger = logging.getLogger("skillanalyzer")
89
+ root_logger.setLevel(target_level)
90
+
91
+ for name in list(logging.Logger.manager.loggerDict.keys()):
92
+ if name.startswith("skillanalyzer"):
93
+ logger = logging.getLogger(name)
94
+ logger.setLevel(target_level)
95
+ for handler in logger.handlers:
96
+ handler.setLevel(target_level)
@@ -0,0 +1,71 @@
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
+ Logging utility functions.
19
+ """
20
+
21
+ import logging
22
+ import sys
23
+
24
+
25
+ def setup_logger(name: str, level: int = logging.INFO, log_file: str | None = None) -> logging.Logger:
26
+ """
27
+ Set up a logger with console and optional file output.
28
+
29
+ Args:
30
+ name: Logger name
31
+ level: Logging level
32
+ log_file: Optional file path for logs
33
+
34
+ Returns:
35
+ Configured logger
36
+ """
37
+ logger = logging.getLogger(name)
38
+ logger.setLevel(level)
39
+
40
+ # Remove existing handlers
41
+ logger.handlers = []
42
+
43
+ # Console handler
44
+ console_handler = logging.StreamHandler(sys.stdout)
45
+ console_handler.setLevel(level)
46
+
47
+ formatter = logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s", datefmt="%Y-%m-%d %H:%M:%S")
48
+ console_handler.setFormatter(formatter)
49
+ logger.addHandler(console_handler)
50
+
51
+ # File handler if specified
52
+ if log_file:
53
+ file_handler = logging.FileHandler(log_file)
54
+ file_handler.setLevel(level)
55
+ file_handler.setFormatter(formatter)
56
+ logger.addHandler(file_handler)
57
+
58
+ return logger
59
+
60
+
61
+ def get_logger(name: str) -> logging.Logger:
62
+ """
63
+ Get or create a logger.
64
+
65
+ Args:
66
+ name: Logger name
67
+
68
+ Returns:
69
+ Logger instance
70
+ """
71
+ return logging.getLogger(name)