synapse-hub 2.2.4 → 2.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.agents/agents/failure-analyst.md +11 -0
- package/.agents/agents/live-explorer.md +28 -0
- package/.agents/agents/orchestrator.md +20 -4
- package/.agents/agents/pom-crafter.md +12 -0
- package/.agents/agents/quality-gatekeeper.md +19 -4
- package/.agents/agents/test-runner.md +11 -0
- package/.agents/rules/communication-style.md +40 -0
- package/.agents/rules/context-engineering.md +23 -0
- package/.agents/rules/git-push-protocol.md +10 -16
- package/.agents/rules/graphify.md +14 -14
- package/.agents/rules/mandatory-playwright-live.md +29 -29
- package/.agents/rules/mobile_appium_rules.md +132 -0
- package/.agents/rules/performance_testing_rules.md +74 -0
- package/.agents/rules/systematic-debugging.md +6 -6
- package/.agents/skills/agent-orchestration-multi-agent-optimize/SKILL.md +64 -215
- package/.agents/skills/agent-orchestration-multi-agent-optimize/examples/swarm_workload_config.json +12 -0
- package/.agents/skills/antigravity-test-sentinel/SKILL.md +3 -3
- package/.agents/skills/antigravity-test-sentinel/examples/dod_gate_policy.json +11 -0
- package/.agents/skills/api-network-mocking-interception/SKILL.md +1 -1
- package/.agents/skills/api-network-mocking-interception/examples/network_route_stub.json +9 -0
- package/.agents/skills/api-spec-to-e2e-matrix/SKILL.md +9 -9
- package/.agents/skills/api-spec-to-e2e-matrix/examples/sample_openapi.json +19 -0
- package/.agents/skills/architecture-decision-records/SKILL.md +35 -412
- package/.agents/skills/architecture-decision-records/examples/0001-record-architecture-decisions.md +12 -0
- package/.agents/skills/auth-session-and-cookie-manager/SKILL.md +1 -1
- package/.agents/skills/auth-session-and-cookie-manager/examples/storage_state_sample.json +11 -0
- package/.agents/skills/backend-load-and-stress-patterns/SKILL.md +80 -186
- package/.agents/skills/backend-load-and-stress-patterns/examples/k6_load_test_sample.js +13 -0
- package/.agents/skills/bats-testing-patterns/SKILL.md +1 -1
- package/.agents/skills/bats-testing-patterns/examples/cli_runner_test.bats +6 -0
- package/.agents/skills/bats-testing-patterns/resources/implementation-playbook.md +614 -614
- package/.agents/skills/clean-pom-crafter/SKILL.md +21 -3
- package/.agents/skills/clean-pom-crafter/examples/standard_test_case.groovy +28 -28
- package/.agents/skills/clean-pom-crafter/references/pom_patterns.groovy +138 -138
- package/.agents/skills/code-refactoring-refactor-clean/SKILL.md +2 -2
- package/.agents/skills/code-refactoring-refactor-clean/examples/refactored_page_sample.groovy +9 -0
- package/.agents/skills/db-state-verifier-and-seeder/SKILL.md +5 -5
- package/.agents/skills/db-state-verifier-and-seeder/examples/db_fixture_seed.json +12 -0
- package/.agents/skills/e2e-testing-patterns/SKILL.md +1 -1
- package/.agents/skills/e2e-testing-patterns/examples/playwright_sharded_config.ts +12 -0
- package/.agents/skills/e2e-testing-patterns/resources/implementation-playbook.md +522 -522
- package/.agents/skills/failure-autopsy-and-healing/SKILL.md +17 -3
- package/.agents/skills/failure-autopsy-and-healing/examples/sample_junit_failure.xml +8 -0
- package/.agents/skills/failure-autopsy-and-healing/references/healing_matrix.md +64 -64
- package/.agents/skills/failure-autopsy-and-healing/references/qa_investigation_protocol.md +56 -56
- package/.agents/skills/failure-autopsy-and-healing/scripts/triage_failures.sh +98 -98
- package/.agents/skills/file-and-report-validator/SKILL.md +1 -1
- package/.agents/skills/file-and-report-validator/examples/sample_export_schema.json +14 -0
- package/.agents/skills/flaky-test-sentinel-and-quarantine/SKILL.md +3 -3
- package/.agents/skills/flaky-test-sentinel-and-quarantine/examples/quarantine_registry.json +11 -0
- package/.agents/skills/gherkin-to-suite/SKILL.md +103 -0
- package/.agents/skills/gherkin-to-suite/examples/checkout.feature +39 -0
- package/.agents/skills/gherkin-to-suite/examples/suite_report_sample.json +334 -0
- package/.agents/skills/github-actions-templates/SKILL.md +47 -304
- package/.agents/skills/github-actions-templates/examples/matrix_test_pipeline.yml +8 -0
- package/.agents/skills/graphify-impact-analyzer/SKILL.md +6 -6
- package/.agents/skills/graphify-impact-analyzer/examples/dependency_blast_radius.json +8 -0
- package/.agents/skills/katalon-cli-runner/SKILL.md +2 -2
- package/.agents/skills/katalon-cli-runner/examples/headless_kre_exec.sh +3 -0
- package/.agents/skills/katalon-cli-runner/references/groovy-patterns.md +209 -209
- package/.agents/skills/katalon-cli-runner/references/katalon-api.md +155 -155
- package/.agents/skills/katalon-cli-runner/references/runner_commands.md +67 -67
- package/.agents/skills/lam-script-generator/SKILL.md +42 -1
- package/.agents/skills/lam-script-generator/examples/user_story_sample.md +8 -0
- package/.agents/skills/lean-code-ladder/SKILL.md +83 -0
- package/.agents/skills/lean-code-ladder/examples/ladder_walkthrough.md +37 -0
- package/.agents/skills/meta-prompt-crafter/SKILL.md +107 -0
- package/.agents/skills/meta-prompt-crafter/examples/qa_prompt_template.md +35 -0
- package/.agents/skills/persistent-planning-files/SKILL.md +108 -0
- package/.agents/skills/persistent-planning-files/examples/task_plan_example.md +56 -0
- package/.agents/skills/playwright-live-interaction/SKILL.md +49 -1
- package/.agents/skills/playwright-live-interaction/examples/live_probe_script.js +5 -0
- package/.agents/skills/playwright-live-interaction/references/advanced_superpowers.md +160 -160
- package/.agents/skills/playwright-live-interaction/references/compact_accessibility_snapshots.md +75 -75
- package/.agents/skills/playwright-live-interaction/references/recipes.md +388 -388
- package/.agents/skills/playwright-live-interaction/references/swarm_orchestration.md +134 -134
- package/.agents/skills/playwright-webapp-testing/SKILL.md +93 -0
- package/.agents/skills/playwright-webapp-testing/examples/server_lifecycle_playwright.py +63 -0
- package/.agents/skills/rbt-manual-testing/SKILL.md +150 -0
- package/.agents/skills/rbt-manual-testing/examples/checkout_rbt_manual_suite.md +25 -0
- package/.agents/skills/security-dast-sentinel/SKILL.md +54 -135
- package/.agents/skills/security-dast-sentinel/examples/dast_scan_config.json +10 -0
- package/.agents/skills/self-healing-locator/SKILL.md +24 -10
- package/.agents/skills/self-healing-locator/examples/aom_diff_sample.json +16 -0
- package/.agents/skills/skillspector-security-guard/SKILL.md +32 -90
- package/.agents/skills/skillspector-security-guard/examples/safe_mcp_config.json +12 -0
- package/.agents/skills/surgical-coding-principles/SKILL.md +89 -0
- package/.agents/skills/surgical-coding-principles/examples/surgical_vs_scope_creep.md +46 -0
- package/.agents/skills/synthetic-test-data-generator/SKILL.md +3 -3
- package/.agents/skills/synthetic-test-data-generator/examples/idempotent_data_recipe.json +9 -0
- package/.agents/skills/systematic-debugging/SKILL.md +100 -0
- package/.agents/skills/systematic-debugging/examples/four_phase_case.md +44 -0
- package/.agents/skills/test-driven-development/SKILL.md +114 -0
- package/.agents/skills/test-driven-development/examples/red_green_refactor_qa.md +78 -0
- package/.agents/skills/token-and-context-optimizer/SKILL.md +1 -1
- package/.agents/skills/token-and-context-optimizer/examples/distilled_log_sample.txt +3 -0
- package/.agents/skills/verification-before-completion/SKILL.md +100 -0
- package/.agents/skills/verification-before-completion/examples/evidence_log_example.md +46 -0
- package/.agents/skills/visual-regression-sentinel/SKILL.md +21 -1
- package/.agents/skills/visual-regression-sentinel/examples/visual_mask_rules.json +9 -0
- package/.agents/skills/wcag-audit-patterns/SKILL.md +3 -3
- package/.agents/skills/wcag-audit-patterns/examples/wcag_compliance_checklist.json +10 -0
- package/.agents/skills/wcag-audit-patterns/resources/implementation-playbook.md +541 -541
- package/.agents/skills/web-vitals-and-performance-profiler/SKILL.md +1 -1
- package/.agents/skills/web-vitals-and-performance-profiler/examples/cwv_budget_policy.json +6 -0
- package/.agents/templates/completion_report.md +57 -57
- package/.agents/workflows/analyze_requirement_document.md +18 -0
- package/.agents/workflows/export_testcases_excel.md +32 -0
- package/.agents/workflows/generate_combinatorial_test_data.md +28 -0
- package/.agents/workflows/generate_cross_module_test_plan.md +37 -0
- package/.agents/workflows/generate_manual_testcases_rbt.md +56 -0
- package/.agents/workflows/generate_requirements_from_website.md +25 -0
- package/.agents/workflows/generate_testcases_from_requirements.md +21 -0
- package/.agents/workflows/graphify.md +10 -10
- package/.agents/workflows/verify.md +2 -2
- package/.cursor/rules/language.mdc +7 -7
- package/.cursor/rules/synapse-qa.mdc +23 -0
- package/.cursorrules +7 -0
- package/.pre-commit-hooks.yaml +14 -0
- package/.windsurfrules +7 -0
- package/AGENTS.md +54 -15
- package/CLAUDE.md +35 -12
- package/LICENSE +30 -0
- package/NOTICE +180 -0
- package/README.en.md +325 -0
- package/README.md +247 -516
- package/action.yml +51 -0
- package/benchmarks/qa_eval/README.md +71 -0
- package/benchmarks/qa_eval/build_dataset.py +677 -0
- package/benchmarks/qa_eval/dataset/api_contract_drift/qa-challenge-06_api_contract_drift.json +22 -0
- package/benchmarks/qa_eval/dataset/api_contract_drift/qa-challenge-07_api_contract_drift.json +22 -0
- package/benchmarks/qa_eval/dataset/api_contract_drift/qa-challenge-08_api_contract_drift.json +22 -0
- package/benchmarks/qa_eval/dataset/api_contract_drift/qa-challenge-09_api_contract_drift.json +22 -0
- package/benchmarks/qa_eval/dataset/api_contract_drift/qa-challenge-10_api_contract_drift.json +22 -0
- package/benchmarks/qa_eval/dataset/fake_pass_tautology/qa-challenge-21_fake_pass_tautology.json +21 -0
- package/benchmarks/qa_eval/dataset/fake_pass_tautology/qa-challenge-22_fake_pass_tautology.json +21 -0
- package/benchmarks/qa_eval/dataset/fake_pass_tautology/qa-challenge-23_fake_pass_tautology.json +21 -0
- package/benchmarks/qa_eval/dataset/fake_pass_tautology/qa-challenge-24_fake_pass_tautology.json +21 -0
- package/benchmarks/qa_eval/dataset/fake_pass_tautology/qa-challenge-25_fake_pass_tautology.json +21 -0
- package/benchmarks/qa_eval/dataset/locator_drift/qa-challenge-01_locator_drift.json +23 -0
- package/benchmarks/qa_eval/dataset/locator_drift/qa-challenge-02_locator_drift.json +23 -0
- package/benchmarks/qa_eval/dataset/locator_drift/qa-challenge-03_locator_drift.json +23 -0
- package/benchmarks/qa_eval/dataset/locator_drift/qa-challenge-04_locator_drift.json +23 -0
- package/benchmarks/qa_eval/dataset/locator_drift/qa-challenge-05_locator_drift.json +23 -0
- package/benchmarks/qa_eval/dataset/stale_and_mutation/qa-challenge-16_stale_and_mutation.json +22 -0
- package/benchmarks/qa_eval/dataset/stale_and_mutation/qa-challenge-17_stale_and_mutation.json +22 -0
- package/benchmarks/qa_eval/dataset/stale_and_mutation/qa-challenge-18_stale_and_mutation.json +22 -0
- package/benchmarks/qa_eval/dataset/stale_and_mutation/qa-challenge-19_stale_and_mutation.json +22 -0
- package/benchmarks/qa_eval/dataset/stale_and_mutation/qa-challenge-20_stale_and_mutation.json +23 -0
- package/benchmarks/qa_eval/dataset/timing_and_race/qa-challenge-11_timing_and_race.json +22 -0
- package/benchmarks/qa_eval/dataset/timing_and_race/qa-challenge-12_timing_and_race.json +22 -0
- package/benchmarks/qa_eval/dataset/timing_and_race/qa-challenge-13_timing_and_race.json +22 -0
- package/benchmarks/qa_eval/dataset/timing_and_race/qa-challenge-14_timing_and_race.json +22 -0
- package/benchmarks/qa_eval/dataset/timing_and_race/qa-challenge-15_timing_and_race.json +22 -0
- package/benchmarks/qa_eval/eval_runner.py +418 -0
- package/benchmarks/qa_eval/reports/AB_METHODOLOGY_REPORT.md +83 -0
- package/benchmarks/qa_eval/reports/BENCHMARK_REPORT.md +69 -0
- package/benchmarks/qa_eval/reports/eval_results.json +105 -0
- package/bin/agent-qa.js +10 -3
- package/docs/ARCHITECTURE.md +107 -0
- package/docs/CI_CD_INTEGRATION.md +94 -0
- package/docs/CLI_REFERENCE.md +157 -0
- package/docs/CONTRIBUTING.md +91 -0
- package/docs/PLUGIN_DEVELOPMENT_GUIDE.md +126 -0
- package/docs/TOKEN_QUOTA_PLAYBOOK.md +41 -0
- package/docs/adopted-ecosystem-tools.md +87 -0
- package/docs/schemas/synapse.config.schema.json +64 -0
- package/examples/cypress-ecommerce-suite/README.md +45 -0
- package/examples/cypress-ecommerce-suite/cypress/e2e/checkout.cy.js +17 -0
- package/examples/cypress-ecommerce-suite/cypress/pages/BasePage.js +29 -0
- package/examples/cypress-ecommerce-suite/cypress/pages/CartPage.js +51 -0
- package/examples/cypress-ecommerce-suite/cypress.config.js +13 -0
- package/examples/cypress-ecommerce-suite/package.json +13 -0
- package/examples/katalon-suite/Keywords/pages/BasePage.groovy +19 -0
- package/examples/katalon-suite/Keywords/pages/DashboardPage.groovy +29 -0
- package/examples/katalon-suite/Keywords/pages/LoginPage.groovy +45 -0
- package/examples/katalon-suite/Profiles/default.glbl +12 -0
- package/examples/katalon-suite/README.md +18 -0
- package/examples/katalon-suite/Scripts/Auth/TC_LoginSuccess/Script1234567890.groovy +12 -0
- package/examples/katalon-suite/Scripts/Dashboard/TC_ViewSummary/Script1234567891.groovy +15 -0
- package/examples/katalon-suite/katalon_suite.prj +21 -0
- package/examples/playwright-crm-suite/README.md +40 -0
- package/examples/playwright-crm-suite/package.json +14 -0
- package/examples/playwright-crm-suite/pages/BasePage.ts +37 -0
- package/examples/playwright-crm-suite/pages/CustomerPage.ts +57 -0
- package/examples/playwright-crm-suite/pages/LoginPage.ts +32 -0
- package/examples/playwright-crm-suite/playwright.config.ts +27 -0
- package/examples/playwright-crm-suite/tests/auth.spec.ts +22 -0
- package/examples/playwright-crm-suite/tests/customers.spec.ts +33 -0
- package/examples/plugins/sample-custom-plugin/README.md +14 -0
- package/examples/plugins/sample-custom-plugin/plugin.json +16 -0
- package/examples/plugins/sample-custom-plugin/skills/custom-security-headers-audit/SKILL.md +29 -0
- package/examples/plugins/sample-custom-plugin/skills/custom-security-headers-audit/examples/audit_fixture.json +8 -0
- package/examples/plugins/sample-custom-plugin/tools/custom_security_headers.py +125 -0
- package/examples/practices/README.md +22 -0
- package/examples/practices/requirements/crm/requirements_contracts.md +292 -0
- package/examples/practices/requirements/crm/requirements_customers.md +462 -0
- package/examples/practices/requirements/crm/requirements_login.md +202 -0
- package/examples/practices/requirements/crm/requirements_projects.md +396 -0
- package/examples/practices/requirements/crm/requirements_tasks.md +343 -0
- package/examples/practices/requirements/ecommerce/requirements_category_brand.txt +402 -0
- package/examples/practices/requirements/jira/KAN-5/KAN-1/2026-04-03_04-28-27.png +0 -0
- package/examples/practices/requirements/jira/KAN-5/KAN-1/KAN-1_requirement.md +52 -0
- package/examples/practices/requirements/jira/KAN-5/KAN-4/2026-04-03_04-28-19.png +0 -0
- package/examples/practices/requirements/jira/KAN-5/KAN-4/2026-04-03_04-28-57.png +0 -0
- package/examples/practices/requirements/jira/KAN-5/KAN-4/KAN-4_requirement.md +119 -0
- package/examples/practices/requirements/jira/KAN-5/KAN-5_overview.md +9 -0
- package/examples/practices/requirements/jira/KAN-5/KAN-6/2026-04-03_05-58-08.png +0 -0
- package/examples/practices/requirements/jira/KAN-5/KAN-6/2026-04-03_05-58-23.png +0 -0
- package/examples/practices/requirements/jira/KAN-5/KAN-6/KAN-6_requirement.md +32 -0
- package/examples/practices/testcases/api/book_management_api_test_cases.md +144 -0
- package/examples/practices/testcases/api/user_management_api_test_cases.md +139 -0
- package/examples/practices/testcases/crm/test_cases_crm_login.md +148 -0
- package/examples/practices/testcases/crm/test_cases_customers.md +156 -0
- package/examples/pytest-api-suite/README.md +45 -0
- package/examples/pytest-api-suite/conftest.py +31 -0
- package/examples/pytest-api-suite/pages/user_api_client.py +36 -0
- package/examples/pytest-api-suite/requirements.txt +2 -0
- package/examples/pytest-api-suite/tests/test_user_api.py +44 -0
- package/examples/selenium-suite/README.md +18 -0
- package/examples/selenium-suite/pages/base_page.py +27 -0
- package/examples/selenium-suite/pages/login_page.py +29 -0
- package/examples/selenium-suite/pages/order_page.py +25 -0
- package/examples/selenium-suite/pytest.ini +6 -0
- package/examples/selenium-suite/requirements.txt +2 -0
- package/examples/selenium-suite/tests/test_login.py +10 -0
- package/examples/selenium-suite/tests/test_order.py +10 -0
- package/package.json +22 -7
- package/scripts/generate_skill_examples.py +244 -0
- package/scripts/install.ps1 +112 -0
- package/scripts/install_global_plugin.ps1 +94 -0
- package/scripts/install_global_plugin.sh +49 -0
- package/scripts/lint_skills.ps1 +57 -0
- package/scripts/package_dist.py +323 -0
- package/scripts/setup.sh +59 -0
- package/scripts/update_hub.ps1 +19 -0
- package/scripts/update_hub.sh +15 -0
- package/skills_manifest.json +209 -6
- package/synapse.config.json +30 -0
- package/templates/PROJECT_AI_GUIDE_TEMPLATE.md +7 -7
- package/templates/PROJECT_STATE_TEMPLATE.md +1 -1
- package/templates/ai_bridges/CLAUDE.md +33 -16
- package/templates/ai_bridges/copilot-instructions.md +14 -21
- package/templates/ai_bridges/cursor_synapse_qa.mdc +17 -20
- package/templates/ai_bridges/windsurfrules +21 -21
- package/templates/ci/github-actions-synapse.yml +71 -4
- package/templates/welcome.html.tmpl +1674 -0
- package/tools/.infer_baseline.json +1102 -0
- package/tools/TOOLS_RUNBOOK.md +92 -130
- package/tools/__init__.py +26 -0
- package/tools/agent_canary.py +501 -501
- package/tools/analyze_project.py +31 -17
- package/tools/api_fuzzer.py +278 -6
- package/tools/author_integrity.py +6 -6
- package/tools/auto_heal.py +1 -1
- package/tools/browser_helpers.js +105 -7
- package/tools/bug_reporter.py +31 -1
- package/tools/bump_version.py +23 -3
- package/tools/cache_resolver.py +248 -14
- package/tools/check_env.py +55 -14
- package/tools/check_version_sync.py +153 -0
- package/tools/ci_healing_hook.py +1 -1
- package/tools/cli.py +263 -13
- package/tools/count_tokens.py +1257 -0
- package/tools/dashboard_generator.py +2638 -0
- package/tools/db_sandbox.py +198 -20
- package/tools/e2e_flow_prober.ps1 +12 -64
- package/tools/e2e_flow_prober.py +99 -0
- package/tools/e2e_flow_prober.sh +4 -0
- package/tools/flaky_detector.py +4 -3
- package/tools/generate_allure_report.ps1 +12 -29
- package/tools/generate_allure_report.py +77 -0
- package/tools/generate_allure_report.sh +4 -0
- package/tools/generate_jmx_plan.py +257 -58
- package/tools/generate_test_reports.ps1 +12 -134
- package/tools/generate_test_reports.py +133 -0
- package/tools/generate_test_reports.sh +4 -0
- package/tools/gherkin_suite.py +758 -0
- package/tools/git_pre_commit_guard.py +12 -6
- package/tools/goldfish_review.py +366 -0
- package/tools/heal_pr_automator.py +1 -1
- package/tools/heuristic_locator_matcher.py +170 -21
- package/tools/infer_linter.py +612 -70
- package/tools/lint_ai_docs.py +64 -0
- package/tools/matrix_sharder.py +1 -1
- package/tools/mcp_excel_server.ps1 +12 -250
- package/tools/mcp_excel_server.py +249 -0
- package/tools/mcp_excel_server.sh +4 -0
- package/tools/mcp_server.py +368 -144
- package/tools/md_to_xlsx.py +307 -0
- package/tools/micro_mutator.py +300 -69
- package/tools/omni_transpiler.py +2 -2
- package/tools/output_formatter.py +128 -0
- package/tools/pinpoint_failure.py +88 -88
- package/tools/project_state.py +2 -2
- package/tools/repo_map.py +1 -1
- package/tools/safety_firewall.py +196 -45
- package/tools/setup_project.py +294 -30
- package/tools/spec_drift_sentinel.py +439 -0
- package/tools/sync_agent_rules.py +248 -0
- package/tools/telemetry_correlator.py +45 -0
- package/tools/test_impact.py +51 -12
- package/tools/tms_sync_bridge.py +21 -5
- package/tools/universal_ir.py +3 -0
- package/tools/update_sentinel.py +87 -15
- package/tools/verify_all.ps1 +13 -0
- package/tools/verify_all.py +463 -79
- package/tools/visual_diff_canvas.py +1 -0
- package/tools/visual_som_helper.py +1 -1
- package/tools/watch_mode.py +305 -0
- package/tools/welcome_page.py +291 -0
package/tools/analyze_project.py
CHANGED
|
@@ -10,7 +10,7 @@ Executes in sequence:
|
|
|
10
10
|
2. Page Class & AST Repository Mapping (tools/repo_map.py)
|
|
11
11
|
3. Static Defect & Fake-Pass Sentinel (tools/infer_linter.py)
|
|
12
12
|
4. Data Flow Lifecycle & Teardown Audit (tools/trace_data_flow.py)
|
|
13
|
-
5. Definition of Done
|
|
13
|
+
5. Definition of Done 9 Quality Gates (tools/verify_all.py)
|
|
14
14
|
|
|
15
15
|
Generates & Updates In-Place inside the Target Project:
|
|
16
16
|
- PROJECT_HEALTH_REPORT.md (Root & reports/ of target project)
|
|
@@ -116,23 +116,37 @@ def run_full_analysis(target_repo):
|
|
|
116
116
|
|
|
117
117
|
# Step 3: Static Defect & Fake-Pass Sentinel
|
|
118
118
|
print(f"\n{BOLD}[3/5] Scanning Static Defects & Fake-Passes (infer_linter)...{RESET}")
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
119
|
+
if mode == "HUB":
|
|
120
|
+
print(f" {GREEN}[PASS] Hub Mode: Repository contains QA Hub assets; Static Defect Sentinel applies to target test suites.{RESET}")
|
|
121
|
+
summary["infer_linter"]["exit_code"] = 0
|
|
122
|
+
else:
|
|
123
|
+
scripts_dir = os.path.join(target_repo, "Scripts")
|
|
124
|
+
if not os.path.isdir(scripts_dir):
|
|
125
|
+
for cand in ["tests", "test", "e2e", "specs", "cypress"]:
|
|
126
|
+
cpath = os.path.join(target_repo, cand)
|
|
127
|
+
if os.path.isdir(cpath):
|
|
128
|
+
scripts_dir = cpath
|
|
129
|
+
break
|
|
130
|
+
try:
|
|
131
|
+
linter_code = infer_linter.run_infer_linter(target_dir=scripts_dir)
|
|
132
|
+
summary["infer_linter"]["exit_code"] = linter_code
|
|
133
|
+
except Exception as e:
|
|
134
|
+
print(f" ⚠️ infer_linter error: {e}")
|
|
125
135
|
|
|
126
136
|
# Step 4: Data Flow Lifecycle & Teardown Hygiene
|
|
127
137
|
print(f"\n{BOLD}[4/5] Auditing Synthetic Data Lifecycle & Teardown (trace_data_flow)...{RESET}")
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
summary["data_flow"]["exit_code"] =
|
|
131
|
-
|
|
132
|
-
|
|
138
|
+
if mode == "HUB":
|
|
139
|
+
print(f" {GREEN}[PASS] Hub Mode: Repository contains QA Hub assets; Data Flow Tracer applies to target test suites.{RESET}")
|
|
140
|
+
summary["data_flow"]["exit_code"] = 0
|
|
141
|
+
else:
|
|
142
|
+
try:
|
|
143
|
+
data_flow_code = trace_data_flow.run_data_flow_tracer(target_dir=scripts_dir, repo_root=target_repo)
|
|
144
|
+
summary["data_flow"]["exit_code"] = data_flow_code
|
|
145
|
+
except Exception as e:
|
|
146
|
+
print(f" ⚠️ trace_data_flow error: {e}")
|
|
133
147
|
|
|
134
|
-
# Step 5:
|
|
135
|
-
print(f"\n{BOLD}[5/5] Verifying
|
|
148
|
+
# Step 5: 9 Quality Gates DoD Verification
|
|
149
|
+
print(f"\n{BOLD}[5/5] Verifying 9 Quality Gates DoD (verify_all)...{RESET}")
|
|
136
150
|
gates_passed = True
|
|
137
151
|
gates = [
|
|
138
152
|
("Gate 1: UTF-8 No BOM", verify_all.gate_1_bom_check),
|
|
@@ -172,7 +186,7 @@ def run_full_analysis(target_repo):
|
|
|
172
186
|
| **Page Classes & Helpers** | `{summary['repo_map'].get('classes_count', 0)}` classes (`{summary['repo_map'].get('methods_count', 0)}` methods) | ✅ INDEXED |
|
|
173
187
|
| **Static Defect Sentinel** | `infer_linter` exit `{summary['infer_linter'].get('exit_code', 0)}` | {'✅ CLEAN' if summary['infer_linter'].get('exit_code', 0) == 0 else '❌ CRITICAL'} |
|
|
174
188
|
| **Lifecycle & Teardown** | `trace_data_flow` exit `{summary['data_flow'].get('exit_code', 0)}` | ✅ AUDITED |
|
|
175
|
-
| **
|
|
189
|
+
| **9 Quality Gates DoD** | `{summary['gates_status']}` | {'✅ 100% PASS' if gates_passed else '❌ BLOCKED'} |
|
|
176
190
|
|
|
177
191
|
---
|
|
178
192
|
|
|
@@ -193,7 +207,7 @@ def run_full_analysis(target_repo):
|
|
|
193
207
|
> 1. `PROJECT_HEALTH_REPORT.md` (immediate architecture status, warnings, and DoD readiness).
|
|
194
208
|
> 2. `repo_map.txt` (instant lookup for Page Classes, methods, and parameters in < 600 tokens).
|
|
195
209
|
> - **Post-Audit Git Push (Mandatory):** Once analysis completes, stage, commit, and push updated in-project artifacts to git (`git add PROJECT_HEALTH_REPORT.md repo_map.txt && git commit -m "docs(audit): update project health report and AST repo map" && git push origin <branch>`).
|
|
196
|
-
> - Whenever the user requests **"analyze project"** or **"project audit"**, re-run `
|
|
210
|
+
> - Whenever the user requests **"analyze project"** or **"project audit"**, re-run `npx synapse-hub analyze` to update this file in-place.
|
|
197
211
|
"""
|
|
198
212
|
with open(report_in_reports, "w", encoding="utf-8") as f:
|
|
199
213
|
f.write(md_content)
|
|
@@ -211,7 +225,7 @@ def run_full_analysis(target_repo):
|
|
|
211
225
|
print(f"{BOLD}{CYAN}======================================================================{RESET}")
|
|
212
226
|
print(f" Elapsed Time : {elapsed:.2f}s")
|
|
213
227
|
print(f" Page Classes Indexed : {summary['repo_map'].get('classes_count', 0)} classes")
|
|
214
|
-
print(f"
|
|
228
|
+
print(f" 9 Quality Gates DoD : {GREEN + '100% PASS' if gates_passed else RED + 'FAILED'}{RESET}")
|
|
215
229
|
print(f" In-Project Report Path : {report_in_root if os.path.abspath(target_repo).lower() != os.path.abspath(HUB_ROOT).lower() else report_in_reports}")
|
|
216
230
|
if os.path.abspath(target_repo).lower() != os.path.abspath(HUB_ROOT).lower():
|
|
217
231
|
print(f" {YELLOW}💡 Post-Analysis Action: Push updated files to Git:{RESET}")
|
package/tools/api_fuzzer.py
CHANGED
|
@@ -61,6 +61,26 @@ STACKTRACE_LEAK_PATTERNS = [
|
|
|
61
61
|
re.compile(r'ODBC SQL Server Driver', re.IGNORECASE),
|
|
62
62
|
]
|
|
63
63
|
|
|
64
|
+
# Severity taxonomy (concept borrowed from OWASP ZAP's alert risk levels,
|
|
65
|
+
# Apache-2.0 - re-expressed for the fuzz oracle). Each alarm class maps to a
|
|
66
|
+
# fixed risk level so CI can gate on CRITICAL findings alone.
|
|
67
|
+
ALARM_SEVERITY = {
|
|
68
|
+
"HTTP_": "HIGH", # HTTP_500/502/503_SERVER_CRASH
|
|
69
|
+
"STACKTRACE_LEAK": "CRITICAL",
|
|
70
|
+
"SLA_TIMEOUT": "LOW",
|
|
71
|
+
"AUTH_BYPASS": "CRITICAL",
|
|
72
|
+
"CONNECTION_ERROR": "MEDIUM",
|
|
73
|
+
}
|
|
74
|
+
SEVERITY_LEVELS = ["INFO", "LOW", "MEDIUM", "HIGH", "CRITICAL"]
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def classify_alarm_severity(alarm: str) -> str:
|
|
78
|
+
"""Maps an alarm string onto the ZAP-style severity taxonomy."""
|
|
79
|
+
for prefix, severity in ALARM_SEVERITY.items():
|
|
80
|
+
if alarm.startswith(prefix):
|
|
81
|
+
return severity
|
|
82
|
+
return "INFO"
|
|
83
|
+
|
|
64
84
|
GREEN = "\033[92m"
|
|
65
85
|
RED = "\033[91m"
|
|
66
86
|
YELLOW = "\033[93m"
|
|
@@ -79,21 +99,108 @@ class ApiFuzzer:
|
|
|
79
99
|
headers: Optional[Dict[str, str]] = None,
|
|
80
100
|
timeout_sec: float = 10.0,
|
|
81
101
|
sla_ms: float = 3000.0,
|
|
82
|
-
verify_ssl: bool =
|
|
102
|
+
verify_ssl: bool = True,
|
|
103
|
+
insecure: bool = False
|
|
83
104
|
):
|
|
84
105
|
self.base_url = base_url.rstrip("/")
|
|
85
106
|
self.spec_path = spec_path_or_url
|
|
86
107
|
self.custom_headers = headers or {"Content-Type": "application/json", "User-Agent": "AgentQA-Fuzzer/2.0"}
|
|
87
108
|
self.timeout_sec = timeout_sec
|
|
88
109
|
self.sla_ms = sla_ms
|
|
89
|
-
self.
|
|
90
|
-
|
|
91
|
-
|
|
110
|
+
self.insecure = insecure or (not verify_ssl)
|
|
111
|
+
self.verify_ssl = not self.insecure
|
|
112
|
+
|
|
113
|
+
# Environment Allowlist Guard (F2 Safety Discipline)
|
|
114
|
+
# Prevents accidental fuzzing or stress bombardment against production environments
|
|
115
|
+
parsed = urllib.parse.urlparse(self.base_url)
|
|
116
|
+
host = (parsed.hostname or "").lower().strip()
|
|
117
|
+
is_safe_env = self._is_safe_non_prod_host(host)
|
|
118
|
+
if host and not is_safe_env and not os.environ.get("SYNAPSE_ALLOW_PROD_FUZZ"):
|
|
119
|
+
raise PermissionError(
|
|
120
|
+
f"[SAFETY FIREWALL - REJECTED] Target host '{host}' is not in the allowed non-production environment allowlist "
|
|
121
|
+
"(localhost, private IP, .local/.internal, or verified dev/staging/qa/test subdomain). "
|
|
122
|
+
"Set SYNAPSE_ALLOW_PROD_FUZZ=1 only if authorized."
|
|
123
|
+
)
|
|
124
|
+
|
|
125
|
+
# Prepare SSL context (support self-signed staging certificates via explicit --insecure flag)
|
|
92
126
|
self.ssl_context = ssl.create_default_context()
|
|
93
|
-
if
|
|
127
|
+
if self.insecure:
|
|
128
|
+
print(" ⚠️ [SECURITY NOTICE] TLS certificate verification is explicitly disabled (--insecure).")
|
|
94
129
|
self.ssl_context.check_hostname = False
|
|
95
130
|
self.ssl_context.verify_mode = ssl.CERT_NONE
|
|
96
131
|
|
|
132
|
+
@staticmethod
|
|
133
|
+
def _is_safe_non_prod_host(host: str) -> bool:
|
|
134
|
+
"""
|
|
135
|
+
Validates whether a hostname strictly belongs to a local, private, or non-production environment.
|
|
136
|
+
Prevents substring false positives on production domains like 'devex.com', 'contest.gov', or 'attestation-bank.com'.
|
|
137
|
+
"""
|
|
138
|
+
if not host:
|
|
139
|
+
return False
|
|
140
|
+
|
|
141
|
+
host = host.lower().strip()
|
|
142
|
+
|
|
143
|
+
# 0. User-specified explicit host allowlist via SYNAPSE_ALLOWED_HOSTS
|
|
144
|
+
allowed_env = os.environ.get("SYNAPSE_ALLOWED_HOSTS", "").strip()
|
|
145
|
+
if allowed_env:
|
|
146
|
+
for item in allowed_env.split(","):
|
|
147
|
+
pattern = item.strip().lower()
|
|
148
|
+
if pattern:
|
|
149
|
+
if pattern.startswith("*.") and (host.endswith(pattern[1:]) or host == pattern[2:]):
|
|
150
|
+
return True
|
|
151
|
+
if host == pattern:
|
|
152
|
+
return True
|
|
153
|
+
|
|
154
|
+
# 1. Exact loopback / special names
|
|
155
|
+
if host in ("localhost", "127.0.0.1", "::1", "0.0.0.0"):
|
|
156
|
+
return True
|
|
157
|
+
|
|
158
|
+
# 2. Check IP address (loopback or private RFC 1918, disallowing link-local metadata like 169.254.169.254)
|
|
159
|
+
try:
|
|
160
|
+
import ipaddress
|
|
161
|
+
ip = ipaddress.ip_address(host)
|
|
162
|
+
if ip.is_link_local or ip in ipaddress.ip_network("169.254.0.0/16"):
|
|
163
|
+
return False
|
|
164
|
+
return ip.is_loopback or ip.is_private
|
|
165
|
+
except ValueError:
|
|
166
|
+
pass
|
|
167
|
+
|
|
168
|
+
# 3. Special-use TLDs (RFC 6761 / RFC 2606)
|
|
169
|
+
if any(host.endswith(tld) for tld in (".local", ".localhost", ".internal", ".test")):
|
|
170
|
+
return True
|
|
171
|
+
|
|
172
|
+
labels = host.split('.')
|
|
173
|
+
|
|
174
|
+
# 4. Reject production markers anywhere in hostname (e.g. prod-test.bank.com)
|
|
175
|
+
if any(re.search(r'(?:^|[-_])(prod|production)(?:$|[-_])', lbl) for lbl in labels):
|
|
176
|
+
return False
|
|
177
|
+
|
|
178
|
+
# 5. Prevent domain spoofing (e.g. bank.com.test.evil.io where public TLD appears in subdomains)
|
|
179
|
+
common_public_tlds = {
|
|
180
|
+
'com', 'org', 'net', 'edu', 'gov', 'mil', 'io', 'co', 'vn', 'uk', 'de',
|
|
181
|
+
'fr', 'jp', 'cn', 'ru', 'br', 'in', 'ca', 'au', 'info', 'biz'
|
|
182
|
+
}
|
|
183
|
+
if any(lbl in common_public_tlds for lbl in labels[:-2]):
|
|
184
|
+
return False
|
|
185
|
+
|
|
186
|
+
# 6. Apex domains (e.g. bank-test.com, test.com) cannot be assumed non-prod without explicit config
|
|
187
|
+
if len(labels) < 3:
|
|
188
|
+
return False
|
|
189
|
+
|
|
190
|
+
# 7. Subdomain verification (must be in leading subdomains preceding the root domain)
|
|
191
|
+
subdomain_labels = labels[:-2]
|
|
192
|
+
safe_exact_tokens = {"dev", "development", "staging", "qa", "test", "sandbox", "uat", "local", "internal"}
|
|
193
|
+
safe_sub_regex = re.compile(
|
|
194
|
+
r"^(dev|development|staging|qa|test|sandbox|uat)(?:[-_][a-zA-Z0-9]+)*$|^(?:[a-zA-Z0-9]+[-_])+(dev|development|staging|qa|test|sandbox|uat)(?:[-_][a-zA-Z0-9]+)*$",
|
|
195
|
+
re.IGNORECASE
|
|
196
|
+
)
|
|
197
|
+
|
|
198
|
+
for sub in subdomain_labels:
|
|
199
|
+
if sub in safe_exact_tokens or safe_sub_regex.match(sub):
|
|
200
|
+
return True
|
|
201
|
+
|
|
202
|
+
return False
|
|
203
|
+
|
|
97
204
|
def load_spec(self) -> Dict[str, Any]:
|
|
98
205
|
"""Loads OpenAPI or Swagger specification from file or URL."""
|
|
99
206
|
if not self.spec_path:
|
|
@@ -275,6 +382,29 @@ class ApiFuzzer:
|
|
|
275
382
|
"headers": mut_headers
|
|
276
383
|
})
|
|
277
384
|
|
|
385
|
+
# 4. Catalog-Driven Vectors via tools/fuzz_payloads.py (XSS polyglots,
|
|
386
|
+
# emoji surrogates, numeric boundaries). Formerly a dead import -
|
|
387
|
+
# now wired into generation so the catalog actually fires.
|
|
388
|
+
if get_field_fuzz_scenarios is not None:
|
|
389
|
+
existing_names = {m["name"] for m in mutations}
|
|
390
|
+
for p_name, p_schema in props.items():
|
|
391
|
+
p_type = p_schema.get("type", "string")
|
|
392
|
+
for scen in get_field_fuzz_scenarios(p_name, p_type):
|
|
393
|
+
mut_body = dict(valid_body)
|
|
394
|
+
mut_body[p_name] = scen["value"]
|
|
395
|
+
name = f"{method} {path} - {scen['scenario']}"
|
|
396
|
+
if name in existing_names:
|
|
397
|
+
continue
|
|
398
|
+
existing_names.add(name)
|
|
399
|
+
mutations.append({
|
|
400
|
+
"name": name,
|
|
401
|
+
"type": scen["category"],
|
|
402
|
+
"path": path,
|
|
403
|
+
"method": method,
|
|
404
|
+
"body": mut_body,
|
|
405
|
+
"headers": self.custom_headers
|
|
406
|
+
})
|
|
407
|
+
|
|
278
408
|
return mutations
|
|
279
409
|
|
|
280
410
|
def execute_request(self, mutation: Dict[str, Any], dry_run: bool = False) -> Dict[str, Any]:
|
|
@@ -295,6 +425,7 @@ class ApiFuzzer:
|
|
|
295
425
|
"status_code": 400 if mutation["type"] != "BASELINE" else 200,
|
|
296
426
|
"duration_ms": 42.5,
|
|
297
427
|
"verdict": "PASS",
|
|
428
|
+
"severity": "INFO",
|
|
298
429
|
"alarms": [],
|
|
299
430
|
"response_preview": '{"status": "handled_by_dry_run"}'
|
|
300
431
|
}
|
|
@@ -354,7 +485,18 @@ class ApiFuzzer:
|
|
|
354
485
|
if mutation["type"] == "AUTH_OMISSION" and status_code in (200, 201):
|
|
355
486
|
alarms.append("AUTH_BYPASS_VULNERABILITY: Endpoint returned 200 OK without Authorization header")
|
|
356
487
|
|
|
488
|
+
# Alarm 5: Target unreachable - an endpoint that could not be tested
|
|
489
|
+
# must NEVER be reported as PASS (Truth Anchor discipline).
|
|
490
|
+
if status_code < 0:
|
|
491
|
+
alarms.append("CONNECTION_ERROR: Target unreachable or transport failure - endpoint could not be verified")
|
|
492
|
+
|
|
357
493
|
verdict = "FAIL" if alarms else "PASS"
|
|
494
|
+
severity = "INFO"
|
|
495
|
+
if alarms:
|
|
496
|
+
severity = max(
|
|
497
|
+
(classify_alarm_severity(a) for a in alarms),
|
|
498
|
+
key=lambda s: SEVERITY_LEVELS.index(s)
|
|
499
|
+
)
|
|
358
500
|
|
|
359
501
|
return {
|
|
360
502
|
"name": mutation["name"],
|
|
@@ -364,6 +506,7 @@ class ApiFuzzer:
|
|
|
364
506
|
"status_code": status_code,
|
|
365
507
|
"duration_ms": duration_ms,
|
|
366
508
|
"verdict": verdict,
|
|
509
|
+
"severity": severity,
|
|
367
510
|
"alarms": alarms,
|
|
368
511
|
"response_preview": response_body[:300]
|
|
369
512
|
}
|
|
@@ -404,11 +547,13 @@ class ApiFuzzer:
|
|
|
404
547
|
scorecard = {
|
|
405
548
|
"base_url": self.base_url,
|
|
406
549
|
"timestamp": time.strftime("%Y-%m-%d %H:%M:%S"),
|
|
550
|
+
"mode": "ACTIVE",
|
|
407
551
|
"total_mutations": len(all_mutations),
|
|
408
552
|
"passed": len(all_mutations) - total_failures,
|
|
409
553
|
"failed": total_failures,
|
|
410
554
|
"sla_ms": self.sla_ms,
|
|
411
555
|
"dry_run": dry_run,
|
|
556
|
+
"severity_summary": self._severity_summary(results),
|
|
412
557
|
"results": results
|
|
413
558
|
}
|
|
414
559
|
|
|
@@ -419,6 +564,7 @@ class ApiFuzzer:
|
|
|
419
564
|
print(f" Mutations Executed: {scorecard['total_mutations']}")
|
|
420
565
|
print(f" Clean Handled : {GREEN}{scorecard['passed']}{RESET}")
|
|
421
566
|
print(f" Defects Detected : {RED if total_failures > 0 else GREEN}{scorecard['failed']}{RESET}")
|
|
567
|
+
print(f" Severity Mix : {scorecard['severity_summary']}")
|
|
422
568
|
if total_failures == 0:
|
|
423
569
|
print(f" Verdict : {GREEN}[PASS] API resilient against input fuzzing & chaos.{RESET}")
|
|
424
570
|
else:
|
|
@@ -434,6 +580,117 @@ class ApiFuzzer:
|
|
|
434
580
|
|
|
435
581
|
return scorecard
|
|
436
582
|
|
|
583
|
+
@staticmethod
|
|
584
|
+
def _severity_summary(results: List[Dict[str, Any]]) -> Dict[str, int]:
|
|
585
|
+
"""Counts findings per severity level (ZAP-style risk mix)."""
|
|
586
|
+
summary = {level: 0 for level in SEVERITY_LEVELS}
|
|
587
|
+
for res in results:
|
|
588
|
+
sev = res.get("severity", "INFO")
|
|
589
|
+
if sev in summary:
|
|
590
|
+
summary[sev] += 1
|
|
591
|
+
return summary
|
|
592
|
+
|
|
593
|
+
def run_passive_scan(self) -> Dict[str, Any]:
|
|
594
|
+
"""Offline spec-level audit (no HTTP dispatched).
|
|
595
|
+
|
|
596
|
+
Concept borrowed from OWASP ZAP's passive scanning (Apache-2.0):
|
|
597
|
+
findings are pre-flight advisories derived purely from the OpenAPI/
|
|
598
|
+
Swagger document - unreachable endpoints cannot fake a pass here
|
|
599
|
+
because nothing is dispatched at all.
|
|
600
|
+
"""
|
|
601
|
+
spec = self.load_spec()
|
|
602
|
+
has_global_security = bool(spec.get("security"))
|
|
603
|
+
findings: List[Dict[str, Any]] = []
|
|
604
|
+
|
|
605
|
+
for path, methods in spec.get("paths", {}).items():
|
|
606
|
+
for method, op in methods.items():
|
|
607
|
+
if method.lower() not in ("get", "post", "put", "patch", "delete"):
|
|
608
|
+
continue
|
|
609
|
+
op_security = op.get("security") if isinstance(op, dict) else None
|
|
610
|
+
is_protected = has_global_security or bool(op_security)
|
|
611
|
+
label = f"{method.upper()} {path}"
|
|
612
|
+
|
|
613
|
+
if not is_protected and method.lower() in ("post", "put", "patch", "delete"):
|
|
614
|
+
severity = "HIGH" if re.search(r"admin|internal|user", path, re.IGNORECASE) else "MEDIUM"
|
|
615
|
+
findings.append({
|
|
616
|
+
"finding": f"AUTH_NOT_DECLARED: Mutating endpoint declares no security requirement",
|
|
617
|
+
"severity": severity,
|
|
618
|
+
"target": label,
|
|
619
|
+
"recommendation": "Declare securitySchemes/security for this operation or document why it is public.",
|
|
620
|
+
})
|
|
621
|
+
|
|
622
|
+
if not is_protected and re.search(r"admin|internal", path, re.IGNORECASE):
|
|
623
|
+
findings.append({
|
|
624
|
+
"finding": "SENSITIVE_PATH_UNPROTECTED: Administrative/internal path is publicly declared",
|
|
625
|
+
"severity": "HIGH",
|
|
626
|
+
"target": label,
|
|
627
|
+
"recommendation": "Gate this path behind an authentication scheme and network policy.",
|
|
628
|
+
})
|
|
629
|
+
|
|
630
|
+
body = op.get("requestBody", {}) if isinstance(op, dict) else {}
|
|
631
|
+
content = body.get("content", {}).get("application/json", {}) if isinstance(body, dict) else {}
|
|
632
|
+
schema = content.get("schema", {})
|
|
633
|
+
props = schema.get("properties", {}) if isinstance(schema, dict) else {}
|
|
634
|
+
unbounded = [
|
|
635
|
+
p_name for p_name, p_schema in props.items()
|
|
636
|
+
if isinstance(p_schema, dict)
|
|
637
|
+
and p_schema.get("type") == "string"
|
|
638
|
+
and not any(k in p_schema for k in ("maxLength", "pattern", "enum", "format"))
|
|
639
|
+
]
|
|
640
|
+
if unbounded:
|
|
641
|
+
findings.append({
|
|
642
|
+
"finding": f"NO_INPUT_VALIDATION_DECLARED: unbounded string props {unbounded[:5]}",
|
|
643
|
+
"severity": "LOW",
|
|
644
|
+
"target": label,
|
|
645
|
+
"recommendation": "Add maxLength/pattern/enum constraints so the contract rejects oversized input.",
|
|
646
|
+
})
|
|
647
|
+
|
|
648
|
+
if not spec.get("info") and spec.get("paths"):
|
|
649
|
+
findings.append({
|
|
650
|
+
"finding": "SPEC_METADATA_MISSING: OpenAPI info block absent",
|
|
651
|
+
"severity": "INFO",
|
|
652
|
+
"target": self.spec_path or "inline-spec",
|
|
653
|
+
"recommendation": "Document title/version/contact so scan reports are traceable to an owner.",
|
|
654
|
+
})
|
|
655
|
+
|
|
656
|
+
severity_summary = {level: 0 for level in SEVERITY_LEVELS}
|
|
657
|
+
for f in findings:
|
|
658
|
+
severity_summary[f["severity"]] += 1
|
|
659
|
+
|
|
660
|
+
blocking = severity_summary["HIGH"] + severity_summary["CRITICAL"]
|
|
661
|
+
|
|
662
|
+
print(f"\n{BOLD}{CYAN}======================================================================{RESET}")
|
|
663
|
+
print(f"{BOLD}{CYAN} API PASSIVE SCAN (Offline Spec Audit - Zero Traffic) {RESET}")
|
|
664
|
+
print(f"{BOLD}{CYAN}======================================================================{RESET}")
|
|
665
|
+
print(f" Spec Source : {self.spec_path or 'Default Archetype'}")
|
|
666
|
+
print(f" Findings : {YELLOW if findings else GREEN}{len(findings)}{RESET}")
|
|
667
|
+
print(f" Severity Mix : {severity_summary}")
|
|
668
|
+
if blocking:
|
|
669
|
+
print(f" Verdict : {RED}[FAIL] {blocking} HIGH/CRITICAL pre-flight finding(s).{RESET}")
|
|
670
|
+
else:
|
|
671
|
+
print(f" Verdict : {GREEN}[PASS] No HIGH/CRITICAL spec-level finding.{RESET}")
|
|
672
|
+
print(f"======================================================================\n")
|
|
673
|
+
for f in findings:
|
|
674
|
+
print(f" - [{f['severity']}] {f['target']} - {f['finding']}")
|
|
675
|
+
print(f" 👉 {f['recommendation']}")
|
|
676
|
+
|
|
677
|
+
os.makedirs(REPORTS_DIR, exist_ok=True)
|
|
678
|
+
report_path = os.path.join(REPORTS_DIR, "api_fuzz_report.json")
|
|
679
|
+
scorecard = {
|
|
680
|
+
"base_url": self.base_url,
|
|
681
|
+
"timestamp": time.strftime("%Y-%m-%d %H:%M:%S"),
|
|
682
|
+
"mode": "PASSIVE",
|
|
683
|
+
"spec_source": self.spec_path or "Default Archetype",
|
|
684
|
+
"total_findings": len(findings),
|
|
685
|
+
"severity_summary": severity_summary,
|
|
686
|
+
"findings": findings,
|
|
687
|
+
}
|
|
688
|
+
with open(report_path, "w", encoding="utf-8") as f:
|
|
689
|
+
json.dump(scorecard, f, indent=2)
|
|
690
|
+
print(f"\nFull telemetry report saved to: {report_path}")
|
|
691
|
+
|
|
692
|
+
return scorecard
|
|
693
|
+
|
|
437
694
|
|
|
438
695
|
def main():
|
|
439
696
|
parser = argparse.ArgumentParser(description="Enterprise Autonomous API Fuzzer & Chaos Breaker")
|
|
@@ -441,6 +698,9 @@ def main():
|
|
|
441
698
|
parser.add_argument("--spec", default=None, help="Path or URL to OpenAPI/Swagger JSON specification")
|
|
442
699
|
parser.add_argument("--token", default=None, help="Bearer token for authenticated endpoints")
|
|
443
700
|
parser.add_argument("--sla-ms", type=float, default=3000.0, help="SLA latency ceiling in ms (default: 3000ms)")
|
|
701
|
+
parser.add_argument("--mode", choices=["active", "passive"], default="active",
|
|
702
|
+
help="active = live fuzzing (default); passive = offline spec audit, zero traffic")
|
|
703
|
+
parser.add_argument("--insecure", action="store_true", help="Explicitly bypass TLS certificate verification (for staging/self-signed certs)")
|
|
444
704
|
parser.add_argument("--dry-run", action="store_true", help="Simulate mutation generation without live requests")
|
|
445
705
|
parser.add_argument("--json", action="store_true", help="Output summary in JSON format")
|
|
446
706
|
|
|
@@ -454,13 +714,25 @@ def main():
|
|
|
454
714
|
base_url=args.base_url,
|
|
455
715
|
spec_path_or_url=args.spec,
|
|
456
716
|
headers=headers,
|
|
457
|
-
sla_ms=args.sla_ms
|
|
717
|
+
sla_ms=args.sla_ms,
|
|
718
|
+
insecure=args.insecure
|
|
458
719
|
)
|
|
459
720
|
|
|
721
|
+
if args.mode == "passive":
|
|
722
|
+
scorecard = fuzzer.run_passive_scan()
|
|
723
|
+
if args.json:
|
|
724
|
+
print(json.dumps(scorecard, indent=2))
|
|
725
|
+
severity_dict = scorecard.get("severity_summary", {})
|
|
726
|
+
blocking = int(severity_dict.get("HIGH", 0)) + int(severity_dict.get("CRITICAL", 0))
|
|
727
|
+
return 1 if blocking > 0 else 0
|
|
728
|
+
|
|
460
729
|
scorecard = fuzzer.run_fuzz_suite(dry_run=args.dry_run)
|
|
461
730
|
if args.json:
|
|
462
731
|
print(json.dumps(scorecard, indent=2))
|
|
463
732
|
|
|
733
|
+
critical = scorecard["severity_summary"].get("CRITICAL", 0)
|
|
734
|
+
if critical > 0:
|
|
735
|
+
return 1
|
|
464
736
|
return 1 if scorecard["failed"] > 0 else 0
|
|
465
737
|
|
|
466
738
|
|
|
@@ -4,9 +4,9 @@
|
|
|
4
4
|
# Project: Synapse-Hub (https://github.com/TeeVee998/Synapse-Hub)
|
|
5
5
|
|
|
6
6
|
"""
|
|
7
|
-
tools/author_integrity.py — Author Attribution &
|
|
7
|
+
tools/author_integrity.py — Author Attribution & MIT License Compliance Guard
|
|
8
8
|
-----------------------------------------------------------------------------
|
|
9
|
-
Verifies the
|
|
9
|
+
Verifies the author provenance and copyright attribution of Synapse Hub.
|
|
10
10
|
Enforces the mandatory Attribution condition of the MIT License:
|
|
11
11
|
"The above copyright notice and this permission notice shall be included
|
|
12
12
|
in all copies or substantial portions of the Software."
|
|
@@ -48,7 +48,7 @@ def get_canonical_watermark() -> str:
|
|
|
48
48
|
|
|
49
49
|
|
|
50
50
|
def verify_author_fingerprint() -> bool:
|
|
51
|
-
"""
|
|
51
|
+
"""Validates that the canonical author attribution metadata matches the reference integrity hash."""
|
|
52
52
|
watermark = get_canonical_watermark()
|
|
53
53
|
computed = hashlib.sha256(watermark.encode("utf-8")).hexdigest()
|
|
54
54
|
return computed == CANONICAL_AUTHOR_SIG
|
|
@@ -56,9 +56,9 @@ def verify_author_fingerprint() -> bool:
|
|
|
56
56
|
|
|
57
57
|
def assert_integrity_or_raise(repo_root: Optional[str] = None) -> bool:
|
|
58
58
|
"""
|
|
59
|
-
Enforces author
|
|
60
|
-
|
|
61
|
-
|
|
59
|
+
Enforces author attribution and MIT License provenance in accordance with open-source licensing.
|
|
60
|
+
Under the MIT License, preserving the original copyright notice in LICENSE is mandatory.
|
|
61
|
+
If mandatory LICENSE copyright notice is missing, halts execution to protect author attribution.
|
|
62
62
|
"""
|
|
63
63
|
if not verify_author_fingerprint():
|
|
64
64
|
raise RuntimeError(
|
package/tools/auto_heal.py
CHANGED
|
@@ -85,7 +85,7 @@ def diagnose_and_plan_heal(target_dir, junit_xml_path=None):
|
|
|
85
85
|
|
|
86
86
|
# 2. Query 0-token memory cache & 3-layer heuristic matcher
|
|
87
87
|
print(f"{BOLD}[1/2] Checking 3-Layer Heuristic Matcher & Memory Cache...{RESET}")
|
|
88
|
-
cache_solution = cache_resolver.
|
|
88
|
+
cache_solution = cache_resolver.query_solution(err_sig, threshold=0.35)
|
|
89
89
|
|
|
90
90
|
# 2b. Query Episodic Reflexion Memory (Top 2 historical lessons)
|
|
91
91
|
lessons = cache_resolver.query_lessons(f"{test_name} {err_sig}", limit=2)
|
package/tools/browser_helpers.js
CHANGED
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
(function () {
|
|
14
14
|
'use strict';
|
|
15
15
|
|
|
16
|
-
if (window.__qa && window.__qa.__version === '2.
|
|
16
|
+
if (window.__qa && window.__qa.__version === '2.3.1') {
|
|
17
17
|
return window.__qa;
|
|
18
18
|
}
|
|
19
19
|
|
|
@@ -47,7 +47,7 @@
|
|
|
47
47
|
}
|
|
48
48
|
|
|
49
49
|
const qa = {
|
|
50
|
-
__version: '2.
|
|
50
|
+
__version: '2.3.1',
|
|
51
51
|
|
|
52
52
|
/**
|
|
53
53
|
* 1. Smart Action Batching
|
|
@@ -193,13 +193,20 @@
|
|
|
193
193
|
*/
|
|
194
194
|
exportSession: function () {
|
|
195
195
|
const storage = {};
|
|
196
|
-
|
|
197
|
-
const
|
|
198
|
-
|
|
196
|
+
try {
|
|
197
|
+
const ls = (typeof window !== "undefined" && window.localStorage) || (typeof localStorage !== "undefined" ? localStorage : null);
|
|
198
|
+
if (ls) {
|
|
199
|
+
for (let i = 0; i < ls.length; i++) {
|
|
200
|
+
const key = ls.key(i);
|
|
201
|
+
if (key) storage[key] = ls.getItem(key);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
} catch (e) {
|
|
205
|
+
// Ignore cross-origin security errors on localStorage access
|
|
199
206
|
}
|
|
200
207
|
return {
|
|
201
|
-
url: window.location.href,
|
|
202
|
-
cookies: document.cookie,
|
|
208
|
+
url: (typeof window !== "undefined" && window.location && window.location.href) || "",
|
|
209
|
+
cookies: (typeof document !== "undefined" && document.cookie) || "",
|
|
203
210
|
localStorage: storage
|
|
204
211
|
};
|
|
205
212
|
},
|
|
@@ -505,6 +512,97 @@
|
|
|
505
512
|
return aomNodes;
|
|
506
513
|
},
|
|
507
514
|
|
|
515
|
+
/**
|
|
516
|
+
* 8. ARIA Snapshot (Playwright-MCP-style YAML serialization)
|
|
517
|
+
* Serializes the visible accessibility tree into the ARIA snapshot text
|
|
518
|
+
* format that MCP-style agents consume: `- role "name" [state] [ref=eN]`.
|
|
519
|
+
* (Format borrowed and adapted from microsoft/playwright-mcp, Apache-2.0.)
|
|
520
|
+
* @param {Element|Document} [root=document]
|
|
521
|
+
* @param {number} [maxDepth=6]
|
|
522
|
+
* @returns {string} YAML-style ARIA snapshot text (one node per line)
|
|
523
|
+
*/
|
|
524
|
+
ariaSnapshot: function (root = document, maxDepth = 6) {
|
|
525
|
+
const lines = [];
|
|
526
|
+
let refCounter = 0;
|
|
527
|
+
|
|
528
|
+
function getRole(el) {
|
|
529
|
+
const explicitRole = el.getAttribute('role');
|
|
530
|
+
if (explicitRole) return explicitRole.toLowerCase();
|
|
531
|
+
const tag = el.tagName.toLowerCase();
|
|
532
|
+
if (tag === 'button') return 'button';
|
|
533
|
+
if (tag === 'a' && el.hasAttribute('href')) return 'link';
|
|
534
|
+
if (tag === 'input') {
|
|
535
|
+
const type = (el.getAttribute('type') || 'text').toLowerCase();
|
|
536
|
+
if (['checkbox', 'radio', 'button', 'submit'].includes(type)) return type;
|
|
537
|
+
return 'textbox';
|
|
538
|
+
}
|
|
539
|
+
if (tag === 'select') return 'combobox';
|
|
540
|
+
if (tag === 'textarea') return 'textbox';
|
|
541
|
+
if (tag === 'h1' || tag === 'h2' || tag === 'h3' || tag === 'h4' || tag === 'h5' || tag === 'h6') return 'heading';
|
|
542
|
+
if (tag === 'img') return 'img';
|
|
543
|
+
if (tag === 'ul' || tag === 'ol') return 'list';
|
|
544
|
+
if (tag === 'li') return 'listitem';
|
|
545
|
+
return null;
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
function getAccessibleName(el) {
|
|
549
|
+
return (
|
|
550
|
+
el.getAttribute('aria-label') ||
|
|
551
|
+
el.getAttribute('aria-placeholder') ||
|
|
552
|
+
el.getAttribute('placeholder') ||
|
|
553
|
+
el.getAttribute('title') ||
|
|
554
|
+
(el.innerText ? el.innerText.trim().slice(0, 80).replace(/\s+/g, ' ') : '') ||
|
|
555
|
+
el.getAttribute('alt') ||
|
|
556
|
+
''
|
|
557
|
+
);
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
function isVisible(el) {
|
|
561
|
+
if (!el || el.nodeType !== 1) return false;
|
|
562
|
+
const style = window.getComputedStyle(el);
|
|
563
|
+
if (style.display === 'none' || style.visibility === 'hidden' || style.opacity === '0') return false;
|
|
564
|
+
const rect = el.getBoundingClientRect();
|
|
565
|
+
return rect.width > 0 && rect.height > 0;
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
function walk(node, depth) {
|
|
569
|
+
if (depth > maxDepth || !node) return;
|
|
570
|
+
const children = node.children || [];
|
|
571
|
+
for (let i = 0; i < children.length; i++) {
|
|
572
|
+
const child = children[i];
|
|
573
|
+
if (!isVisible(child)) continue;
|
|
574
|
+
|
|
575
|
+
const role = getRole(child);
|
|
576
|
+
if (role) {
|
|
577
|
+
refCounter += 1;
|
|
578
|
+
const rawName = getAccessibleName(child);
|
|
579
|
+
const namePart = rawName ? ' "' + rawName.replace(/"/g, "'") + '"' : '';
|
|
580
|
+
let states = '';
|
|
581
|
+
if (child.disabled || child.getAttribute('aria-disabled') === 'true') states += ' [disabled]';
|
|
582
|
+
if (child.getAttribute('aria-checked') === 'true') states += ' [checked]';
|
|
583
|
+
if (child.getAttribute('aria-expanded') !== null) {
|
|
584
|
+
states += child.getAttribute('aria-expanded') === 'true' ? ' [expanded]' : ' [collapsed]';
|
|
585
|
+
}
|
|
586
|
+
if (child.getAttribute('aria-selected') === 'true') states += ' [selected]';
|
|
587
|
+
const tagLower = child.tagName.toLowerCase();
|
|
588
|
+
const level = (role === 'heading' && /^h[1-6]$/.test(tagLower)) ? ' [level=' + tagLower[1] + ']' : '';
|
|
589
|
+
const testId = child.getAttribute('data-testid') || child.getAttribute('data-cy') || '';
|
|
590
|
+
const idPart = testId ? ' [testid=' + testId + ']' : '';
|
|
591
|
+
lines.push(' '.repeat(depth - 1) + '- ' + role + namePart + level + states + ' [ref=e' + refCounter + ']' + idPart);
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
// Pierce Shadow DOM
|
|
595
|
+
if (child.shadowRoot) {
|
|
596
|
+
walk(child.shadowRoot, depth + 1);
|
|
597
|
+
}
|
|
598
|
+
walk(child, depth + 1);
|
|
599
|
+
}
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
walk(root === document ? document.body : root, 1);
|
|
603
|
+
return lines.join('\n');
|
|
604
|
+
},
|
|
605
|
+
|
|
508
606
|
/**
|
|
509
607
|
* 8. Dynamic Stability Probe (Event-Driven Wait Weaver)
|
|
510
608
|
* Replaces flaky static sleeps (Thread.sleep / cy.wait) by dynamically probing:
|