bps-kit 1.0.1 → 1.0.2
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/package.json +1 -1
- package/templates/.agents/agents/backend-specialist.md +263 -0
- package/templates/.agents/agents/code-archaeologist.md +106 -0
- package/templates/.agents/agents/database-architect.md +226 -0
- package/templates/.agents/agents/debugger.md +225 -0
- package/templates/.agents/agents/devops-engineer.md +242 -0
- package/templates/.agents/agents/documentation-writer.md +104 -0
- package/templates/.agents/agents/explorer-agent.md +73 -0
- package/templates/.agents/agents/frontend-specialist.md +593 -0
- package/templates/.agents/agents/game-developer.md +162 -0
- package/templates/.agents/agents/mobile-developer.md +377 -0
- package/templates/.agents/agents/orchestrator.md +416 -0
- package/templates/.agents/agents/penetration-tester.md +188 -0
- package/templates/.agents/agents/performance-optimizer.md +187 -0
- package/templates/.agents/agents/product-manager.md +112 -0
- package/templates/.agents/agents/product-owner.md +95 -0
- package/templates/.agents/agents/project-planner.md +406 -0
- package/templates/.agents/agents/qa-automation-engineer.md +103 -0
- package/templates/.agents/agents/security-auditor.md +170 -0
- package/templates/.agents/agents/seo-specialist.md +111 -0
- package/templates/.agents/agents/test-engineer.md +158 -0
- package/templates/.agents/rules/GEMINI.md +219 -0
- package/templates/.agents/scripts/auto_preview.py +148 -0
- package/templates/.agents/scripts/checklist.py +217 -0
- package/templates/.agents/scripts/session_manager.py +120 -0
- package/templates/.agents/scripts/verify_all.py +327 -0
- package/templates/.agents/workflows/brainstorm.md +113 -0
- package/templates/.agents/workflows/create.md +59 -0
- package/templates/.agents/workflows/debug.md +103 -0
- package/templates/.agents/workflows/deploy.md +176 -0
- package/templates/.agents/workflows/enhance.md +63 -0
- package/templates/.agents/workflows/orchestrate.md +237 -0
- package/templates/.agents/workflows/plan.md +89 -0
- package/templates/.agents/workflows/preview.md +81 -0
- package/templates/.agents/workflows/setup-brain.md +39 -0
- package/templates/.agents/workflows/status.md +86 -0
- package/templates/.agents/workflows/test.md +144 -0
- package/templates/.agents/workflows/ui-ux-pro-max.md +296 -0
- package/templates/skills_normal/api-patterns/scripts/api_validator.py +211 -0
- package/templates/skills_normal/database-design/scripts/schema_validator.py +172 -0
- package/templates/skills_normal/frontend-design/scripts/accessibility_checker.py +183 -0
- package/templates/skills_normal/frontend-design/scripts/ux_audit.py +722 -0
- package/templates/skills_normal/git-pushing/scripts/smart_commit.sh +19 -0
- package/templates/skills_normal/lint-and-validate/scripts/lint_runner.py +184 -0
- package/templates/skills_normal/lint-and-validate/scripts/type_coverage.py +173 -0
- package/templates/skills_normal/performance-profiling/scripts/lighthouse_audit.py +76 -0
- package/templates/skills_normal/senior-fullstack/scripts/code_quality_analyzer.py +114 -0
- package/templates/skills_normal/senior-fullstack/scripts/fullstack_scaffolder.py +114 -0
- package/templates/skills_normal/senior-fullstack/scripts/project_scaffolder.py +114 -0
- package/templates/skills_normal/seo-fundamentals/scripts/seo_checker.py +219 -0
- package/templates/skills_normal/testing-patterns/scripts/test_runner.py +219 -0
- package/templates/skills_normal/vulnerability-scanner/scripts/security_scan.py +458 -0
- package/templates/vault/007/scripts/config.py +472 -0
- package/templates/vault/007/scripts/full_audit.py +1306 -0
- package/templates/vault/007/scripts/quick_scan.py +481 -0
- package/templates/vault/007/scripts/requirements.txt +26 -0
- package/templates/vault/007/scripts/scanners/__init__.py +0 -0
- package/templates/vault/007/scripts/scanners/dependency_scanner.py +1305 -0
- package/templates/vault/007/scripts/scanners/injection_scanner.py +1104 -0
- package/templates/vault/007/scripts/scanners/secrets_scanner.py +1008 -0
- package/templates/vault/007/scripts/score_calculator.py +693 -0
- package/templates/vault/agent-orchestrator/scripts/match_skills.py +329 -0
- package/templates/vault/agent-orchestrator/scripts/orchestrate.py +304 -0
- package/templates/vault/agent-orchestrator/scripts/requirements.txt +1 -0
- package/templates/vault/agent-orchestrator/scripts/scan_registry.py +508 -0
- package/templates/vault/ai-studio-image/scripts/config.py +613 -0
- package/templates/vault/ai-studio-image/scripts/generate.py +630 -0
- package/templates/vault/ai-studio-image/scripts/prompt_engine.py +424 -0
- package/templates/vault/ai-studio-image/scripts/requirements.txt +4 -0
- package/templates/vault/ai-studio-image/scripts/templates.py +349 -0
- package/templates/vault/android_ui_verification/scripts/verify_ui.sh +32 -0
- package/templates/vault/apify-audience-analysis/reference/scripts/run_actor.js +363 -0
- package/templates/vault/apify-brand-reputation-monitoring/reference/scripts/run_actor.js +363 -0
- package/templates/vault/apify-competitor-intelligence/reference/scripts/run_actor.js +363 -0
- package/templates/vault/apify-content-analytics/reference/scripts/run_actor.js +363 -0
- package/templates/vault/apify-ecommerce/reference/scripts/package.json +3 -0
- package/templates/vault/apify-ecommerce/reference/scripts/run_actor.js +369 -0
- package/templates/vault/apify-influencer-discovery/reference/scripts/run_actor.js +363 -0
- package/templates/vault/apify-lead-generation/reference/scripts/run_actor.js +363 -0
- package/templates/vault/apify-market-research/reference/scripts/run_actor.js +363 -0
- package/templates/vault/apify-trend-analysis/reference/scripts/run_actor.js +363 -0
- package/templates/vault/apify-ultimate-scraper/reference/scripts/run_actor.js +363 -0
- package/templates/vault/audio-transcriber/scripts/install-requirements.sh +190 -0
- package/templates/vault/audio-transcriber/scripts/transcribe.py +486 -0
- package/templates/vault/claude-monitor/scripts/api_bench.py +240 -0
- package/templates/vault/claude-monitor/scripts/config.py +69 -0
- package/templates/vault/claude-monitor/scripts/health_check.py +362 -0
- package/templates/vault/claude-monitor/scripts/monitor.py +296 -0
- package/templates/vault/content-creator/scripts/brand_voice_analyzer.py +185 -0
- package/templates/vault/content-creator/scripts/seo_optimizer.py +419 -0
- package/templates/vault/context-agent/scripts/active_context.py +227 -0
- package/templates/vault/context-agent/scripts/compressor.py +149 -0
- package/templates/vault/context-agent/scripts/config.py +69 -0
- package/templates/vault/context-agent/scripts/context_loader.py +155 -0
- package/templates/vault/context-agent/scripts/context_manager.py +302 -0
- package/templates/vault/context-agent/scripts/models.py +103 -0
- package/templates/vault/context-agent/scripts/project_registry.py +132 -0
- package/templates/vault/context-agent/scripts/requirements.txt +6 -0
- package/templates/vault/context-agent/scripts/search.py +115 -0
- package/templates/vault/context-agent/scripts/session_parser.py +206 -0
- package/templates/vault/context-agent/scripts/session_summary.py +319 -0
- package/templates/vault/context-guardian/scripts/context_snapshot.py +229 -0
- package/templates/vault/docx/ooxml/scripts/pack.py +159 -0
- package/templates/vault/docx/ooxml/scripts/unpack.py +29 -0
- package/templates/vault/docx/ooxml/scripts/validate.py +69 -0
- package/templates/vault/docx/ooxml/scripts/validation/__init__.py +15 -0
- package/templates/vault/docx/ooxml/scripts/validation/base.py +951 -0
- package/templates/vault/docx/ooxml/scripts/validation/docx.py +274 -0
- package/templates/vault/docx/ooxml/scripts/validation/pptx.py +315 -0
- package/templates/vault/docx/ooxml/scripts/validation/redlining.py +279 -0
- package/templates/vault/docx/scripts/__init__.py +1 -0
- package/templates/vault/docx/scripts/document.py +1276 -0
- package/templates/vault/docx/scripts/templates/comments.xml +3 -0
- package/templates/vault/docx/scripts/templates/commentsExtended.xml +3 -0
- package/templates/vault/docx/scripts/templates/commentsExtensible.xml +3 -0
- package/templates/vault/docx/scripts/templates/commentsIds.xml +3 -0
- package/templates/vault/docx/scripts/templates/people.xml +3 -0
- package/templates/vault/docx/scripts/utilities.py +374 -0
- package/templates/vault/docx-official/ooxml/scripts/pack.py +159 -0
- package/templates/vault/docx-official/ooxml/scripts/unpack.py +29 -0
- package/templates/vault/docx-official/ooxml/scripts/validate.py +69 -0
- package/templates/vault/docx-official/ooxml/scripts/validation/__init__.py +15 -0
- package/templates/vault/docx-official/ooxml/scripts/validation/base.py +951 -0
- package/templates/vault/docx-official/ooxml/scripts/validation/docx.py +274 -0
- package/templates/vault/docx-official/ooxml/scripts/validation/pptx.py +315 -0
- package/templates/vault/docx-official/ooxml/scripts/validation/redlining.py +279 -0
- package/templates/vault/docx-official/scripts/__init__.py +1 -0
- package/templates/vault/docx-official/scripts/document.py +1276 -0
- package/templates/vault/docx-official/scripts/templates/comments.xml +3 -0
- package/templates/vault/docx-official/scripts/templates/commentsExtended.xml +3 -0
- package/templates/vault/docx-official/scripts/templates/commentsExtensible.xml +3 -0
- package/templates/vault/docx-official/scripts/templates/commentsIds.xml +3 -0
- package/templates/vault/docx-official/scripts/templates/people.xml +3 -0
- package/templates/vault/docx-official/scripts/utilities.py +374 -0
- package/templates/vault/geo-fundamentals/scripts/geo_checker.py +289 -0
- package/templates/vault/helm-chart-scaffolding/scripts/validate-chart.sh +244 -0
- package/templates/vault/i18n-localization/scripts/i18n_checker.py +241 -0
- package/templates/vault/instagram/scripts/account_setup.py +233 -0
- package/templates/vault/instagram/scripts/analyze.py +221 -0
- package/templates/vault/instagram/scripts/api_client.py +444 -0
- package/templates/vault/instagram/scripts/auth.py +411 -0
- package/templates/vault/instagram/scripts/comments.py +160 -0
- package/templates/vault/instagram/scripts/config.py +111 -0
- package/templates/vault/instagram/scripts/db.py +467 -0
- package/templates/vault/instagram/scripts/export.py +138 -0
- package/templates/vault/instagram/scripts/governance.py +233 -0
- package/templates/vault/instagram/scripts/hashtags.py +114 -0
- package/templates/vault/instagram/scripts/insights.py +170 -0
- package/templates/vault/instagram/scripts/media.py +65 -0
- package/templates/vault/instagram/scripts/messages.py +103 -0
- package/templates/vault/instagram/scripts/profile.py +58 -0
- package/templates/vault/instagram/scripts/publish.py +449 -0
- package/templates/vault/instagram/scripts/requirements.txt +5 -0
- package/templates/vault/instagram/scripts/run_all.py +189 -0
- package/templates/vault/instagram/scripts/schedule.py +189 -0
- package/templates/vault/instagram/scripts/serve_api.py +234 -0
- package/templates/vault/instagram/scripts/templates.py +155 -0
- package/templates/vault/junta-leiloeiros/scripts/db.py +216 -0
- package/templates/vault/junta-leiloeiros/scripts/export.py +137 -0
- package/templates/vault/junta-leiloeiros/scripts/requirements.txt +15 -0
- package/templates/vault/junta-leiloeiros/scripts/run_all.py +190 -0
- package/templates/vault/junta-leiloeiros/scripts/scraper/__init__.py +4 -0
- package/templates/vault/junta-leiloeiros/scripts/scraper/base_scraper.py +209 -0
- package/templates/vault/junta-leiloeiros/scripts/scraper/generic_scraper.py +110 -0
- package/templates/vault/junta-leiloeiros/scripts/scraper/jucap.py +110 -0
- package/templates/vault/junta-leiloeiros/scripts/scraper/juceac.py +72 -0
- package/templates/vault/junta-leiloeiros/scripts/scraper/juceal.py +72 -0
- package/templates/vault/junta-leiloeiros/scripts/scraper/juceb.py +68 -0
- package/templates/vault/junta-leiloeiros/scripts/scraper/jucec.py +63 -0
- package/templates/vault/junta-leiloeiros/scripts/scraper/jucema.py +211 -0
- package/templates/vault/junta-leiloeiros/scripts/scraper/jucemg.py +218 -0
- package/templates/vault/junta-leiloeiros/scripts/scraper/jucep.py +70 -0
- package/templates/vault/junta-leiloeiros/scripts/scraper/jucepa.py +74 -0
- package/templates/vault/junta-leiloeiros/scripts/scraper/jucepar.py +80 -0
- package/templates/vault/junta-leiloeiros/scripts/scraper/jucepe.py +78 -0
- package/templates/vault/junta-leiloeiros/scripts/scraper/jucepi.py +69 -0
- package/templates/vault/junta-leiloeiros/scripts/scraper/jucer.py +256 -0
- package/templates/vault/junta-leiloeiros/scripts/scraper/jucerja.py +170 -0
- package/templates/vault/junta-leiloeiros/scripts/scraper/jucern.py +71 -0
- package/templates/vault/junta-leiloeiros/scripts/scraper/jucesc.py +89 -0
- package/templates/vault/junta-leiloeiros/scripts/scraper/jucesp.py +233 -0
- package/templates/vault/junta-leiloeiros/scripts/scraper/jucetins.py +134 -0
- package/templates/vault/junta-leiloeiros/scripts/scraper/jucis_df.py +63 -0
- package/templates/vault/junta-leiloeiros/scripts/scraper/jucisrs.py +299 -0
- package/templates/vault/junta-leiloeiros/scripts/scraper/states.py +99 -0
- package/templates/vault/junta-leiloeiros/scripts/serve_api.py +164 -0
- package/templates/vault/junta-leiloeiros/scripts/web_scraper_fallback.py +233 -0
- package/templates/vault/last30days/scripts/last30days.py +521 -0
- package/templates/vault/last30days/scripts/lib/__init__.py +1 -0
- package/templates/vault/last30days/scripts/lib/cache.py +152 -0
- package/templates/vault/last30days/scripts/lib/dates.py +124 -0
- package/templates/vault/last30days/scripts/lib/dedupe.py +120 -0
- package/templates/vault/last30days/scripts/lib/env.py +149 -0
- package/templates/vault/last30days/scripts/lib/http.py +152 -0
- package/templates/vault/last30days/scripts/lib/models.py +175 -0
- package/templates/vault/last30days/scripts/lib/normalize.py +160 -0
- package/templates/vault/last30days/scripts/lib/openai_reddit.py +230 -0
- package/templates/vault/last30days/scripts/lib/reddit_enrich.py +232 -0
- package/templates/vault/last30days/scripts/lib/render.py +383 -0
- package/templates/vault/last30days/scripts/lib/schema.py +336 -0
- package/templates/vault/last30days/scripts/lib/score.py +311 -0
- package/templates/vault/last30days/scripts/lib/ui.py +324 -0
- package/templates/vault/last30days/scripts/lib/websearch.py +401 -0
- package/templates/vault/last30days/scripts/lib/xai_x.py +217 -0
- package/templates/vault/leiloeiro-avaliacao/scripts/governance.py +106 -0
- package/templates/vault/leiloeiro-avaliacao/scripts/requirements.txt +1 -0
- package/templates/vault/leiloeiro-edital/scripts/governance.py +106 -0
- package/templates/vault/leiloeiro-edital/scripts/requirements.txt +1 -0
- package/templates/vault/leiloeiro-ia/scripts/governance.py +106 -0
- package/templates/vault/leiloeiro-ia/scripts/requirements.txt +1 -0
- package/templates/vault/leiloeiro-juridico/scripts/governance.py +106 -0
- package/templates/vault/leiloeiro-juridico/scripts/requirements.txt +1 -0
- package/templates/vault/leiloeiro-mercado/scripts/governance.py +106 -0
- package/templates/vault/leiloeiro-mercado/scripts/requirements.txt +1 -0
- package/templates/vault/leiloeiro-risco/scripts/governance.py +106 -0
- package/templates/vault/leiloeiro-risco/scripts/requirements.txt +1 -0
- package/templates/vault/loki-mode/examples/todo-app-generated/backend/src/db/database.ts +24 -0
- package/templates/vault/loki-mode/examples/todo-app-generated/backend/src/db/db.ts +35 -0
- package/templates/vault/loki-mode/examples/todo-app-generated/backend/src/db/index.ts +2 -0
- package/templates/vault/loki-mode/examples/todo-app-generated/backend/src/db/migrations.ts +31 -0
- package/templates/vault/loki-mode/examples/todo-app-generated/backend/src/db/schema.sql +8 -0
- package/templates/vault/loki-mode/examples/todo-app-generated/backend/src/index.ts +44 -0
- package/templates/vault/loki-mode/examples/todo-app-generated/backend/src/routes/todos.ts +155 -0
- package/templates/vault/loki-mode/examples/todo-app-generated/backend/src/types/index.ts +35 -0
- package/templates/vault/loki-mode/examples/todo-app-generated/frontend/src/App.css +384 -0
- package/templates/vault/loki-mode/examples/todo-app-generated/frontend/src/App.tsx +81 -0
- package/templates/vault/loki-mode/examples/todo-app-generated/frontend/src/api/todos.ts +57 -0
- package/templates/vault/loki-mode/examples/todo-app-generated/frontend/src/components/ConfirmDialog.tsx +26 -0
- package/templates/vault/loki-mode/examples/todo-app-generated/frontend/src/components/EmptyState.tsx +8 -0
- package/templates/vault/loki-mode/examples/todo-app-generated/frontend/src/components/TodoForm.tsx +43 -0
- package/templates/vault/loki-mode/examples/todo-app-generated/frontend/src/components/TodoItem.tsx +36 -0
- package/templates/vault/loki-mode/examples/todo-app-generated/frontend/src/components/TodoList.tsx +27 -0
- package/templates/vault/loki-mode/examples/todo-app-generated/frontend/src/hooks/useTodos.ts +81 -0
- package/templates/vault/loki-mode/examples/todo-app-generated/frontend/src/index.css +48 -0
- package/templates/vault/loki-mode/examples/todo-app-generated/frontend/src/main.tsx +10 -0
- package/templates/vault/loki-mode/examples/todo-app-generated/frontend/src/vite-env.d.ts +1 -0
- package/templates/vault/loki-mode/scripts/export-to-vibe-kanban.sh +178 -0
- package/templates/vault/loki-mode/scripts/loki-wrapper.sh +281 -0
- package/templates/vault/loki-mode/scripts/take-screenshots.js +55 -0
- package/templates/vault/matematico-tao/scripts/complexity_analyzer.py +544 -0
- package/templates/vault/matematico-tao/scripts/dependency_graph.py +538 -0
- package/templates/vault/mcp-builder/scripts/connections.py +151 -0
- package/templates/vault/mcp-builder/scripts/evaluation.py +373 -0
- package/templates/vault/mcp-builder/scripts/example_evaluation.xml +22 -0
- package/templates/vault/mcp-builder/scripts/requirements.txt +2 -0
- package/templates/vault/mobile-design/scripts/mobile_audit.py +670 -0
- package/templates/vault/notebooklm/scripts/__init__.py +81 -0
- package/templates/vault/notebooklm/scripts/ask_question.py +256 -0
- package/templates/vault/notebooklm/scripts/auth_manager.py +358 -0
- package/templates/vault/notebooklm/scripts/browser_session.py +255 -0
- package/templates/vault/notebooklm/scripts/browser_utils.py +107 -0
- package/templates/vault/notebooklm/scripts/cleanup_manager.py +302 -0
- package/templates/vault/notebooklm/scripts/config.py +44 -0
- package/templates/vault/notebooklm/scripts/notebook_manager.py +410 -0
- package/templates/vault/notebooklm/scripts/run.py +102 -0
- package/templates/vault/notebooklm/scripts/setup_environment.py +204 -0
- package/templates/vault/pdf/scripts/check_bounding_boxes.py +70 -0
- package/templates/vault/pdf/scripts/check_bounding_boxes_test.py +226 -0
- package/templates/vault/pdf/scripts/check_fillable_fields.py +12 -0
- package/templates/vault/pdf/scripts/convert_pdf_to_images.py +35 -0
- package/templates/vault/pdf/scripts/create_validation_image.py +41 -0
- package/templates/vault/pdf/scripts/extract_form_field_info.py +152 -0
- package/templates/vault/pdf/scripts/fill_fillable_fields.py +114 -0
- package/templates/vault/pdf/scripts/fill_pdf_form_with_annotations.py +108 -0
- package/templates/vault/pdf-official/scripts/check_bounding_boxes.py +70 -0
- package/templates/vault/pdf-official/scripts/check_bounding_boxes_test.py +226 -0
- package/templates/vault/pdf-official/scripts/check_fillable_fields.py +12 -0
- package/templates/vault/pdf-official/scripts/convert_pdf_to_images.py +35 -0
- package/templates/vault/pdf-official/scripts/create_validation_image.py +41 -0
- package/templates/vault/pdf-official/scripts/extract_form_field_info.py +152 -0
- package/templates/vault/pdf-official/scripts/fill_fillable_fields.py +114 -0
- package/templates/vault/pdf-official/scripts/fill_pdf_form_with_annotations.py +108 -0
- package/templates/vault/planning-with-files/scripts/check-complete.sh +44 -0
- package/templates/vault/planning-with-files/scripts/init-session.sh +120 -0
- package/templates/vault/pptx/ooxml/scripts/pack.py +159 -0
- package/templates/vault/pptx/ooxml/scripts/unpack.py +29 -0
- package/templates/vault/pptx/ooxml/scripts/validate.py +69 -0
- package/templates/vault/pptx/ooxml/scripts/validation/__init__.py +15 -0
- package/templates/vault/pptx/ooxml/scripts/validation/base.py +951 -0
- package/templates/vault/pptx/ooxml/scripts/validation/docx.py +274 -0
- package/templates/vault/pptx/ooxml/scripts/validation/pptx.py +315 -0
- package/templates/vault/pptx/ooxml/scripts/validation/redlining.py +279 -0
- package/templates/vault/pptx/scripts/html2pptx.js +979 -0
- package/templates/vault/pptx/scripts/inventory.py +1020 -0
- package/templates/vault/pptx/scripts/rearrange.py +231 -0
- package/templates/vault/pptx/scripts/replace.py +385 -0
- package/templates/vault/pptx/scripts/thumbnail.py +450 -0
- package/templates/vault/pptx-official/ooxml/scripts/pack.py +159 -0
- package/templates/vault/pptx-official/ooxml/scripts/unpack.py +29 -0
- package/templates/vault/pptx-official/ooxml/scripts/validate.py +69 -0
- package/templates/vault/pptx-official/ooxml/scripts/validation/__init__.py +15 -0
- package/templates/vault/pptx-official/ooxml/scripts/validation/base.py +951 -0
- package/templates/vault/pptx-official/ooxml/scripts/validation/docx.py +274 -0
- package/templates/vault/pptx-official/ooxml/scripts/validation/pptx.py +315 -0
- package/templates/vault/pptx-official/ooxml/scripts/validation/redlining.py +279 -0
- package/templates/vault/pptx-official/scripts/html2pptx.js +979 -0
- package/templates/vault/pptx-official/scripts/inventory.py +1020 -0
- package/templates/vault/pptx-official/scripts/rearrange.py +231 -0
- package/templates/vault/pptx-official/scripts/replace.py +385 -0
- package/templates/vault/pptx-official/scripts/thumbnail.py +450 -0
- package/templates/vault/product-manager-toolkit/scripts/customer_interview_analyzer.py +441 -0
- package/templates/vault/product-manager-toolkit/scripts/rice_prioritizer.py +296 -0
- package/templates/vault/prompt-engineering-patterns/scripts/optimize-prompt.py +279 -0
- package/templates/vault/scripts/.skill_cache.json +7538 -0
- package/templates/vault/scripts/skill_search.py +228 -0
- package/templates/vault/senior-architect/scripts/architecture_diagram_generator.py +114 -0
- package/templates/vault/senior-architect/scripts/dependency_analyzer.py +114 -0
- package/templates/vault/senior-architect/scripts/project_architect.py +114 -0
- package/templates/vault/shopify-development/scripts/requirements.txt +19 -0
- package/templates/vault/shopify-development/scripts/shopify_graphql.py +428 -0
- package/templates/vault/shopify-development/scripts/shopify_init.py +441 -0
- package/templates/vault/shopify-development/scripts/tests/test_shopify_init.py +379 -0
- package/templates/vault/skill-creator/scripts/init_skill.py +303 -0
- package/templates/vault/skill-creator/scripts/package_skill.py +110 -0
- package/templates/vault/skill-creator/scripts/quick_validate.py +95 -0
- package/templates/vault/skill-installer/scripts/detect_skills.py +318 -0
- package/templates/vault/skill-installer/scripts/install_skill.py +1708 -0
- package/templates/vault/skill-installer/scripts/package_skill.py +417 -0
- package/templates/vault/skill-installer/scripts/requirements.txt +1 -0
- package/templates/vault/skill-installer/scripts/validate_skill.py +430 -0
- package/templates/vault/skill-sentinel/scripts/analyzers/__init__.py +13 -0
- package/templates/vault/skill-sentinel/scripts/analyzers/code_quality.py +247 -0
- package/templates/vault/skill-sentinel/scripts/analyzers/cross_skill.py +134 -0
- package/templates/vault/skill-sentinel/scripts/analyzers/dependencies.py +121 -0
- package/templates/vault/skill-sentinel/scripts/analyzers/documentation.py +189 -0
- package/templates/vault/skill-sentinel/scripts/analyzers/governance_audit.py +153 -0
- package/templates/vault/skill-sentinel/scripts/analyzers/performance.py +164 -0
- package/templates/vault/skill-sentinel/scripts/analyzers/security.py +189 -0
- package/templates/vault/skill-sentinel/scripts/config.py +158 -0
- package/templates/vault/skill-sentinel/scripts/cost_optimizer.py +146 -0
- package/templates/vault/skill-sentinel/scripts/db.py +354 -0
- package/templates/vault/skill-sentinel/scripts/governance.py +58 -0
- package/templates/vault/skill-sentinel/scripts/recommender.py +228 -0
- package/templates/vault/skill-sentinel/scripts/report_generator.py +224 -0
- package/templates/vault/skill-sentinel/scripts/requirements.txt +1 -0
- package/templates/vault/skill-sentinel/scripts/run_audit.py +290 -0
- package/templates/vault/skill-sentinel/scripts/scanner.py +271 -0
- package/templates/vault/stability-ai/scripts/config.py +266 -0
- package/templates/vault/stability-ai/scripts/generate.py +687 -0
- package/templates/vault/stability-ai/scripts/requirements.txt +4 -0
- package/templates/vault/stability-ai/scripts/styles.py +174 -0
- package/templates/vault/telegram/assets/boilerplate/nodejs/src/bot-client.ts +86 -0
- package/templates/vault/telegram/assets/boilerplate/nodejs/src/handlers.ts +79 -0
- package/templates/vault/telegram/assets/boilerplate/nodejs/src/index.ts +32 -0
- package/templates/vault/telegram/scripts/send_message.py +143 -0
- package/templates/vault/telegram/scripts/setup_project.py +103 -0
- package/templates/vault/telegram/scripts/test_bot.py +144 -0
- package/templates/vault/typescript-expert/scripts/ts_diagnostic.py +203 -0
- package/templates/vault/ui-ux-pro-max/scripts/__pycache__/core.cpython-314.pyc +0 -0
- package/templates/vault/ui-ux-pro-max/scripts/__pycache__/design_system.cpython-314.pyc +0 -0
- package/templates/vault/ui-ux-pro-max/scripts/core.py +257 -0
- package/templates/vault/ui-ux-pro-max/scripts/design_system.py +487 -0
- package/templates/vault/ui-ux-pro-max/scripts/search.py +76 -0
- package/templates/vault/videodb/scripts/ws_listener.py +204 -0
- package/templates/vault/web-artifacts-builder/scripts/bundle-artifact.sh +54 -0
- package/templates/vault/web-artifacts-builder/scripts/init-artifact.sh +322 -0
- package/templates/vault/web-artifacts-builder/scripts/shadcn-components.tar.gz +0 -0
- package/templates/vault/webapp-testing/scripts/with_server.py +106 -0
- package/templates/vault/whatsapp-cloud-api/assets/boilerplate/nodejs/src/index.ts +125 -0
- package/templates/vault/whatsapp-cloud-api/assets/boilerplate/nodejs/src/template-manager.ts +67 -0
- package/templates/vault/whatsapp-cloud-api/assets/boilerplate/nodejs/src/types.ts +216 -0
- package/templates/vault/whatsapp-cloud-api/assets/boilerplate/nodejs/src/webhook-handler.ts +173 -0
- package/templates/vault/whatsapp-cloud-api/assets/boilerplate/nodejs/src/whatsapp-client.ts +193 -0
- package/templates/vault/whatsapp-cloud-api/scripts/send_test_message.py +137 -0
- package/templates/vault/whatsapp-cloud-api/scripts/setup_project.py +118 -0
- package/templates/vault/whatsapp-cloud-api/scripts/validate_config.py +190 -0
- package/templates/vault/youtube-summarizer/scripts/extract-transcript.py +65 -0
- package/templates/vault/youtube-summarizer/scripts/install-dependencies.sh +28 -0
|
@@ -0,0 +1,1104 @@
|
|
|
1
|
+
"""007 Injection Scanner -- Specialized scanner for injection vulnerabilities.
|
|
2
|
+
|
|
3
|
+
Detects code injection, SQL injection, command injection, prompt injection,
|
|
4
|
+
XSS, SSRF, and path traversal patterns across Python, JavaScript/Node.js,
|
|
5
|
+
and shell codebases. Performs context-aware analysis to reduce false positives
|
|
6
|
+
by tracking user-input sources and adjusting severity for hardcoded values,
|
|
7
|
+
test files, comments, and docstrings.
|
|
8
|
+
|
|
9
|
+
Usage:
|
|
10
|
+
python injection_scanner.py --target /path/to/project
|
|
11
|
+
python injection_scanner.py --target /path/to/project --output json --verbose
|
|
12
|
+
python injection_scanner.py --target /path/to/project --include-low
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
import argparse
|
|
16
|
+
import json
|
|
17
|
+
import os
|
|
18
|
+
import re
|
|
19
|
+
import sys
|
|
20
|
+
import time
|
|
21
|
+
from pathlib import Path
|
|
22
|
+
|
|
23
|
+
# ---------------------------------------------------------------------------
|
|
24
|
+
# Import from the 007 config hub (parent directory)
|
|
25
|
+
# ---------------------------------------------------------------------------
|
|
26
|
+
sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
|
|
27
|
+
|
|
28
|
+
import config # noqa: E402
|
|
29
|
+
|
|
30
|
+
# ---------------------------------------------------------------------------
|
|
31
|
+
# Logger
|
|
32
|
+
# ---------------------------------------------------------------------------
|
|
33
|
+
logger = config.setup_logging("007-injection-scanner")
|
|
34
|
+
|
|
35
|
+
# ---------------------------------------------------------------------------
|
|
36
|
+
# Context markers: sources of user input
|
|
37
|
+
# ---------------------------------------------------------------------------
|
|
38
|
+
# If a line (or nearby lines) contain any of these tokens, variables on that
|
|
39
|
+
# line are treated as *tainted* (user-controlled). When a dangerous pattern
|
|
40
|
+
# uses only a hardcoded literal, severity is reduced.
|
|
41
|
+
|
|
42
|
+
_USER_INPUT_MARKERS_PY = re.compile(
|
|
43
|
+
r"""(?:request\.(?:args|form|json|data|files|values|headers|cookies|get_json)|"""
|
|
44
|
+
r"""request\.GET|request\.POST|request\.query_params|"""
|
|
45
|
+
r"""sys\.argv|input\s*\(|os\.environ|"""
|
|
46
|
+
r"""flask\.request|django\.http|"""
|
|
47
|
+
r"""click\.argument|click\.option|argparse|"""
|
|
48
|
+
r"""websocket\.recv|channel\.receive|"""
|
|
49
|
+
r"""getattr\s*\(\s*request)""",
|
|
50
|
+
re.IGNORECASE,
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
_USER_INPUT_MARKERS_JS = re.compile(
|
|
54
|
+
r"""(?:req\.(?:body|params|query|headers|cookies)|"""
|
|
55
|
+
r"""request\.(?:body|params|query|headers)|"""
|
|
56
|
+
r"""process\.argv|"""
|
|
57
|
+
r"""\.useParams|\.useSearchParams|"""
|
|
58
|
+
r"""window\.location|document\.location|"""
|
|
59
|
+
r"""location\.(?:search|hash|href)|"""
|
|
60
|
+
r"""URLSearchParams|"""
|
|
61
|
+
r"""event\.(?:target|data)|"""
|
|
62
|
+
r"""document\.(?:getElementById|querySelector)|\.value|"""
|
|
63
|
+
r"""localStorage|sessionStorage|"""
|
|
64
|
+
r"""socket\.on)""",
|
|
65
|
+
re.IGNORECASE,
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
_USER_INPUT_MARKERS = re.compile(
|
|
69
|
+
_USER_INPUT_MARKERS_PY.pattern + r"|" + _USER_INPUT_MARKERS_JS.pattern,
|
|
70
|
+
re.IGNORECASE,
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
# ---------------------------------------------------------------------------
|
|
74
|
+
# Comment / docstring detection
|
|
75
|
+
# ---------------------------------------------------------------------------
|
|
76
|
+
|
|
77
|
+
_COMMENT_LINE_RE = re.compile(
|
|
78
|
+
r"""^\s*(?:#|//|/\*|\*|;|rem\b|@rem\b)""", re.IGNORECASE
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
_TRIPLE_QUOTE_RE = re.compile(r'''^\s*(?:\"{3}|'{3})''')
|
|
82
|
+
|
|
83
|
+
_MARKDOWN_CODE_FENCE = re.compile(r"""^\s*```""")
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def _is_comment_line(line: str) -> bool:
|
|
87
|
+
"""Return True if the line is a single-line comment."""
|
|
88
|
+
return bool(_COMMENT_LINE_RE.match(line))
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
# ---------------------------------------------------------------------------
|
|
92
|
+
# Test file detection
|
|
93
|
+
# ---------------------------------------------------------------------------
|
|
94
|
+
|
|
95
|
+
_TEST_FILE_RE = re.compile(
|
|
96
|
+
r"""(?i)(?:^test_|_test\.py$|\.test\.[jt]sx?$|\.spec\.[jt]sx?$|"""
|
|
97
|
+
r"""__tests__|fixtures?[/\\]|test[/\\]|tests[/\\]|"""
|
|
98
|
+
r"""mocks?[/\\]|__mocks__[/\\])"""
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def _is_test_file(filepath: Path) -> bool:
|
|
103
|
+
"""Return True if *filepath* looks like a test or fixture file."""
|
|
104
|
+
return bool(_TEST_FILE_RE.search(filepath.name)) or bool(
|
|
105
|
+
_TEST_FILE_RE.search(str(filepath))
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
# ---------------------------------------------------------------------------
|
|
110
|
+
# Severity helpers
|
|
111
|
+
# ---------------------------------------------------------------------------
|
|
112
|
+
|
|
113
|
+
def _lower_severity(severity: str) -> str:
|
|
114
|
+
"""Return the next-lower severity level."""
|
|
115
|
+
order = ["CRITICAL", "HIGH", "MEDIUM", "LOW", "INFO"]
|
|
116
|
+
idx = order.index(severity) if severity in order else 0
|
|
117
|
+
return order[min(idx + 1, len(order) - 1)]
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
def _has_user_input(line: str) -> bool:
|
|
121
|
+
"""Return True if *line* references a known user-input source."""
|
|
122
|
+
return bool(_USER_INPUT_MARKERS.search(line))
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def _has_variable_interpolation(line: str) -> bool:
|
|
126
|
+
"""Return True if *line* contains f-string braces, .format(), or % formatting."""
|
|
127
|
+
# f-string-style braces (not escaped)
|
|
128
|
+
if re.search(r"""(?<!\{)\{[^{}\s][^{}]*\}(?!\})""", line):
|
|
129
|
+
return True
|
|
130
|
+
# .format() call
|
|
131
|
+
if ".format(" in line:
|
|
132
|
+
return True
|
|
133
|
+
# %-style formatting with a variable (%s, %d etc followed by %)
|
|
134
|
+
if re.search(r"""%[sdifr]""", line) and "%" in line:
|
|
135
|
+
return True
|
|
136
|
+
return False
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
def _only_hardcoded_string(line: str) -> bool:
|
|
140
|
+
"""Heuristic: return True if the dangerous call appears to use only literals.
|
|
141
|
+
|
|
142
|
+
For example, ``eval("1+1")`` or ``os.system("clear")`` with no variables.
|
|
143
|
+
"""
|
|
144
|
+
# If there is variable interpolation, not hardcoded
|
|
145
|
+
if _has_variable_interpolation(line):
|
|
146
|
+
return False
|
|
147
|
+
# If there's a user input marker, not hardcoded
|
|
148
|
+
if _has_user_input(line):
|
|
149
|
+
return False
|
|
150
|
+
# Check for variable references inside the call parens
|
|
151
|
+
# Look for identifiers that aren't string literals
|
|
152
|
+
paren = line.find("(")
|
|
153
|
+
if paren == -1:
|
|
154
|
+
return False
|
|
155
|
+
inside = line[paren:]
|
|
156
|
+
# If the argument is just a string literal, treat as hardcoded
|
|
157
|
+
if re.match(r"""\(\s*['\"]{1,3}[^'\"]*['\"]{1,3}\s*\)""", inside):
|
|
158
|
+
return True
|
|
159
|
+
return False
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
# =========================================================================
|
|
163
|
+
# INJECTION PATTERN DEFINITIONS
|
|
164
|
+
# =========================================================================
|
|
165
|
+
# Each entry: (pattern_name, compiled_regex, base_severity, injection_type,
|
|
166
|
+
# description)
|
|
167
|
+
# The scanner applies context analysis on top of base_severity.
|
|
168
|
+
|
|
169
|
+
_INJECTION_DEFS: list[tuple[str, str, str, str, str]] = [
|
|
170
|
+
|
|
171
|
+
# -----------------------------------------------------------------
|
|
172
|
+
# 1. CODE INJECTION (Python)
|
|
173
|
+
# -----------------------------------------------------------------
|
|
174
|
+
(
|
|
175
|
+
"py_eval_user_input",
|
|
176
|
+
r"""\beval\s*\([^)]*(?:\bvar\b|\bdata\b|\brequest\b|\binput\b|\bargv\b|\bparams?\b|"""
|
|
177
|
+
r"""\bquery\b|\bform\b|\buser\b|\bf['\"])""",
|
|
178
|
+
"CRITICAL",
|
|
179
|
+
"code_injection",
|
|
180
|
+
"eval() with potential user input",
|
|
181
|
+
),
|
|
182
|
+
(
|
|
183
|
+
"py_eval_any",
|
|
184
|
+
r"""\beval\s*\(""",
|
|
185
|
+
"CRITICAL",
|
|
186
|
+
"code_injection",
|
|
187
|
+
"eval() usage -- verify input is not user-controlled",
|
|
188
|
+
),
|
|
189
|
+
(
|
|
190
|
+
"py_exec_any",
|
|
191
|
+
r"""\bexec\s*\(""",
|
|
192
|
+
"CRITICAL",
|
|
193
|
+
"code_injection",
|
|
194
|
+
"exec() usage -- verify input is not user-controlled",
|
|
195
|
+
),
|
|
196
|
+
(
|
|
197
|
+
"py_compile_external",
|
|
198
|
+
r"""\bcompile\s*\([^)]*(?:\bvar\b|\bdata\b|\brequest\b|\binput\b|\bargv\b|"""
|
|
199
|
+
r"""\bparams?\b|\bquery\b|\bform\b|\buser\b|\bf['\"])""",
|
|
200
|
+
"CRITICAL",
|
|
201
|
+
"code_injection",
|
|
202
|
+
"compile() with potential user input",
|
|
203
|
+
),
|
|
204
|
+
(
|
|
205
|
+
"py_dunder_import_dynamic",
|
|
206
|
+
r"""\b__import__\s*\([^'\"][^)]*\)""",
|
|
207
|
+
"HIGH",
|
|
208
|
+
"code_injection",
|
|
209
|
+
"__import__() with dynamic name",
|
|
210
|
+
),
|
|
211
|
+
(
|
|
212
|
+
"py_importlib_dynamic",
|
|
213
|
+
r"""\bimportlib\.import_module\s*\([^'\"][^)]*\)""",
|
|
214
|
+
"HIGH",
|
|
215
|
+
"code_injection",
|
|
216
|
+
"importlib.import_module() with dynamic name",
|
|
217
|
+
),
|
|
218
|
+
# Node.js code injection
|
|
219
|
+
(
|
|
220
|
+
"js_eval_any",
|
|
221
|
+
r"""\beval\s*\(""",
|
|
222
|
+
"CRITICAL",
|
|
223
|
+
"code_injection",
|
|
224
|
+
"eval() in JavaScript -- verify input is not user-controlled",
|
|
225
|
+
),
|
|
226
|
+
(
|
|
227
|
+
"js_function_constructor",
|
|
228
|
+
r"""\bnew\s+Function\s*\(""",
|
|
229
|
+
"CRITICAL",
|
|
230
|
+
"code_injection",
|
|
231
|
+
"Function() constructor -- equivalent to eval",
|
|
232
|
+
),
|
|
233
|
+
(
|
|
234
|
+
"js_vm_run",
|
|
235
|
+
r"""\bvm\.run(?:InNewContext|InThisContext|InContext)?\s*\(""",
|
|
236
|
+
"HIGH",
|
|
237
|
+
"code_injection",
|
|
238
|
+
"vm.run*() -- verify input is not user-controlled",
|
|
239
|
+
),
|
|
240
|
+
# Template injection
|
|
241
|
+
(
|
|
242
|
+
"template_injection_fstring",
|
|
243
|
+
r"""(?:render|template|jinja|mako|render_template_string)\s*\(.*\bf['\"]""",
|
|
244
|
+
"CRITICAL",
|
|
245
|
+
"code_injection",
|
|
246
|
+
"f-string in template rendering context (template injection)",
|
|
247
|
+
),
|
|
248
|
+
(
|
|
249
|
+
"template_injection_format",
|
|
250
|
+
r"""(?:render|template|jinja|mako|render_template_string)\s*\(.*\.format\s*\(""",
|
|
251
|
+
"CRITICAL",
|
|
252
|
+
"code_injection",
|
|
253
|
+
".format() in template rendering context (template injection)",
|
|
254
|
+
),
|
|
255
|
+
|
|
256
|
+
# -----------------------------------------------------------------
|
|
257
|
+
# 2. COMMAND INJECTION
|
|
258
|
+
# -----------------------------------------------------------------
|
|
259
|
+
(
|
|
260
|
+
"subprocess_shell_true",
|
|
261
|
+
r"""\bsubprocess\.(?:call|run|Popen|check_output|check_call)\s*\("""
|
|
262
|
+
r"""[^)]*shell\s*=\s*True""",
|
|
263
|
+
"CRITICAL",
|
|
264
|
+
"command_injection",
|
|
265
|
+
"subprocess with shell=True -- command injection risk if input is variable",
|
|
266
|
+
),
|
|
267
|
+
(
|
|
268
|
+
"os_system_var",
|
|
269
|
+
r"""\bos\.system\s*\(""",
|
|
270
|
+
"CRITICAL",
|
|
271
|
+
"command_injection",
|
|
272
|
+
"os.system() -- always uses a shell; prefer subprocess without shell=True",
|
|
273
|
+
),
|
|
274
|
+
(
|
|
275
|
+
"os_popen_var",
|
|
276
|
+
r"""\bos\.popen\s*\(""",
|
|
277
|
+
"HIGH",
|
|
278
|
+
"command_injection",
|
|
279
|
+
"os.popen() -- shell command execution",
|
|
280
|
+
),
|
|
281
|
+
(
|
|
282
|
+
"child_process_exec",
|
|
283
|
+
r"""\b(?:child_process\.exec|execSync|exec)\s*\(""",
|
|
284
|
+
"CRITICAL",
|
|
285
|
+
"command_injection",
|
|
286
|
+
"child_process.exec() in Node.js -- uses shell by default",
|
|
287
|
+
),
|
|
288
|
+
(
|
|
289
|
+
"shell_backtick_var",
|
|
290
|
+
r"""`[^`]*\$\{?\w+\}?[^`]*`""",
|
|
291
|
+
"HIGH",
|
|
292
|
+
"command_injection",
|
|
293
|
+
"Backtick execution with variable interpolation",
|
|
294
|
+
),
|
|
295
|
+
|
|
296
|
+
# -----------------------------------------------------------------
|
|
297
|
+
# 3. SQL INJECTION
|
|
298
|
+
# -----------------------------------------------------------------
|
|
299
|
+
(
|
|
300
|
+
"sql_fstring",
|
|
301
|
+
r"""(?i)\bf['\"](?:[^'\"]*?)(?:SELECT|INSERT|UPDATE|DELETE|DROP|ALTER|CREATE|"""
|
|
302
|
+
r"""TRUNCATE|UNION|EXEC|EXECUTE)\b""",
|
|
303
|
+
"CRITICAL",
|
|
304
|
+
"sql_injection",
|
|
305
|
+
"f-string in SQL query (SQL injection)",
|
|
306
|
+
),
|
|
307
|
+
(
|
|
308
|
+
"sql_format_method",
|
|
309
|
+
r"""(?i)(?:['\"]\s*(?:SELECT|INSERT|UPDATE|DELETE|DROP|ALTER|CREATE|"""
|
|
310
|
+
r"""TRUNCATE|UNION|EXEC|EXECUTE)\b[^'\"]*['\"])\.format\s*\(""",
|
|
311
|
+
"CRITICAL",
|
|
312
|
+
"sql_injection",
|
|
313
|
+
".format() in SQL query string (SQL injection)",
|
|
314
|
+
),
|
|
315
|
+
(
|
|
316
|
+
"sql_concat",
|
|
317
|
+
r"""(?i)(?:SELECT|INSERT|UPDATE|DELETE|DROP|ALTER|CREATE)\b[^;]*?\+\s*(?!['\"]\s*\+)""",
|
|
318
|
+
"HIGH",
|
|
319
|
+
"sql_injection",
|
|
320
|
+
"String concatenation in SQL query",
|
|
321
|
+
),
|
|
322
|
+
(
|
|
323
|
+
"sql_percent_format",
|
|
324
|
+
r"""(?i)(?:cursor\.execute|execute|executemany)\s*\(\s*['\"]"""
|
|
325
|
+
r"""[^'\"]*(?:SELECT|INSERT|UPDATE|DELETE|DROP)\b[^'\"]*%[sd]""",
|
|
326
|
+
"CRITICAL",
|
|
327
|
+
"sql_injection",
|
|
328
|
+
"%-format in cursor.execute() (SQL injection)",
|
|
329
|
+
),
|
|
330
|
+
(
|
|
331
|
+
"sql_fstring_execute",
|
|
332
|
+
r"""(?i)(?:cursor\.execute|execute|executemany)\s*\(\s*f['\"]""",
|
|
333
|
+
"CRITICAL",
|
|
334
|
+
"sql_injection",
|
|
335
|
+
"f-string in execute() call (SQL injection)",
|
|
336
|
+
),
|
|
337
|
+
|
|
338
|
+
# -----------------------------------------------------------------
|
|
339
|
+
# 4. PROMPT INJECTION
|
|
340
|
+
# -----------------------------------------------------------------
|
|
341
|
+
(
|
|
342
|
+
"prompt_injection_fstring",
|
|
343
|
+
r"""(?i)(?:prompt|system_prompt|user_prompt|message|messages)\s*=\s*f['\"]"""
|
|
344
|
+
r"""[^'\"]*\{(?:user|input|query|request|data|text|content|message)""",
|
|
345
|
+
"HIGH",
|
|
346
|
+
"prompt_injection",
|
|
347
|
+
"User input directly in LLM prompt via f-string",
|
|
348
|
+
),
|
|
349
|
+
(
|
|
350
|
+
"prompt_injection_concat",
|
|
351
|
+
r"""(?i)(?:prompt|system_prompt|user_prompt|messages?)\s*(?:=|\+=)\s*"""
|
|
352
|
+
r"""[^=\n]*(?:user_input|user_message|request\.(?:body|data|form|json)|input\()""",
|
|
353
|
+
"HIGH",
|
|
354
|
+
"prompt_injection",
|
|
355
|
+
"User input concatenated into LLM prompt",
|
|
356
|
+
),
|
|
357
|
+
(
|
|
358
|
+
"prompt_injection_openai",
|
|
359
|
+
r"""(?i)(?:openai|anthropic|llm|chat|completion).*\bf['\"][^'\"]*\{"""
|
|
360
|
+
r"""(?:user|input|query|request|data|prompt|text|content|message)""",
|
|
361
|
+
"HIGH",
|
|
362
|
+
"prompt_injection",
|
|
363
|
+
"User variable in f-string near LLM API call",
|
|
364
|
+
),
|
|
365
|
+
(
|
|
366
|
+
"prompt_injection_format",
|
|
367
|
+
r"""(?i)(?:prompt|system_prompt|user_prompt)\s*=\s*['\"][^'\"]*['\"]"""
|
|
368
|
+
r"""\.format\s*\([^)]*(?:user|input|query|request|data)""",
|
|
369
|
+
"HIGH",
|
|
370
|
+
"prompt_injection",
|
|
371
|
+
".format() with user input in prompt template",
|
|
372
|
+
),
|
|
373
|
+
(
|
|
374
|
+
"prompt_no_sanitize_direct",
|
|
375
|
+
r"""(?i)(?:messages|prompt)\s*(?:\.\s*append|\[\s*\{).*(?:content|text)\s*"""
|
|
376
|
+
r"""[:=]\s*(?:user_input|user_message|request\.|input\()""",
|
|
377
|
+
"MEDIUM",
|
|
378
|
+
"prompt_injection",
|
|
379
|
+
"User input passed directly to LLM messages without sanitization",
|
|
380
|
+
),
|
|
381
|
+
|
|
382
|
+
# -----------------------------------------------------------------
|
|
383
|
+
# 5. XSS (Cross-Site Scripting)
|
|
384
|
+
# -----------------------------------------------------------------
|
|
385
|
+
(
|
|
386
|
+
"xss_innerhtml",
|
|
387
|
+
r"""\.innerHTML\s*=\s*(?!['\"]\s*$)[^;]+""",
|
|
388
|
+
"HIGH",
|
|
389
|
+
"xss",
|
|
390
|
+
"innerHTML assignment with variable (XSS risk)",
|
|
391
|
+
),
|
|
392
|
+
(
|
|
393
|
+
"xss_document_write",
|
|
394
|
+
r"""\bdocument\.write\s*\([^)]*(?:\+|\$\{|\bvar\b|\bdata\b)""",
|
|
395
|
+
"HIGH",
|
|
396
|
+
"xss",
|
|
397
|
+
"document.write() with variable content",
|
|
398
|
+
),
|
|
399
|
+
(
|
|
400
|
+
"xss_document_write_any",
|
|
401
|
+
r"""\bdocument\.write(?:ln)?\s*\(""",
|
|
402
|
+
"MEDIUM",
|
|
403
|
+
"xss",
|
|
404
|
+
"document.write() usage -- verify no user content",
|
|
405
|
+
),
|
|
406
|
+
(
|
|
407
|
+
"xss_dangerously_set",
|
|
408
|
+
r"""\bdangerouslySetInnerHTML\s*=\s*\{""",
|
|
409
|
+
"HIGH",
|
|
410
|
+
"xss",
|
|
411
|
+
"dangerouslySetInnerHTML in React (XSS risk)",
|
|
412
|
+
),
|
|
413
|
+
(
|
|
414
|
+
"xss_template_literal_html",
|
|
415
|
+
r"""(?:innerHTML|outerHTML|insertAdjacentHTML)\s*(?:=|\()\s*`[^`]*\$\{""",
|
|
416
|
+
"HIGH",
|
|
417
|
+
"xss",
|
|
418
|
+
"Template literal with interpolation in HTML context",
|
|
419
|
+
),
|
|
420
|
+
(
|
|
421
|
+
"xss_jquery_html",
|
|
422
|
+
r"""\$\s*\([^)]*\)\s*\.html\s*\([^)]*(?:\+|\$\{|\bvar\b|\bdata\b)""",
|
|
423
|
+
"HIGH",
|
|
424
|
+
"xss",
|
|
425
|
+
"jQuery .html() with variable content",
|
|
426
|
+
),
|
|
427
|
+
|
|
428
|
+
# -----------------------------------------------------------------
|
|
429
|
+
# 6. SSRF (Server-Side Request Forgery)
|
|
430
|
+
# -----------------------------------------------------------------
|
|
431
|
+
(
|
|
432
|
+
"ssrf_requests",
|
|
433
|
+
r"""\brequests\.(?:get|post|put|patch|delete|head|options|request)\s*\("""
|
|
434
|
+
r"""[^)]*(?:\bvar\b|\bdata\b|\brequest\b|\bparams?\b|\bquery\b|"""
|
|
435
|
+
r"""\bform\b|\buser\b|\burl\b|\bf['\"])""",
|
|
436
|
+
"HIGH",
|
|
437
|
+
"ssrf",
|
|
438
|
+
"requests.get/post with potentially user-controlled URL",
|
|
439
|
+
),
|
|
440
|
+
(
|
|
441
|
+
"ssrf_urllib",
|
|
442
|
+
r"""\b(?:urllib\.request\.urlopen|urllib\.request\.Request|"""
|
|
443
|
+
r"""urllib2\.urlopen|urlopen)\s*\([^)]*(?:\bvar\b|\bdata\b|\brequest\b|"""
|
|
444
|
+
r"""\bparams?\b|\burl\b|\buser\b|\bf['\"])""",
|
|
445
|
+
"HIGH",
|
|
446
|
+
"ssrf",
|
|
447
|
+
"urllib with potentially user-controlled URL",
|
|
448
|
+
),
|
|
449
|
+
(
|
|
450
|
+
"ssrf_fetch",
|
|
451
|
+
r"""\bfetch\s*\([^)]*(?:\bvar\b|\bdata\b|\breq\b|\bparams?\b|"""
|
|
452
|
+
r"""\burl\b|\buser\b|\$\{)""",
|
|
453
|
+
"HIGH",
|
|
454
|
+
"ssrf",
|
|
455
|
+
"fetch() with potentially user-controlled URL",
|
|
456
|
+
),
|
|
457
|
+
(
|
|
458
|
+
"ssrf_axios",
|
|
459
|
+
r"""\baxios\.(?:get|post|put|patch|delete|head|options|request)\s*\("""
|
|
460
|
+
r"""[^)]*(?:\bvar\b|\bdata\b|\breq\b|\bparams?\b|\burl\b|\buser\b|\$\{)""",
|
|
461
|
+
"HIGH",
|
|
462
|
+
"ssrf",
|
|
463
|
+
"axios with potentially user-controlled URL",
|
|
464
|
+
),
|
|
465
|
+
(
|
|
466
|
+
"ssrf_no_allowlist",
|
|
467
|
+
r"""\brequests\.(?:get|post|put|patch|delete)\s*\(""",
|
|
468
|
+
"MEDIUM",
|
|
469
|
+
"ssrf",
|
|
470
|
+
"HTTP request without visible URL allowlist/blocklist validation",
|
|
471
|
+
),
|
|
472
|
+
|
|
473
|
+
# -----------------------------------------------------------------
|
|
474
|
+
# 7. PATH TRAVERSAL
|
|
475
|
+
# -----------------------------------------------------------------
|
|
476
|
+
(
|
|
477
|
+
"path_traversal_open",
|
|
478
|
+
r"""\bopen\s*\([^)]*(?:\brequest\b|\bparams?\b|\bquery\b|\bform\b|"""
|
|
479
|
+
r"""\buser\b|\bargv\b|\binput\s*\()""",
|
|
480
|
+
"HIGH",
|
|
481
|
+
"path_traversal",
|
|
482
|
+
"open() with user-controlled path (path traversal risk)",
|
|
483
|
+
),
|
|
484
|
+
(
|
|
485
|
+
"path_traversal_join",
|
|
486
|
+
r"""\bos\.path\.join\s*\([^)]*(?:\brequest\b|\bparams?\b|\bquery\b|"""
|
|
487
|
+
r"""\bform\b|\buser\b|\bargv\b|\binput\s*\()""",
|
|
488
|
+
"HIGH",
|
|
489
|
+
"path_traversal",
|
|
490
|
+
"os.path.join with user input (can bypass with absolute paths)",
|
|
491
|
+
),
|
|
492
|
+
(
|
|
493
|
+
"path_traversal_pathlib",
|
|
494
|
+
r"""\bPath\s*\([^)]*(?:\brequest\b|\bparams?\b|\bquery\b|\bform\b|"""
|
|
495
|
+
r"""\buser\b|\bargv\b|\binput\s*\()""",
|
|
496
|
+
"MEDIUM",
|
|
497
|
+
"path_traversal",
|
|
498
|
+
"Path() with user input -- verify resolve() and containment check",
|
|
499
|
+
),
|
|
500
|
+
(
|
|
501
|
+
"path_traversal_send_file",
|
|
502
|
+
r"""\bsend_file\s*\([^)]*(?:\brequest\b|\bparams?\b|\bquery\b|\bform\b|"""
|
|
503
|
+
r"""\buser\b)""",
|
|
504
|
+
"HIGH",
|
|
505
|
+
"path_traversal",
|
|
506
|
+
"send_file() with user-controlled path",
|
|
507
|
+
),
|
|
508
|
+
(
|
|
509
|
+
"path_traversal_no_resolve",
|
|
510
|
+
r"""\bopen\s*\(\s*(?:os\.path\.join|Path)\s*\(""",
|
|
511
|
+
"MEDIUM",
|
|
512
|
+
"path_traversal",
|
|
513
|
+
"File open via path join without visible resolve()/realpath() check",
|
|
514
|
+
),
|
|
515
|
+
]
|
|
516
|
+
|
|
517
|
+
# Compile all patterns
|
|
518
|
+
INJECTION_PATTERNS: list[tuple[str, re.Pattern, str, str, str]] = []
|
|
519
|
+
for _name, _pat, _sev, _itype, _desc in _INJECTION_DEFS:
|
|
520
|
+
try:
|
|
521
|
+
INJECTION_PATTERNS.append((_name, re.compile(_pat), _sev, _itype, _desc))
|
|
522
|
+
except re.error as exc:
|
|
523
|
+
logger.warning("Failed to compile pattern %s: %s", _name, exc)
|
|
524
|
+
|
|
525
|
+
|
|
526
|
+
# =========================================================================
|
|
527
|
+
# File collection
|
|
528
|
+
# =========================================================================
|
|
529
|
+
|
|
530
|
+
def _should_scan_file(filepath: Path) -> bool:
|
|
531
|
+
"""Decide if a file should be included for injection scanning."""
|
|
532
|
+
name = filepath.name.lower()
|
|
533
|
+
suffix = filepath.suffix.lower()
|
|
534
|
+
|
|
535
|
+
for ext in config.SCANNABLE_EXTENSIONS:
|
|
536
|
+
if name.endswith(ext):
|
|
537
|
+
return True
|
|
538
|
+
if suffix in config.SCANNABLE_EXTENSIONS:
|
|
539
|
+
return True
|
|
540
|
+
|
|
541
|
+
return False
|
|
542
|
+
|
|
543
|
+
|
|
544
|
+
def collect_files(target: Path) -> list[Path]:
|
|
545
|
+
"""Walk *target* recursively and return files for injection scanning."""
|
|
546
|
+
files: list[Path] = []
|
|
547
|
+
max_files = config.LIMITS["max_files_per_scan"]
|
|
548
|
+
|
|
549
|
+
for root, dirs, filenames in os.walk(target):
|
|
550
|
+
dirs[:] = [d for d in dirs if d not in config.SKIP_DIRECTORIES]
|
|
551
|
+
|
|
552
|
+
for fname in filenames:
|
|
553
|
+
if len(files) >= max_files:
|
|
554
|
+
logger.warning(
|
|
555
|
+
"Reached max_files_per_scan limit (%d). Stopping.", max_files
|
|
556
|
+
)
|
|
557
|
+
return files
|
|
558
|
+
|
|
559
|
+
fpath = Path(root) / fname
|
|
560
|
+
if _should_scan_file(fpath):
|
|
561
|
+
files.append(fpath)
|
|
562
|
+
|
|
563
|
+
return files
|
|
564
|
+
|
|
565
|
+
|
|
566
|
+
# =========================================================================
|
|
567
|
+
# Core scanning logic
|
|
568
|
+
# =========================================================================
|
|
569
|
+
|
|
570
|
+
def _snippet(line: str, match_start: int, context: int = 80) -> str:
|
|
571
|
+
"""Extract a short snippet around the match position."""
|
|
572
|
+
start = max(0, match_start - context // 4)
|
|
573
|
+
end = min(len(line), match_start + context)
|
|
574
|
+
raw = line[start:end].strip()
|
|
575
|
+
if len(raw) > context:
|
|
576
|
+
raw = raw[:context] + "..."
|
|
577
|
+
return raw
|
|
578
|
+
|
|
579
|
+
|
|
580
|
+
def _is_in_docstring(lines: list[str], line_idx: int) -> bool:
|
|
581
|
+
"""Rough heuristic: check if line_idx falls inside a Python docstring.
|
|
582
|
+
|
|
583
|
+
Counts triple-quote occurrences above the current line. Odd count
|
|
584
|
+
means we are inside a docstring.
|
|
585
|
+
"""
|
|
586
|
+
count = 0
|
|
587
|
+
for i in range(line_idx):
|
|
588
|
+
# Count triple quotes in each preceding line
|
|
589
|
+
content = lines[i]
|
|
590
|
+
count += len(re.findall(r'''(?:\"{3}|'{3})''', content))
|
|
591
|
+
return count % 2 == 1
|
|
592
|
+
|
|
593
|
+
|
|
594
|
+
def scan_file(filepath: Path, verbose: bool = False) -> list[dict]:
|
|
595
|
+
"""Scan a single file for injection vulnerabilities.
|
|
596
|
+
|
|
597
|
+
Returns a list of finding dicts.
|
|
598
|
+
"""
|
|
599
|
+
findings: list[dict] = []
|
|
600
|
+
max_findings = config.LIMITS["max_findings_per_file"]
|
|
601
|
+
file_str = str(filepath)
|
|
602
|
+
is_test = _is_test_file(filepath)
|
|
603
|
+
|
|
604
|
+
# --- File size check ---
|
|
605
|
+
try:
|
|
606
|
+
size = filepath.stat().st_size
|
|
607
|
+
except OSError:
|
|
608
|
+
return findings
|
|
609
|
+
|
|
610
|
+
if size > config.LIMITS["max_file_size_bytes"]:
|
|
611
|
+
if verbose:
|
|
612
|
+
logger.debug("Skipping oversized file: %s (%d bytes)", filepath, size)
|
|
613
|
+
return findings
|
|
614
|
+
|
|
615
|
+
# --- Read content ---
|
|
616
|
+
try:
|
|
617
|
+
text = filepath.read_text(encoding="utf-8", errors="replace")
|
|
618
|
+
except OSError as exc:
|
|
619
|
+
if verbose:
|
|
620
|
+
logger.debug("Cannot read %s: %s", filepath, exc)
|
|
621
|
+
return findings
|
|
622
|
+
|
|
623
|
+
lines = text.splitlines()
|
|
624
|
+
in_markdown_block = False
|
|
625
|
+
|
|
626
|
+
# Build a *nearby user-input context* -- for each line, check if the
|
|
627
|
+
# surrounding +/-5 lines mention user input sources. This helps detect
|
|
628
|
+
# indirect taint (variable assigned from request on line N, used on N+3).
|
|
629
|
+
_CONTEXT_WINDOW = 5
|
|
630
|
+
line_has_user_input = [False] * len(lines)
|
|
631
|
+
for idx, ln in enumerate(lines):
|
|
632
|
+
if _has_user_input(ln):
|
|
633
|
+
lo = max(0, idx - _CONTEXT_WINDOW)
|
|
634
|
+
hi = min(len(lines), idx + _CONTEXT_WINDOW + 1)
|
|
635
|
+
for j in range(lo, hi):
|
|
636
|
+
line_has_user_input[j] = True
|
|
637
|
+
|
|
638
|
+
# Track patterns already matched per line to avoid duplicates
|
|
639
|
+
# (more specific patterns override generic ones)
|
|
640
|
+
line_patterns: dict[int, set[str]] = {}
|
|
641
|
+
|
|
642
|
+
for line_idx, line in enumerate(lines):
|
|
643
|
+
if len(findings) >= max_findings:
|
|
644
|
+
break
|
|
645
|
+
|
|
646
|
+
line_num = line_idx + 1
|
|
647
|
+
stripped = line.strip()
|
|
648
|
+
|
|
649
|
+
if not stripped:
|
|
650
|
+
continue
|
|
651
|
+
|
|
652
|
+
# Markdown code fence tracking
|
|
653
|
+
if _MARKDOWN_CODE_FENCE.match(stripped):
|
|
654
|
+
in_markdown_block = not in_markdown_block
|
|
655
|
+
continue
|
|
656
|
+
|
|
657
|
+
# Skip comments
|
|
658
|
+
if _is_comment_line(stripped):
|
|
659
|
+
continue
|
|
660
|
+
|
|
661
|
+
# Skip if inside markdown code block
|
|
662
|
+
if in_markdown_block:
|
|
663
|
+
continue
|
|
664
|
+
|
|
665
|
+
# Skip if inside docstring (for Python files)
|
|
666
|
+
if filepath.suffix.lower() == ".py" and _is_in_docstring(lines, line_idx):
|
|
667
|
+
continue
|
|
668
|
+
|
|
669
|
+
for pat_name, regex, base_severity, injection_type, description in INJECTION_PATTERNS:
|
|
670
|
+
m = regex.search(line)
|
|
671
|
+
if not m:
|
|
672
|
+
continue
|
|
673
|
+
|
|
674
|
+
# --- De-duplication: skip generic if specific already matched ---
|
|
675
|
+
# e.g., if py_eval_user_input matched, skip py_eval_any on same line
|
|
676
|
+
if line_num not in line_patterns:
|
|
677
|
+
line_patterns[line_num] = set()
|
|
678
|
+
|
|
679
|
+
# Build a group key from injection_type + rough function name
|
|
680
|
+
group_key = injection_type + ":" + pat_name.rsplit("_", 1)[0]
|
|
681
|
+
if group_key in line_patterns.get(line_num, set()):
|
|
682
|
+
continue
|
|
683
|
+
|
|
684
|
+
# More specific: if a *_user_input variant matched, mark its group
|
|
685
|
+
if "user_input" in pat_name or "var" in pat_name:
|
|
686
|
+
generic_group = injection_type + ":" + pat_name.replace("_user_input", "").replace("_var", "").rsplit("_", 1)[0]
|
|
687
|
+
line_patterns[line_num].add(generic_group)
|
|
688
|
+
|
|
689
|
+
line_patterns[line_num].add(group_key)
|
|
690
|
+
|
|
691
|
+
# --- Context-aware severity adjustment ---
|
|
692
|
+
adjusted_severity = base_severity
|
|
693
|
+
|
|
694
|
+
# 1. If only hardcoded string, lower to INFO
|
|
695
|
+
if _only_hardcoded_string(line):
|
|
696
|
+
adjusted_severity = "INFO"
|
|
697
|
+
|
|
698
|
+
# 2. If no user input nearby, lower by one level (but not below MEDIUM
|
|
699
|
+
# for CRITICAL patterns, since the pattern itself is dangerous)
|
|
700
|
+
elif not line_has_user_input[line_idx] and not _has_user_input(line):
|
|
701
|
+
if not _has_variable_interpolation(line):
|
|
702
|
+
adjusted_severity = _lower_severity(base_severity)
|
|
703
|
+
# For the generic "any" patterns, lower further if no vars
|
|
704
|
+
if pat_name.endswith("_any"):
|
|
705
|
+
adjusted_severity = _lower_severity(adjusted_severity)
|
|
706
|
+
|
|
707
|
+
# 3. Test files: lower severity by one level
|
|
708
|
+
if is_test:
|
|
709
|
+
adjusted_severity = _lower_severity(adjusted_severity)
|
|
710
|
+
|
|
711
|
+
findings.append({
|
|
712
|
+
"type": "injection",
|
|
713
|
+
"injection_type": injection_type,
|
|
714
|
+
"pattern": pat_name,
|
|
715
|
+
"severity": adjusted_severity,
|
|
716
|
+
"file": file_str,
|
|
717
|
+
"line": line_num,
|
|
718
|
+
"snippet": _snippet(line, m.start()),
|
|
719
|
+
"description": description,
|
|
720
|
+
"has_user_input_nearby": line_has_user_input[line_idx],
|
|
721
|
+
})
|
|
722
|
+
|
|
723
|
+
return findings
|
|
724
|
+
|
|
725
|
+
|
|
726
|
+
# =========================================================================
|
|
727
|
+
# Aggregation and scoring
|
|
728
|
+
# =========================================================================
|
|
729
|
+
|
|
730
|
+
SCORE_DEDUCTIONS = {
|
|
731
|
+
"CRITICAL": 12,
|
|
732
|
+
"HIGH": 6,
|
|
733
|
+
"MEDIUM": 3,
|
|
734
|
+
"LOW": 1,
|
|
735
|
+
"INFO": 0,
|
|
736
|
+
}
|
|
737
|
+
|
|
738
|
+
|
|
739
|
+
def aggregate_by_severity(findings: list[dict]) -> dict[str, int]:
|
|
740
|
+
"""Count findings per severity level."""
|
|
741
|
+
counts: dict[str, int] = {sev: 0 for sev in config.SEVERITY}
|
|
742
|
+
for f in findings:
|
|
743
|
+
sev = f.get("severity", "INFO")
|
|
744
|
+
if sev in counts:
|
|
745
|
+
counts[sev] += 1
|
|
746
|
+
return counts
|
|
747
|
+
|
|
748
|
+
|
|
749
|
+
def aggregate_by_injection_type(findings: list[dict]) -> dict[str, int]:
|
|
750
|
+
"""Count findings per injection type."""
|
|
751
|
+
counts: dict[str, int] = {}
|
|
752
|
+
for f in findings:
|
|
753
|
+
itype = f.get("injection_type", "unknown")
|
|
754
|
+
counts[itype] = counts.get(itype, 0) + 1
|
|
755
|
+
return counts
|
|
756
|
+
|
|
757
|
+
|
|
758
|
+
def aggregate_by_pattern(findings: list[dict]) -> dict[str, int]:
|
|
759
|
+
"""Count findings per pattern name."""
|
|
760
|
+
counts: dict[str, int] = {}
|
|
761
|
+
for f in findings:
|
|
762
|
+
pattern = f.get("pattern", "unknown")
|
|
763
|
+
counts[pattern] = counts.get(pattern, 0) + 1
|
|
764
|
+
return counts
|
|
765
|
+
|
|
766
|
+
|
|
767
|
+
def compute_score(findings: list[dict]) -> int:
|
|
768
|
+
"""Compute injection security score starting at 100, deducting by severity."""
|
|
769
|
+
score = 100
|
|
770
|
+
for f in findings:
|
|
771
|
+
deduction = SCORE_DEDUCTIONS.get(f["severity"], 0)
|
|
772
|
+
score -= deduction
|
|
773
|
+
return max(0, score)
|
|
774
|
+
|
|
775
|
+
|
|
776
|
+
# =========================================================================
|
|
777
|
+
# Report formatters
|
|
778
|
+
# =========================================================================
|
|
779
|
+
|
|
780
|
+
_INJECTION_TYPE_LABELS = {
|
|
781
|
+
"code_injection": "Code Injection",
|
|
782
|
+
"command_injection": "Command Injection",
|
|
783
|
+
"sql_injection": "SQL Injection",
|
|
784
|
+
"prompt_injection": "Prompt Injection",
|
|
785
|
+
"xss": "Cross-Site Scripting (XSS)",
|
|
786
|
+
"ssrf": "Server-Side Request Forgery (SSRF)",
|
|
787
|
+
"path_traversal": "Path Traversal",
|
|
788
|
+
}
|
|
789
|
+
|
|
790
|
+
|
|
791
|
+
def format_text_report(
|
|
792
|
+
target: str,
|
|
793
|
+
total_files: int,
|
|
794
|
+
findings: list[dict],
|
|
795
|
+
severity_counts: dict[str, int],
|
|
796
|
+
type_counts: dict[str, int],
|
|
797
|
+
pattern_counts: dict[str, int],
|
|
798
|
+
score: int,
|
|
799
|
+
verdict: dict,
|
|
800
|
+
elapsed: float,
|
|
801
|
+
include_low: bool = False,
|
|
802
|
+
) -> str:
|
|
803
|
+
"""Build a human-readable text report grouped by injection type."""
|
|
804
|
+
lines: list[str] = []
|
|
805
|
+
|
|
806
|
+
lines.append("=" * 72)
|
|
807
|
+
lines.append(" 007 INJECTION SCANNER -- VULNERABILITY REPORT")
|
|
808
|
+
lines.append("=" * 72)
|
|
809
|
+
lines.append("")
|
|
810
|
+
|
|
811
|
+
# Metadata
|
|
812
|
+
lines.append(f" Target: {target}")
|
|
813
|
+
lines.append(f" Timestamp: {config.get_timestamp()}")
|
|
814
|
+
lines.append(f" Duration: {elapsed:.2f}s")
|
|
815
|
+
lines.append(f" Files scanned: {total_files}")
|
|
816
|
+
lines.append(f" Total findings: {len(findings)}")
|
|
817
|
+
lines.append("")
|
|
818
|
+
|
|
819
|
+
# Severity distribution
|
|
820
|
+
lines.append("-" * 72)
|
|
821
|
+
lines.append(" SEVERITY DISTRIBUTION")
|
|
822
|
+
lines.append("-" * 72)
|
|
823
|
+
for sev in ("CRITICAL", "HIGH", "MEDIUM", "LOW", "INFO"):
|
|
824
|
+
count = severity_counts.get(sev, 0)
|
|
825
|
+
bar = "#" * min(count, 40)
|
|
826
|
+
lines.append(f" {sev:<10} {count:>5} {bar}")
|
|
827
|
+
lines.append("")
|
|
828
|
+
|
|
829
|
+
# Injection type breakdown
|
|
830
|
+
if type_counts:
|
|
831
|
+
lines.append("-" * 72)
|
|
832
|
+
lines.append(" FINDINGS BY INJECTION TYPE")
|
|
833
|
+
lines.append("-" * 72)
|
|
834
|
+
sorted_types = sorted(type_counts.items(), key=lambda x: x[1], reverse=True)
|
|
835
|
+
for itype, count in sorted_types:
|
|
836
|
+
label = _INJECTION_TYPE_LABELS.get(itype, itype)
|
|
837
|
+
lines.append(f" {label:<40} {count:>5}")
|
|
838
|
+
lines.append("")
|
|
839
|
+
|
|
840
|
+
# Detailed findings grouped by injection type
|
|
841
|
+
min_severity = config.SEVERITY["LOW"] if include_low else config.SEVERITY["MEDIUM"]
|
|
842
|
+
|
|
843
|
+
displayed = [
|
|
844
|
+
f for f in findings
|
|
845
|
+
if config.SEVERITY.get(f.get("severity", "INFO"), 0) >= min_severity
|
|
846
|
+
]
|
|
847
|
+
|
|
848
|
+
if displayed:
|
|
849
|
+
# Group by injection type
|
|
850
|
+
by_type: dict[str, list[dict]] = {}
|
|
851
|
+
for f in displayed:
|
|
852
|
+
itype = f.get("injection_type", "unknown")
|
|
853
|
+
by_type.setdefault(itype, []).append(f)
|
|
854
|
+
|
|
855
|
+
# Order: code_injection, command_injection, sql_injection, prompt_injection,
|
|
856
|
+
# xss, ssrf, path_traversal, then anything else
|
|
857
|
+
type_order = [
|
|
858
|
+
"code_injection", "command_injection", "sql_injection",
|
|
859
|
+
"prompt_injection", "xss", "ssrf", "path_traversal",
|
|
860
|
+
]
|
|
861
|
+
# Add any types not in the predefined order
|
|
862
|
+
for t in by_type:
|
|
863
|
+
if t not in type_order:
|
|
864
|
+
type_order.append(t)
|
|
865
|
+
|
|
866
|
+
for itype in type_order:
|
|
867
|
+
itype_findings = by_type.get(itype, [])
|
|
868
|
+
if not itype_findings:
|
|
869
|
+
continue
|
|
870
|
+
|
|
871
|
+
label = _INJECTION_TYPE_LABELS.get(itype, itype)
|
|
872
|
+
lines.append("-" * 72)
|
|
873
|
+
lines.append(f" [{label.upper()}] ({len(itype_findings)} findings)")
|
|
874
|
+
lines.append("-" * 72)
|
|
875
|
+
|
|
876
|
+
# Sub-group by severity
|
|
877
|
+
for sev in ("CRITICAL", "HIGH", "MEDIUM", "LOW"):
|
|
878
|
+
sev_group = [f for f in itype_findings if f["severity"] == sev]
|
|
879
|
+
if not sev_group:
|
|
880
|
+
continue
|
|
881
|
+
|
|
882
|
+
for f in sorted(sev_group, key=lambda x: (x["file"], x.get("line", 0))):
|
|
883
|
+
taint_marker = " [TAINTED]" if f.get("has_user_input_nearby") else ""
|
|
884
|
+
lines.append(
|
|
885
|
+
f" [{sev}] {f['file']}:L{f.get('line', 0)}{taint_marker}"
|
|
886
|
+
)
|
|
887
|
+
lines.append(f" {f['description']}")
|
|
888
|
+
if f.get("snippet"):
|
|
889
|
+
lines.append(f" > {f['snippet']}")
|
|
890
|
+
lines.append("")
|
|
891
|
+
else:
|
|
892
|
+
lines.append(" No injection findings above the display threshold.")
|
|
893
|
+
lines.append("")
|
|
894
|
+
|
|
895
|
+
# Score and verdict
|
|
896
|
+
lines.append("=" * 72)
|
|
897
|
+
lines.append(f" INJECTION SECURITY SCORE: {score} / 100")
|
|
898
|
+
lines.append(f" VERDICT: {verdict['emoji']} {verdict['label']}")
|
|
899
|
+
lines.append(f" {verdict['description']}")
|
|
900
|
+
lines.append("=" * 72)
|
|
901
|
+
lines.append("")
|
|
902
|
+
|
|
903
|
+
return "\n".join(lines)
|
|
904
|
+
|
|
905
|
+
|
|
906
|
+
def build_json_report(
|
|
907
|
+
target: str,
|
|
908
|
+
total_files: int,
|
|
909
|
+
findings: list[dict],
|
|
910
|
+
severity_counts: dict[str, int],
|
|
911
|
+
type_counts: dict[str, int],
|
|
912
|
+
pattern_counts: dict[str, int],
|
|
913
|
+
score: int,
|
|
914
|
+
verdict: dict,
|
|
915
|
+
elapsed: float,
|
|
916
|
+
) -> dict:
|
|
917
|
+
"""Build a structured JSON-serializable report dict."""
|
|
918
|
+
return {
|
|
919
|
+
"scan": "injection_scanner",
|
|
920
|
+
"target": target,
|
|
921
|
+
"timestamp": config.get_timestamp(),
|
|
922
|
+
"duration_seconds": round(elapsed, 3),
|
|
923
|
+
"total_files_scanned": total_files,
|
|
924
|
+
"total_findings": len(findings),
|
|
925
|
+
"severity_counts": severity_counts,
|
|
926
|
+
"injection_type_counts": type_counts,
|
|
927
|
+
"pattern_counts": pattern_counts,
|
|
928
|
+
"score": score,
|
|
929
|
+
"verdict": {
|
|
930
|
+
"label": verdict["label"],
|
|
931
|
+
"description": verdict["description"],
|
|
932
|
+
"emoji": verdict["emoji"],
|
|
933
|
+
},
|
|
934
|
+
"findings": findings,
|
|
935
|
+
}
|
|
936
|
+
|
|
937
|
+
|
|
938
|
+
# =========================================================================
|
|
939
|
+
# Main entry point
|
|
940
|
+
# =========================================================================
|
|
941
|
+
|
|
942
|
+
def run_scan(
|
|
943
|
+
target_path: str,
|
|
944
|
+
output_format: str = "text",
|
|
945
|
+
verbose: bool = False,
|
|
946
|
+
include_low: bool = False,
|
|
947
|
+
) -> dict:
|
|
948
|
+
"""Execute the injection vulnerability scan and return the report dict.
|
|
949
|
+
|
|
950
|
+
Args:
|
|
951
|
+
target_path: Path to the directory to scan.
|
|
952
|
+
output_format: 'text' or 'json'.
|
|
953
|
+
verbose: Enable debug-level logging.
|
|
954
|
+
include_low: Include LOW severity findings in text output.
|
|
955
|
+
|
|
956
|
+
Returns:
|
|
957
|
+
JSON-compatible report dict.
|
|
958
|
+
"""
|
|
959
|
+
if verbose:
|
|
960
|
+
logger.setLevel("DEBUG")
|
|
961
|
+
|
|
962
|
+
config.ensure_directories()
|
|
963
|
+
|
|
964
|
+
target = Path(target_path).resolve()
|
|
965
|
+
if not target.exists():
|
|
966
|
+
logger.error("Target path does not exist: %s", target)
|
|
967
|
+
sys.exit(1)
|
|
968
|
+
if not target.is_dir():
|
|
969
|
+
logger.error("Target is not a directory: %s", target)
|
|
970
|
+
sys.exit(1)
|
|
971
|
+
|
|
972
|
+
logger.info("Starting injection vulnerability scan of %s", target)
|
|
973
|
+
start_time = time.time()
|
|
974
|
+
|
|
975
|
+
# Collect files
|
|
976
|
+
files = collect_files(target)
|
|
977
|
+
total_files = len(files)
|
|
978
|
+
logger.info("Collected %d files for injection scanning", total_files)
|
|
979
|
+
|
|
980
|
+
# Scan each file
|
|
981
|
+
all_findings: list[dict] = []
|
|
982
|
+
max_report = config.LIMITS["max_report_findings"]
|
|
983
|
+
|
|
984
|
+
for fpath in files:
|
|
985
|
+
if len(all_findings) >= max_report:
|
|
986
|
+
logger.warning(
|
|
987
|
+
"Reached max_report_findings limit (%d). Truncating.", max_report
|
|
988
|
+
)
|
|
989
|
+
break
|
|
990
|
+
|
|
991
|
+
file_findings = scan_file(fpath, verbose=verbose)
|
|
992
|
+
remaining = max_report - len(all_findings)
|
|
993
|
+
all_findings.extend(file_findings[:remaining])
|
|
994
|
+
|
|
995
|
+
elapsed = time.time() - start_time
|
|
996
|
+
logger.info(
|
|
997
|
+
"Injection scan complete: %d files, %d findings in %.2fs",
|
|
998
|
+
total_files, len(all_findings), elapsed,
|
|
999
|
+
)
|
|
1000
|
+
|
|
1001
|
+
# Aggregation
|
|
1002
|
+
severity_counts = aggregate_by_severity(all_findings)
|
|
1003
|
+
type_counts = aggregate_by_injection_type(all_findings)
|
|
1004
|
+
pattern_counts = aggregate_by_pattern(all_findings)
|
|
1005
|
+
score = compute_score(all_findings)
|
|
1006
|
+
verdict = config.get_verdict(score)
|
|
1007
|
+
|
|
1008
|
+
# Audit log
|
|
1009
|
+
config.log_audit_event(
|
|
1010
|
+
action="injection_scan",
|
|
1011
|
+
target=str(target),
|
|
1012
|
+
result=f"score={score}, findings={len(all_findings)}, verdict={verdict['label']}",
|
|
1013
|
+
details={
|
|
1014
|
+
"total_files": total_files,
|
|
1015
|
+
"severity_counts": severity_counts,
|
|
1016
|
+
"injection_type_counts": type_counts,
|
|
1017
|
+
"pattern_counts": pattern_counts,
|
|
1018
|
+
"duration_seconds": round(elapsed, 3),
|
|
1019
|
+
},
|
|
1020
|
+
)
|
|
1021
|
+
|
|
1022
|
+
# Build report
|
|
1023
|
+
report = build_json_report(
|
|
1024
|
+
target=str(target),
|
|
1025
|
+
total_files=total_files,
|
|
1026
|
+
findings=all_findings,
|
|
1027
|
+
severity_counts=severity_counts,
|
|
1028
|
+
type_counts=type_counts,
|
|
1029
|
+
pattern_counts=pattern_counts,
|
|
1030
|
+
score=score,
|
|
1031
|
+
verdict=verdict,
|
|
1032
|
+
elapsed=elapsed,
|
|
1033
|
+
)
|
|
1034
|
+
|
|
1035
|
+
# Output
|
|
1036
|
+
if output_format == "json":
|
|
1037
|
+
print(json.dumps(report, indent=2, ensure_ascii=False))
|
|
1038
|
+
else:
|
|
1039
|
+
print(format_text_report(
|
|
1040
|
+
target=str(target),
|
|
1041
|
+
total_files=total_files,
|
|
1042
|
+
findings=all_findings,
|
|
1043
|
+
severity_counts=severity_counts,
|
|
1044
|
+
type_counts=type_counts,
|
|
1045
|
+
pattern_counts=pattern_counts,
|
|
1046
|
+
score=score,
|
|
1047
|
+
verdict=verdict,
|
|
1048
|
+
elapsed=elapsed,
|
|
1049
|
+
include_low=include_low,
|
|
1050
|
+
))
|
|
1051
|
+
|
|
1052
|
+
return report
|
|
1053
|
+
|
|
1054
|
+
|
|
1055
|
+
# =========================================================================
|
|
1056
|
+
# CLI
|
|
1057
|
+
# =========================================================================
|
|
1058
|
+
|
|
1059
|
+
if __name__ == "__main__":
|
|
1060
|
+
parser = argparse.ArgumentParser(
|
|
1061
|
+
description=(
|
|
1062
|
+
"007 Injection Scanner -- Specialized scanner for injection "
|
|
1063
|
+
"vulnerabilities (code injection, SQL injection, command injection, "
|
|
1064
|
+
"prompt injection, XSS, SSRF, path traversal)."
|
|
1065
|
+
),
|
|
1066
|
+
epilog=(
|
|
1067
|
+
"Examples:\n"
|
|
1068
|
+
" python injection_scanner.py --target ./my-project\n"
|
|
1069
|
+
" python injection_scanner.py --target ./my-project --output json\n"
|
|
1070
|
+
" python injection_scanner.py --target ./my-project --verbose --include-low"
|
|
1071
|
+
),
|
|
1072
|
+
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
1073
|
+
)
|
|
1074
|
+
parser.add_argument(
|
|
1075
|
+
"--target",
|
|
1076
|
+
required=True,
|
|
1077
|
+
help="Path to the directory to scan (required).",
|
|
1078
|
+
)
|
|
1079
|
+
parser.add_argument(
|
|
1080
|
+
"--output",
|
|
1081
|
+
choices=["text", "json"],
|
|
1082
|
+
default="text",
|
|
1083
|
+
help="Output format: 'text' (default) or 'json'.",
|
|
1084
|
+
)
|
|
1085
|
+
parser.add_argument(
|
|
1086
|
+
"--verbose",
|
|
1087
|
+
action="store_true",
|
|
1088
|
+
default=False,
|
|
1089
|
+
help="Enable verbose/debug logging.",
|
|
1090
|
+
)
|
|
1091
|
+
parser.add_argument(
|
|
1092
|
+
"--include-low",
|
|
1093
|
+
action="store_true",
|
|
1094
|
+
default=False,
|
|
1095
|
+
help="Include LOW severity findings in text output (hidden by default).",
|
|
1096
|
+
)
|
|
1097
|
+
|
|
1098
|
+
args = parser.parse_args()
|
|
1099
|
+
run_scan(
|
|
1100
|
+
target_path=args.target,
|
|
1101
|
+
output_format=args.output,
|
|
1102
|
+
verbose=args.verbose,
|
|
1103
|
+
include_low=args.include_low,
|
|
1104
|
+
)
|