@rfxlamia/skillkit 1.1.0 → 1.2.0
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.
- package/agents/agents/creative-copywriter.md +212 -0
- package/agents/agents/dario-amodei.md +135 -0
- package/agents/agents/doc-simplifier.md +63 -0
- package/agents/agents/kotlin-pro.md +433 -0
- package/agents/agents/red-team.md +136 -0
- package/agents/agents/sam-altman.md +121 -0
- package/agents/agents/seo-manager.md +184 -0
- package/package.json +1 -1
- package/skills/skillkit-help/SKILL.md +81 -0
- package/skills/skillkit-help/knowledge/application/09-case-studies.md +257 -0
- package/skills/skillkit-help/knowledge/application/12-testing-and-validation.md +276 -0
- package/skills/skillkit-help/knowledge/foundation/01-why-skills-exist.md +246 -0
- package/skills/skillkit-help/knowledge/foundation/02-skills-vs-subagents-comparison.md +312 -0
- package/skills/skillkit-help/knowledge/foundation/03-skills-vs-subagents-decision-tree.md +346 -0
- package/skills/skillkit-help/knowledge/foundation/06-platform-constraints.md +237 -0
- package/skills/skillkit-help/knowledge/foundation/08-when-not-to-use-skills.md +270 -0
- package/skills/skillkit-help/template/SKILL.md +52 -0
- package/skills/skills/adversarial-review/SKILL.md +219 -0
- package/skills/skills/baby-education/SKILL.md +260 -0
- package/skills/skills/baby-education/references/advanced-techniques.md +323 -0
- package/skills/skills/baby-education/references/transformations.md +345 -0
- package/skills/skills/been-there-done-that/SKILL.md +455 -0
- package/skills/skills/been-there-done-that/references/analysis-patterns.md +162 -0
- package/skills/skills/been-there-done-that/references/git-commands.md +132 -0
- package/skills/skills/been-there-done-that/references/tree-insertion-logic.md +145 -0
- package/skills/skills/coolhunter/SKILL.md +270 -0
- package/skills/skills/coolhunter/assets/elicitation-methods.csv +51 -0
- package/skills/skills/coolhunter/knowledge/elicitation-methods.md +312 -0
- package/skills/skills/coolhunter/references/workflow-execution.md +238 -0
- package/skills/skills/coolhunter/workflow-plan-coolhunter.md +232 -0
- package/skills/skills/creative-copywriting/SKILL.md +324 -0
- package/skills/skills/creative-copywriting/databases/README.md +60 -0
- package/skills/skills/creative-copywriting/databases/carousel-structures.csv +16 -0
- package/skills/skills/creative-copywriting/databases/emotional-arcs.csv +11 -0
- package/skills/skills/creative-copywriting/databases/hook-formulas.csv +51 -0
- package/skills/skills/creative-copywriting/databases/power-words.csv +201 -0
- package/skills/skills/creative-copywriting/databases/psychological-triggers.csv +21 -0
- package/skills/skills/creative-copywriting/databases/read-more-patterns.csv +26 -0
- package/skills/skills/creative-copywriting/databases/swipe-triggers.csv +31 -0
- package/skills/skills/creative-copywriting/references/carousel-psychology.md +223 -0
- package/skills/skills/creative-copywriting/references/hook-anatomy.md +169 -0
- package/skills/skills/creative-copywriting/references/power-word-science.md +134 -0
- package/skills/skills/creative-copywriting/references/storytelling-frameworks.md +157 -0
- package/skills/skills/diverse-content-gen/SKILL.md +201 -0
- package/skills/skills/diverse-content-gen/references/advanced-techniques.md +320 -0
- package/skills/skills/diverse-content-gen/references/research-findings.md +379 -0
- package/skills/skills/diverse-content-gen/references/task-workflows.md +241 -0
- package/skills/skills/diverse-content-gen/references/tool-integration.md +419 -0
- package/skills/skills/diverse-content-gen/references/troubleshooting.md +426 -0
- package/skills/skills/diverse-content-gen/references/vs-core-technique.md +240 -0
- package/skills/skills/framework-critical-thinking/SKILL.md +220 -0
- package/skills/skills/framework-critical-thinking/references/bias_detector.md +375 -0
- package/skills/skills/framework-critical-thinking/references/fallback_handler.md +239 -0
- package/skills/skills/framework-critical-thinking/references/memory_curator.md +161 -0
- package/skills/skills/framework-critical-thinking/references/metacognitive_monitor.md +297 -0
- package/skills/skills/framework-critical-thinking/references/producer_critic_orchestrator.md +333 -0
- package/skills/skills/framework-critical-thinking/references/reasoning_router.md +235 -0
- package/skills/skills/framework-critical-thinking/references/reasoning_validator.md +97 -0
- package/skills/skills/framework-critical-thinking/references/reflection_trigger.md +78 -0
- package/skills/skills/framework-critical-thinking/references/self_verification.md +388 -0
- package/skills/skills/framework-critical-thinking/references/uncertainty_quantifier.md +207 -0
- package/skills/skills/framework-initiative/SKILL.md +231 -0
- package/skills/skills/framework-initiative/references/examples.md +150 -0
- package/skills/skills/framework-initiative/references/impact-analysis.md +157 -0
- package/skills/skills/framework-initiative/references/intent-patterns.md +145 -0
- package/skills/skills/framework-initiative/references/star-framework.md +165 -0
- package/skills/skills/humanize-docs/SKILL.md +203 -0
- package/skills/skills/humanize-docs/references/advanced-techniques.md +13 -0
- package/skills/skills/humanize-docs/references/core-transformations.md +368 -0
- package/skills/skills/humanize-docs/references/detection-patterns.md +400 -0
- package/skills/skills/humanize-docs/references/examples-gallery.md +374 -0
- package/skills/skills/imagine/SKILL.md +190 -0
- package/skills/skills/imagine/references/artstyle-corporate-memphis.md +625 -0
- package/skills/skills/imagine/references/artstyle-crewdson-hyperrealism.md +295 -0
- package/skills/skills/imagine/references/artstyle-iphone-social-media.md +426 -0
- package/skills/skills/imagine/references/artstyle-sciencesaru.md +276 -0
- package/skills/skills/pre-deploy-checklist/README.md +26 -0
- package/skills/skills/pre-deploy-checklist/SKILL.md +153 -0
- package/skills/skills/pre-deploy-checklist/references/checklist-categories.md +174 -0
- package/skills/skills/pre-deploy-checklist/references/domain-prompts.md +216 -0
- package/skills/skills/prompt-engineering/SKILL.md +209 -0
- package/skills/skills/prompt-engineering/references/advanced-combinations.md +444 -0
- package/skills/skills/prompt-engineering/references/chain-of-thought.md +140 -0
- package/skills/skills/prompt-engineering/references/decision_matrix.md +220 -0
- package/skills/skills/prompt-engineering/references/few-shot.md +346 -0
- package/skills/skills/prompt-engineering/references/json-format.md +270 -0
- package/skills/skills/prompt-engineering/references/natural-language.md +420 -0
- package/skills/skills/prompt-engineering/references/pitfalls.md +365 -0
- package/skills/skills/prompt-engineering/references/prompt-chaining.md +498 -0
- package/skills/skills/prompt-engineering/references/react.md +108 -0
- package/skills/skills/prompt-engineering/references/self-consistency.md +322 -0
- package/skills/skills/prompt-engineering/references/tree-of-thoughts.md +386 -0
- package/skills/skills/prompt-engineering/references/xml-format.md +220 -0
- package/skills/skills/prompt-engineering/references/yaml-format.md +488 -0
- package/skills/skills/prompt-engineering/references/zero-shot.md +74 -0
- package/skills/skills/quick-spec/SKILL.md +280 -0
- package/skills/skills/quick-spec/assets/tech-spec-template.md +74 -0
- package/skills/skills/quick-spec/references/step-01-understand.md +189 -0
- package/skills/skills/quick-spec/references/step-02-investigate.md +144 -0
- package/skills/skills/quick-spec/references/step-03-generate.md +128 -0
- package/skills/skills/quick-spec/references/step-04-review.md +173 -0
- package/skills/skills/quick-spec/tests/__pycache__/test_skill.cpython-314-pytest-9.0.2.pyc +0 -0
- package/skills/skills/quick-spec/tests/test_scenarios.md +83 -0
- package/skills/skills/quick-spec/tests/test_skill.py +136 -0
- package/skills/skills/readme-expert/SKILL.md +538 -0
- package/skills/skills/readme-expert/knowledge/INDEX.md +192 -0
- package/skills/skills/readme-expert/knowledge/application/quality-standards.md +470 -0
- package/skills/skills/readme-expert/knowledge/application/script-executor.md +604 -0
- package/skills/skills/readme-expert/knowledge/application/template-library.md +822 -0
- package/skills/skills/readme-expert/knowledge/foundation/codebase-scanner.md +361 -0
- package/skills/skills/readme-expert/knowledge/foundation/validation-checklist.md +481 -0
- package/skills/skills/red-teaming/SKILL.md +321 -0
- package/skills/skills/red-teaming/references/ai-llm-redteam.md +517 -0
- package/skills/skills/red-teaming/references/attack-techniques.md +410 -0
- package/skills/skills/red-teaming/references/cybersecurity-redteam.md +383 -0
- package/skills/skills/red-teaming/references/tools-frameworks.md +446 -0
- package/skills/skills/releasing/.skillkit-mode +1 -0
- package/skills/skills/releasing/SKILL.md +225 -0
- package/skills/skills/releasing/references/version-detection.md +108 -0
- package/skills/skills/screenwriter/SKILL.md +273 -0
- package/skills/skills/screenwriter/references/advanced-techniques.md +216 -0
- package/skills/skills/screenwriter/references/pipeline-integration.md +266 -0
- package/skills/skills/skillkit/.claude/settings.local.json +7 -0
- package/skills/skills/skillkit/.claude-plugin/plugin.json +27 -0
- package/skills/skills/skillkit/CHANGELOG.md +484 -0
- package/skills/skills/skillkit/SKILL.md +511 -0
- package/skills/skills/skillkit/commands/skillkit.md +6 -0
- package/skills/skills/skillkit/commands/validate-plan.md +6 -0
- package/skills/skills/skillkit/commands/verify.md +6 -0
- package/skills/skills/skillkit/knowledge/INDEX.md +352 -0
- package/skills/skills/skillkit/knowledge/application/09-case-studies.md +257 -0
- package/skills/skills/skillkit/knowledge/application/10-technical-architecture.md +324 -0
- package/skills/skills/skillkit/knowledge/application/11-adoption-strategy.md +267 -0
- package/skills/skills/skillkit/knowledge/application/12-testing-and-validation.md +276 -0
- package/skills/skills/skillkit/knowledge/application/13-competitive-landscape.md +198 -0
- package/skills/skills/skillkit/knowledge/foundation/01-why-skills-exist.md +246 -0
- package/skills/skills/skillkit/knowledge/foundation/02-skills-vs-subagents-comparison.md +312 -0
- package/skills/skills/skillkit/knowledge/foundation/03-skills-vs-subagents-decision-tree.md +346 -0
- package/skills/skills/skillkit/knowledge/foundation/04-hybrid-patterns.md +308 -0
- package/skills/skills/skillkit/knowledge/foundation/05-token-economics.md +275 -0
- package/skills/skills/skillkit/knowledge/foundation/06-platform-constraints.md +237 -0
- package/skills/skills/skillkit/knowledge/foundation/07-security-concerns.md +322 -0
- package/skills/skills/skillkit/knowledge/foundation/08-when-not-to-use-skills.md +270 -0
- package/skills/skills/skillkit/knowledge/plugin-guide.md +614 -0
- package/skills/skills/skillkit/knowledge/tools/14-validation-tools-guide.md +150 -0
- package/skills/skills/skillkit/knowledge/tools/15-cost-tools-guide.md +157 -0
- package/skills/skills/skillkit/knowledge/tools/16-security-tools-guide.md +122 -0
- package/skills/skills/skillkit/knowledge/tools/17-pattern-tools-guide.md +161 -0
- package/skills/skills/skillkit/knowledge/tools/18-decision-helper-guide.md +243 -0
- package/skills/skills/skillkit/knowledge/tools/19-test-generator-guide.md +275 -0
- package/skills/skills/skillkit/knowledge/tools/20-split-skill-guide.md +149 -0
- package/skills/skills/skillkit/knowledge/tools/21-quality-scorer-guide.md +226 -0
- package/skills/skills/skillkit/knowledge/tools/22-migration-helper-guide.md +356 -0
- package/skills/skills/skillkit/knowledge/tools/23-subagent-creation-guide.md +448 -0
- package/skills/skills/skillkit/knowledge/tools/24-behavioral-testing-guide.md +122 -0
- package/skills/skills/skillkit/references/proposal-generation.md +982 -0
- package/skills/skills/skillkit/references/rationalization-catalog.md +75 -0
- package/skills/skills/skillkit/references/research-methodology.md +661 -0
- package/skills/skills/skillkit/references/section-2-full-creation-workflow.md +452 -0
- package/skills/skills/skillkit/references/section-3-validation-workflow-existing-skill.md +63 -0
- package/skills/skills/skillkit/references/section-4-decision-workflow-skills-vs-subagents.md +64 -0
- package/skills/skills/skillkit/references/section-5-migration-workflow-doc-to-skill.md +58 -0
- package/skills/skills/skillkit/references/section-6-subagent-creation-workflow.md +499 -0
- package/skills/skills/skillkit/references/section-7-knowledge-reference-map.md +72 -0
- package/skills/skills/skillkit/scripts/__pycache__/decision_helper.cpython-314.pyc +0 -0
- package/skills/skills/skillkit/scripts/__pycache__/quick_validate.cpython-312.pyc +0 -0
- package/skills/skills/skillkit/scripts/__pycache__/quick_validate.cpython-314.pyc +0 -0
- package/skills/skills/skillkit/scripts/__pycache__/test_generator.cpython-314-pytest-9.0.2.pyc +0 -0
- package/skills/skills/skillkit/scripts/decision_helper.py +799 -0
- package/skills/skills/skillkit/scripts/init_skill.py +400 -0
- package/skills/skills/skillkit/scripts/init_subagent.py +231 -0
- package/skills/skills/skillkit/scripts/migration_helper.py +669 -0
- package/skills/skills/skillkit/scripts/package_skill.py +211 -0
- package/skills/skills/skillkit/scripts/pattern_detector.py +381 -0
- package/skills/skills/skillkit/scripts/pattern_detector_new.py +382 -0
- package/skills/skills/skillkit/scripts/pressure_tester.py +157 -0
- package/skills/skills/skillkit/scripts/quality_scorer.py +999 -0
- package/skills/skills/skillkit/scripts/quick_validate.py +100 -0
- package/skills/skills/skillkit/scripts/security_scanner.py +474 -0
- package/skills/skills/skillkit/scripts/split_skill.py +540 -0
- package/skills/skills/skillkit/scripts/test_generator.py +695 -0
- package/skills/skills/skillkit/scripts/token_estimator.py +493 -0
- package/skills/skills/skillkit/scripts/utils/__init__.py +49 -0
- package/skills/skills/skillkit/scripts/utils/__pycache__/__init__.cpython-312.pyc +0 -0
- package/skills/skills/skillkit/scripts/utils/__pycache__/__init__.cpython-314.pyc +0 -0
- package/skills/skills/skillkit/scripts/utils/__pycache__/budget_tracker.cpython-312.pyc +0 -0
- package/skills/skills/skillkit/scripts/utils/__pycache__/budget_tracker.cpython-314.pyc +0 -0
- package/skills/skills/skillkit/scripts/utils/__pycache__/output_formatter.cpython-312.pyc +0 -0
- package/skills/skills/skillkit/scripts/utils/__pycache__/output_formatter.cpython-314.pyc +0 -0
- package/skills/skills/skillkit/scripts/utils/__pycache__/reference_validator.cpython-312.pyc +0 -0
- package/skills/skills/skillkit/scripts/utils/__pycache__/reference_validator.cpython-314.pyc +0 -0
- package/skills/skills/skillkit/scripts/utils/budget_tracker.py +388 -0
- package/skills/skills/skillkit/scripts/utils/output_formatter.py +263 -0
- package/skills/skills/skillkit/scripts/utils/reference_validator.py +401 -0
- package/skills/skills/skillkit/scripts/validate_skill.py +594 -0
- package/skills/skills/skillkit/tests/test_behavioral.py +39 -0
- package/skills/skills/skillkit/tests/test_scenarios.md +83 -0
- package/skills/skills/skillkit/tests/test_skill.py +136 -0
- package/skills/skills/skillkit-help/SKILL.md +81 -0
- package/skills/skills/skillkit-help/knowledge/application/09-case-studies.md +257 -0
- package/skills/skills/skillkit-help/knowledge/application/12-testing-and-validation.md +276 -0
- package/skills/skills/skillkit-help/knowledge/foundation/01-why-skills-exist.md +246 -0
- package/skills/skills/skillkit-help/knowledge/foundation/02-skills-vs-subagents-comparison.md +312 -0
- package/skills/skills/skillkit-help/knowledge/foundation/03-skills-vs-subagents-decision-tree.md +346 -0
- package/skills/skills/skillkit-help/knowledge/foundation/06-platform-constraints.md +237 -0
- package/skills/skills/skillkit-help/knowledge/foundation/08-when-not-to-use-skills.md +270 -0
- package/skills/skills/skillkit-help/template/SKILL.md +52 -0
- package/skills/skills/social-media-seo/SKILL.md +278 -0
- package/skills/skills/social-media-seo/databases/caption-styles.csv +31 -0
- package/skills/skills/social-media-seo/databases/engagement-tactics.csv +16 -0
- package/skills/skills/social-media-seo/databases/hashtag-strategies.csv +21 -0
- package/skills/skills/social-media-seo/databases/hook-formulas.csv +26 -0
- package/skills/skills/social-media-seo/databases/keyword-clusters.csv +11 -0
- package/skills/skills/social-media-seo/databases/thread-structures.csv +26 -0
- package/skills/skills/social-media-seo/databases/viral-patterns.csv +21 -0
- package/skills/skills/social-media-seo/references/analytics-guide.md +321 -0
- package/skills/skills/social-media-seo/references/instagram-seo.md +235 -0
- package/skills/skills/social-media-seo/references/threads-seo.md +305 -0
- package/skills/skills/social-media-seo/references/x-twitter-seo.md +337 -0
- package/skills/skills/social-media-seo/scripts/query_database.py +191 -0
- package/skills/skills/storyteller/SKILL.md +241 -0
- package/skills/skills/storyteller/references/transformation-methodology.md +293 -0
- package/skills/skills/storyteller/references/visual-vocabulary.md +177 -0
- package/skills/skills/thread-pro/SKILL.md +162 -0
- package/skills/skills/thread-pro/anti-ai-patterns.md +120 -0
- package/skills/skills/thread-pro/hook-formulas.md +138 -0
- package/skills/skills/thread-pro/references/anti-ai-patterns.md +120 -0
- package/skills/skills/thread-pro/references/hook-formulas.md +138 -0
- package/skills/skills/thread-pro/references/thread-structures.md +240 -0
- package/skills/skills/thread-pro/references/voice-injection.md +130 -0
- package/skills/skills/thread-pro/thread-structures.md +240 -0
- package/skills/skills/thread-pro/voice-injection.md +130 -0
- package/skills/skills/tinkering/SKILL.md +251 -0
- package/skills/skills/tinkering/references/graduation-checklist.md +100 -0
- package/skills/skills/validate-plan/.skillkit-mode +1 -0
- package/skills/skills/validate-plan/SKILL.md +406 -0
- package/skills/skills/validate-plan/references/dry-principles.md +251 -0
- package/skills/skills/validate-plan/references/gap-analysis-guide.md +320 -0
- package/skills/skills/validate-plan/references/tdd-patterns.md +413 -0
- package/skills/skills/validate-plan/references/yagni-checklist.md +330 -0
- package/skills/skills/verify-before-ship/.skillkit-mode +1 -0
- package/skills/skills/verify-before-ship/SKILL.md +116 -0
- package/skills/skills/verify-before-ship/references/anti-rationalization.md +212 -0
- package/skills/skills/verify-before-ship/references/verification-gates.md +305 -0
- package/skills-manifest.json +8 -2
- package/src/picker.js +11 -5
- package/src/picker.test.js +36 -1
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Quick validation script for skills - minimal version
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import sys
|
|
7
|
+
import os
|
|
8
|
+
import re
|
|
9
|
+
import yaml
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
|
|
12
|
+
def validate_skill(skill_path):
|
|
13
|
+
"""Basic validation of a skill"""
|
|
14
|
+
skill_path = Path(skill_path)
|
|
15
|
+
|
|
16
|
+
# Check SKILL.md exists
|
|
17
|
+
skill_md = skill_path / 'SKILL.md'
|
|
18
|
+
if not skill_md.exists():
|
|
19
|
+
return False, "SKILL.md not found"
|
|
20
|
+
|
|
21
|
+
# Read and validate frontmatter
|
|
22
|
+
content = skill_md.read_text()
|
|
23
|
+
if not content.startswith('---'):
|
|
24
|
+
return False, "No YAML frontmatter found"
|
|
25
|
+
|
|
26
|
+
# Extract frontmatter
|
|
27
|
+
match = re.match(r'^---\n(.*?)\n---', content, re.DOTALL)
|
|
28
|
+
if not match:
|
|
29
|
+
return False, "Invalid frontmatter format"
|
|
30
|
+
|
|
31
|
+
frontmatter_text = match.group(1)
|
|
32
|
+
|
|
33
|
+
# Parse YAML frontmatter
|
|
34
|
+
try:
|
|
35
|
+
frontmatter = yaml.safe_load(frontmatter_text)
|
|
36
|
+
if not isinstance(frontmatter, dict):
|
|
37
|
+
return False, "Frontmatter must be a YAML dictionary"
|
|
38
|
+
except yaml.YAMLError as e:
|
|
39
|
+
return False, f"Invalid YAML in frontmatter: {e}"
|
|
40
|
+
|
|
41
|
+
# Define allowed properties
|
|
42
|
+
ALLOWED_PROPERTIES = {'name', 'description', 'license', 'allowed-tools', 'metadata'}
|
|
43
|
+
|
|
44
|
+
# Check for unexpected properties (excluding nested keys under metadata)
|
|
45
|
+
unexpected_keys = set(frontmatter.keys()) - ALLOWED_PROPERTIES
|
|
46
|
+
if unexpected_keys:
|
|
47
|
+
return False, (
|
|
48
|
+
f"Unexpected key(s) in SKILL.md frontmatter: {', '.join(sorted(unexpected_keys))}. "
|
|
49
|
+
f"Allowed properties are: {', '.join(sorted(ALLOWED_PROPERTIES))}"
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
# Check required fields
|
|
53
|
+
if 'name' not in frontmatter:
|
|
54
|
+
return False, "Missing 'name' in frontmatter"
|
|
55
|
+
if 'description' not in frontmatter:
|
|
56
|
+
return False, "Missing 'description' in frontmatter"
|
|
57
|
+
|
|
58
|
+
# Extract name for validation
|
|
59
|
+
name = frontmatter.get('name', '')
|
|
60
|
+
if not isinstance(name, str):
|
|
61
|
+
return False, f"Name must be a string, got {type(name).__name__}"
|
|
62
|
+
name = name.strip()
|
|
63
|
+
if name:
|
|
64
|
+
# Check naming convention (hyphen-case: lowercase with hyphens)
|
|
65
|
+
if not re.match(r'^[a-z0-9-]+$', name):
|
|
66
|
+
return False, f"Name '{name}' should be hyphen-case (lowercase letters, digits, and hyphens only)"
|
|
67
|
+
if name.startswith('-') or name.endswith('-') or '--' in name:
|
|
68
|
+
return False, f"Name '{name}' cannot start/end with hyphen or contain consecutive hyphens"
|
|
69
|
+
# Check name length (max 64 characters per spec)
|
|
70
|
+
if len(name) > 64:
|
|
71
|
+
return False, f"Name is too long ({len(name)} characters). Maximum is 64 characters."
|
|
72
|
+
|
|
73
|
+
# Extract and validate description
|
|
74
|
+
description = frontmatter.get('description', '')
|
|
75
|
+
if not isinstance(description, str):
|
|
76
|
+
return False, f"Description must be a string, got {type(description).__name__}"
|
|
77
|
+
description = description.strip()
|
|
78
|
+
if description:
|
|
79
|
+
# Check for angle brackets
|
|
80
|
+
if '<' in description or '>' in description:
|
|
81
|
+
return False, "Description cannot contain angle brackets (< or >)"
|
|
82
|
+
# Check description length (max 1024 characters per spec)
|
|
83
|
+
if len(description) > 1024:
|
|
84
|
+
return False, f"Description is too long ({len(description)} characters). Maximum is 1024 characters."
|
|
85
|
+
|
|
86
|
+
return True, "Skill is valid!"
|
|
87
|
+
|
|
88
|
+
if __name__ == "__main__":
|
|
89
|
+
import argparse
|
|
90
|
+
|
|
91
|
+
parser = argparse.ArgumentParser(
|
|
92
|
+
description='Quick validation script for skills - minimal version'
|
|
93
|
+
)
|
|
94
|
+
parser.add_argument('skill_path', help='Path to skill directory')
|
|
95
|
+
|
|
96
|
+
args = parser.parse_args()
|
|
97
|
+
|
|
98
|
+
valid, message = validate_skill(args.skill_path)
|
|
99
|
+
print(message)
|
|
100
|
+
sys.exit(0 if valid else 1)
|
|
@@ -0,0 +1,474 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Security vulnerability scanner for Claude skills.
|
|
4
|
+
Detects common security anti-patterns and provides remediation guidance.
|
|
5
|
+
|
|
6
|
+
Usage:
|
|
7
|
+
python security_scanner.py <skill_path> [--severity LEVEL] [--format FORMAT]
|
|
8
|
+
|
|
9
|
+
References:
|
|
10
|
+
- File 07: Security concerns and best practices
|
|
11
|
+
- File 16: Vulnerability patterns and prevention
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
import re
|
|
15
|
+
import sys
|
|
16
|
+
from pathlib import Path
|
|
17
|
+
from typing import Dict, List
|
|
18
|
+
from dataclasses import dataclass
|
|
19
|
+
from enum import Enum
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class Severity(Enum):
|
|
23
|
+
"""Security finding severity levels."""
|
|
24
|
+
CRITICAL = 0
|
|
25
|
+
HIGH = 1
|
|
26
|
+
MEDIUM = 2
|
|
27
|
+
LOW = 3
|
|
28
|
+
INFO = 4
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
@dataclass
|
|
32
|
+
class Finding:
|
|
33
|
+
"""Single security finding."""
|
|
34
|
+
severity: Severity
|
|
35
|
+
finding_type: str
|
|
36
|
+
file: str
|
|
37
|
+
line: int
|
|
38
|
+
description: str
|
|
39
|
+
evidence: str
|
|
40
|
+
remediation: str
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class SecurityScanner:
|
|
44
|
+
"""Automated security vulnerability detection for skills."""
|
|
45
|
+
|
|
46
|
+
def __init__(self, skill_path: str):
|
|
47
|
+
"""Initialize scanner with skill directory path."""
|
|
48
|
+
self.skill_path = Path(skill_path)
|
|
49
|
+
self.findings: List[Finding] = []
|
|
50
|
+
|
|
51
|
+
if not self.skill_path.exists():
|
|
52
|
+
raise FileNotFoundError(f"Skill path not found: {skill_path}")
|
|
53
|
+
|
|
54
|
+
# ========== SECRET DETECTION ==========
|
|
55
|
+
|
|
56
|
+
def scan_hardcoded_secrets(self) -> List[Finding]:
|
|
57
|
+
"""
|
|
58
|
+
Scan for hardcoded secrets in all files.
|
|
59
|
+
Reference: File 07 (credential management)
|
|
60
|
+
"""
|
|
61
|
+
findings = []
|
|
62
|
+
|
|
63
|
+
secret_patterns = [
|
|
64
|
+
(r'api[_-]?key\s*=\s*["\'][\w\-]+["\']', 'API key'),
|
|
65
|
+
(r'password\s*=\s*["\'][^"\']+["\']', 'Password'),
|
|
66
|
+
(r'token\s*=\s*["\'][\w\-]+["\']', 'Token'),
|
|
67
|
+
(r'secret\s*=\s*["\'][\w\-]+["\']', 'Secret'),
|
|
68
|
+
(r'Authorization:\s*Bearer\s+[\w\-\.]+', 'Bearer token'),
|
|
69
|
+
(r'sk-[a-zA-Z0-9]{32,}', 'API key pattern'),
|
|
70
|
+
]
|
|
71
|
+
|
|
72
|
+
for file_path in self._get_scannable_files():
|
|
73
|
+
content = file_path.read_text(encoding='utf-8', errors='ignore')
|
|
74
|
+
|
|
75
|
+
for pattern, secret_type in secret_patterns:
|
|
76
|
+
for match in re.finditer(pattern, content, re.IGNORECASE):
|
|
77
|
+
line_num = content[:match.start()].count('\n') + 1
|
|
78
|
+
findings.append(Finding(
|
|
79
|
+
severity=Severity.CRITICAL,
|
|
80
|
+
finding_type='Hardcoded Secret',
|
|
81
|
+
file=str(file_path.relative_to(self.skill_path)),
|
|
82
|
+
line=line_num,
|
|
83
|
+
description=f'{secret_type} detected in code',
|
|
84
|
+
evidence=match.group(0)[:50] + '...',
|
|
85
|
+
remediation='Use environment variables or secret management (File 07)'
|
|
86
|
+
))
|
|
87
|
+
|
|
88
|
+
return findings
|
|
89
|
+
|
|
90
|
+
# ========== COMMAND INJECTION ==========
|
|
91
|
+
|
|
92
|
+
def scan_command_injection(self) -> List[Finding]:
|
|
93
|
+
"""
|
|
94
|
+
Scan for command injection vulnerabilities.
|
|
95
|
+
Reference: File 07 (injection risks)
|
|
96
|
+
"""
|
|
97
|
+
findings = []
|
|
98
|
+
|
|
99
|
+
dangerous_patterns = [
|
|
100
|
+
(r'subprocess\.\w+\([^)]*shell\s*=\s*True', 'shell=True', Severity.CRITICAL),
|
|
101
|
+
(r'os\.system\s*\(', 'os.system()', Severity.CRITICAL),
|
|
102
|
+
(r'\beval\s*\(', 'eval()', Severity.CRITICAL),
|
|
103
|
+
(r'\bexec\s*\(', 'exec()', Severity.CRITICAL),
|
|
104
|
+
]
|
|
105
|
+
|
|
106
|
+
for file_path in self._get_python_files():
|
|
107
|
+
content = file_path.read_text(encoding='utf-8', errors='ignore')
|
|
108
|
+
|
|
109
|
+
for pattern, name, severity in dangerous_patterns:
|
|
110
|
+
for match in re.finditer(pattern, content):
|
|
111
|
+
line_num = content[:match.start()].count('\n') + 1
|
|
112
|
+
findings.append(Finding(
|
|
113
|
+
severity=severity,
|
|
114
|
+
finding_type='Command Injection Risk',
|
|
115
|
+
file=str(file_path.relative_to(self.skill_path)),
|
|
116
|
+
line=line_num,
|
|
117
|
+
description=f'Dangerous function: {name}',
|
|
118
|
+
evidence=match.group(0),
|
|
119
|
+
remediation='Use parameterized commands, avoid shell=True/eval/exec'
|
|
120
|
+
))
|
|
121
|
+
|
|
122
|
+
return findings
|
|
123
|
+
|
|
124
|
+
# ========== SQL INJECTION ==========
|
|
125
|
+
|
|
126
|
+
def scan_sql_injection(self) -> List[Finding]:
|
|
127
|
+
"""
|
|
128
|
+
Scan for SQL injection patterns.
|
|
129
|
+
Reference: File 16 (SQL injection prevention)
|
|
130
|
+
"""
|
|
131
|
+
findings = []
|
|
132
|
+
|
|
133
|
+
sql_patterns = [
|
|
134
|
+
(r'(SELECT|INSERT|UPDATE|DELETE).*\+.*', 'string concatenation'),
|
|
135
|
+
(r'(SELECT|INSERT|UPDATE|DELETE).*f["\'].*\{', 'f-string formatting'),
|
|
136
|
+
(r'(SELECT|INSERT|UPDATE|DELETE).*\.format\(', '.format() usage'),
|
|
137
|
+
]
|
|
138
|
+
|
|
139
|
+
for file_path in self._get_python_files():
|
|
140
|
+
content = file_path.read_text(encoding='utf-8', errors='ignore')
|
|
141
|
+
|
|
142
|
+
for pattern, name in sql_patterns:
|
|
143
|
+
for match in re.finditer(pattern, content, re.IGNORECASE | re.MULTILINE):
|
|
144
|
+
line_num = content[:match.start()].count('\n') + 1
|
|
145
|
+
findings.append(Finding(
|
|
146
|
+
severity=Severity.HIGH,
|
|
147
|
+
finding_type='SQL Injection Risk',
|
|
148
|
+
file=str(file_path.relative_to(self.skill_path)),
|
|
149
|
+
line=line_num,
|
|
150
|
+
description=f'SQL query with {name}',
|
|
151
|
+
evidence=match.group(0)[:80],
|
|
152
|
+
remediation='Use parameterized queries with placeholders (?)'
|
|
153
|
+
))
|
|
154
|
+
|
|
155
|
+
return findings
|
|
156
|
+
|
|
157
|
+
# ========== PATH TRAVERSAL ==========
|
|
158
|
+
|
|
159
|
+
def scan_path_traversal(self) -> List[Finding]:
|
|
160
|
+
"""
|
|
161
|
+
Scan for path traversal vulnerabilities.
|
|
162
|
+
Reference: File 16 (path security)
|
|
163
|
+
"""
|
|
164
|
+
findings = []
|
|
165
|
+
|
|
166
|
+
file_operations = [
|
|
167
|
+
'open(', 'Path(', 'read_text(', 'write_text(',
|
|
168
|
+
'os.path.join(', 'shutil.copy(', 'shutil.move('
|
|
169
|
+
]
|
|
170
|
+
|
|
171
|
+
for file_path in self._get_python_files():
|
|
172
|
+
content = file_path.read_text(encoding='utf-8', errors='ignore')
|
|
173
|
+
|
|
174
|
+
# Heuristic: file operations + user input handling
|
|
175
|
+
has_file_ops = any(op in content for op in file_operations)
|
|
176
|
+
has_user_input = 'input(' in content or 'args.' in content or 'argv' in content
|
|
177
|
+
|
|
178
|
+
if has_file_ops and has_user_input:
|
|
179
|
+
findings.append(Finding(
|
|
180
|
+
severity=Severity.MEDIUM,
|
|
181
|
+
finding_type='Path Traversal Risk',
|
|
182
|
+
file=str(file_path.relative_to(self.skill_path)),
|
|
183
|
+
line=0,
|
|
184
|
+
description='File operations with potential user input',
|
|
185
|
+
evidence='File has both file operations and user input handling',
|
|
186
|
+
remediation='Validate paths, use Path.resolve(), check for .. patterns'
|
|
187
|
+
))
|
|
188
|
+
|
|
189
|
+
return findings
|
|
190
|
+
|
|
191
|
+
# ========== DANGEROUS IMPORTS ==========
|
|
192
|
+
|
|
193
|
+
def scan_dangerous_imports(self) -> List[Finding]:
|
|
194
|
+
"""
|
|
195
|
+
Scan for dangerous library imports.
|
|
196
|
+
Reference: File 16 (dangerous imports)
|
|
197
|
+
"""
|
|
198
|
+
findings = []
|
|
199
|
+
|
|
200
|
+
dangerous_imports = [
|
|
201
|
+
('import pickle', 'pickle', Severity.HIGH,
|
|
202
|
+
'Arbitrary code execution via deserialization. Use json instead.'),
|
|
203
|
+
('from pickle', 'pickle', Severity.HIGH,
|
|
204
|
+
'Arbitrary code execution via deserialization. Use json instead.'),
|
|
205
|
+
('yaml.load(', 'yaml.load()', Severity.HIGH,
|
|
206
|
+
'Unsafe YAML loading. Use yaml.safe_load() instead.'),
|
|
207
|
+
]
|
|
208
|
+
|
|
209
|
+
for file_path in self._get_python_files():
|
|
210
|
+
content = file_path.read_text(encoding='utf-8', errors='ignore')
|
|
211
|
+
|
|
212
|
+
for pattern, name, severity, remediation in dangerous_imports:
|
|
213
|
+
if pattern in content:
|
|
214
|
+
line_num = content.split(pattern)[0].count('\n') + 1
|
|
215
|
+
findings.append(Finding(
|
|
216
|
+
severity=severity,
|
|
217
|
+
finding_type='Dangerous Import',
|
|
218
|
+
file=str(file_path.relative_to(self.skill_path)),
|
|
219
|
+
line=line_num,
|
|
220
|
+
description=f'Risky library: {name}',
|
|
221
|
+
evidence=pattern,
|
|
222
|
+
remediation=remediation
|
|
223
|
+
))
|
|
224
|
+
|
|
225
|
+
return findings
|
|
226
|
+
|
|
227
|
+
# ========== NETWORK CONNECTIONS ==========
|
|
228
|
+
|
|
229
|
+
def scan_network_connections(self) -> List[Finding]:
|
|
230
|
+
"""
|
|
231
|
+
Scan for external network connections.
|
|
232
|
+
Reference: File 16 (network security)
|
|
233
|
+
"""
|
|
234
|
+
findings = []
|
|
235
|
+
|
|
236
|
+
network_patterns = [
|
|
237
|
+
(r'https?://(?!localhost|127\.0\.0\.1)[^\s\'"]+', 'External URL'),
|
|
238
|
+
(r'requests\.get\(', 'HTTP GET request'),
|
|
239
|
+
(r'requests\.post\(', 'HTTP POST request'),
|
|
240
|
+
(r'socket\.connect\(', 'Socket connection'),
|
|
241
|
+
(r'urllib\.request\.urlopen\(', 'URL open'),
|
|
242
|
+
]
|
|
243
|
+
|
|
244
|
+
for file_path in self._get_scannable_files():
|
|
245
|
+
content = file_path.read_text(encoding='utf-8', errors='ignore')
|
|
246
|
+
|
|
247
|
+
for pattern, name in network_patterns:
|
|
248
|
+
for match in re.finditer(pattern, content, re.IGNORECASE):
|
|
249
|
+
line_num = content[:match.start()].count('\n') + 1
|
|
250
|
+
findings.append(Finding(
|
|
251
|
+
severity=Severity.MEDIUM,
|
|
252
|
+
finding_type='External Network Connection',
|
|
253
|
+
file=str(file_path.relative_to(self.skill_path)),
|
|
254
|
+
line=line_num,
|
|
255
|
+
description=f'{name} detected',
|
|
256
|
+
evidence=match.group(0)[:60],
|
|
257
|
+
remediation='Validate necessity, use HTTPS, verify certificates'
|
|
258
|
+
))
|
|
259
|
+
|
|
260
|
+
return findings
|
|
261
|
+
|
|
262
|
+
# ========== PROMPT INJECTION ==========
|
|
263
|
+
|
|
264
|
+
def scan_prompt_injection(self) -> List[Finding]:
|
|
265
|
+
"""
|
|
266
|
+
Scan for prompt injection vulnerabilities in SKILL.md.
|
|
267
|
+
Reference: File 07 (prompt injection prevention)
|
|
268
|
+
"""
|
|
269
|
+
findings = []
|
|
270
|
+
|
|
271
|
+
skill_md = self.skill_path / 'SKILL.md'
|
|
272
|
+
if skill_md.exists():
|
|
273
|
+
content = skill_md.read_text(encoding='utf-8')
|
|
274
|
+
|
|
275
|
+
# Check for user input handling without validation mention
|
|
276
|
+
has_user_input = 'user input' in content.lower() or 'user data' in content.lower()
|
|
277
|
+
has_validation = 'validat' in content.lower() or 'sanitiz' in content.lower()
|
|
278
|
+
|
|
279
|
+
if has_user_input and not has_validation:
|
|
280
|
+
findings.append(Finding(
|
|
281
|
+
severity=Severity.MEDIUM,
|
|
282
|
+
finding_type='Prompt Injection Risk',
|
|
283
|
+
file='SKILL.md',
|
|
284
|
+
line=0,
|
|
285
|
+
description='User input mentioned without validation guidance',
|
|
286
|
+
evidence='Instructions reference user input without validation',
|
|
287
|
+
remediation='Add input validation/sanitization instructions (File 07)'
|
|
288
|
+
))
|
|
289
|
+
|
|
290
|
+
return findings
|
|
291
|
+
|
|
292
|
+
# ========== UTILITY METHODS ==========
|
|
293
|
+
|
|
294
|
+
def _get_scannable_files(self) -> List[Path]:
|
|
295
|
+
"""Get all files that should be scanned."""
|
|
296
|
+
extensions = ['.py', '.md', '.sh', '.yaml', '.yml']
|
|
297
|
+
files = []
|
|
298
|
+
for ext in extensions:
|
|
299
|
+
files.extend(self.skill_path.rglob(f'*{ext}'))
|
|
300
|
+
return files
|
|
301
|
+
|
|
302
|
+
def _get_python_files(self) -> List[Path]:
|
|
303
|
+
"""Get all Python files in skill directory."""
|
|
304
|
+
return list(self.skill_path.rglob('*.py'))
|
|
305
|
+
|
|
306
|
+
# ========== SCAN EXECUTION ==========
|
|
307
|
+
|
|
308
|
+
def run_all_scans(self) -> List[Finding]:
|
|
309
|
+
"""Run all security scans and return findings."""
|
|
310
|
+
self.findings = []
|
|
311
|
+
|
|
312
|
+
self.findings.extend(self.scan_hardcoded_secrets())
|
|
313
|
+
self.findings.extend(self.scan_command_injection())
|
|
314
|
+
self.findings.extend(self.scan_sql_injection())
|
|
315
|
+
self.findings.extend(self.scan_path_traversal())
|
|
316
|
+
self.findings.extend(self.scan_dangerous_imports())
|
|
317
|
+
self.findings.extend(self.scan_network_connections())
|
|
318
|
+
self.findings.extend(self.scan_prompt_injection())
|
|
319
|
+
|
|
320
|
+
return self.findings
|
|
321
|
+
|
|
322
|
+
# ========== REPORT GENERATION ==========
|
|
323
|
+
|
|
324
|
+
def generate_report(self, min_severity: Severity = Severity.LOW, format: str = 'text') -> str:
|
|
325
|
+
"""Generate security scan report."""
|
|
326
|
+
if format == 'json':
|
|
327
|
+
return self._generate_json_report(min_severity)
|
|
328
|
+
return self._generate_text_report(min_severity)
|
|
329
|
+
|
|
330
|
+
def _generate_text_report(self, min_severity: Severity) -> str:
|
|
331
|
+
"""Generate human-readable text report."""
|
|
332
|
+
# Filter by severity
|
|
333
|
+
filtered = [f for f in self.findings if f.severity.value <= min_severity.value]
|
|
334
|
+
|
|
335
|
+
# Categorize
|
|
336
|
+
critical = [f for f in filtered if f.severity == Severity.CRITICAL]
|
|
337
|
+
high = [f for f in filtered if f.severity == Severity.HIGH]
|
|
338
|
+
medium = [f for f in filtered if f.severity == Severity.MEDIUM]
|
|
339
|
+
low = [f for f in filtered if f.severity == Severity.LOW]
|
|
340
|
+
|
|
341
|
+
lines = []
|
|
342
|
+
lines.append(f"\n{'='*60}")
|
|
343
|
+
lines.append(f"Security Scan Report: {self.skill_path.name}")
|
|
344
|
+
lines.append('='*60 + '\n')
|
|
345
|
+
|
|
346
|
+
# Critical issues
|
|
347
|
+
if critical:
|
|
348
|
+
lines.append("🔴 CRITICAL ISSUES (must fix before deployment):\n")
|
|
349
|
+
for i, finding in enumerate(critical, 1):
|
|
350
|
+
lines.append(f"{i}. {finding.finding_type}")
|
|
351
|
+
lines.append(f" File: {finding.file}:{finding.line}")
|
|
352
|
+
lines.append(f" Issue: {finding.description}")
|
|
353
|
+
lines.append(f" Evidence: {finding.evidence}")
|
|
354
|
+
lines.append(f" Fix: {finding.remediation}\n")
|
|
355
|
+
|
|
356
|
+
# High severity
|
|
357
|
+
if high:
|
|
358
|
+
lines.append("🟠HIGH SEVERITY (review and fix):\n")
|
|
359
|
+
for i, finding in enumerate(high, 1):
|
|
360
|
+
lines.append(f"{i}. {finding.finding_type}")
|
|
361
|
+
lines.append(f" File: {finding.file}:{finding.line}")
|
|
362
|
+
lines.append(f" Issue: {finding.description}")
|
|
363
|
+
lines.append(f" Fix: {finding.remediation}\n")
|
|
364
|
+
|
|
365
|
+
# Medium severity
|
|
366
|
+
if medium:
|
|
367
|
+
lines.append("🟡 MEDIUM SEVERITY (review required):\n")
|
|
368
|
+
for i, finding in enumerate(medium, 1):
|
|
369
|
+
lines.append(f"{i}. {finding.finding_type} in {finding.file}")
|
|
370
|
+
lines.append(f" {finding.description}\n")
|
|
371
|
+
|
|
372
|
+
# Summary
|
|
373
|
+
total_issues = len(filtered)
|
|
374
|
+
if total_issues == 0:
|
|
375
|
+
lines.append("🟢 No security issues found!\n")
|
|
376
|
+
else:
|
|
377
|
+
lines.append('-'*60)
|
|
378
|
+
lines.append(f"Security Score: {len(critical)} critical, "
|
|
379
|
+
f"{len(high)} high, {len(medium)} medium, {len(low)} low\n")
|
|
380
|
+
|
|
381
|
+
if critical:
|
|
382
|
+
lines.append("âš ï¸ CRITICAL ISSUES FOUND - Do NOT deploy until fixed!")
|
|
383
|
+
elif high:
|
|
384
|
+
lines.append("âš ï¸ HIGH SEVERITY ISSUES - Fix before production")
|
|
385
|
+
|
|
386
|
+
# Recommendations
|
|
387
|
+
lines.append("\nGeneral Security Best Practices:")
|
|
388
|
+
lines.append(" • Never hardcode credentials - use environment variables")
|
|
389
|
+
lines.append(" • Never use shell=True with user input")
|
|
390
|
+
lines.append(" • Always validate and sanitize inputs")
|
|
391
|
+
lines.append(" • Use parameterized queries for SQL")
|
|
392
|
+
lines.append(" • Test skills in isolated environment first")
|
|
393
|
+
lines.append("\nReferences: File 07 (security-concerns.md) for guidance\n")
|
|
394
|
+
|
|
395
|
+
return '\n'.join(lines)
|
|
396
|
+
|
|
397
|
+
def _generate_json_report(self, min_severity: Severity) -> str:
|
|
398
|
+
"""Generate machine-readable JSON report."""
|
|
399
|
+
import json
|
|
400
|
+
|
|
401
|
+
filtered = [f for f in self.findings if f.severity.value <= min_severity.value]
|
|
402
|
+
|
|
403
|
+
report = {
|
|
404
|
+
'skill_name': self.skill_path.name,
|
|
405
|
+
'findings': [
|
|
406
|
+
{
|
|
407
|
+
'severity': f.severity.name,
|
|
408
|
+
'type': f.finding_type,
|
|
409
|
+
'file': f.file,
|
|
410
|
+
'line': f.line,
|
|
411
|
+
'description': f.description,
|
|
412
|
+
'evidence': f.evidence,
|
|
413
|
+
'remediation': f.remediation
|
|
414
|
+
}
|
|
415
|
+
for f in filtered
|
|
416
|
+
],
|
|
417
|
+
'summary': {
|
|
418
|
+
'total': len(filtered),
|
|
419
|
+
'critical': len([f for f in filtered if f.severity == Severity.CRITICAL]),
|
|
420
|
+
'high': len([f for f in filtered if f.severity == Severity.HIGH]),
|
|
421
|
+
'medium': len([f for f in filtered if f.severity == Severity.MEDIUM]),
|
|
422
|
+
'low': len([f for f in filtered if f.severity == Severity.LOW])
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
return json.dumps(report, indent=2)
|
|
426
|
+
|
|
427
|
+
def get_exit_code(self) -> int:
|
|
428
|
+
"""Get appropriate exit code based on findings."""
|
|
429
|
+
if any(f.severity == Severity.CRITICAL for f in self.findings):
|
|
430
|
+
return 2 # Critical issues
|
|
431
|
+
if any(f.severity == Severity.HIGH for f in self.findings):
|
|
432
|
+
return 1 # High severity
|
|
433
|
+
return 0 # All clear
|
|
434
|
+
|
|
435
|
+
|
|
436
|
+
def main():
|
|
437
|
+
"""CLI entry point."""
|
|
438
|
+
import argparse
|
|
439
|
+
|
|
440
|
+
parser = argparse.ArgumentParser(
|
|
441
|
+
description='Scan Claude skill for security vulnerabilities',
|
|
442
|
+
epilog='References: Files 07, 16 for security guidance'
|
|
443
|
+
)
|
|
444
|
+
parser.add_argument('skill_path', help='Path to skill directory')
|
|
445
|
+
parser.add_argument('--severity',
|
|
446
|
+
choices=['CRITICAL', 'HIGH', 'MEDIUM', 'LOW'],
|
|
447
|
+
default='LOW',
|
|
448
|
+
help='Minimum severity to report (default: LOW)')
|
|
449
|
+
parser.add_argument('--format', choices=['text', 'json'], default='text',
|
|
450
|
+
help='Output format (default: text)')
|
|
451
|
+
|
|
452
|
+
args = parser.parse_args()
|
|
453
|
+
|
|
454
|
+
try:
|
|
455
|
+
scanner = SecurityScanner(args.skill_path)
|
|
456
|
+
scanner.run_all_scans()
|
|
457
|
+
|
|
458
|
+
min_sev = Severity[args.severity]
|
|
459
|
+
report = scanner.generate_report(min_severity=min_sev, format=args.format)
|
|
460
|
+
|
|
461
|
+
print(report)
|
|
462
|
+
sys.exit(scanner.get_exit_code())
|
|
463
|
+
|
|
464
|
+
except FileNotFoundError as e:
|
|
465
|
+
print(f"Error: {e}", file=sys.stderr)
|
|
466
|
+
sys.exit(2)
|
|
467
|
+
|
|
468
|
+
except Exception as e:
|
|
469
|
+
print(f"Error: {e}", file=sys.stderr)
|
|
470
|
+
sys.exit(2)
|
|
471
|
+
|
|
472
|
+
|
|
473
|
+
if __name__ == '__main__':
|
|
474
|
+
main()
|