shadowcat 2.2.0__tar.gz → 2.2.2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (188) hide show
  1. {shadowcat-2.2.0 → shadowcat-2.2.2}/PKG-INFO +1 -1
  2. {shadowcat-2.2.0 → shadowcat-2.2.2}/backend/reporting/generator.py +8 -2
  3. {shadowcat-2.2.0 → shadowcat-2.2.2}/backend/tools/general_report_tool.py +27 -0
  4. {shadowcat-2.2.0 → shadowcat-2.2.2}/frontend/src/components/StatusChip.tsx +3 -2
  5. {shadowcat-2.2.0 → shadowcat-2.2.2}/frontend/src/lib/severity.ts +12 -5
  6. {shadowcat-2.2.0 → shadowcat-2.2.2}/pyproject.toml +1 -1
  7. {shadowcat-2.2.0 → shadowcat-2.2.2}/tests/test_general_report_tool.py +31 -0
  8. {shadowcat-2.2.0 → shadowcat-2.2.2}/uv.lock +1 -1
  9. {shadowcat-2.2.0 → shadowcat-2.2.2}/.clineignore +0 -0
  10. {shadowcat-2.2.0 → shadowcat-2.2.2}/.env.example +0 -0
  11. {shadowcat-2.2.0 → shadowcat-2.2.2}/.gitignore +0 -0
  12. {shadowcat-2.2.0 → shadowcat-2.2.2}/.gitmodules +0 -0
  13. {shadowcat-2.2.0 → shadowcat-2.2.2}/.mcp.json +0 -0
  14. {shadowcat-2.2.0 → shadowcat-2.2.2}/CLAUDE.md +0 -0
  15. {shadowcat-2.2.0 → shadowcat-2.2.2}/Dockerfile +0 -0
  16. {shadowcat-2.2.0 → shadowcat-2.2.2}/LICENSE.md +0 -0
  17. {shadowcat-2.2.0 → shadowcat-2.2.2}/Makefile +0 -0
  18. {shadowcat-2.2.0 → shadowcat-2.2.2}/README.md +0 -0
  19. {shadowcat-2.2.0 → shadowcat-2.2.2}/agent/__init__.py +0 -0
  20. {shadowcat-2.2.0 → shadowcat-2.2.2}/agent/benchmark/__init__.py +0 -0
  21. {shadowcat-2.2.0 → shadowcat-2.2.2}/agent/benchmark/cli.py +0 -0
  22. {shadowcat-2.2.0 → shadowcat-2.2.2}/agent/benchmark/config.py +0 -0
  23. {shadowcat-2.2.0 → shadowcat-2.2.2}/agent/benchmark/docker.py +0 -0
  24. {shadowcat-2.2.0 → shadowcat-2.2.2}/agent/benchmark/registry.py +0 -0
  25. {shadowcat-2.2.0 → shadowcat-2.2.2}/agent/core/__init__.py +0 -0
  26. {shadowcat-2.2.0 → shadowcat-2.2.2}/agent/core/agent.py +0 -0
  27. {shadowcat-2.2.0 → shadowcat-2.2.2}/agent/core/backend.py +0 -0
  28. {shadowcat-2.2.0 → shadowcat-2.2.2}/agent/core/config.py +0 -0
  29. {shadowcat-2.2.0 → shadowcat-2.2.2}/agent/core/controller.py +0 -0
  30. {shadowcat-2.2.0 → shadowcat-2.2.2}/agent/core/events.py +0 -0
  31. {shadowcat-2.2.0 → shadowcat-2.2.2}/agent/core/langfuse.py +0 -0
  32. {shadowcat-2.2.0 → shadowcat-2.2.2}/agent/core/phantom.py +0 -0
  33. {shadowcat-2.2.0 → shadowcat-2.2.2}/agent/core/planner.py +0 -0
  34. {shadowcat-2.2.0 → shadowcat-2.2.2}/agent/core/profiling.py +0 -0
  35. {shadowcat-2.2.0 → shadowcat-2.2.2}/agent/core/sanitizer.py +0 -0
  36. {shadowcat-2.2.0 → shadowcat-2.2.2}/agent/core/session.py +0 -0
  37. {shadowcat-2.2.0 → shadowcat-2.2.2}/agent/core/tracer.py +0 -0
  38. {shadowcat-2.2.0 → shadowcat-2.2.2}/agent/interface/__init__.py +0 -0
  39. {shadowcat-2.2.0 → shadowcat-2.2.2}/agent/interface/components/__init__.py +0 -0
  40. {shadowcat-2.2.0 → shadowcat-2.2.2}/agent/interface/components/activity_feed.py +0 -0
  41. {shadowcat-2.2.0 → shadowcat-2.2.2}/agent/interface/components/renderers.py +0 -0
  42. {shadowcat-2.2.0 → shadowcat-2.2.2}/agent/interface/components/splash.py +0 -0
  43. {shadowcat-2.2.0 → shadowcat-2.2.2}/agent/interface/main.py +0 -0
  44. {shadowcat-2.2.0 → shadowcat-2.2.2}/agent/interface/styles.tcss +0 -0
  45. {shadowcat-2.2.0 → shadowcat-2.2.2}/agent/interface/tui.py +0 -0
  46. {shadowcat-2.2.0 → shadowcat-2.2.2}/agent/parsing/html_distiller.py +0 -0
  47. {shadowcat-2.2.0 → shadowcat-2.2.2}/agent/parsing/tool_parser.py +0 -0
  48. {shadowcat-2.2.0 → shadowcat-2.2.2}/agent/prompts/__init__.py +0 -0
  49. {shadowcat-2.2.0 → shadowcat-2.2.2}/agent/prompts/pentesting.py +0 -0
  50. {shadowcat-2.2.0 → shadowcat-2.2.2}/agent/rag_module/knowledge_base.py +0 -0
  51. {shadowcat-2.2.0 → shadowcat-2.2.2}/agent/tests/benchmark_phantom.py +0 -0
  52. {shadowcat-2.2.0 → shadowcat-2.2.2}/agent/tools/__init__.py +0 -0
  53. {shadowcat-2.2.0 → shadowcat-2.2.2}/agent/tools/base.py +0 -0
  54. {shadowcat-2.2.0 → shadowcat-2.2.2}/agent/tools/executor.py +0 -0
  55. {shadowcat-2.2.0 → shadowcat-2.2.2}/agent/tools/registry.py +0 -0
  56. {shadowcat-2.2.0 → shadowcat-2.2.2}/backend/README.md +0 -0
  57. {shadowcat-2.2.0 → shadowcat-2.2.2}/backend/__init__.py +0 -0
  58. {shadowcat-2.2.0 → shadowcat-2.2.2}/backend/api/__init__.py +0 -0
  59. {shadowcat-2.2.0 → shadowcat-2.2.2}/backend/api/routes_scan.py +0 -0
  60. {shadowcat-2.2.0 → shadowcat-2.2.2}/backend/authz.py +0 -0
  61. {shadowcat-2.2.0 → shadowcat-2.2.2}/backend/cli.py +0 -0
  62. {shadowcat-2.2.0 → shadowcat-2.2.2}/backend/compliance/__init__.py +0 -0
  63. {shadowcat-2.2.0 → shadowcat-2.2.2}/backend/compliance/pdpa_mapping.py +0 -0
  64. {shadowcat-2.2.0 → shadowcat-2.2.2}/backend/core/__init__.py +0 -0
  65. {shadowcat-2.2.0 → shadowcat-2.2.2}/backend/core/coverage.py +0 -0
  66. {shadowcat-2.2.0 → shadowcat-2.2.2}/backend/core/events.py +0 -0
  67. {shadowcat-2.2.0 → shadowcat-2.2.2}/backend/core/llm_client.py +0 -0
  68. {shadowcat-2.2.0 → shadowcat-2.2.2}/backend/core/orchestrator.py +0 -0
  69. {shadowcat-2.2.0 → shadowcat-2.2.2}/backend/core/scan_memory.py +0 -0
  70. {shadowcat-2.2.0 → shadowcat-2.2.2}/backend/crawler/__init__.py +0 -0
  71. {shadowcat-2.2.0 → shadowcat-2.2.2}/backend/crawler/csrf.py +0 -0
  72. {shadowcat-2.2.0 → shadowcat-2.2.2}/backend/crawler/headless.py +0 -0
  73. {shadowcat-2.2.0 → shadowcat-2.2.2}/backend/crawler/js_analyzer.py +0 -0
  74. {shadowcat-2.2.0 → shadowcat-2.2.2}/backend/crawler/spider.py +0 -0
  75. {shadowcat-2.2.0 → shadowcat-2.2.2}/backend/crawler/subdomains.py +0 -0
  76. {shadowcat-2.2.0 → shadowcat-2.2.2}/backend/daemon.py +0 -0
  77. {shadowcat-2.2.0 → shadowcat-2.2.2}/backend/db/README.md +0 -0
  78. {shadowcat-2.2.0 → shadowcat-2.2.2}/backend/db/__init__.py +0 -0
  79. {shadowcat-2.2.0 → shadowcat-2.2.2}/backend/db/engine.py +0 -0
  80. {shadowcat-2.2.0 → shadowcat-2.2.2}/backend/db/models.py +0 -0
  81. {shadowcat-2.2.0 → shadowcat-2.2.2}/backend/db/repository.py +0 -0
  82. {shadowcat-2.2.0 → shadowcat-2.2.2}/backend/db/schema.sql +0 -0
  83. {shadowcat-2.2.0 → shadowcat-2.2.2}/backend/modes/__init__.py +0 -0
  84. {shadowcat-2.2.0 → shadowcat-2.2.2}/backend/modes/base.py +0 -0
  85. {shadowcat-2.2.0 → shadowcat-2.2.2}/backend/modes/ctf.py +0 -0
  86. {shadowcat-2.2.0 → shadowcat-2.2.2}/backend/modes/enterprise.py +0 -0
  87. {shadowcat-2.2.0 → shadowcat-2.2.2}/backend/modes/general.py +0 -0
  88. {shadowcat-2.2.0 → shadowcat-2.2.2}/backend/modes/registry.py +0 -0
  89. {shadowcat-2.2.0 → shadowcat-2.2.2}/backend/reporting/__init__.py +0 -0
  90. {shadowcat-2.2.0 → shadowcat-2.2.2}/backend/schemas/__init__.py +0 -0
  91. {shadowcat-2.2.0 → shadowcat-2.2.2}/backend/schemas/api.py +0 -0
  92. {shadowcat-2.2.0 → shadowcat-2.2.2}/backend/tools/__init__.py +0 -0
  93. {shadowcat-2.2.0 → shadowcat-2.2.2}/backend/tools/dirbrute_tool.py +0 -0
  94. {shadowcat-2.2.0 → shadowcat-2.2.2}/backend/tools/http_tool.py +0 -0
  95. {shadowcat-2.2.0 → shadowcat-2.2.2}/backend/tools/nuclei_tool.py +0 -0
  96. {shadowcat-2.2.0 → shadowcat-2.2.2}/backend/tools/report_tool.py +0 -0
  97. {shadowcat-2.2.0 → shadowcat-2.2.2}/backend/tools/shell_tools.py +0 -0
  98. {shadowcat-2.2.0 → shadowcat-2.2.2}/backend/user_config.py +0 -0
  99. {shadowcat-2.2.0 → shadowcat-2.2.2}/backend/verification/__init__.py +0 -0
  100. {shadowcat-2.2.0 → shadowcat-2.2.2}/backend/verification/evidence_store.py +0 -0
  101. {shadowcat-2.2.0 → shadowcat-2.2.2}/backend/verification/general_oracle.py +0 -0
  102. {shadowcat-2.2.0 → shadowcat-2.2.2}/backend/verification/idor_oracle.py +0 -0
  103. {shadowcat-2.2.0 → shadowcat-2.2.2}/backend/waf/__init__.py +0 -0
  104. {shadowcat-2.2.0 → shadowcat-2.2.2}/backend/waf/detector.py +0 -0
  105. {shadowcat-2.2.0 → shadowcat-2.2.2}/backend/waf/evasion.py +0 -0
  106. {shadowcat-2.2.0 → shadowcat-2.2.2}/backend/webui/index.html +0 -0
  107. {shadowcat-2.2.0 → shadowcat-2.2.2}/backend/workspace.py +0 -0
  108. {shadowcat-2.2.0 → shadowcat-2.2.2}/custom_exploits/example_exploit.py +0 -0
  109. {shadowcat-2.2.0 → shadowcat-2.2.2}/docker-compose.yml +0 -0
  110. {shadowcat-2.2.0 → shadowcat-2.2.2}/docs/CHANGES_SUMMARY_TH.md +0 -0
  111. {shadowcat-2.2.0 → shadowcat-2.2.2}/docs/DASHBOARD_IMPROVEMENTS_TH.md +0 -0
  112. {shadowcat-2.2.0 → shadowcat-2.2.2}/docs/ENTERPRISE_ARCH.md +0 -0
  113. {shadowcat-2.2.0 → shadowcat-2.2.2}/docs/HOW_TO_RUN_TH.md +0 -0
  114. {shadowcat-2.2.0 → shadowcat-2.2.2}/docs/NSC_PREP_TH.md +0 -0
  115. {shadowcat-2.2.0 → shadowcat-2.2.2}/docs/PROGRESS.md +0 -0
  116. {shadowcat-2.2.0 → shadowcat-2.2.2}/docs/ROADMAP.md +0 -0
  117. {shadowcat-2.2.0 → shadowcat-2.2.2}/docs/ShadowCat_/340/271/200/340/270/255/340/270/201/340/270/252/340/270/262/340/270/243/340/270/233/340/270/243/340/270/260/340/270/201/340/270/255/340/270/232/340/271/202/340/270/233/340/270/243/340/271/200/340/270/210/340/270/204_NSC2026.pdf" +0 -0
  118. {shadowcat-2.2.0 → shadowcat-2.2.2}/docs/TECHNICAL_DEEP_DIVE_TH.md +0 -0
  119. {shadowcat-2.2.0 → shadowcat-2.2.2}/docs/WEBSITE_SPEC.md +0 -0
  120. {shadowcat-2.2.0 → shadowcat-2.2.2}/docs/nsc-shadowcat-pitch.md +0 -0
  121. {shadowcat-2.2.0 → shadowcat-2.2.2}/docs/research/README.md +0 -0
  122. {shadowcat-2.2.0 → shadowcat-2.2.2}/fix-workspace-permissions.sh +0 -0
  123. {shadowcat-2.2.0 → shadowcat-2.2.2}/frontend/next.config.ts +0 -0
  124. {shadowcat-2.2.0 → shadowcat-2.2.2}/frontend/postcss.config.mjs +0 -0
  125. {shadowcat-2.2.0 → shadowcat-2.2.2}/frontend/public/logo-mark.png +0 -0
  126. {shadowcat-2.2.0 → shadowcat-2.2.2}/frontend/public/logo.png +0 -0
  127. {shadowcat-2.2.0 → shadowcat-2.2.2}/frontend/src/app/globals.css +0 -0
  128. {shadowcat-2.2.0 → shadowcat-2.2.2}/frontend/src/app/layout.tsx +0 -0
  129. {shadowcat-2.2.0 → shadowcat-2.2.2}/frontend/src/app/page.tsx +0 -0
  130. {shadowcat-2.2.0 → shadowcat-2.2.2}/frontend/src/components/AppShell.tsx +0 -0
  131. {shadowcat-2.2.0 → shadowcat-2.2.2}/frontend/src/components/EmptyBlock.tsx +0 -0
  132. {shadowcat-2.2.0 → shadowcat-2.2.2}/frontend/src/components/NewScanModal.tsx +0 -0
  133. {shadowcat-2.2.0 → shadowcat-2.2.2}/frontend/src/components/PageHeader.tsx +0 -0
  134. {shadowcat-2.2.0 → shadowcat-2.2.2}/frontend/src/components/ReportButtons.tsx +0 -0
  135. {shadowcat-2.2.0 → shadowcat-2.2.2}/frontend/src/components/Sidebar.tsx +0 -0
  136. {shadowcat-2.2.0 → shadowcat-2.2.2}/frontend/src/components/Topbar.tsx +0 -0
  137. {shadowcat-2.2.0 → shadowcat-2.2.2}/frontend/src/components/charts/SeverityBarChart.tsx +0 -0
  138. {shadowcat-2.2.0 → shadowcat-2.2.2}/frontend/src/components/charts/VerdictBar.tsx +0 -0
  139. {shadowcat-2.2.0 → shadowcat-2.2.2}/frontend/src/components/icons.tsx +0 -0
  140. {shadowcat-2.2.0 → shadowcat-2.2.2}/frontend/src/components/views/FindingsView.tsx +0 -0
  141. {shadowcat-2.2.0 → shadowcat-2.2.2}/frontend/src/components/views/LiveFeedView.tsx +0 -0
  142. {shadowcat-2.2.0 → shadowcat-2.2.2}/frontend/src/components/views/MemoryView.tsx +0 -0
  143. {shadowcat-2.2.0 → shadowcat-2.2.2}/frontend/src/components/views/OverviewView.tsx +0 -0
  144. {shadowcat-2.2.0 → shadowcat-2.2.2}/frontend/src/components/views/SessionsView.tsx +0 -0
  145. {shadowcat-2.2.0 → shadowcat-2.2.2}/frontend/src/components/views/SettingsView.tsx +0 -0
  146. {shadowcat-2.2.0 → shadowcat-2.2.2}/frontend/src/lib/api.ts +0 -0
  147. {shadowcat-2.2.0 → shadowcat-2.2.2}/frontend/src/lib/useActiveScan.ts +0 -0
  148. {shadowcat-2.2.0 → shadowcat-2.2.2}/frontend/src/lib/useScanDetails.ts +0 -0
  149. {shadowcat-2.2.0 → shadowcat-2.2.2}/frontend/tsconfig.json +0 -0
  150. {shadowcat-2.2.0 → shadowcat-2.2.2}/hatch_build.py +0 -0
  151. {shadowcat-2.2.0 → shadowcat-2.2.2}/scripts/config.sh +0 -0
  152. {shadowcat-2.2.0 → shadowcat-2.2.2}/scripts/entrypoint.sh +0 -0
  153. {shadowcat-2.2.0 → shadowcat-2.2.2}/scripts/gen_report.js +0 -0
  154. {shadowcat-2.2.0 → shadowcat-2.2.2}/scripts/test_client.py +0 -0
  155. {shadowcat-2.2.0 → shadowcat-2.2.2}/setup.sh +0 -0
  156. {shadowcat-2.2.0 → shadowcat-2.2.2}/tests/__init__.py +0 -0
  157. {shadowcat-2.2.0 → shadowcat-2.2.2}/tests/conftest.py +0 -0
  158. {shadowcat-2.2.0 → shadowcat-2.2.2}/tests/docker/__init__.py +0 -0
  159. {shadowcat-2.2.0 → shadowcat-2.2.2}/tests/docker/test_container_health.py +0 -0
  160. {shadowcat-2.2.0 → shadowcat-2.2.2}/tests/docker/test_docker_build.py +0 -0
  161. {shadowcat-2.2.0 → shadowcat-2.2.2}/tests/integration/__init__.py +0 -0
  162. {shadowcat-2.2.0 → shadowcat-2.2.2}/tests/integration/test_benchmark_cli.py +0 -0
  163. {shadowcat-2.2.0 → shadowcat-2.2.2}/tests/integration/test_controller.py +0 -0
  164. {shadowcat-2.2.0 → shadowcat-2.2.2}/tests/test_config_cli.py +0 -0
  165. {shadowcat-2.2.0 → shadowcat-2.2.2}/tests/test_coverage_ledger.py +0 -0
  166. {shadowcat-2.2.0 → shadowcat-2.2.2}/tests/test_daemon_cli.py +0 -0
  167. {shadowcat-2.2.0 → shadowcat-2.2.2}/tests/test_db_persistence.py +0 -0
  168. {shadowcat-2.2.0 → shadowcat-2.2.2}/tests/test_enterprise_api.py +0 -0
  169. {shadowcat-2.2.0 → shadowcat-2.2.2}/tests/test_enterprise_engine.py +0 -0
  170. {shadowcat-2.2.0 → shadowcat-2.2.2}/tests/test_executor.py +0 -0
  171. {shadowcat-2.2.0 → shadowcat-2.2.2}/tests/test_general_oracle.py +0 -0
  172. {shadowcat-2.2.0 → shadowcat-2.2.2}/tests/test_headless.py +0 -0
  173. {shadowcat-2.2.0 → shadowcat-2.2.2}/tests/test_llm_wiring.py +0 -0
  174. {shadowcat-2.2.0 → shadowcat-2.2.2}/tests/test_memory_briefing.py +0 -0
  175. {shadowcat-2.2.0 → shadowcat-2.2.2}/tests/test_pdpa_mapping.py +0 -0
  176. {shadowcat-2.2.0 → shadowcat-2.2.2}/tests/test_scan_memory.py +0 -0
  177. {shadowcat-2.2.0 → shadowcat-2.2.2}/tests/test_sse_replay.py +0 -0
  178. {shadowcat-2.2.0 → shadowcat-2.2.2}/tests/test_user_config.py +0 -0
  179. {shadowcat-2.2.0 → shadowcat-2.2.2}/tests/unit/__init__.py +0 -0
  180. {shadowcat-2.2.0 → shadowcat-2.2.2}/tests/unit/test_backend_interface.py +0 -0
  181. {shadowcat-2.2.0 → shadowcat-2.2.2}/tests/unit/test_benchmark_registry.py +0 -0
  182. {shadowcat-2.2.0 → shadowcat-2.2.2}/tests/unit/test_config.py +0 -0
  183. {shadowcat-2.2.0 → shadowcat-2.2.2}/tests/unit/test_events.py +0 -0
  184. {shadowcat-2.2.0 → shadowcat-2.2.2}/tests/unit/test_flag_detection.py +0 -0
  185. {shadowcat-2.2.0 → shadowcat-2.2.2}/tests/unit/test_langfuse.py +0 -0
  186. {shadowcat-2.2.0 → shadowcat-2.2.2}/tests/unit/test_phantom_two_tier.py +0 -0
  187. {shadowcat-2.2.0 → shadowcat-2.2.2}/tests/unit/test_planner_context.py +0 -0
  188. {shadowcat-2.2.0 → shadowcat-2.2.2}/tests/unit/test_session.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: shadowcat
3
- Version: 2.2.0
3
+ Version: 2.2.2
4
4
  Summary: AI-powered autonomous web security scanner with real-time streaming — enterprise DAST platform
5
5
  Project-URL: Homepage, https://github.com/shadowcat-dast/shadowcat
6
6
  Project-URL: Repository, https://github.com/shadowcat-dast/shadowcat
@@ -122,6 +122,7 @@ h3 { font-size: 1rem; color: #334155; margin-bottom: 8px; }
122
122
  padding:2px 8px; font-size:0.8rem; font-family:monospace; }
123
123
  .verdict { padding:6px 12px; border-radius:6px; font-size:0.85rem; margin-top:10px; display:inline-block; }
124
124
  .verdict-confirmed { background:#dcfce7; color:#16a34a; }
125
+ .verdict-likely { background:#fef3c7; color:#b45309; }
125
126
  .verdict-inconclusive { background:#ffedd5; color:#c2410c; }
126
127
  .verdict-refuted { background:#f1f5f9; color:#64748b; }
127
128
  .verdict-pending { background:#fef9c3; color:#92400e; }
@@ -165,12 +166,17 @@ def _render_finding(idx: int, finding: dict[str, Any], verdicts: dict[str, Any])
165
166
  elif reproduced is False:
166
167
  repro_badge = ' <span class="req-id" title="Did not reproduce on a clean-session replay">⚠ did not reproduce</span>'
167
168
  # The oracle's ruling vocabulary is exactly confirmed / refuted / inconclusive
168
- # (see idor_oracle.py, general_oracle.py). A finding with no verdict entry at
169
- # all hasn't been through the oracle yet that's the only true "pending" case.
169
+ # (see idor_oracle.py, general_oracle.py) plus GeneralOracle's "likely" tier
170
+ # (a suggestive but context-blind signalworth attention, not proof). A
171
+ # finding with no verdict entry at all hasn't been through the oracle yet —
172
+ # that's the only true "pending" case.
170
173
  verdict_label = verdict_info.get("status")
171
174
  if verdict_label == "confirmed":
172
175
  vc = "verdict-confirmed"
173
176
  vt = "Confirmed"
177
+ elif verdict_label == "likely":
178
+ vc = "verdict-likely"
179
+ vt = "Likely"
174
180
  elif verdict_label == "refuted":
175
181
  vc = "verdict-refuted"
176
182
  vt = "Refuted"
@@ -15,6 +15,19 @@ from pydantic import ValidationError
15
15
  from backend.schemas.api import GeneralFindingReport
16
16
  from backend.verification.evidence_store import EvidenceStore
17
17
 
18
+ # CWE classes where GeneralOracle's verdict is structurally incapable of
19
+ # rising above "inconclusive" without a baseline (see general_oracle.py's
20
+ # CWE-639/284 branch: no baseline -> no way to prove the response is even
21
+ # unusual, let alone another user's data). Rejecting these at submission time
22
+ # means the agent never wastes a report on a hypothesis whose ceiling is known
23
+ # in advance — the sufficiency gate is enforced here, not left as a "the
24
+ # oracle will just say inconclusive" dead end downstream.
25
+ _IDOR_REQUIRES_BASELINE = {"639", "284"}
26
+
27
+
28
+ def _cwe_digits(cwe: str) -> str:
29
+ return "".join(ch for ch in (cwe or "") if ch.isdigit())
30
+
18
31
 
19
32
  class ReportGeneralFindingTool:
20
33
  """Validate, ground, and record a General DAST candidate finding."""
@@ -107,6 +120,20 @@ class ReportGeneralFindingTool:
107
120
  "note": "Fix the fields and resubmit. You cannot add fields or declare a verdict.",
108
121
  }
109
122
 
123
+ if _cwe_digits(report.vuln_class.cwe) in _IDOR_REQUIRES_BASELINE and not report.evidence.baseline:
124
+ return {
125
+ "error": "missing_baseline",
126
+ "note": (
127
+ "CWE-639/284 (IDOR / broken object-level access) can only ever be "
128
+ "verified against a baseline showing normal, authorized access to "
129
+ "compare with — without one the oracle can never return more than "
130
+ "'inconclusive', so don't file it yet. Either capture a baseline "
131
+ "request_id first, or if you're only testing 'does this endpoint "
132
+ "require authentication at all' (no comparison needed), reclassify "
133
+ "as CWE-287 or CWE-306 instead — that class doesn't need a baseline."
134
+ ),
135
+ }
136
+
110
137
  cited = report.cited_request_ids()
111
138
  missing = [rid for rid in cited if not self._evidence.has(rid)]
112
139
  if missing:
@@ -1,8 +1,8 @@
1
1
  import { Chip } from "@heroui/react";
2
2
 
3
3
  // Covers both scan RunStatus (queued/running/verifying/completed/failed/cancelled)
4
- // and the oracle's verdict vocabulary (confirmed/refuted/inconclusive — see
5
- // backend/verification/{idor,general}_oracle.py). Anything else (e.g. a
4
+ // and the oracle's verdict vocabulary (confirmed/likely/refuted/inconclusive —
5
+ // see backend/verification/{idor,general}_oracle.py). Anything else (e.g. a
6
6
  // finding's own "pending_verification" lifecycle field) falls back to default.
7
7
  const COLOR: Record<string, "accent" | "success" | "danger" | "warning" | "default"> = {
8
8
  queued: "accent",
@@ -10,6 +10,7 @@ const COLOR: Record<string, "accent" | "success" | "danger" | "warning" | "defau
10
10
  verifying: "accent",
11
11
  completed: "success",
12
12
  confirmed: "success",
13
+ likely: "warning",
13
14
  failed: "danger",
14
15
  error: "danger",
15
16
  cancelled: "default",
@@ -23,12 +23,19 @@ export function severityOf(finding: Finding): Severity {
23
23
  return (cwe && SEVERITY_BY_CWE[cwe]) || "medium";
24
24
  }
25
25
 
26
- export type Verdict = "confirmed" | "refuted" | "inconclusive";
26
+ export type Verdict = "confirmed" | "likely" | "refuted" | "inconclusive";
27
27
 
28
- /** The oracle's ruling vocabulary is exactly these three (see
29
- * backend/verification/{idor,general}_oracle.py) anything else means the
30
- * finding hasn't been verified yet. */
28
+ /** The oracle's ruling vocabulary confirmed/refuted/inconclusive from both
29
+ * oracles, plus GeneralOracle's "likely" tier (a suggestive but context-blind
30
+ * signal worth attention, not proof; see backend/verification/general_oracle.py).
31
+ * Anything else means the finding hasn't been verified yet. */
31
32
  export function normalizeVerdict(status: string | undefined): Verdict | "pending" {
32
- if (status === "confirmed" || status === "refuted" || status === "inconclusive") return status;
33
+ if (
34
+ status === "confirmed" ||
35
+ status === "likely" ||
36
+ status === "refuted" ||
37
+ status === "inconclusive"
38
+ )
39
+ return status;
33
40
  return "pending";
34
41
  }
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "shadowcat"
3
- version = "2.2.0"
3
+ version = "2.2.2"
4
4
  description = "AI-powered autonomous web security scanner with real-time streaming — enterprise DAST platform"
5
5
  authors = [{ name = "ShadowCat Team" }]
6
6
  readme = "README.md"
@@ -45,6 +45,37 @@ async def test_report_finding_rejects_duplicate_signal() -> None:
45
45
  assert len(tool.findings) == 1 # the duplicate was never recorded
46
46
 
47
47
 
48
+ async def test_report_finding_rejects_idor_without_baseline() -> None:
49
+ """CWE-639/284 without a baseline is guaranteed 'inconclusive' downstream —
50
+ reject at submission so the agent doesn't waste a report on a known dead end."""
51
+ store = EvidenceStore("s")
52
+ rid = store.put({"method": "GET", "url": "https://t/rest/order-history"}, {"status": 200})
53
+ tool = ReportGeneralFindingTool(store)
54
+ result = await tool.run(
55
+ title="Possible IDOR on /rest/order-history",
56
+ hypothesis="h",
57
+ vuln_class={"cwe": "CWE-639"},
58
+ evidence={"probe": rid},
59
+ )
60
+ assert result["error"] == "missing_baseline"
61
+ assert tool.findings == []
62
+
63
+
64
+ async def test_report_finding_accepts_idor_with_baseline() -> None:
65
+ store = EvidenceStore("s")
66
+ base = store.put({"method": "GET", "url": "https://t/rest/order-history/1"}, {"status": 200})
67
+ probe = store.put({"method": "GET", "url": "https://t/rest/order-history/2"}, {"status": 200})
68
+ tool = ReportGeneralFindingTool(store)
69
+ result = await tool.run(
70
+ title="IDOR on /rest/order-history",
71
+ hypothesis="h",
72
+ vuln_class={"cwe": "CWE-639"},
73
+ evidence={"probe": probe, "baseline": base},
74
+ )
75
+ assert "error" not in result
76
+ assert len(tool.findings) == 1
77
+
78
+
48
79
  async def test_report_finding_allows_same_probe_for_a_different_cwe() -> None:
49
80
  """The same request_id may legitimately support two different findings."""
50
81
  store = EvidenceStore("s")
@@ -1648,7 +1648,7 @@ wheels = [
1648
1648
 
1649
1649
  [[package]]
1650
1650
  name = "shadowcat"
1651
- version = "2.1.0"
1651
+ version = "2.2.0"
1652
1652
  source = { editable = "." }
1653
1653
  dependencies = [
1654
1654
  { name = "anthropic" },
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes