@elizaos/skills 2.0.0-alpha.3
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/README.md +126 -0
- package/package.json +53 -0
- package/skills/1password/SKILL.md +70 -0
- package/skills/1password/references/cli-examples.md +29 -0
- package/skills/1password/references/get-started.md +17 -0
- package/skills/apple-notes/SKILL.md +77 -0
- package/skills/apple-reminders/SKILL.md +96 -0
- package/skills/bear-notes/SKILL.md +107 -0
- package/skills/bird/SKILL.md +224 -0
- package/skills/blogwatcher/SKILL.md +69 -0
- package/skills/blucli/SKILL.md +47 -0
- package/skills/bluebubbles/SKILL.md +131 -0
- package/skills/camsnap/SKILL.md +45 -0
- package/skills/canvas/SKILL.md +203 -0
- package/skills/clawhub/SKILL.md +77 -0
- package/skills/coding-agent/SKILL.md +284 -0
- package/skills/discord/SKILL.md +578 -0
- package/skills/eightctl/SKILL.md +50 -0
- package/skills/food-order/SKILL.md +48 -0
- package/skills/gemini/SKILL.md +43 -0
- package/skills/gifgrep/SKILL.md +79 -0
- package/skills/github/SKILL.md +77 -0
- package/skills/gog/SKILL.md +116 -0
- package/skills/goplaces/SKILL.md +52 -0
- package/skills/healthcheck/SKILL.md +245 -0
- package/skills/himalaya/SKILL.md +257 -0
- package/skills/himalaya/references/configuration.md +184 -0
- package/skills/himalaya/references/message-composition.md +199 -0
- package/skills/imsg/SKILL.md +74 -0
- package/skills/local-places/SERVER_README.md +101 -0
- package/skills/local-places/SKILL.md +102 -0
- package/skills/local-places/pyproject.toml +21 -0
- package/skills/local-places/src/local_places/__init__.py +2 -0
- package/skills/local-places/src/local_places/google_places.py +314 -0
- package/skills/local-places/src/local_places/main.py +65 -0
- package/skills/local-places/src/local_places/schemas.py +107 -0
- package/skills/mcporter/SKILL.md +61 -0
- package/skills/model-usage/SKILL.md +69 -0
- package/skills/model-usage/references/codexbar-cli.md +33 -0
- package/skills/model-usage/scripts/model_usage.py +310 -0
- package/skills/nano-banana-pro/SKILL.md +58 -0
- package/skills/nano-banana-pro/scripts/generate_image.py +184 -0
- package/skills/nano-pdf/SKILL.md +38 -0
- package/skills/notion/SKILL.md +172 -0
- package/skills/obsidian/SKILL.md +81 -0
- package/skills/openai-image-gen/SKILL.md +89 -0
- package/skills/openai-image-gen/scripts/gen.py +240 -0
- package/skills/openai-whisper/SKILL.md +38 -0
- package/skills/openai-whisper-api/SKILL.md +52 -0
- package/skills/openai-whisper-api/scripts/transcribe.sh +85 -0
- package/skills/openhue/SKILL.md +51 -0
- package/skills/oracle/SKILL.md +125 -0
- package/skills/ordercli/SKILL.md +78 -0
- package/skills/peekaboo/SKILL.md +190 -0
- package/skills/sag/SKILL.md +87 -0
- package/skills/security-ask-questions-if-underspecified/.claude-plugin/plugin.json +10 -0
- package/skills/security-ask-questions-if-underspecified/README.md +24 -0
- package/skills/security-ask-questions-if-underspecified/skills/ask-questions-if-underspecified/SKILL.md +85 -0
- package/skills/security-audit-context-building/.claude-plugin/plugin.json +10 -0
- package/skills/security-audit-context-building/README.md +58 -0
- package/skills/security-audit-context-building/commands/audit-context.md +21 -0
- package/skills/security-audit-context-building/skills/audit-context-building/SKILL.md +297 -0
- package/skills/security-audit-context-building/skills/audit-context-building/resources/COMPLETENESS_CHECKLIST.md +47 -0
- package/skills/security-audit-context-building/skills/audit-context-building/resources/FUNCTION_MICRO_ANALYSIS_EXAMPLE.md +355 -0
- package/skills/security-audit-context-building/skills/audit-context-building/resources/OUTPUT_REQUIREMENTS.md +71 -0
- package/skills/security-building-secure-contracts/.claude-plugin/plugin.json +10 -0
- package/skills/security-building-secure-contracts/README.md +241 -0
- package/skills/security-building-secure-contracts/skills/algorand-vulnerability-scanner/SKILL.md +284 -0
- package/skills/security-building-secure-contracts/skills/algorand-vulnerability-scanner/resources/VULNERABILITY_PATTERNS.md +405 -0
- package/skills/security-building-secure-contracts/skills/audit-prep-assistant/SKILL.md +409 -0
- package/skills/security-building-secure-contracts/skills/cairo-vulnerability-scanner/SKILL.md +329 -0
- package/skills/security-building-secure-contracts/skills/cairo-vulnerability-scanner/resources/VULNERABILITY_PATTERNS.md +722 -0
- package/skills/security-building-secure-contracts/skills/code-maturity-assessor/SKILL.md +218 -0
- package/skills/security-building-secure-contracts/skills/code-maturity-assessor/resources/ASSESSMENT_CRITERIA.md +355 -0
- package/skills/security-building-secure-contracts/skills/code-maturity-assessor/resources/EXAMPLE_REPORT.md +248 -0
- package/skills/security-building-secure-contracts/skills/code-maturity-assessor/resources/REPORT_FORMAT.md +33 -0
- package/skills/security-building-secure-contracts/skills/cosmos-vulnerability-scanner/SKILL.md +334 -0
- package/skills/security-building-secure-contracts/skills/cosmos-vulnerability-scanner/resources/VULNERABILITY_PATTERNS.md +740 -0
- package/skills/security-building-secure-contracts/skills/guidelines-advisor/SKILL.md +252 -0
- package/skills/security-building-secure-contracts/skills/guidelines-advisor/resources/ASSESSMENT_AREAS.md +329 -0
- package/skills/security-building-secure-contracts/skills/guidelines-advisor/resources/DELIVERABLES.md +118 -0
- package/skills/security-building-secure-contracts/skills/guidelines-advisor/resources/EXAMPLE_REPORT.md +298 -0
- package/skills/security-building-secure-contracts/skills/secure-workflow-guide/SKILL.md +161 -0
- package/skills/security-building-secure-contracts/skills/secure-workflow-guide/resources/EXAMPLE_REPORT.md +279 -0
- package/skills/security-building-secure-contracts/skills/secure-workflow-guide/resources/WORKFLOW_STEPS.md +132 -0
- package/skills/security-building-secure-contracts/skills/solana-vulnerability-scanner/SKILL.md +389 -0
- package/skills/security-building-secure-contracts/skills/solana-vulnerability-scanner/resources/VULNERABILITY_PATTERNS.md +669 -0
- package/skills/security-building-secure-contracts/skills/substrate-vulnerability-scanner/SKILL.md +298 -0
- package/skills/security-building-secure-contracts/skills/substrate-vulnerability-scanner/resources/VULNERABILITY_PATTERNS.md +791 -0
- package/skills/security-building-secure-contracts/skills/token-integration-analyzer/SKILL.md +362 -0
- package/skills/security-building-secure-contracts/skills/token-integration-analyzer/resources/ASSESSMENT_CATEGORIES.md +571 -0
- package/skills/security-building-secure-contracts/skills/token-integration-analyzer/resources/REPORT_TEMPLATES.md +141 -0
- package/skills/security-building-secure-contracts/skills/ton-vulnerability-scanner/SKILL.md +388 -0
- package/skills/security-building-secure-contracts/skills/ton-vulnerability-scanner/resources/VULNERABILITY_PATTERNS.md +595 -0
- package/skills/security-burpsuite-project-parser/.claude-plugin/plugin.json +10 -0
- package/skills/security-burpsuite-project-parser/README.md +103 -0
- package/skills/security-burpsuite-project-parser/commands/burp-search.md +18 -0
- package/skills/security-burpsuite-project-parser/skills/SKILL.md +358 -0
- package/skills/security-burpsuite-project-parser/skills/scripts/burp-search.sh +99 -0
- package/skills/security-claude-in-chrome-troubleshooting/.claude-plugin/plugin.json +8 -0
- package/skills/security-claude-in-chrome-troubleshooting/README.md +31 -0
- package/skills/security-claude-in-chrome-troubleshooting/skills/claude-in-chrome-troubleshooting/SKILL.md +251 -0
- package/skills/security-constant-time-analysis/.claude-plugin/plugin.json +9 -0
- package/skills/security-constant-time-analysis/README.md +381 -0
- package/skills/security-constant-time-analysis/commands/ct-check.md +20 -0
- package/skills/security-constant-time-analysis/ct_analyzer/__init__.py +49 -0
- package/skills/security-constant-time-analysis/ct_analyzer/analyzer.py +1284 -0
- package/skills/security-constant-time-analysis/ct_analyzer/script_analyzers.py +3081 -0
- package/skills/security-constant-time-analysis/ct_analyzer/tests/__init__.py +1 -0
- package/skills/security-constant-time-analysis/ct_analyzer/tests/test_analyzer.py +1397 -0
- package/skills/security-constant-time-analysis/ct_analyzer/tests/test_samples/bn_excerpt.js +205 -0
- package/skills/security-constant-time-analysis/ct_analyzer/tests/test_samples/decompose_constant_time.c +181 -0
- package/skills/security-constant-time-analysis/ct_analyzer/tests/test_samples/decompose_vulnerable.c +74 -0
- package/skills/security-constant-time-analysis/ct_analyzer/tests/test_samples/decompose_vulnerable.go +78 -0
- package/skills/security-constant-time-analysis/ct_analyzer/tests/test_samples/decompose_vulnerable.rs +92 -0
- package/skills/security-constant-time-analysis/ct_analyzer/tests/test_samples/vulnerable.cs +174 -0
- package/skills/security-constant-time-analysis/ct_analyzer/tests/test_samples/vulnerable.java +161 -0
- package/skills/security-constant-time-analysis/ct_analyzer/tests/test_samples/vulnerable.kt +181 -0
- package/skills/security-constant-time-analysis/ct_analyzer/tests/test_samples/vulnerable.php +140 -0
- package/skills/security-constant-time-analysis/ct_analyzer/tests/test_samples/vulnerable.py +252 -0
- package/skills/security-constant-time-analysis/ct_analyzer/tests/test_samples/vulnerable.rb +188 -0
- package/skills/security-constant-time-analysis/ct_analyzer/tests/test_samples/vulnerable.swift +199 -0
- package/skills/security-constant-time-analysis/ct_analyzer/tests/test_samples/vulnerable.ts +154 -0
- package/skills/security-constant-time-analysis/pyproject.toml +52 -0
- package/skills/security-constant-time-analysis/skills/constant-time-analysis/README.md +90 -0
- package/skills/security-constant-time-analysis/skills/constant-time-analysis/SKILL.md +219 -0
- package/skills/security-constant-time-analysis/skills/constant-time-analysis/references/compiled.md +129 -0
- package/skills/security-constant-time-analysis/skills/constant-time-analysis/references/javascript.md +136 -0
- package/skills/security-constant-time-analysis/skills/constant-time-analysis/references/kotlin.md +252 -0
- package/skills/security-constant-time-analysis/skills/constant-time-analysis/references/php.md +172 -0
- package/skills/security-constant-time-analysis/skills/constant-time-analysis/references/python.md +179 -0
- package/skills/security-constant-time-analysis/skills/constant-time-analysis/references/ruby.md +198 -0
- package/skills/security-constant-time-analysis/skills/constant-time-analysis/references/swift.md +288 -0
- package/skills/security-constant-time-analysis/skills/constant-time-analysis/references/vm-compiled.md +354 -0
- package/skills/security-constant-time-analysis/uv.lock +8 -0
- package/skills/security-culture-index/.claude-plugin/plugin.json +8 -0
- package/skills/security-culture-index/README.md +79 -0
- package/skills/security-culture-index/skills/interpreting-culture-index/SKILL.md +293 -0
- package/skills/security-culture-index/skills/interpreting-culture-index/references/anti-patterns.md +255 -0
- package/skills/security-culture-index/skills/interpreting-culture-index/references/conversation-starters.md +408 -0
- package/skills/security-culture-index/skills/interpreting-culture-index/references/interview-trait-signals.md +253 -0
- package/skills/security-culture-index/skills/interpreting-culture-index/references/motivators.md +158 -0
- package/skills/security-culture-index/skills/interpreting-culture-index/references/patterns-archetypes.md +147 -0
- package/skills/security-culture-index/skills/interpreting-culture-index/references/primary-traits.md +307 -0
- package/skills/security-culture-index/skills/interpreting-culture-index/references/secondary-traits.md +228 -0
- package/skills/security-culture-index/skills/interpreting-culture-index/references/team-composition.md +148 -0
- package/skills/security-culture-index/skills/interpreting-culture-index/scripts/check_deps.py +108 -0
- package/skills/security-culture-index/skills/interpreting-culture-index/scripts/culture_index/__init__.py +20 -0
- package/skills/security-culture-index/skills/interpreting-culture-index/scripts/culture_index/constants.py +122 -0
- package/skills/security-culture-index/skills/interpreting-culture-index/scripts/culture_index/extract.py +187 -0
- package/skills/security-culture-index/skills/interpreting-culture-index/scripts/culture_index/models.py +16 -0
- package/skills/security-culture-index/skills/interpreting-culture-index/scripts/culture_index/opencv_extractor.py +520 -0
- package/skills/security-culture-index/skills/interpreting-culture-index/scripts/extract_pdf.py +237 -0
- package/skills/security-culture-index/skills/interpreting-culture-index/scripts/pyproject.toml +18 -0
- package/skills/security-culture-index/skills/interpreting-culture-index/templates/burnout-report.md +113 -0
- package/skills/security-culture-index/skills/interpreting-culture-index/templates/comparison-report.md +103 -0
- package/skills/security-culture-index/skills/interpreting-culture-index/templates/hiring-profile.md +127 -0
- package/skills/security-culture-index/skills/interpreting-culture-index/templates/individual-report.md +85 -0
- package/skills/security-culture-index/skills/interpreting-culture-index/templates/predicted-profile.md +165 -0
- package/skills/security-culture-index/skills/interpreting-culture-index/templates/team-report.md +109 -0
- package/skills/security-culture-index/skills/interpreting-culture-index/workflows/analyze-team.md +188 -0
- package/skills/security-culture-index/skills/interpreting-culture-index/workflows/coach-manager.md +267 -0
- package/skills/security-culture-index/skills/interpreting-culture-index/workflows/compare-profiles.md +188 -0
- package/skills/security-culture-index/skills/interpreting-culture-index/workflows/define-hiring-profile.md +220 -0
- package/skills/security-culture-index/skills/interpreting-culture-index/workflows/detect-burnout.md +206 -0
- package/skills/security-culture-index/skills/interpreting-culture-index/workflows/extract-from-pdf.md +121 -0
- package/skills/security-culture-index/skills/interpreting-culture-index/workflows/interpret-individual.md +183 -0
- package/skills/security-culture-index/skills/interpreting-culture-index/workflows/interview-debrief.md +234 -0
- package/skills/security-culture-index/skills/interpreting-culture-index/workflows/mediate-conflict.md +306 -0
- package/skills/security-culture-index/skills/interpreting-culture-index/workflows/plan-onboarding.md +322 -0
- package/skills/security-culture-index/skills/interpreting-culture-index/workflows/predict-from-interview.md +250 -0
- package/skills/security-differential-review/.claude-plugin/plugin.json +10 -0
- package/skills/security-differential-review/README.md +109 -0
- package/skills/security-differential-review/commands/diff-review.md +21 -0
- package/skills/security-differential-review/skills/differential-review/SKILL.md +220 -0
- package/skills/security-differential-review/skills/differential-review/adversarial.md +203 -0
- package/skills/security-differential-review/skills/differential-review/methodology.md +234 -0
- package/skills/security-differential-review/skills/differential-review/patterns.md +300 -0
- package/skills/security-differential-review/skills/differential-review/reporting.md +369 -0
- package/skills/security-dwarf-expert/.claude-plugin/plugin.json +10 -0
- package/skills/security-dwarf-expert/README.md +38 -0
- package/skills/security-dwarf-expert/skills/dwarf-expert/SKILL.md +93 -0
- package/skills/security-dwarf-expert/skills/dwarf-expert/reference/coding.md +31 -0
- package/skills/security-dwarf-expert/skills/dwarf-expert/reference/dwarfdump.md +50 -0
- package/skills/security-dwarf-expert/skills/dwarf-expert/reference/readelf.md +8 -0
- package/skills/security-entry-point-analyzer/.claude-plugin/plugin.json +10 -0
- package/skills/security-entry-point-analyzer/README.md +74 -0
- package/skills/security-entry-point-analyzer/commands/entry-points.md +18 -0
- package/skills/security-entry-point-analyzer/skills/entry-point-analyzer/SKILL.md +251 -0
- package/skills/security-entry-point-analyzer/skills/entry-point-analyzer/references/cosmwasm.md +182 -0
- package/skills/security-entry-point-analyzer/skills/entry-point-analyzer/references/move-aptos.md +107 -0
- package/skills/security-entry-point-analyzer/skills/entry-point-analyzer/references/move-sui.md +87 -0
- package/skills/security-entry-point-analyzer/skills/entry-point-analyzer/references/solana.md +155 -0
- package/skills/security-entry-point-analyzer/skills/entry-point-analyzer/references/solidity.md +135 -0
- package/skills/security-entry-point-analyzer/skills/entry-point-analyzer/references/ton.md +185 -0
- package/skills/security-entry-point-analyzer/skills/entry-point-analyzer/references/vyper.md +141 -0
- package/skills/security-firebase-apk-scanner/.claude-plugin/plugin.json +10 -0
- package/skills/security-firebase-apk-scanner/README.md +85 -0
- package/skills/security-firebase-apk-scanner/commands/scan-apk.md +18 -0
- package/skills/security-firebase-apk-scanner/scanner.sh +1408 -0
- package/skills/security-firebase-apk-scanner/skills/firebase-apk-scanner/SKILL.md +197 -0
- package/skills/security-firebase-apk-scanner/skills/firebase-apk-scanner/references/vulnerabilities.md +803 -0
- package/skills/security-fix-review/.claude-plugin/plugin.json +13 -0
- package/skills/security-fix-review/README.md +118 -0
- package/skills/security-fix-review/commands/fix-review.md +24 -0
- package/skills/security-fix-review/skills/fix-review/SKILL.md +264 -0
- package/skills/security-fix-review/skills/fix-review/references/bug-detection.md +408 -0
- package/skills/security-fix-review/skills/fix-review/references/finding-matching.md +298 -0
- package/skills/security-fix-review/skills/fix-review/references/report-parsing.md +398 -0
- package/skills/security-insecure-defaults/.claude-plugin/plugin.json +10 -0
- package/skills/security-insecure-defaults/README.md +45 -0
- package/skills/security-insecure-defaults/skills/insecure-defaults/SKILL.md +117 -0
- package/skills/security-insecure-defaults/skills/insecure-defaults/references/examples.md +409 -0
- package/skills/security-modern-python/.claude-plugin/plugin.json +10 -0
- package/skills/security-modern-python/README.md +58 -0
- package/skills/security-modern-python/hooks/hooks.json +16 -0
- package/skills/security-modern-python/hooks/intercept-legacy-python.bats +388 -0
- package/skills/security-modern-python/hooks/intercept-legacy-python.sh +109 -0
- package/skills/security-modern-python/hooks/test_helper.bash +75 -0
- package/skills/security-modern-python/skills/modern-python/SKILL.md +333 -0
- package/skills/security-modern-python/skills/modern-python/references/dependabot.md +43 -0
- package/skills/security-modern-python/skills/modern-python/references/migration-checklist.md +141 -0
- package/skills/security-modern-python/skills/modern-python/references/pep723-scripts.md +259 -0
- package/skills/security-modern-python/skills/modern-python/references/prek.md +211 -0
- package/skills/security-modern-python/skills/modern-python/references/pyproject.md +254 -0
- package/skills/security-modern-python/skills/modern-python/references/ruff-config.md +240 -0
- package/skills/security-modern-python/skills/modern-python/references/security-setup.md +255 -0
- package/skills/security-modern-python/skills/modern-python/references/testing.md +284 -0
- package/skills/security-modern-python/skills/modern-python/references/uv-commands.md +200 -0
- package/skills/security-modern-python/skills/modern-python/templates/dependabot.yml +36 -0
- package/skills/security-modern-python/skills/modern-python/templates/pre-commit-config.yaml +66 -0
- package/skills/security-property-based-testing/.claude-plugin/plugin.json +9 -0
- package/skills/security-property-based-testing/README.md +47 -0
- package/skills/security-property-based-testing/skills/property-based-testing/README.md +88 -0
- package/skills/security-property-based-testing/skills/property-based-testing/SKILL.md +109 -0
- package/skills/security-property-based-testing/skills/property-based-testing/references/design.md +191 -0
- package/skills/security-property-based-testing/skills/property-based-testing/references/generating.md +200 -0
- package/skills/security-property-based-testing/skills/property-based-testing/references/libraries.md +130 -0
- package/skills/security-property-based-testing/skills/property-based-testing/references/refactoring.md +181 -0
- package/skills/security-property-based-testing/skills/property-based-testing/references/reviewing.md +209 -0
- package/skills/security-property-based-testing/skills/property-based-testing/references/strategies.md +124 -0
- package/skills/semgrep-rule-creator/.claude-plugin/plugin.json +8 -0
- package/skills/semgrep-rule-creator/README.md +43 -0
- package/skills/semgrep-rule-creator/commands/semgrep-rule.md +26 -0
- package/skills/semgrep-rule-creator/skills/semgrep-rule-creator/SKILL.md +168 -0
- package/skills/semgrep-rule-creator/skills/semgrep-rule-creator/references/quick-reference.md +203 -0
- package/skills/semgrep-rule-creator/skills/semgrep-rule-creator/references/workflow.md +240 -0
- package/skills/semgrep-rule-variant-creator/.claude-plugin/plugin.json +9 -0
- package/skills/semgrep-rule-variant-creator/README.md +86 -0
- package/skills/semgrep-rule-variant-creator/skills/semgrep-rule-variant-creator/SKILL.md +205 -0
- package/skills/semgrep-rule-variant-creator/skills/semgrep-rule-variant-creator/references/applicability-analysis.md +250 -0
- package/skills/semgrep-rule-variant-creator/skills/semgrep-rule-variant-creator/references/language-syntax-guide.md +324 -0
- package/skills/semgrep-rule-variant-creator/skills/semgrep-rule-variant-creator/references/workflow.md +518 -0
- package/skills/session-logs/SKILL.md +115 -0
- package/skills/sharp-edges/.claude-plugin/plugin.json +10 -0
- package/skills/sharp-edges/README.md +48 -0
- package/skills/sharp-edges/skills/sharp-edges/SKILL.md +292 -0
- package/skills/sharp-edges/skills/sharp-edges/references/auth-patterns.md +252 -0
- package/skills/sharp-edges/skills/sharp-edges/references/case-studies.md +274 -0
- package/skills/sharp-edges/skills/sharp-edges/references/config-patterns.md +333 -0
- package/skills/sharp-edges/skills/sharp-edges/references/crypto-apis.md +190 -0
- package/skills/sharp-edges/skills/sharp-edges/references/lang-c.md +205 -0
- package/skills/sharp-edges/skills/sharp-edges/references/lang-csharp.md +285 -0
- package/skills/sharp-edges/skills/sharp-edges/references/lang-go.md +270 -0
- package/skills/sharp-edges/skills/sharp-edges/references/lang-java.md +263 -0
- package/skills/sharp-edges/skills/sharp-edges/references/lang-javascript.md +269 -0
- package/skills/sharp-edges/skills/sharp-edges/references/lang-kotlin.md +265 -0
- package/skills/sharp-edges/skills/sharp-edges/references/lang-php.md +245 -0
- package/skills/sharp-edges/skills/sharp-edges/references/lang-python.md +274 -0
- package/skills/sharp-edges/skills/sharp-edges/references/lang-ruby.md +273 -0
- package/skills/sharp-edges/skills/sharp-edges/references/lang-rust.md +272 -0
- package/skills/sharp-edges/skills/sharp-edges/references/lang-swift.md +287 -0
- package/skills/sharp-edges/skills/sharp-edges/references/language-specific.md +588 -0
- package/skills/sherpa-onnx-tts/SKILL.md +103 -0
- package/skills/sherpa-onnx-tts/bin/sherpa-onnx-tts +178 -0
- package/skills/skill-creator/SKILL.md +370 -0
- package/skills/skill-creator/license.txt +202 -0
- package/skills/skill-creator/scripts/init_skill.py +378 -0
- package/skills/skill-creator/scripts/package_skill.py +111 -0
- package/skills/skill-creator/scripts/quick_validate.py +101 -0
- package/skills/slack/SKILL.md +144 -0
- package/skills/songsee/SKILL.md +49 -0
- package/skills/sonoscli/SKILL.md +46 -0
- package/skills/spec-to-code-compliance/.claude-plugin/plugin.json +10 -0
- package/skills/spec-to-code-compliance/README.md +67 -0
- package/skills/spec-to-code-compliance/commands/spec-compliance.md +22 -0
- package/skills/spec-to-code-compliance/skills/spec-to-code-compliance/SKILL.md +349 -0
- package/skills/spec-to-code-compliance/skills/spec-to-code-compliance/resources/COMPLETENESS_CHECKLIST.md +69 -0
- package/skills/spec-to-code-compliance/skills/spec-to-code-compliance/resources/IR_EXAMPLES.md +417 -0
- package/skills/spec-to-code-compliance/skills/spec-to-code-compliance/resources/OUTPUT_REQUIREMENTS.md +105 -0
- package/skills/spotify-player/SKILL.md +64 -0
- package/skills/static-analysis/.claude-plugin/plugin.json +8 -0
- package/skills/static-analysis/README.md +59 -0
- package/skills/static-analysis/skills/codeql/SKILL.md +315 -0
- package/skills/static-analysis/skills/sarif-parsing/SKILL.md +479 -0
- package/skills/static-analysis/skills/sarif-parsing/resources/jq-queries.md +162 -0
- package/skills/static-analysis/skills/sarif-parsing/resources/sarif_helpers.py +331 -0
- package/skills/static-analysis/skills/semgrep/SKILL.md +337 -0
- package/skills/summarize/SKILL.md +87 -0
- package/skills/testing-handbook-skills/.claude-plugin/plugin.json +8 -0
- package/skills/testing-handbook-skills/README.md +241 -0
- package/skills/testing-handbook-skills/scripts/pyproject.toml +8 -0
- package/skills/testing-handbook-skills/scripts/validate-skills.py +657 -0
- package/skills/testing-handbook-skills/skills/address-sanitizer/SKILL.md +341 -0
- package/skills/testing-handbook-skills/skills/aflpp/SKILL.md +640 -0
- package/skills/testing-handbook-skills/skills/atheris/SKILL.md +515 -0
- package/skills/testing-handbook-skills/skills/cargo-fuzz/SKILL.md +454 -0
- package/skills/testing-handbook-skills/skills/codeql/SKILL.md +549 -0
- package/skills/testing-handbook-skills/skills/constant-time-testing/SKILL.md +507 -0
- package/skills/testing-handbook-skills/skills/coverage-analysis/SKILL.md +607 -0
- package/skills/testing-handbook-skills/skills/fuzzing-dictionary/SKILL.md +297 -0
- package/skills/testing-handbook-skills/skills/fuzzing-obstacles/SKILL.md +426 -0
- package/skills/testing-handbook-skills/skills/harness-writing/SKILL.md +614 -0
- package/skills/testing-handbook-skills/skills/libafl/SKILL.md +625 -0
- package/skills/testing-handbook-skills/skills/libfuzzer/SKILL.md +795 -0
- package/skills/testing-handbook-skills/skills/ossfuzz/SKILL.md +426 -0
- package/skills/testing-handbook-skills/skills/ruzzy/SKILL.md +443 -0
- package/skills/testing-handbook-skills/skills/semgrep/SKILL.md +601 -0
- package/skills/testing-handbook-skills/skills/testing-handbook-generator/SKILL.md +372 -0
- package/skills/testing-handbook-skills/skills/testing-handbook-generator/agent-prompt.md +280 -0
- package/skills/testing-handbook-skills/skills/testing-handbook-generator/discovery.md +452 -0
- package/skills/testing-handbook-skills/skills/testing-handbook-generator/templates/domain-skill.md +504 -0
- package/skills/testing-handbook-skills/skills/testing-handbook-generator/templates/fuzzer-skill.md +454 -0
- package/skills/testing-handbook-skills/skills/testing-handbook-generator/templates/technique-skill.md +527 -0
- package/skills/testing-handbook-skills/skills/testing-handbook-generator/templates/tool-skill.md +366 -0
- package/skills/testing-handbook-skills/skills/testing-handbook-generator/testing.md +482 -0
- package/skills/testing-handbook-skills/skills/wycheproof/SKILL.md +533 -0
- package/skills/things-mac/SKILL.md +86 -0
- package/skills/tmux/SKILL.md +135 -0
- package/skills/tmux/scripts/find-sessions.sh +112 -0
- package/skills/tmux/scripts/wait-for-text.sh +83 -0
- package/skills/trello/SKILL.md +95 -0
- package/skills/variant-analysis/.claude-plugin/plugin.json +8 -0
- package/skills/variant-analysis/README.md +41 -0
- package/skills/variant-analysis/commands/variants.md +23 -0
- package/skills/variant-analysis/skills/variant-analysis/METHODOLOGY.md +327 -0
- package/skills/variant-analysis/skills/variant-analysis/SKILL.md +142 -0
- package/skills/variant-analysis/skills/variant-analysis/resources/codeql/cpp.ql +119 -0
- package/skills/variant-analysis/skills/variant-analysis/resources/codeql/go.ql +69 -0
- package/skills/variant-analysis/skills/variant-analysis/resources/codeql/java.ql +71 -0
- package/skills/variant-analysis/skills/variant-analysis/resources/codeql/javascript.ql +63 -0
- package/skills/variant-analysis/skills/variant-analysis/resources/codeql/python.ql +80 -0
- package/skills/variant-analysis/skills/variant-analysis/resources/semgrep/cpp.yaml +98 -0
- package/skills/variant-analysis/skills/variant-analysis/resources/semgrep/go.yaml +63 -0
- package/skills/variant-analysis/skills/variant-analysis/resources/semgrep/java.yaml +61 -0
- package/skills/variant-analysis/skills/variant-analysis/resources/semgrep/javascript.yaml +60 -0
- package/skills/variant-analysis/skills/variant-analysis/resources/semgrep/python.yaml +72 -0
- package/skills/variant-analysis/skills/variant-analysis/resources/variant-report-template.md +75 -0
- package/skills/video-frames/SKILL.md +46 -0
- package/skills/video-frames/scripts/frame.sh +81 -0
- package/skills/voice-call/SKILL.md +45 -0
- package/skills/wacli/SKILL.md +72 -0
- package/skills/weather/SKILL.md +54 -0
- package/skills/yara-authoring/.claude-plugin/plugin.json +9 -0
- package/skills/yara-authoring/README.md +131 -0
- package/skills/yara-authoring/skills/yara-rule-authoring/SKILL.md +645 -0
- package/skills/yara-authoring/skills/yara-rule-authoring/examples/MAL_Mac_ProtonRAT_Jan25.yar +99 -0
- package/skills/yara-authoring/skills/yara-rule-authoring/examples/MAL_NPM_SupplyChain_Jan25.yar +170 -0
- package/skills/yara-authoring/skills/yara-rule-authoring/examples/MAL_Win_Remcos_Jan25.yar +103 -0
- package/skills/yara-authoring/skills/yara-rule-authoring/examples/SUSP_CRX_SuspiciousPermissions.yar +134 -0
- package/skills/yara-authoring/skills/yara-rule-authoring/examples/SUSP_JS_Obfuscation_Jan25.yar +185 -0
- package/skills/yara-authoring/skills/yara-rule-authoring/references/crx-module.md +214 -0
- package/skills/yara-authoring/skills/yara-rule-authoring/references/dex-module.md +383 -0
- package/skills/yara-authoring/skills/yara-rule-authoring/references/performance.md +333 -0
- package/skills/yara-authoring/skills/yara-rule-authoring/references/strings.md +433 -0
- package/skills/yara-authoring/skills/yara-rule-authoring/references/style-guide.md +257 -0
- package/skills/yara-authoring/skills/yara-rule-authoring/references/testing.md +399 -0
- package/skills/yara-authoring/skills/yara-rule-authoring/scripts/atom_analyzer.py +526 -0
- package/skills/yara-authoring/skills/yara-rule-authoring/scripts/pyproject.toml +25 -0
- package/skills/yara-authoring/skills/yara-rule-authoring/scripts/yara_lint.py +631 -0
- package/skills/yara-authoring/skills/yara-rule-authoring/workflows/rule-development.md +493 -0
|
@@ -0,0 +1,1397 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Unit tests for the constant-time analyzer.
|
|
4
|
+
|
|
5
|
+
These tests verify that the analyzer correctly detects timing side-channel
|
|
6
|
+
vulnerabilities in compiled cryptographic code.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import os
|
|
10
|
+
import subprocess
|
|
11
|
+
import sys
|
|
12
|
+
import unittest
|
|
13
|
+
from pathlib import Path
|
|
14
|
+
|
|
15
|
+
# Add parent directory to path for imports
|
|
16
|
+
sys.path.insert(0, str(Path(__file__).parent.parent))
|
|
17
|
+
|
|
18
|
+
from analyzer import (
|
|
19
|
+
DANGEROUS_INSTRUCTIONS,
|
|
20
|
+
AssemblyParser,
|
|
21
|
+
OutputFormat,
|
|
22
|
+
Severity,
|
|
23
|
+
analyze_assembly,
|
|
24
|
+
analyze_source,
|
|
25
|
+
detect_language,
|
|
26
|
+
format_report,
|
|
27
|
+
get_native_arch,
|
|
28
|
+
normalize_arch,
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class TestArchitectureNormalization(unittest.TestCase):
|
|
33
|
+
"""Test architecture name normalization."""
|
|
34
|
+
|
|
35
|
+
def test_normalize_common_aliases(self):
|
|
36
|
+
self.assertEqual(normalize_arch("amd64"), "x86_64")
|
|
37
|
+
self.assertEqual(normalize_arch("x64"), "x86_64")
|
|
38
|
+
self.assertEqual(normalize_arch("aarch64"), "arm64")
|
|
39
|
+
self.assertEqual(normalize_arch("386"), "i386")
|
|
40
|
+
self.assertEqual(normalize_arch("x86"), "i386")
|
|
41
|
+
|
|
42
|
+
def test_normalize_case_insensitive(self):
|
|
43
|
+
self.assertEqual(normalize_arch("AMD64"), "x86_64")
|
|
44
|
+
self.assertEqual(normalize_arch("AARCH64"), "arm64")
|
|
45
|
+
self.assertEqual(normalize_arch("X86_64"), "x86_64")
|
|
46
|
+
|
|
47
|
+
def test_normalize_passthrough(self):
|
|
48
|
+
self.assertEqual(normalize_arch("x86_64"), "x86_64")
|
|
49
|
+
self.assertEqual(normalize_arch("arm64"), "arm64")
|
|
50
|
+
self.assertEqual(normalize_arch("riscv64"), "riscv64")
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
class TestLanguageDetection(unittest.TestCase):
|
|
54
|
+
"""Test source file language detection."""
|
|
55
|
+
|
|
56
|
+
def test_detect_c(self):
|
|
57
|
+
self.assertEqual(detect_language("foo.c"), "c")
|
|
58
|
+
self.assertEqual(detect_language("foo.h"), "c")
|
|
59
|
+
self.assertEqual(detect_language("/path/to/crypto.c"), "c")
|
|
60
|
+
|
|
61
|
+
def test_detect_cpp(self):
|
|
62
|
+
self.assertEqual(detect_language("foo.cpp"), "cpp")
|
|
63
|
+
self.assertEqual(detect_language("foo.cc"), "cpp")
|
|
64
|
+
self.assertEqual(detect_language("foo.cxx"), "cpp")
|
|
65
|
+
self.assertEqual(detect_language("foo.hpp"), "cpp")
|
|
66
|
+
|
|
67
|
+
def test_detect_go(self):
|
|
68
|
+
self.assertEqual(detect_language("main.go"), "go")
|
|
69
|
+
self.assertEqual(detect_language("/path/to/crypto.go"), "go")
|
|
70
|
+
|
|
71
|
+
def test_detect_rust(self):
|
|
72
|
+
self.assertEqual(detect_language("lib.rs"), "rust")
|
|
73
|
+
self.assertEqual(detect_language("/path/to/crypto.rs"), "rust")
|
|
74
|
+
|
|
75
|
+
def test_detect_python(self):
|
|
76
|
+
self.assertEqual(detect_language("crypto.py"), "python")
|
|
77
|
+
self.assertEqual(detect_language("crypto.pyw"), "python")
|
|
78
|
+
self.assertEqual(detect_language("/path/to/crypto.py"), "python")
|
|
79
|
+
|
|
80
|
+
def test_detect_ruby(self):
|
|
81
|
+
self.assertEqual(detect_language("crypto.rb"), "ruby")
|
|
82
|
+
self.assertEqual(detect_language("/path/to/crypto.rb"), "ruby")
|
|
83
|
+
|
|
84
|
+
def test_detect_java(self):
|
|
85
|
+
self.assertEqual(detect_language("CryptoUtils.java"), "java")
|
|
86
|
+
self.assertEqual(detect_language("/path/to/Crypto.java"), "java")
|
|
87
|
+
|
|
88
|
+
def test_detect_csharp(self):
|
|
89
|
+
self.assertEqual(detect_language("CryptoUtils.cs"), "csharp")
|
|
90
|
+
self.assertEqual(detect_language("/path/to/Crypto.cs"), "csharp")
|
|
91
|
+
|
|
92
|
+
def test_detect_unknown(self):
|
|
93
|
+
self.assertEqual(detect_language("foo.txt"), "unknown")
|
|
94
|
+
self.assertEqual(detect_language("foo.scala"), "unknown")
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
class TestDangerousInstructions(unittest.TestCase):
|
|
98
|
+
"""Test that dangerous instruction lists are properly defined."""
|
|
99
|
+
|
|
100
|
+
def test_all_architectures_have_errors(self):
|
|
101
|
+
for arch in DANGEROUS_INSTRUCTIONS:
|
|
102
|
+
self.assertIn(
|
|
103
|
+
"errors", DANGEROUS_INSTRUCTIONS[arch], f"Architecture {arch} missing 'errors' key"
|
|
104
|
+
)
|
|
105
|
+
self.assertGreater(
|
|
106
|
+
len(DANGEROUS_INSTRUCTIONS[arch]["errors"]),
|
|
107
|
+
0,
|
|
108
|
+
f"Architecture {arch} has no error instructions",
|
|
109
|
+
)
|
|
110
|
+
|
|
111
|
+
def test_all_architectures_have_division(self):
|
|
112
|
+
"""Every architecture should flag some form of division."""
|
|
113
|
+
division_patterns = ["div", "idiv", "udiv", "sdiv", "d", "dr"]
|
|
114
|
+
|
|
115
|
+
for arch, instructions in DANGEROUS_INSTRUCTIONS.items():
|
|
116
|
+
errors = instructions.get("errors", {})
|
|
117
|
+
has_division = any(
|
|
118
|
+
any(pattern in mnemonic.lower() for pattern in division_patterns)
|
|
119
|
+
for mnemonic in errors.keys()
|
|
120
|
+
)
|
|
121
|
+
self.assertTrue(has_division, f"Architecture {arch} should flag division instructions")
|
|
122
|
+
|
|
123
|
+
def test_x86_64_has_known_dangerous(self):
|
|
124
|
+
"""x86_64 should flag DIV, IDIV, and their variants."""
|
|
125
|
+
x86 = DANGEROUS_INSTRUCTIONS["x86_64"]["errors"]
|
|
126
|
+
self.assertIn("div", x86)
|
|
127
|
+
self.assertIn("idiv", x86)
|
|
128
|
+
self.assertIn("divq", x86)
|
|
129
|
+
self.assertIn("idivq", x86)
|
|
130
|
+
|
|
131
|
+
def test_arm64_has_known_dangerous(self):
|
|
132
|
+
"""ARM64 should flag UDIV and SDIV."""
|
|
133
|
+
arm64 = DANGEROUS_INSTRUCTIONS["arm64"]["errors"]
|
|
134
|
+
self.assertIn("udiv", arm64)
|
|
135
|
+
self.assertIn("sdiv", arm64)
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
class TestAssemblyParser(unittest.TestCase):
|
|
139
|
+
"""Test assembly parsing and violation detection."""
|
|
140
|
+
|
|
141
|
+
def test_parse_x86_64_division(self):
|
|
142
|
+
"""Parser should detect x86_64 division instructions."""
|
|
143
|
+
assembly = """
|
|
144
|
+
decompose:
|
|
145
|
+
movq %rdi, %rax
|
|
146
|
+
cqto
|
|
147
|
+
idivq %rsi
|
|
148
|
+
movq %rax, (%rdx)
|
|
149
|
+
movq %rdx, (%rcx)
|
|
150
|
+
ret
|
|
151
|
+
"""
|
|
152
|
+
|
|
153
|
+
parser = AssemblyParser("x86_64", "clang")
|
|
154
|
+
functions, violations = parser.parse(assembly)
|
|
155
|
+
|
|
156
|
+
self.assertEqual(len(functions), 1)
|
|
157
|
+
self.assertEqual(functions[0]["name"], "decompose")
|
|
158
|
+
|
|
159
|
+
# Should find the IDIVQ instruction
|
|
160
|
+
error_violations = [v for v in violations if v.severity == Severity.ERROR]
|
|
161
|
+
self.assertGreater(len(error_violations), 0, "Should detect IDIVQ")
|
|
162
|
+
self.assertEqual(error_violations[0].mnemonic, "IDIVQ")
|
|
163
|
+
|
|
164
|
+
def test_parse_arm64_division(self):
|
|
165
|
+
"""Parser should detect ARM64 division instructions."""
|
|
166
|
+
assembly = """
|
|
167
|
+
decompose:
|
|
168
|
+
sdiv w8, w0, w1
|
|
169
|
+
msub w9, w8, w1, w0
|
|
170
|
+
str w8, [x2]
|
|
171
|
+
str w9, [x3]
|
|
172
|
+
ret
|
|
173
|
+
"""
|
|
174
|
+
|
|
175
|
+
parser = AssemblyParser("arm64", "clang")
|
|
176
|
+
functions, violations = parser.parse(assembly)
|
|
177
|
+
|
|
178
|
+
error_violations = [v for v in violations if v.severity == Severity.ERROR]
|
|
179
|
+
self.assertGreater(len(error_violations), 0, "Should detect SDIV")
|
|
180
|
+
self.assertEqual(error_violations[0].mnemonic, "SDIV")
|
|
181
|
+
|
|
182
|
+
def test_parse_conditional_branches_as_warnings(self):
|
|
183
|
+
"""Parser should detect conditional branches as warnings."""
|
|
184
|
+
assembly = """
|
|
185
|
+
check_value:
|
|
186
|
+
cmpq $0, %rdi
|
|
187
|
+
je .Lzero
|
|
188
|
+
movq $1, %rax
|
|
189
|
+
ret
|
|
190
|
+
.Lzero:
|
|
191
|
+
xorq %rax, %rax
|
|
192
|
+
ret
|
|
193
|
+
"""
|
|
194
|
+
|
|
195
|
+
parser = AssemblyParser("x86_64", "clang")
|
|
196
|
+
functions, violations = parser.parse(assembly, include_warnings=True)
|
|
197
|
+
|
|
198
|
+
warning_violations = [v for v in violations if v.severity == Severity.WARNING]
|
|
199
|
+
self.assertGreater(len(warning_violations), 0, "Should detect JE as warning")
|
|
200
|
+
|
|
201
|
+
def test_parse_no_false_positives_on_clean_code(self):
|
|
202
|
+
"""Parser should not flag clean constant-time code."""
|
|
203
|
+
assembly = """
|
|
204
|
+
constant_time_select:
|
|
205
|
+
movq %rdx, %rax
|
|
206
|
+
negq %rax
|
|
207
|
+
andq %rdi, %rax
|
|
208
|
+
notq %rdx
|
|
209
|
+
andq %rsi, %rdx
|
|
210
|
+
orq %rdx, %rax
|
|
211
|
+
ret
|
|
212
|
+
"""
|
|
213
|
+
|
|
214
|
+
parser = AssemblyParser("x86_64", "clang")
|
|
215
|
+
functions, violations = parser.parse(assembly)
|
|
216
|
+
|
|
217
|
+
error_violations = [v for v in violations if v.severity == Severity.ERROR]
|
|
218
|
+
self.assertEqual(len(error_violations), 0, "Clean code should have no violations")
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
class TestReportFormatting(unittest.TestCase):
|
|
222
|
+
"""Test report output formatting."""
|
|
223
|
+
|
|
224
|
+
def test_json_format(self):
|
|
225
|
+
"""JSON format should produce valid JSON."""
|
|
226
|
+
import json
|
|
227
|
+
|
|
228
|
+
from analyzer import AnalysisReport
|
|
229
|
+
|
|
230
|
+
report = AnalysisReport(
|
|
231
|
+
architecture="x86_64",
|
|
232
|
+
compiler="clang",
|
|
233
|
+
optimization="O2",
|
|
234
|
+
source_file="test.c",
|
|
235
|
+
total_functions=1,
|
|
236
|
+
total_instructions=10,
|
|
237
|
+
violations=[],
|
|
238
|
+
)
|
|
239
|
+
|
|
240
|
+
output = format_report(report, OutputFormat.JSON)
|
|
241
|
+
parsed = json.loads(output)
|
|
242
|
+
|
|
243
|
+
self.assertEqual(parsed["architecture"], "x86_64")
|
|
244
|
+
self.assertEqual(parsed["passed"], True)
|
|
245
|
+
|
|
246
|
+
def test_text_format_passed(self):
|
|
247
|
+
"""Text format should show PASSED for clean code."""
|
|
248
|
+
from analyzer import AnalysisReport
|
|
249
|
+
|
|
250
|
+
report = AnalysisReport(
|
|
251
|
+
architecture="x86_64",
|
|
252
|
+
compiler="clang",
|
|
253
|
+
optimization="O2",
|
|
254
|
+
source_file="test.c",
|
|
255
|
+
total_functions=1,
|
|
256
|
+
total_instructions=10,
|
|
257
|
+
violations=[],
|
|
258
|
+
)
|
|
259
|
+
|
|
260
|
+
output = format_report(report, OutputFormat.TEXT)
|
|
261
|
+
self.assertIn("PASSED", output)
|
|
262
|
+
self.assertIn("No violations found", output)
|
|
263
|
+
|
|
264
|
+
def test_text_format_failed(self):
|
|
265
|
+
"""Text format should show FAILED when violations exist."""
|
|
266
|
+
from analyzer import AnalysisReport, Violation
|
|
267
|
+
|
|
268
|
+
report = AnalysisReport(
|
|
269
|
+
architecture="x86_64",
|
|
270
|
+
compiler="clang",
|
|
271
|
+
optimization="O2",
|
|
272
|
+
source_file="test.c",
|
|
273
|
+
total_functions=1,
|
|
274
|
+
total_instructions=10,
|
|
275
|
+
violations=[
|
|
276
|
+
Violation(
|
|
277
|
+
function="decompose",
|
|
278
|
+
file="test.c",
|
|
279
|
+
line=10,
|
|
280
|
+
address="0x1234",
|
|
281
|
+
instruction="idivq %rsi",
|
|
282
|
+
mnemonic="IDIVQ",
|
|
283
|
+
reason="IDIVQ has data-dependent timing",
|
|
284
|
+
severity=Severity.ERROR,
|
|
285
|
+
)
|
|
286
|
+
],
|
|
287
|
+
)
|
|
288
|
+
|
|
289
|
+
output = format_report(report, OutputFormat.TEXT)
|
|
290
|
+
self.assertIn("FAILED", output)
|
|
291
|
+
self.assertIn("IDIVQ", output)
|
|
292
|
+
|
|
293
|
+
|
|
294
|
+
class TestIntegration(unittest.TestCase):
|
|
295
|
+
"""Integration tests that compile actual code.
|
|
296
|
+
|
|
297
|
+
These tests require clang/gcc to be installed and may be skipped
|
|
298
|
+
in environments without compilers.
|
|
299
|
+
"""
|
|
300
|
+
|
|
301
|
+
@classmethod
|
|
302
|
+
def setUpClass(cls):
|
|
303
|
+
"""Check if compilers are available."""
|
|
304
|
+
cls.samples_dir = Path(__file__).parent / "test_samples"
|
|
305
|
+
cls.has_clang = cls._check_compiler("clang")
|
|
306
|
+
cls.has_gcc = cls._check_compiler("gcc")
|
|
307
|
+
|
|
308
|
+
@staticmethod
|
|
309
|
+
def _check_compiler(name):
|
|
310
|
+
try:
|
|
311
|
+
subprocess.run([name, "--version"], capture_output=True, check=True)
|
|
312
|
+
return True
|
|
313
|
+
except (subprocess.CalledProcessError, FileNotFoundError):
|
|
314
|
+
return False
|
|
315
|
+
|
|
316
|
+
@unittest.skipUnless(lambda self: self.has_clang or self.has_gcc, "No C compiler available")
|
|
317
|
+
def test_vulnerable_c_detected(self):
|
|
318
|
+
"""Vulnerable C implementation should be detected."""
|
|
319
|
+
if not (self.has_clang or self.has_gcc):
|
|
320
|
+
self.skipTest("No C compiler available")
|
|
321
|
+
|
|
322
|
+
vulnerable_file = self.samples_dir / "decompose_vulnerable.c"
|
|
323
|
+
if not vulnerable_file.exists():
|
|
324
|
+
self.skipTest("Test sample not found")
|
|
325
|
+
|
|
326
|
+
compiler = "clang" if self.has_clang else "gcc"
|
|
327
|
+
|
|
328
|
+
try:
|
|
329
|
+
report = analyze_source(
|
|
330
|
+
str(vulnerable_file),
|
|
331
|
+
compiler=compiler,
|
|
332
|
+
optimization="O2",
|
|
333
|
+
)
|
|
334
|
+
|
|
335
|
+
# Should detect division instructions
|
|
336
|
+
self.assertFalse(report.passed, "Vulnerable code should fail analysis")
|
|
337
|
+
self.assertGreater(report.error_count, 0, "Should find error-level violations")
|
|
338
|
+
|
|
339
|
+
# Check that we found division-related violations
|
|
340
|
+
div_violations = [v for v in report.violations if "div" in v.mnemonic.lower()]
|
|
341
|
+
self.assertGreater(len(div_violations), 0, "Should detect division instructions")
|
|
342
|
+
|
|
343
|
+
except RuntimeError as e:
|
|
344
|
+
if "Compilation failed" in str(e):
|
|
345
|
+
self.skipTest(f"Compilation failed: {e}")
|
|
346
|
+
raise
|
|
347
|
+
|
|
348
|
+
@unittest.skipUnless(lambda self: self.has_clang or self.has_gcc, "No C compiler available")
|
|
349
|
+
def test_constant_time_c_clean(self):
|
|
350
|
+
"""Constant-time C implementation should pass."""
|
|
351
|
+
if not (self.has_clang or self.has_gcc):
|
|
352
|
+
self.skipTest("No C compiler available")
|
|
353
|
+
|
|
354
|
+
ct_file = self.samples_dir / "decompose_constant_time.c"
|
|
355
|
+
if not ct_file.exists():
|
|
356
|
+
self.skipTest("Test sample not found")
|
|
357
|
+
|
|
358
|
+
compiler = "clang" if self.has_clang else "gcc"
|
|
359
|
+
|
|
360
|
+
try:
|
|
361
|
+
report = analyze_source(
|
|
362
|
+
str(ct_file),
|
|
363
|
+
compiler=compiler,
|
|
364
|
+
optimization="O2",
|
|
365
|
+
)
|
|
366
|
+
|
|
367
|
+
# Constant-time implementation should not have division
|
|
368
|
+
div_violations = [
|
|
369
|
+
v
|
|
370
|
+
for v in report.violations
|
|
371
|
+
if "div" in v.mnemonic.lower() and v.severity == Severity.ERROR
|
|
372
|
+
]
|
|
373
|
+
|
|
374
|
+
# Note: We allow this to be empty OR the compiler might have
|
|
375
|
+
# optimized in unexpected ways
|
|
376
|
+
if div_violations:
|
|
377
|
+
print(f"WARNING: Found {len(div_violations)} division violations")
|
|
378
|
+
print("This may indicate the compiler optimized differently than expected")
|
|
379
|
+
|
|
380
|
+
except RuntimeError as e:
|
|
381
|
+
if "Compilation failed" in str(e):
|
|
382
|
+
self.skipTest(f"Compilation failed: {e}")
|
|
383
|
+
raise
|
|
384
|
+
|
|
385
|
+
def test_multiple_optimization_levels(self):
|
|
386
|
+
"""Test that analysis works across optimization levels."""
|
|
387
|
+
if not (self.has_clang or self.has_gcc):
|
|
388
|
+
self.skipTest("No C compiler available")
|
|
389
|
+
|
|
390
|
+
vulnerable_file = self.samples_dir / "decompose_vulnerable.c"
|
|
391
|
+
if not vulnerable_file.exists():
|
|
392
|
+
self.skipTest("Test sample not found")
|
|
393
|
+
|
|
394
|
+
compiler = "clang" if self.has_clang else "gcc"
|
|
395
|
+
|
|
396
|
+
for opt in ["O0", "O1", "O2", "O3"]:
|
|
397
|
+
with self.subTest(optimization=opt):
|
|
398
|
+
try:
|
|
399
|
+
report = analyze_source(
|
|
400
|
+
str(vulnerable_file),
|
|
401
|
+
compiler=compiler,
|
|
402
|
+
optimization=opt,
|
|
403
|
+
)
|
|
404
|
+
# Just verify it runs without error
|
|
405
|
+
self.assertIsNotNone(report)
|
|
406
|
+
|
|
407
|
+
except RuntimeError as e:
|
|
408
|
+
if "Compilation failed" in str(e):
|
|
409
|
+
# Some optimization levels may not work on all systems
|
|
410
|
+
continue
|
|
411
|
+
raise
|
|
412
|
+
|
|
413
|
+
|
|
414
|
+
class TestCrossArchitecture(unittest.TestCase):
|
|
415
|
+
"""Test cross-architecture compilation and analysis.
|
|
416
|
+
|
|
417
|
+
These tests verify that the analyzer can handle different target
|
|
418
|
+
architectures, even when cross-compiling from a different host.
|
|
419
|
+
"""
|
|
420
|
+
|
|
421
|
+
@classmethod
|
|
422
|
+
def setUpClass(cls):
|
|
423
|
+
cls.samples_dir = Path(__file__).parent / "test_samples"
|
|
424
|
+
cls.has_clang = TestIntegration._check_compiler("clang")
|
|
425
|
+
|
|
426
|
+
@unittest.skipUnless(lambda self: self.has_clang, "Clang required for cross-compilation")
|
|
427
|
+
def test_cross_compile_arm64(self):
|
|
428
|
+
"""Test cross-compilation to ARM64."""
|
|
429
|
+
if not self.has_clang:
|
|
430
|
+
self.skipTest("Clang not available")
|
|
431
|
+
|
|
432
|
+
vulnerable_file = self.samples_dir / "decompose_vulnerable.c"
|
|
433
|
+
if not vulnerable_file.exists():
|
|
434
|
+
self.skipTest("Test sample not found")
|
|
435
|
+
|
|
436
|
+
try:
|
|
437
|
+
report = analyze_source(
|
|
438
|
+
str(vulnerable_file),
|
|
439
|
+
arch="arm64",
|
|
440
|
+
compiler="clang",
|
|
441
|
+
optimization="O2",
|
|
442
|
+
)
|
|
443
|
+
|
|
444
|
+
# Should still detect violations, just ARM64 specific ones
|
|
445
|
+
self.assertIsNotNone(report)
|
|
446
|
+
self.assertEqual(report.architecture, "arm64")
|
|
447
|
+
|
|
448
|
+
except RuntimeError as e:
|
|
449
|
+
if "target" in str(e).lower() or "triple" in str(e).lower():
|
|
450
|
+
self.skipTest("ARM64 cross-compilation not supported")
|
|
451
|
+
raise
|
|
452
|
+
|
|
453
|
+
|
|
454
|
+
class TestScriptingLanguageDetection(unittest.TestCase):
|
|
455
|
+
"""Test language detection for scripting languages."""
|
|
456
|
+
|
|
457
|
+
def test_detect_php(self):
|
|
458
|
+
self.assertEqual(detect_language("crypto.php"), "php")
|
|
459
|
+
self.assertEqual(detect_language("/path/to/crypto.php"), "php")
|
|
460
|
+
|
|
461
|
+
def test_detect_javascript(self):
|
|
462
|
+
self.assertEqual(detect_language("crypto.js"), "javascript")
|
|
463
|
+
self.assertEqual(detect_language("crypto.mjs"), "javascript")
|
|
464
|
+
self.assertEqual(detect_language("crypto.cjs"), "javascript")
|
|
465
|
+
|
|
466
|
+
def test_detect_typescript(self):
|
|
467
|
+
self.assertEqual(detect_language("crypto.ts"), "typescript")
|
|
468
|
+
self.assertEqual(detect_language("crypto.tsx"), "typescript")
|
|
469
|
+
self.assertEqual(detect_language("crypto.mts"), "typescript")
|
|
470
|
+
|
|
471
|
+
|
|
472
|
+
class TestPHPAnalyzerParsing(unittest.TestCase):
|
|
473
|
+
"""Test PHP opcode parsing."""
|
|
474
|
+
|
|
475
|
+
def test_parse_vld_division(self):
|
|
476
|
+
"""Parser should detect PHP division opcodes."""
|
|
477
|
+
from script_analyzers import PHPAnalyzer
|
|
478
|
+
|
|
479
|
+
# Sample VLD output with division
|
|
480
|
+
vld_output = """
|
|
481
|
+
Finding entry points
|
|
482
|
+
Branch analysis from position: 0
|
|
483
|
+
filename: /path/to/test.php
|
|
484
|
+
function name: vulnerable_mod
|
|
485
|
+
number of ops: 8
|
|
486
|
+
compiled vars: !0 = $value, !1 = $modulus
|
|
487
|
+
line #* E I O op fetch ext return operands
|
|
488
|
+
-------------------------------------------------------------------------------------
|
|
489
|
+
5 0 E > RECV !0
|
|
490
|
+
1 RECV !1
|
|
491
|
+
6 2 DIV ~2 !0, !1
|
|
492
|
+
7 3 ASSIGN !2, ~2
|
|
493
|
+
8 4 MOD ~4 !0, !1
|
|
494
|
+
5 ASSIGN !3, ~4
|
|
495
|
+
9 6 RETURN !3
|
|
496
|
+
10 7 > RETURN null
|
|
497
|
+
"""
|
|
498
|
+
|
|
499
|
+
analyzer = PHPAnalyzer()
|
|
500
|
+
functions, violations = analyzer._parse_vld_output(vld_output)
|
|
501
|
+
|
|
502
|
+
# Should find DIV and MOD opcodes
|
|
503
|
+
error_violations = [v for v in violations if v.severity == Severity.ERROR]
|
|
504
|
+
self.assertGreater(len(error_violations), 0, "Should detect DIV/MOD opcodes")
|
|
505
|
+
|
|
506
|
+
div_violations = [v for v in error_violations if v.mnemonic in ("DIV", "MOD")]
|
|
507
|
+
self.assertEqual(len(div_violations), 2, "Should find both DIV and MOD")
|
|
508
|
+
|
|
509
|
+
def test_parse_vld_function_call(self):
|
|
510
|
+
"""Parser should detect dangerous PHP function calls."""
|
|
511
|
+
from script_analyzers import PHPAnalyzer
|
|
512
|
+
|
|
513
|
+
vld_output = """
|
|
514
|
+
filename: /path/to/test.php
|
|
515
|
+
function name: vulnerable_encode
|
|
516
|
+
number of ops: 5
|
|
517
|
+
compiled vars: !0 = $data
|
|
518
|
+
line #* E I O op fetch ext return operands
|
|
519
|
+
-------------------------------------------------------------------------------------
|
|
520
|
+
3 0 E > RECV !0
|
|
521
|
+
4 1 INIT_FCALL 'bin2hex'
|
|
522
|
+
2 SEND_VAR !0
|
|
523
|
+
3 DO_ICALL $1
|
|
524
|
+
5 4 > RETURN $1
|
|
525
|
+
"""
|
|
526
|
+
|
|
527
|
+
analyzer = PHPAnalyzer()
|
|
528
|
+
functions, violations = analyzer._parse_vld_output(vld_output)
|
|
529
|
+
|
|
530
|
+
# Should detect bin2hex call
|
|
531
|
+
error_violations = [v for v in violations if v.severity == Severity.ERROR]
|
|
532
|
+
self.assertGreater(len(error_violations), 0, "Should detect bin2hex call")
|
|
533
|
+
|
|
534
|
+
bin2hex_violations = [v for v in error_violations if "bin2hex" in v.mnemonic.lower()]
|
|
535
|
+
self.assertEqual(len(bin2hex_violations), 1)
|
|
536
|
+
|
|
537
|
+
def test_parse_vld_mt_rand(self):
|
|
538
|
+
"""Parser should detect mt_rand as dangerous."""
|
|
539
|
+
from script_analyzers import PHPAnalyzer
|
|
540
|
+
|
|
541
|
+
vld_output = """
|
|
542
|
+
filename: /path/to/test.php
|
|
543
|
+
function name: generate_token
|
|
544
|
+
line #* E I O op fetch ext return operands
|
|
545
|
+
-------------------------------------------------------------------------------------
|
|
546
|
+
3 0 E > INIT_FCALL 'mt_rand'
|
|
547
|
+
1 SEND_VAL 0
|
|
548
|
+
2 SEND_VAL 100
|
|
549
|
+
3 DO_ICALL $0
|
|
550
|
+
4 4 > RETURN $0
|
|
551
|
+
"""
|
|
552
|
+
|
|
553
|
+
analyzer = PHPAnalyzer()
|
|
554
|
+
functions, violations = analyzer._parse_vld_output(vld_output)
|
|
555
|
+
|
|
556
|
+
error_violations = [v for v in violations if v.severity == Severity.ERROR]
|
|
557
|
+
self.assertGreater(len(error_violations), 0, "Should detect mt_rand")
|
|
558
|
+
|
|
559
|
+
|
|
560
|
+
class TestJavaScriptAnalyzerParsing(unittest.TestCase):
|
|
561
|
+
"""Test JavaScript V8 bytecode parsing."""
|
|
562
|
+
|
|
563
|
+
def test_parse_v8_division(self):
|
|
564
|
+
"""Parser should detect V8 division bytecodes."""
|
|
565
|
+
from script_analyzers import JavaScriptAnalyzer
|
|
566
|
+
|
|
567
|
+
v8_output = """
|
|
568
|
+
[generated bytecode for function: vulnerableDiv (0x1234)]
|
|
569
|
+
Bytecode length: 20
|
|
570
|
+
Parameter count 3
|
|
571
|
+
Register count 2
|
|
572
|
+
Frame size 16
|
|
573
|
+
0 : Ldar a0
|
|
574
|
+
2 : Star0
|
|
575
|
+
3 : Ldar a1
|
|
576
|
+
5 : Div r0
|
|
577
|
+
7 : Star1
|
|
578
|
+
8 : Ldar r1
|
|
579
|
+
10 : Return
|
|
580
|
+
"""
|
|
581
|
+
|
|
582
|
+
analyzer = JavaScriptAnalyzer()
|
|
583
|
+
functions, violations = analyzer._parse_v8_bytecode(
|
|
584
|
+
v8_output, "test.js", include_warnings=False
|
|
585
|
+
)
|
|
586
|
+
|
|
587
|
+
self.assertEqual(len(functions), 1)
|
|
588
|
+
self.assertEqual(functions[0]["name"], "vulnerableDiv")
|
|
589
|
+
|
|
590
|
+
# Should find Div bytecode
|
|
591
|
+
error_violations = [v for v in violations if v.severity == Severity.ERROR]
|
|
592
|
+
self.assertGreater(len(error_violations), 0, "Should detect Div bytecode")
|
|
593
|
+
|
|
594
|
+
def test_parse_v8_modulo(self):
|
|
595
|
+
"""Parser should detect V8 modulo bytecodes."""
|
|
596
|
+
from script_analyzers import JavaScriptAnalyzer
|
|
597
|
+
|
|
598
|
+
v8_output = """
|
|
599
|
+
[generated bytecode for function: vulnerableMod (0x5678)]
|
|
600
|
+
Bytecode length: 15
|
|
601
|
+
Parameter count 3
|
|
602
|
+
Register count 1
|
|
603
|
+
Frame size 8
|
|
604
|
+
0 : Ldar a0
|
|
605
|
+
2 : Mod a1
|
|
606
|
+
4 : Return
|
|
607
|
+
"""
|
|
608
|
+
|
|
609
|
+
analyzer = JavaScriptAnalyzer()
|
|
610
|
+
functions, violations = analyzer._parse_v8_bytecode(v8_output, "test.js")
|
|
611
|
+
|
|
612
|
+
error_violations = [v for v in violations if v.severity == Severity.ERROR]
|
|
613
|
+
self.assertGreater(len(error_violations), 0, "Should detect Mod bytecode")
|
|
614
|
+
|
|
615
|
+
def test_detect_math_sqrt_in_source(self):
|
|
616
|
+
"""Should detect Math.sqrt() calls in source."""
|
|
617
|
+
# Create a temp file with Math.sqrt
|
|
618
|
+
import tempfile
|
|
619
|
+
|
|
620
|
+
from script_analyzers import JavaScriptAnalyzer
|
|
621
|
+
|
|
622
|
+
with tempfile.NamedTemporaryFile(mode="w", suffix=".js", delete=False) as f:
|
|
623
|
+
f.write("""
|
|
624
|
+
function vulnerable(x) {
|
|
625
|
+
return Math.sqrt(x);
|
|
626
|
+
}
|
|
627
|
+
""")
|
|
628
|
+
temp_path = f.name
|
|
629
|
+
|
|
630
|
+
try:
|
|
631
|
+
analyzer = JavaScriptAnalyzer()
|
|
632
|
+
violations = analyzer._detect_dangerous_function_calls(temp_path)
|
|
633
|
+
|
|
634
|
+
sqrt_violations = [v for v in violations if "SQRT" in v.mnemonic.upper()]
|
|
635
|
+
self.assertGreater(len(sqrt_violations), 0, "Should detect Math.sqrt()")
|
|
636
|
+
finally:
|
|
637
|
+
os.unlink(temp_path)
|
|
638
|
+
|
|
639
|
+
def test_detect_math_random_in_source(self):
|
|
640
|
+
"""Should detect Math.random() calls in source."""
|
|
641
|
+
import tempfile
|
|
642
|
+
|
|
643
|
+
from script_analyzers import JavaScriptAnalyzer
|
|
644
|
+
|
|
645
|
+
with tempfile.NamedTemporaryFile(mode="w", suffix=".js", delete=False) as f:
|
|
646
|
+
f.write("""
|
|
647
|
+
function generateToken() {
|
|
648
|
+
return Math.random().toString(36);
|
|
649
|
+
}
|
|
650
|
+
""")
|
|
651
|
+
temp_path = f.name
|
|
652
|
+
|
|
653
|
+
try:
|
|
654
|
+
analyzer = JavaScriptAnalyzer()
|
|
655
|
+
violations = analyzer._detect_dangerous_function_calls(temp_path)
|
|
656
|
+
|
|
657
|
+
random_violations = [v for v in violations if "RANDOM" in v.mnemonic.upper()]
|
|
658
|
+
self.assertGreater(len(random_violations), 0, "Should detect Math.random()")
|
|
659
|
+
finally:
|
|
660
|
+
os.unlink(temp_path)
|
|
661
|
+
|
|
662
|
+
|
|
663
|
+
class TestPythonAnalyzerParsing(unittest.TestCase):
|
|
664
|
+
"""Test Python dis bytecode parsing."""
|
|
665
|
+
|
|
666
|
+
def test_parse_dis_division_python311(self):
|
|
667
|
+
"""Parser should detect Python 3.11+ BINARY_OP division."""
|
|
668
|
+
from script_analyzers import PythonAnalyzer
|
|
669
|
+
|
|
670
|
+
# Python 3.11+ dis output format
|
|
671
|
+
dis_output = """
|
|
672
|
+
Disassembly of <code object vulnerable_div at 0x1234>:
|
|
673
|
+
3 0 RESUME 0
|
|
674
|
+
|
|
675
|
+
4 2 LOAD_FAST 0 (value)
|
|
676
|
+
4 LOAD_FAST 1 (modulus)
|
|
677
|
+
6 BINARY_OP 11 (/)
|
|
678
|
+
8 STORE_FAST 2 (result)
|
|
679
|
+
|
|
680
|
+
5 10 LOAD_FAST 0 (value)
|
|
681
|
+
12 LOAD_FAST 1 (modulus)
|
|
682
|
+
14 BINARY_OP 6 (%)
|
|
683
|
+
16 STORE_FAST 3 (remainder)
|
|
684
|
+
18 RETURN_VALUE
|
|
685
|
+
"""
|
|
686
|
+
|
|
687
|
+
analyzer = PythonAnalyzer()
|
|
688
|
+
functions, violations = analyzer._parse_dis_output(dis_output, "test.py")
|
|
689
|
+
|
|
690
|
+
self.assertEqual(len(functions), 1)
|
|
691
|
+
self.assertEqual(functions[0]["name"], "vulnerable_div")
|
|
692
|
+
|
|
693
|
+
# Should find BINARY_OP division and modulo
|
|
694
|
+
error_violations = [v for v in violations if v.severity == Severity.ERROR]
|
|
695
|
+
self.assertEqual(len(error_violations), 2, "Should detect both / and % operators")
|
|
696
|
+
|
|
697
|
+
def test_parse_dis_division_python310(self):
|
|
698
|
+
"""Parser should detect Python < 3.11 division bytecodes."""
|
|
699
|
+
from script_analyzers import PythonAnalyzer
|
|
700
|
+
|
|
701
|
+
# Python < 3.11 dis output format
|
|
702
|
+
dis_output = """
|
|
703
|
+
Disassembly of <code object vulnerable_div at 0x1234>:
|
|
704
|
+
3 0 LOAD_FAST 0 (value)
|
|
705
|
+
2 LOAD_FAST 1 (modulus)
|
|
706
|
+
4 BINARY_TRUE_DIVIDE
|
|
707
|
+
6 STORE_FAST 2 (result)
|
|
708
|
+
8 LOAD_FAST 0 (value)
|
|
709
|
+
10 LOAD_FAST 1 (modulus)
|
|
710
|
+
12 BINARY_MODULO
|
|
711
|
+
14 STORE_FAST 3 (remainder)
|
|
712
|
+
16 LOAD_CONST 0 (None)
|
|
713
|
+
18 RETURN_VALUE
|
|
714
|
+
"""
|
|
715
|
+
|
|
716
|
+
analyzer = PythonAnalyzer()
|
|
717
|
+
functions, violations = analyzer._parse_dis_output(dis_output, "test.py")
|
|
718
|
+
|
|
719
|
+
error_violations = [v for v in violations if v.severity == Severity.ERROR]
|
|
720
|
+
self.assertEqual(
|
|
721
|
+
len(error_violations), 2, "Should detect BINARY_TRUE_DIVIDE and BINARY_MODULO"
|
|
722
|
+
)
|
|
723
|
+
|
|
724
|
+
mnemonics = {v.mnemonic for v in error_violations}
|
|
725
|
+
self.assertIn("BINARY_TRUE_DIVIDE", mnemonics)
|
|
726
|
+
self.assertIn("BINARY_MODULO", mnemonics)
|
|
727
|
+
|
|
728
|
+
def test_detect_random_in_source(self):
|
|
729
|
+
"""Should detect random.random() calls in source."""
|
|
730
|
+
import tempfile
|
|
731
|
+
|
|
732
|
+
from script_analyzers import PythonAnalyzer
|
|
733
|
+
|
|
734
|
+
with tempfile.NamedTemporaryFile(mode="w", suffix=".py", delete=False) as f:
|
|
735
|
+
f.write("""
|
|
736
|
+
import random
|
|
737
|
+
|
|
738
|
+
def generate_token():
|
|
739
|
+
return random.random()
|
|
740
|
+
|
|
741
|
+
def generate_int():
|
|
742
|
+
return random.randint(0, 100)
|
|
743
|
+
""")
|
|
744
|
+
temp_path = f.name
|
|
745
|
+
|
|
746
|
+
try:
|
|
747
|
+
analyzer = PythonAnalyzer()
|
|
748
|
+
violations = analyzer._detect_dangerous_function_calls(temp_path)
|
|
749
|
+
|
|
750
|
+
random_violations = [v for v in violations if "RANDOM" in v.mnemonic.upper()]
|
|
751
|
+
self.assertEqual(
|
|
752
|
+
len(random_violations), 2, "Should detect random.random() and random.randint()"
|
|
753
|
+
)
|
|
754
|
+
finally:
|
|
755
|
+
os.unlink(temp_path)
|
|
756
|
+
|
|
757
|
+
def test_detect_math_sqrt_in_source(self):
|
|
758
|
+
"""Should detect math.sqrt() calls in source."""
|
|
759
|
+
import tempfile
|
|
760
|
+
|
|
761
|
+
from script_analyzers import PythonAnalyzer
|
|
762
|
+
|
|
763
|
+
with tempfile.NamedTemporaryFile(mode="w", suffix=".py", delete=False) as f:
|
|
764
|
+
f.write("""
|
|
765
|
+
import math
|
|
766
|
+
|
|
767
|
+
def vulnerable(x):
|
|
768
|
+
return math.sqrt(x)
|
|
769
|
+
""")
|
|
770
|
+
temp_path = f.name
|
|
771
|
+
|
|
772
|
+
try:
|
|
773
|
+
analyzer = PythonAnalyzer()
|
|
774
|
+
violations = analyzer._detect_dangerous_function_calls(temp_path)
|
|
775
|
+
|
|
776
|
+
sqrt_violations = [v for v in violations if "SQRT" in v.mnemonic.upper()]
|
|
777
|
+
self.assertGreater(len(sqrt_violations), 0, "Should detect math.sqrt()")
|
|
778
|
+
finally:
|
|
779
|
+
os.unlink(temp_path)
|
|
780
|
+
|
|
781
|
+
|
|
782
|
+
class TestRubyAnalyzerParsing(unittest.TestCase):
|
|
783
|
+
"""Test Ruby YARV bytecode parsing."""
|
|
784
|
+
|
|
785
|
+
def test_parse_yarv_division(self):
|
|
786
|
+
"""Parser should detect Ruby opt_div bytecodes."""
|
|
787
|
+
from script_analyzers import RubyAnalyzer
|
|
788
|
+
|
|
789
|
+
# Ruby YARV output format
|
|
790
|
+
yarv_output = """
|
|
791
|
+
== disasm: #<ISeq:<main>@test.rb:1 (1,0)-(10,3)>
|
|
792
|
+
0000 putobject 10
|
|
793
|
+
0002 putobject 3
|
|
794
|
+
0004 opt_div <calldata!mid:/, argc:1, ARGS_SIMPLE>
|
|
795
|
+
0006 leave
|
|
796
|
+
|
|
797
|
+
== disasm: #<ISeq:vulnerable_mod@test.rb:5 (5,0)-(8,3)>
|
|
798
|
+
local table (size: 2, argc: 2 [opts: 0, rest: -1, post: 0, block: -1, kw: -1@-1, kwrest: -1])
|
|
799
|
+
[ 2] value@0 [ 1] modulus@1
|
|
800
|
+
0000 getlocal_WC_0 value@0
|
|
801
|
+
0002 getlocal_WC_0 modulus@1
|
|
802
|
+
0004 opt_mod <calldata!mid:%, argc:1, ARGS_SIMPLE>
|
|
803
|
+
0006 leave
|
|
804
|
+
"""
|
|
805
|
+
|
|
806
|
+
analyzer = RubyAnalyzer()
|
|
807
|
+
functions, violations = analyzer._parse_yarv_output(yarv_output, "test.rb")
|
|
808
|
+
|
|
809
|
+
self.assertEqual(len(functions), 2)
|
|
810
|
+
self.assertEqual(functions[0]["name"], "<main>")
|
|
811
|
+
self.assertEqual(functions[1]["name"], "vulnerable_mod")
|
|
812
|
+
|
|
813
|
+
# Should find opt_div and opt_mod
|
|
814
|
+
error_violations = [v for v in violations if v.severity == Severity.ERROR]
|
|
815
|
+
self.assertEqual(len(error_violations), 2, "Should detect opt_div and opt_mod")
|
|
816
|
+
|
|
817
|
+
mnemonics = {v.mnemonic for v in error_violations}
|
|
818
|
+
self.assertIn("OPT_DIV", mnemonics)
|
|
819
|
+
self.assertIn("OPT_MOD", mnemonics)
|
|
820
|
+
|
|
821
|
+
def test_parse_yarv_warnings(self):
|
|
822
|
+
"""Parser should detect Ruby comparison bytecodes as warnings."""
|
|
823
|
+
from script_analyzers import RubyAnalyzer
|
|
824
|
+
|
|
825
|
+
yarv_output = """
|
|
826
|
+
== disasm: #<ISeq:compare@test.rb:1 (1,0)-(3,3)>
|
|
827
|
+
0000 getlocal_WC_0 a@0
|
|
828
|
+
0002 getlocal_WC_0 b@1
|
|
829
|
+
0004 opt_eq <calldata!mid:==, argc:1, ARGS_SIMPLE>
|
|
830
|
+
0006 branchif 10
|
|
831
|
+
0008 putnil
|
|
832
|
+
0009 leave
|
|
833
|
+
0010 putobject true
|
|
834
|
+
0012 leave
|
|
835
|
+
"""
|
|
836
|
+
|
|
837
|
+
analyzer = RubyAnalyzer()
|
|
838
|
+
functions, violations = analyzer._parse_yarv_output(
|
|
839
|
+
yarv_output, "test.rb", include_warnings=True
|
|
840
|
+
)
|
|
841
|
+
|
|
842
|
+
warning_violations = [v for v in violations if v.severity == Severity.WARNING]
|
|
843
|
+
self.assertGreater(
|
|
844
|
+
len(warning_violations), 0, "Should detect opt_eq and branchif as warnings"
|
|
845
|
+
)
|
|
846
|
+
|
|
847
|
+
def test_detect_rand_in_source(self):
|
|
848
|
+
"""Should detect rand() calls in source."""
|
|
849
|
+
import tempfile
|
|
850
|
+
|
|
851
|
+
from script_analyzers import RubyAnalyzer
|
|
852
|
+
|
|
853
|
+
with tempfile.NamedTemporaryFile(mode="w", suffix=".rb", delete=False) as f:
|
|
854
|
+
f.write("""
|
|
855
|
+
def generate_token
|
|
856
|
+
rand(100)
|
|
857
|
+
end
|
|
858
|
+
|
|
859
|
+
def generate_random
|
|
860
|
+
Random.new.rand
|
|
861
|
+
end
|
|
862
|
+
""")
|
|
863
|
+
temp_path = f.name
|
|
864
|
+
|
|
865
|
+
try:
|
|
866
|
+
analyzer = RubyAnalyzer()
|
|
867
|
+
violations = analyzer._detect_dangerous_function_calls(temp_path)
|
|
868
|
+
|
|
869
|
+
rand_violations = [v for v in violations if "RAND" in v.mnemonic.upper()]
|
|
870
|
+
self.assertGreater(len(rand_violations), 0, "Should detect rand() calls")
|
|
871
|
+
finally:
|
|
872
|
+
os.unlink(temp_path)
|
|
873
|
+
|
|
874
|
+
|
|
875
|
+
class TestJavaAnalyzerParsing(unittest.TestCase):
|
|
876
|
+
"""Test Java bytecode parsing."""
|
|
877
|
+
|
|
878
|
+
def test_parse_javap_division(self):
|
|
879
|
+
"""Parser should detect Java division bytecodes."""
|
|
880
|
+
from script_analyzers import JavaAnalyzer
|
|
881
|
+
|
|
882
|
+
# Sample javap output with division
|
|
883
|
+
javap_output = """
|
|
884
|
+
public class CryptoUtils {
|
|
885
|
+
public int vulnerableDiv(int, int);
|
|
886
|
+
Code:
|
|
887
|
+
0: iload_1
|
|
888
|
+
1: iload_2
|
|
889
|
+
2: idiv
|
|
890
|
+
3: istore_3
|
|
891
|
+
4: iload_1
|
|
892
|
+
5: iload_2
|
|
893
|
+
6: irem
|
|
894
|
+
7: istore 4
|
|
895
|
+
9: iload_3
|
|
896
|
+
10: ireturn
|
|
897
|
+
LineNumberTable:
|
|
898
|
+
line 5: 0
|
|
899
|
+
line 6: 4
|
|
900
|
+
line 7: 9
|
|
901
|
+
}
|
|
902
|
+
"""
|
|
903
|
+
|
|
904
|
+
analyzer = JavaAnalyzer()
|
|
905
|
+
functions, violations = analyzer._parse_javap_output(javap_output, "CryptoUtils.java")
|
|
906
|
+
|
|
907
|
+
self.assertEqual(len(functions), 1)
|
|
908
|
+
self.assertEqual(functions[0]["name"], "CryptoUtils.vulnerableDiv")
|
|
909
|
+
|
|
910
|
+
# Should find idiv and irem bytecodes
|
|
911
|
+
error_violations = [v for v in violations if v.severity == Severity.ERROR]
|
|
912
|
+
self.assertEqual(len(error_violations), 2, "Should detect idiv and irem")
|
|
913
|
+
|
|
914
|
+
mnemonics = {v.mnemonic for v in error_violations}
|
|
915
|
+
self.assertIn("IDIV", mnemonics)
|
|
916
|
+
self.assertIn("IREM", mnemonics)
|
|
917
|
+
|
|
918
|
+
def test_parse_javap_long_division(self):
|
|
919
|
+
"""Parser should detect Java long division bytecodes."""
|
|
920
|
+
from script_analyzers import JavaAnalyzer
|
|
921
|
+
|
|
922
|
+
javap_output = """
|
|
923
|
+
public class CryptoUtils {
|
|
924
|
+
public long vulnerableLongDiv(long, long);
|
|
925
|
+
Code:
|
|
926
|
+
0: lload_1
|
|
927
|
+
1: lload_3
|
|
928
|
+
2: ldiv
|
|
929
|
+
3: lreturn
|
|
930
|
+
}
|
|
931
|
+
"""
|
|
932
|
+
|
|
933
|
+
analyzer = JavaAnalyzer()
|
|
934
|
+
functions, violations = analyzer._parse_javap_output(javap_output, "CryptoUtils.java")
|
|
935
|
+
|
|
936
|
+
error_violations = [v for v in violations if v.severity == Severity.ERROR]
|
|
937
|
+
self.assertGreater(len(error_violations), 0, "Should detect ldiv")
|
|
938
|
+
self.assertEqual(error_violations[0].mnemonic, "LDIV")
|
|
939
|
+
|
|
940
|
+
def test_parse_javap_float_division(self):
|
|
941
|
+
"""Parser should detect Java float division bytecodes."""
|
|
942
|
+
from script_analyzers import JavaAnalyzer
|
|
943
|
+
|
|
944
|
+
javap_output = """
|
|
945
|
+
public class CryptoUtils {
|
|
946
|
+
public double vulnerableFloatDiv(double, double);
|
|
947
|
+
Code:
|
|
948
|
+
0: dload_1
|
|
949
|
+
1: dload_3
|
|
950
|
+
2: ddiv
|
|
951
|
+
3: dreturn
|
|
952
|
+
}
|
|
953
|
+
"""
|
|
954
|
+
|
|
955
|
+
analyzer = JavaAnalyzer()
|
|
956
|
+
functions, violations = analyzer._parse_javap_output(javap_output, "CryptoUtils.java")
|
|
957
|
+
|
|
958
|
+
error_violations = [v for v in violations if v.severity == Severity.ERROR]
|
|
959
|
+
self.assertGreater(len(error_violations), 0, "Should detect ddiv")
|
|
960
|
+
self.assertEqual(error_violations[0].mnemonic, "DDIV")
|
|
961
|
+
|
|
962
|
+
def test_parse_javap_warnings(self):
|
|
963
|
+
"""Parser should detect Java conditional branches as warnings."""
|
|
964
|
+
from script_analyzers import JavaAnalyzer
|
|
965
|
+
|
|
966
|
+
javap_output = """
|
|
967
|
+
public class CryptoUtils {
|
|
968
|
+
public boolean compare(int, int);
|
|
969
|
+
Code:
|
|
970
|
+
0: iload_1
|
|
971
|
+
1: iload_2
|
|
972
|
+
2: if_icmpne 9
|
|
973
|
+
5: iconst_1
|
|
974
|
+
6: goto 10
|
|
975
|
+
9: iconst_0
|
|
976
|
+
10: ireturn
|
|
977
|
+
}
|
|
978
|
+
"""
|
|
979
|
+
|
|
980
|
+
analyzer = JavaAnalyzer()
|
|
981
|
+
functions, violations = analyzer._parse_javap_output(
|
|
982
|
+
javap_output, "CryptoUtils.java", include_warnings=True
|
|
983
|
+
)
|
|
984
|
+
|
|
985
|
+
warning_violations = [v for v in violations if v.severity == Severity.WARNING]
|
|
986
|
+
self.assertGreater(len(warning_violations), 0, "Should detect if_icmpne as warning")
|
|
987
|
+
|
|
988
|
+
def test_detect_java_random_in_source(self):
|
|
989
|
+
"""Should detect new Random() calls in Java source."""
|
|
990
|
+
import tempfile
|
|
991
|
+
|
|
992
|
+
from script_analyzers import JavaAnalyzer
|
|
993
|
+
|
|
994
|
+
with tempfile.NamedTemporaryFile(mode="w", suffix=".java", delete=False) as f:
|
|
995
|
+
f.write("""
|
|
996
|
+
public class Test {
|
|
997
|
+
public int generate() {
|
|
998
|
+
Random rand = new Random();
|
|
999
|
+
return rand.nextInt(100);
|
|
1000
|
+
}
|
|
1001
|
+
}
|
|
1002
|
+
""")
|
|
1003
|
+
temp_path = f.name
|
|
1004
|
+
|
|
1005
|
+
try:
|
|
1006
|
+
analyzer = JavaAnalyzer()
|
|
1007
|
+
violations = analyzer._detect_dangerous_function_calls(temp_path)
|
|
1008
|
+
|
|
1009
|
+
random_violations = [v for v in violations if "RANDOM" in v.mnemonic.upper()]
|
|
1010
|
+
self.assertGreater(len(random_violations), 0, "Should detect new Random()")
|
|
1011
|
+
finally:
|
|
1012
|
+
os.unlink(temp_path)
|
|
1013
|
+
|
|
1014
|
+
def test_detect_math_sqrt_in_java_source(self):
|
|
1015
|
+
"""Should detect Math.sqrt() calls in Java source."""
|
|
1016
|
+
import tempfile
|
|
1017
|
+
|
|
1018
|
+
from script_analyzers import JavaAnalyzer
|
|
1019
|
+
|
|
1020
|
+
with tempfile.NamedTemporaryFile(mode="w", suffix=".java", delete=False) as f:
|
|
1021
|
+
f.write("""
|
|
1022
|
+
public class Test {
|
|
1023
|
+
public double calculate(double x) {
|
|
1024
|
+
return Math.sqrt(x);
|
|
1025
|
+
}
|
|
1026
|
+
}
|
|
1027
|
+
""")
|
|
1028
|
+
temp_path = f.name
|
|
1029
|
+
|
|
1030
|
+
try:
|
|
1031
|
+
analyzer = JavaAnalyzer()
|
|
1032
|
+
violations = analyzer._detect_dangerous_function_calls(temp_path)
|
|
1033
|
+
|
|
1034
|
+
sqrt_violations = [v for v in violations if "SQRT" in v.mnemonic.upper()]
|
|
1035
|
+
self.assertGreater(len(sqrt_violations), 0, "Should detect Math.sqrt()")
|
|
1036
|
+
finally:
|
|
1037
|
+
os.unlink(temp_path)
|
|
1038
|
+
|
|
1039
|
+
|
|
1040
|
+
class TestCSharpAnalyzerParsing(unittest.TestCase):
|
|
1041
|
+
"""Test C# IL bytecode parsing."""
|
|
1042
|
+
|
|
1043
|
+
def test_parse_il_division(self):
|
|
1044
|
+
"""Parser should detect C# division IL opcodes."""
|
|
1045
|
+
from script_analyzers import CSharpAnalyzer
|
|
1046
|
+
|
|
1047
|
+
# Sample IL output with division
|
|
1048
|
+
il_output = """
|
|
1049
|
+
.method public hidebysig instance int32 VulnerableDiv(int32, int32) cil managed
|
|
1050
|
+
{
|
|
1051
|
+
.maxstack 2
|
|
1052
|
+
.locals init (int32 V_0)
|
|
1053
|
+
IL_0000: ldarg.1
|
|
1054
|
+
IL_0001: ldarg.2
|
|
1055
|
+
IL_0002: div
|
|
1056
|
+
IL_0003: stloc.0
|
|
1057
|
+
IL_0004: ldarg.1
|
|
1058
|
+
IL_0005: ldarg.2
|
|
1059
|
+
IL_0006: rem
|
|
1060
|
+
IL_0007: stloc.1
|
|
1061
|
+
IL_0008: ldloc.0
|
|
1062
|
+
IL_0009: ret
|
|
1063
|
+
}
|
|
1064
|
+
"""
|
|
1065
|
+
|
|
1066
|
+
analyzer = CSharpAnalyzer()
|
|
1067
|
+
functions, violations = analyzer._parse_il_output(il_output, "CryptoUtils.cs")
|
|
1068
|
+
|
|
1069
|
+
self.assertEqual(len(functions), 1)
|
|
1070
|
+
self.assertEqual(functions[0]["name"], "VulnerableDiv")
|
|
1071
|
+
|
|
1072
|
+
# Should find div and rem opcodes
|
|
1073
|
+
error_violations = [v for v in violations if v.severity == Severity.ERROR]
|
|
1074
|
+
self.assertEqual(len(error_violations), 2, "Should detect div and rem")
|
|
1075
|
+
|
|
1076
|
+
mnemonics = {v.mnemonic for v in error_violations}
|
|
1077
|
+
self.assertIn("DIV", mnemonics)
|
|
1078
|
+
self.assertIn("REM", mnemonics)
|
|
1079
|
+
|
|
1080
|
+
def test_parse_il_unsigned_division(self):
|
|
1081
|
+
"""Parser should detect C# unsigned division IL opcodes."""
|
|
1082
|
+
from script_analyzers import CSharpAnalyzer
|
|
1083
|
+
|
|
1084
|
+
il_output = """
|
|
1085
|
+
.method public hidebysig static uint32 UnsignedDiv(uint32, uint32) cil managed
|
|
1086
|
+
{
|
|
1087
|
+
.maxstack 2
|
|
1088
|
+
IL_0000: ldarg.0
|
|
1089
|
+
IL_0001: ldarg.1
|
|
1090
|
+
IL_0002: div.un
|
|
1091
|
+
IL_0003: ret
|
|
1092
|
+
}
|
|
1093
|
+
"""
|
|
1094
|
+
|
|
1095
|
+
analyzer = CSharpAnalyzer()
|
|
1096
|
+
functions, violations = analyzer._parse_il_output(il_output, "CryptoUtils.cs")
|
|
1097
|
+
|
|
1098
|
+
error_violations = [v for v in violations if v.severity == Severity.ERROR]
|
|
1099
|
+
self.assertGreater(len(error_violations), 0, "Should detect div.un")
|
|
1100
|
+
self.assertEqual(error_violations[0].mnemonic, "DIV.UN")
|
|
1101
|
+
|
|
1102
|
+
def test_parse_il_warnings(self):
|
|
1103
|
+
"""Parser should detect C# conditional branches as warnings."""
|
|
1104
|
+
from script_analyzers import CSharpAnalyzer
|
|
1105
|
+
|
|
1106
|
+
il_output = """
|
|
1107
|
+
.method public hidebysig instance bool Compare(int32, int32) cil managed
|
|
1108
|
+
{
|
|
1109
|
+
.maxstack 2
|
|
1110
|
+
IL_0000: ldarg.1
|
|
1111
|
+
IL_0001: ldarg.2
|
|
1112
|
+
IL_0002: beq.s IL_0006
|
|
1113
|
+
IL_0004: ldc.i4.0
|
|
1114
|
+
IL_0005: ret
|
|
1115
|
+
IL_0006: ldc.i4.1
|
|
1116
|
+
IL_0007: ret
|
|
1117
|
+
}
|
|
1118
|
+
"""
|
|
1119
|
+
|
|
1120
|
+
analyzer = CSharpAnalyzer()
|
|
1121
|
+
functions, violations = analyzer._parse_il_output(
|
|
1122
|
+
il_output, "CryptoUtils.cs", include_warnings=True
|
|
1123
|
+
)
|
|
1124
|
+
|
|
1125
|
+
warning_violations = [v for v in violations if v.severity == Severity.WARNING]
|
|
1126
|
+
self.assertGreater(len(warning_violations), 0, "Should detect beq.s as warning")
|
|
1127
|
+
|
|
1128
|
+
def test_detect_csharp_random_in_source(self):
|
|
1129
|
+
"""Should detect new Random() calls in C# source."""
|
|
1130
|
+
import tempfile
|
|
1131
|
+
|
|
1132
|
+
from script_analyzers import CSharpAnalyzer
|
|
1133
|
+
|
|
1134
|
+
with tempfile.NamedTemporaryFile(mode="w", suffix=".cs", delete=False) as f:
|
|
1135
|
+
f.write("""
|
|
1136
|
+
public class Test {
|
|
1137
|
+
public int Generate() {
|
|
1138
|
+
Random rand = new Random();
|
|
1139
|
+
return rand.Next(100);
|
|
1140
|
+
}
|
|
1141
|
+
}
|
|
1142
|
+
""")
|
|
1143
|
+
temp_path = f.name
|
|
1144
|
+
|
|
1145
|
+
try:
|
|
1146
|
+
analyzer = CSharpAnalyzer()
|
|
1147
|
+
violations = analyzer._detect_dangerous_function_calls(temp_path)
|
|
1148
|
+
|
|
1149
|
+
random_violations = [v for v in violations if "RANDOM" in v.mnemonic.upper()]
|
|
1150
|
+
self.assertGreater(len(random_violations), 0, "Should detect new Random()")
|
|
1151
|
+
finally:
|
|
1152
|
+
os.unlink(temp_path)
|
|
1153
|
+
|
|
1154
|
+
def test_detect_math_sqrt_in_csharp_source(self):
|
|
1155
|
+
"""Should detect Math.Sqrt() calls in C# source."""
|
|
1156
|
+
import tempfile
|
|
1157
|
+
|
|
1158
|
+
from script_analyzers import CSharpAnalyzer
|
|
1159
|
+
|
|
1160
|
+
with tempfile.NamedTemporaryFile(mode="w", suffix=".cs", delete=False) as f:
|
|
1161
|
+
f.write("""
|
|
1162
|
+
public class Test {
|
|
1163
|
+
public double Calculate(double x) {
|
|
1164
|
+
return Math.Sqrt(x);
|
|
1165
|
+
}
|
|
1166
|
+
}
|
|
1167
|
+
""")
|
|
1168
|
+
temp_path = f.name
|
|
1169
|
+
|
|
1170
|
+
try:
|
|
1171
|
+
analyzer = CSharpAnalyzer()
|
|
1172
|
+
violations = analyzer._detect_dangerous_function_calls(temp_path)
|
|
1173
|
+
|
|
1174
|
+
sqrt_violations = [v for v in violations if "SQRT" in v.mnemonic.upper()]
|
|
1175
|
+
self.assertGreater(len(sqrt_violations), 0, "Should detect Math.Sqrt()")
|
|
1176
|
+
finally:
|
|
1177
|
+
os.unlink(temp_path)
|
|
1178
|
+
|
|
1179
|
+
def test_source_only_fallback(self):
|
|
1180
|
+
"""Source-only analysis should detect division operators."""
|
|
1181
|
+
import tempfile
|
|
1182
|
+
|
|
1183
|
+
from script_analyzers import CSharpAnalyzer
|
|
1184
|
+
|
|
1185
|
+
with tempfile.NamedTemporaryFile(mode="w", suffix=".cs", delete=False) as f:
|
|
1186
|
+
f.write("""
|
|
1187
|
+
public class Test {
|
|
1188
|
+
public int Divide(int a, int b) {
|
|
1189
|
+
return a / b;
|
|
1190
|
+
}
|
|
1191
|
+
public int Modulo(int a, int b) {
|
|
1192
|
+
return a % b;
|
|
1193
|
+
}
|
|
1194
|
+
}
|
|
1195
|
+
""")
|
|
1196
|
+
temp_path = f.name
|
|
1197
|
+
|
|
1198
|
+
try:
|
|
1199
|
+
analyzer = CSharpAnalyzer()
|
|
1200
|
+
report = analyzer._analyze_source_only(temp_path)
|
|
1201
|
+
|
|
1202
|
+
# Should detect division and modulo operators
|
|
1203
|
+
div_violations = [v for v in report.violations if "DIV" in v.mnemonic]
|
|
1204
|
+
mod_violations = [v for v in report.violations if "REM" in v.mnemonic]
|
|
1205
|
+
|
|
1206
|
+
self.assertGreater(len(div_violations), 0, "Should detect / operator")
|
|
1207
|
+
self.assertGreater(len(mod_violations), 0, "Should detect % operator")
|
|
1208
|
+
finally:
|
|
1209
|
+
os.unlink(temp_path)
|
|
1210
|
+
|
|
1211
|
+
|
|
1212
|
+
class TestScriptAnalyzerIntegration(unittest.TestCase):
|
|
1213
|
+
"""Integration tests for scripting language analyzers.
|
|
1214
|
+
|
|
1215
|
+
These tests require PHP/Node.js/Python/Ruby to be installed.
|
|
1216
|
+
"""
|
|
1217
|
+
|
|
1218
|
+
@classmethod
|
|
1219
|
+
def setUpClass(cls):
|
|
1220
|
+
cls.samples_dir = Path(__file__).parent / "test_samples"
|
|
1221
|
+
cls.has_php = cls._check_runtime("php")
|
|
1222
|
+
cls.has_node = cls._check_runtime("node")
|
|
1223
|
+
cls.has_python = cls._check_runtime("python3")
|
|
1224
|
+
cls.has_ruby = cls._check_runtime("ruby")
|
|
1225
|
+
|
|
1226
|
+
@staticmethod
|
|
1227
|
+
def _check_runtime(name):
|
|
1228
|
+
try:
|
|
1229
|
+
subprocess.run([name, "--version"], capture_output=True, check=True)
|
|
1230
|
+
return True
|
|
1231
|
+
except (subprocess.CalledProcessError, FileNotFoundError):
|
|
1232
|
+
return False
|
|
1233
|
+
|
|
1234
|
+
def test_php_vulnerable_detected(self):
|
|
1235
|
+
"""Vulnerable PHP code should be detected."""
|
|
1236
|
+
if not self.has_php:
|
|
1237
|
+
self.skipTest("PHP not available")
|
|
1238
|
+
|
|
1239
|
+
vulnerable_file = self.samples_dir / "vulnerable.php"
|
|
1240
|
+
if not vulnerable_file.exists():
|
|
1241
|
+
self.skipTest("PHP test sample not found")
|
|
1242
|
+
|
|
1243
|
+
try:
|
|
1244
|
+
report = analyze_source(str(vulnerable_file), include_warnings=False)
|
|
1245
|
+
|
|
1246
|
+
# Should detect dangerous operations
|
|
1247
|
+
self.assertIsNotNone(report)
|
|
1248
|
+
self.assertEqual(report.architecture, "zend")
|
|
1249
|
+
|
|
1250
|
+
# Check for expected violations (div, mod, or dangerous functions)
|
|
1251
|
+
if report.error_count > 0:
|
|
1252
|
+
self.assertFalse(report.passed, "Should fail with violations")
|
|
1253
|
+
|
|
1254
|
+
except RuntimeError as e:
|
|
1255
|
+
if "VLD" in str(e) or "opcache" in str(e).lower():
|
|
1256
|
+
# VLD/opcache may not produce output for simple files
|
|
1257
|
+
pass
|
|
1258
|
+
else:
|
|
1259
|
+
raise
|
|
1260
|
+
|
|
1261
|
+
def test_javascript_vulnerable_detected(self):
|
|
1262
|
+
"""Vulnerable JavaScript code should be detected."""
|
|
1263
|
+
if not self.has_node:
|
|
1264
|
+
self.skipTest("Node.js not available")
|
|
1265
|
+
|
|
1266
|
+
# Create a simple vulnerable JS file for testing
|
|
1267
|
+
import tempfile
|
|
1268
|
+
|
|
1269
|
+
with tempfile.NamedTemporaryFile(mode="w", suffix=".js", delete=False) as f:
|
|
1270
|
+
f.write("""
|
|
1271
|
+
function vulnerableDiv(a, b) {
|
|
1272
|
+
return a / b;
|
|
1273
|
+
}
|
|
1274
|
+
|
|
1275
|
+
function vulnerableRandom() {
|
|
1276
|
+
return Math.random();
|
|
1277
|
+
}
|
|
1278
|
+
|
|
1279
|
+
// Call functions to ensure they're compiled
|
|
1280
|
+
console.log(vulnerableDiv(10, 3));
|
|
1281
|
+
console.log(vulnerableRandom());
|
|
1282
|
+
""")
|
|
1283
|
+
temp_path = f.name
|
|
1284
|
+
|
|
1285
|
+
try:
|
|
1286
|
+
report = analyze_source(temp_path, include_warnings=False)
|
|
1287
|
+
|
|
1288
|
+
self.assertIsNotNone(report)
|
|
1289
|
+
self.assertEqual(report.architecture, "v8")
|
|
1290
|
+
|
|
1291
|
+
# Should detect Math.random at minimum (via source analysis)
|
|
1292
|
+
# V8 bytecode detection depends on function being compiled
|
|
1293
|
+
|
|
1294
|
+
except RuntimeError as e:
|
|
1295
|
+
if "bytecode" in str(e).lower():
|
|
1296
|
+
# V8 bytecode output can be tricky
|
|
1297
|
+
pass
|
|
1298
|
+
else:
|
|
1299
|
+
raise
|
|
1300
|
+
finally:
|
|
1301
|
+
os.unlink(temp_path)
|
|
1302
|
+
|
|
1303
|
+
def test_python_vulnerable_detected(self):
|
|
1304
|
+
"""Vulnerable Python code should be detected."""
|
|
1305
|
+
if not self.has_python:
|
|
1306
|
+
self.skipTest("Python not available")
|
|
1307
|
+
|
|
1308
|
+
vulnerable_file = self.samples_dir / "vulnerable.py"
|
|
1309
|
+
if not vulnerable_file.exists():
|
|
1310
|
+
self.skipTest("Python test sample not found")
|
|
1311
|
+
|
|
1312
|
+
try:
|
|
1313
|
+
report = analyze_source(str(vulnerable_file), include_warnings=False)
|
|
1314
|
+
|
|
1315
|
+
# Should detect dangerous operations
|
|
1316
|
+
self.assertIsNotNone(report)
|
|
1317
|
+
self.assertEqual(report.architecture, "cpython")
|
|
1318
|
+
|
|
1319
|
+
# Should detect division operations and dangerous functions
|
|
1320
|
+
if report.error_count > 0:
|
|
1321
|
+
self.assertFalse(report.passed, "Should fail with violations")
|
|
1322
|
+
|
|
1323
|
+
# Check for expected violation types
|
|
1324
|
+
div_violations = [
|
|
1325
|
+
v
|
|
1326
|
+
for v in report.violations
|
|
1327
|
+
if "DIV" in v.mnemonic.upper() or "MODULO" in v.mnemonic.upper()
|
|
1328
|
+
]
|
|
1329
|
+
func_violations = [
|
|
1330
|
+
v
|
|
1331
|
+
for v in report.violations
|
|
1332
|
+
if "RANDOM" in v.mnemonic.upper() or "SQRT" in v.mnemonic.upper()
|
|
1333
|
+
]
|
|
1334
|
+
|
|
1335
|
+
# Should detect at least some violations
|
|
1336
|
+
self.assertGreater(
|
|
1337
|
+
len(div_violations) + len(func_violations),
|
|
1338
|
+
0,
|
|
1339
|
+
"Should detect division or dangerous function calls",
|
|
1340
|
+
)
|
|
1341
|
+
|
|
1342
|
+
except RuntimeError as e:
|
|
1343
|
+
if "dis" in str(e).lower():
|
|
1344
|
+
# dis module issues
|
|
1345
|
+
pass
|
|
1346
|
+
else:
|
|
1347
|
+
raise
|
|
1348
|
+
|
|
1349
|
+
def test_ruby_vulnerable_detected(self):
|
|
1350
|
+
"""Vulnerable Ruby code should be detected."""
|
|
1351
|
+
if not self.has_ruby:
|
|
1352
|
+
self.skipTest("Ruby not available")
|
|
1353
|
+
|
|
1354
|
+
vulnerable_file = self.samples_dir / "vulnerable.rb"
|
|
1355
|
+
if not vulnerable_file.exists():
|
|
1356
|
+
self.skipTest("Ruby test sample not found")
|
|
1357
|
+
|
|
1358
|
+
try:
|
|
1359
|
+
report = analyze_source(str(vulnerable_file), include_warnings=False)
|
|
1360
|
+
|
|
1361
|
+
# Should detect dangerous operations
|
|
1362
|
+
self.assertIsNotNone(report)
|
|
1363
|
+
self.assertEqual(report.architecture, "yarv")
|
|
1364
|
+
|
|
1365
|
+
# Should detect division operations and dangerous functions
|
|
1366
|
+
if report.error_count > 0:
|
|
1367
|
+
self.assertFalse(report.passed, "Should fail with violations")
|
|
1368
|
+
|
|
1369
|
+
# Check for expected violation types
|
|
1370
|
+
div_violations = [
|
|
1371
|
+
v
|
|
1372
|
+
for v in report.violations
|
|
1373
|
+
if "DIV" in v.mnemonic.upper() or "MOD" in v.mnemonic.upper()
|
|
1374
|
+
]
|
|
1375
|
+
func_violations = [
|
|
1376
|
+
v
|
|
1377
|
+
for v in report.violations
|
|
1378
|
+
if "RAND" in v.mnemonic.upper() or "SQRT" in v.mnemonic.upper()
|
|
1379
|
+
]
|
|
1380
|
+
|
|
1381
|
+
# Should detect at least some violations
|
|
1382
|
+
self.assertGreater(
|
|
1383
|
+
len(div_violations) + len(func_violations),
|
|
1384
|
+
0,
|
|
1385
|
+
"Should detect division or dangerous function calls",
|
|
1386
|
+
)
|
|
1387
|
+
|
|
1388
|
+
except RuntimeError as e:
|
|
1389
|
+
if "yarv" in str(e).lower() or "dump" in str(e).lower():
|
|
1390
|
+
# Ruby YARV issues
|
|
1391
|
+
pass
|
|
1392
|
+
else:
|
|
1393
|
+
raise
|
|
1394
|
+
|
|
1395
|
+
|
|
1396
|
+
if __name__ == "__main__":
|
|
1397
|
+
unittest.main(verbosity=2)
|