stryx-cli 0.1.0__py3-none-any.whl

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 (74) hide show
  1. stryx/__init__.py +35 -0
  2. stryx/ai/__init__.py +5 -0
  3. stryx/ai/attack_planner.py +199 -0
  4. stryx/ai/payload_generator.py +212 -0
  5. stryx/ai/prompts.py +85 -0
  6. stryx/ai/providers.py +249 -0
  7. stryx/attacks/__init__.py +6 -0
  8. stryx/attacks/attack_chain.py +111 -0
  9. stryx/attacks/replay.py +186 -0
  10. stryx/auth/__init__.py +1 -0
  11. stryx/auth/session_manager.py +335 -0
  12. stryx/cli.py +675 -0
  13. stryx/comparison/__init__.py +1 -0
  14. stryx/comparison/differ.py +255 -0
  15. stryx/config/__init__.py +6 -0
  16. stryx/config/default_config.yaml +12 -0
  17. stryx/config/loader.py +111 -0
  18. stryx/config/schema.py +80 -0
  19. stryx/crawler/__init__.py +5 -0
  20. stryx/crawler/discovery.py +178 -0
  21. stryx/crawler/graphql_discovery.py +86 -0
  22. stryx/crawler/html_crawler.py +294 -0
  23. stryx/crawler/js_endpoints.py +165 -0
  24. stryx/crawler/openapi.py +76 -0
  25. stryx/crawler/sitemap.py +94 -0
  26. stryx/orchestrator.py +347 -0
  27. stryx/payloads/cmdi.txt +31 -0
  28. stryx/payloads/header_injection.txt +15 -0
  29. stryx/payloads/ldap.txt +19 -0
  30. stryx/payloads/nosqli.txt +21 -0
  31. stryx/payloads/open_redirect.txt +23 -0
  32. stryx/payloads/path_traversal.txt +26 -0
  33. stryx/payloads/sqli.txt +31 -0
  34. stryx/payloads/ssrf.txt +30 -0
  35. stryx/payloads/ssti.txt +21 -0
  36. stryx/payloads/xss.txt +48 -0
  37. stryx/payloads/xxe.txt +35 -0
  38. stryx/plugins/__init__.py +5 -0
  39. stryx/plugins/base.py +85 -0
  40. stryx/policy/__init__.py +1 -0
  41. stryx/policy/engine.py +201 -0
  42. stryx/py.typed +0 -0
  43. stryx/reports/__init__.py +5 -0
  44. stryx/reports/generator.py +122 -0
  45. stryx/reports/json_report.py +72 -0
  46. stryx/reports/markdown_report.py +101 -0
  47. stryx/reports/sarif_report.py +200 -0
  48. stryx/reports/templates/report.html.j2 +163 -0
  49. stryx/reports/terminal_report.py +138 -0
  50. stryx/scanners/__init__.py +17 -0
  51. stryx/scanners/auth.py +444 -0
  52. stryx/scanners/authorization.py +220 -0
  53. stryx/scanners/blind.py +328 -0
  54. stryx/scanners/cloud_ssrf.py +208 -0
  55. stryx/scanners/cors.py +158 -0
  56. stryx/scanners/dependencies.py +296 -0
  57. stryx/scanners/disclosure.py +339 -0
  58. stryx/scanners/fuzz.py +391 -0
  59. stryx/scanners/graphql.py +309 -0
  60. stryx/scanners/injection.py +511 -0
  61. stryx/scanners/race.py +257 -0
  62. stryx/signatures/framework_fingerprints.yaml +122 -0
  63. stryx/signatures/known_vulns.yaml +210 -0
  64. stryx/utils/__init__.py +7 -0
  65. stryx/utils/evidence.py +109 -0
  66. stryx/utils/http_client.py +159 -0
  67. stryx/utils/logging.py +51 -0
  68. stryx/utils/rate_limiter.py +37 -0
  69. stryx_cli-0.1.0.dist-info/METADATA +236 -0
  70. stryx_cli-0.1.0.dist-info/RECORD +74 -0
  71. stryx_cli-0.1.0.dist-info/WHEEL +5 -0
  72. stryx_cli-0.1.0.dist-info/entry_points.txt +2 -0
  73. stryx_cli-0.1.0.dist-info/licenses/LICENSE +190 -0
  74. stryx_cli-0.1.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,200 @@
1
+ """SARIF 2.1.0 report generator for CI/CD integration.
2
+
3
+ Generates Static Analysis Results Interchange Format (SARIF) reports
4
+ compatible with GitHub Security tab, Azure DevOps, and other SARIF consumers.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ import json
10
+ from datetime import datetime, timezone
11
+ from typing import Any
12
+
13
+ from stryx.utils.evidence import Finding, Severity
14
+ from stryx.utils.logging import get_logger
15
+
16
+ logger = get_logger("reports.sarif")
17
+
18
+ # SARIF severity mapping
19
+ SEVERITY_MAP = {
20
+ "critical": {"level": "error", "score": 1.0},
21
+ "high": {"level": "error", "score": 0.8},
22
+ "medium": {"level": "warning", "score": 0.5},
23
+ "low": {"level": "note", "score": 0.2},
24
+ "info": {"level": "none", "score": 0.0},
25
+ }
26
+
27
+
28
+ class SarifReport:
29
+ """Generates SARIF 2.1.0 compliant reports."""
30
+
31
+ def __init__(
32
+ self,
33
+ target_url: str,
34
+ findings: list[Finding],
35
+ scan_time: str | None = None,
36
+ ):
37
+ self.target_url = target_url
38
+ self.findings = findings
39
+ self.scan_time = scan_time or datetime.now(timezone.utc).isoformat()
40
+
41
+ def generate(self) -> dict[str, Any]:
42
+ """Generate SARIF report as a dictionary."""
43
+ # Build rules from findings
44
+ rules: list[dict[str, Any]] = []
45
+ rule_ids: set[str] = set()
46
+
47
+ for finding in self.findings:
48
+ rule_id = self._get_rule_id(finding)
49
+ if rule_id not in rule_ids:
50
+ rule_ids.add(rule_id)
51
+ rules.append(self._build_rule(finding, rule_id))
52
+
53
+ # Build results
54
+ results = [self._build_result(f) for f in self.findings]
55
+
56
+ sarif = {
57
+ "$schema": "https://raw.githubusercontent.com/oasis-tcs/sarif-spec/master/Schemata/sarif-schema-2.1.0.json",
58
+ "version": "2.1.0",
59
+ "runs": [
60
+ {
61
+ "tool": {
62
+ "driver": {
63
+ "name": "STRYX",
64
+ "version": "0.1.0",
65
+ "semanticVersion": "0.1.0",
66
+ "informationUri": "https://github.com/medusa-Security/stryx",
67
+ "rules": rules,
68
+ }
69
+ },
70
+ "artifacts": [
71
+ {
72
+ "location": {
73
+ "uri": self.target_url,
74
+ "uriBaseId": "%SRCROOT%",
75
+ },
76
+ }
77
+ ],
78
+ "results": results,
79
+ "invocations": [
80
+ {
81
+ "startTimeUtc": self.scan_time,
82
+ "toolExecutionNotifications": [],
83
+ }
84
+ ],
85
+ }
86
+ ],
87
+ }
88
+
89
+ return sarif
90
+
91
+ def to_json(self, indent: int = 2) -> str:
92
+ """Generate SARIF report as JSON string."""
93
+ return json.dumps(self.generate(), indent=indent, default=str)
94
+
95
+ def save(self, path: str) -> None:
96
+ """Save SARIF report to file."""
97
+ with open(path, "w", encoding="utf-8") as f:
98
+ f.write(self.to_json())
99
+ logger.info(f"SARIF report saved to {path}")
100
+
101
+ def _get_rule_id(self, finding: Finding) -> str:
102
+ """Generate a stable rule ID for a finding."""
103
+ # Use CWE if available
104
+ if finding.cwe:
105
+ cwe_num = finding.cwe.replace("CWE-", "")
106
+ return f"stryx-cwe-{cwe_num}"
107
+
108
+ # Use scanner + title hash
109
+ import hashlib
110
+ title_hash = hashlib.md5(finding.title.encode()).hexdigest()[:8]
111
+ return f"stryx-{finding.scanner}-{title_hash}"
112
+
113
+ def _build_rule(self, finding: Finding, rule_id: str) -> dict[str, Any]:
114
+ """Build a SARIF rule definition."""
115
+ rule: dict[str, Any] = {
116
+ "id": rule_id,
117
+ "name": finding.title[:80],
118
+ "shortDescription": {
119
+ "text": finding.title,
120
+ },
121
+ "fullDescription": {
122
+ "text": finding.description or finding.title,
123
+ },
124
+ "helpUri": "",
125
+ "properties": {
126
+ "tags": finding.tags if hasattr(finding, 'tags') and finding.tags else [],
127
+ },
128
+ }
129
+
130
+ # Add CWE reference
131
+ if finding.cwe:
132
+ cwe_num = finding.cwe.replace("CWE-", "")
133
+ rule["helpUri"] = f"https://cwe.mitre.org/data/definitions/{cwe_num}.html"
134
+ rule["properties"]["tags"].append(f"security")
135
+ rule["properties"]["tags"].append(f"external/cwe/cwe-{cwe_num}")
136
+
137
+ # Add OWASP reference
138
+ if finding.owasp:
139
+ rule["properties"]["owasp"] = finding.owasp
140
+
141
+ return rule
142
+
143
+ def _build_result(self, finding: Finding) -> dict[str, Any]:
144
+ """Build a SARIF result for a finding."""
145
+ sev_data = SEVERITY_MAP.get(finding.severity.value, SEVERITY_MAP["info"])
146
+
147
+ result: dict[str, Any] = {
148
+ "ruleId": self._get_rule_id(finding),
149
+ "level": sev_data["level"],
150
+ "message": {
151
+ "text": finding.description or finding.title,
152
+ },
153
+ "locations": [
154
+ {
155
+ "physicalLocation": {
156
+ "artifactLocation": {
157
+ "uri": finding.endpoint or self.target_url,
158
+ },
159
+ "region": {
160
+ "startLine": 1,
161
+ },
162
+ }
163
+ }
164
+ ],
165
+ "properties": {
166
+ "severity": finding.severity.value,
167
+ "confidence": finding.evidence.confidence,
168
+ "scanner": finding.scanner,
169
+ },
170
+ }
171
+
172
+ # Add CWE/OWASP to properties
173
+ if finding.cwe:
174
+ result["properties"]["cwe"] = finding.cwe
175
+ if finding.owasp:
176
+ result["properties"]["owasp"] = finding.owasp
177
+
178
+ # Add remediation as help text
179
+ if finding.remediation:
180
+ result["fixes"] = [
181
+ {
182
+ "description": {
183
+ "text": finding.remediation,
184
+ },
185
+ }
186
+ ]
187
+
188
+ # Add request/response evidence
189
+ if finding.evidence:
190
+ evidence_parts = []
191
+ evidence_parts.append(f"Request: {finding.evidence.request_method} {finding.evidence.request_url}")
192
+ evidence_parts.append(f"Status: {finding.evidence.response_status}")
193
+ if finding.evidence.payload:
194
+ evidence_parts.append(f"Payload: {finding.evidence.payload}")
195
+ if finding.evidence.response_snippet:
196
+ evidence_parts.append(f"Response: {finding.evidence.response_snippet[:200]}")
197
+
198
+ result["properties"]["evidence"] = "\n".join(evidence_parts)
199
+
200
+ return result
@@ -0,0 +1,163 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>STRYX Security Report - {{ target }}</title>
7
+ <style>
8
+ * { margin: 0; padding: 0; box-sizing: border-box; }
9
+ body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; background: #0d1117; color: #c9d1d9; line-height: 1.6; }
10
+ .container { max-width: 1200px; margin: 0 auto; padding: 20px; }
11
+ .header { background: #161b22; border: 1px solid #30363d; border-radius: 8px; padding: 24px; margin-bottom: 24px; }
12
+ .header h1 { color: #58a6ff; font-size: 24px; margin-bottom: 8px; }
13
+ .header .meta { color: #8b949e; font-size: 14px; }
14
+ .summary { display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 16px; margin-bottom: 24px; }
15
+ .summary-card { background: #161b22; border: 1px solid #30363d; border-radius: 8px; padding: 16px; text-align: center; }
16
+ .summary-card .count { font-size: 32px; font-weight: bold; }
17
+ .summary-card .label { color: #8b949e; font-size: 12px; text-transform: uppercase; }
18
+ .critical { color: #f85149; }
19
+ .high { color: #f0883e; }
20
+ .medium { color: #d29922; }
21
+ .low { color: #58a6ff; }
22
+ .info { color: #8b949e; }
23
+ .finding { background: #161b22; border: 1px solid #30363d; border-radius: 8px; margin-bottom: 16px; overflow: hidden; }
24
+ .finding-header { padding: 16px; border-bottom: 1px solid #30363d; display: flex; justify-content: space-between; align-items: center; }
25
+ .finding-title { font-size: 16px; font-weight: 600; }
26
+ .severity-badge { padding: 4px 8px; border-radius: 4px; font-size: 12px; font-weight: bold; text-transform: uppercase; }
27
+ .severity-critical { background: #f85149; color: #fff; }
28
+ .severity-high { background: #f0883e; color: #fff; }
29
+ .severity-medium { background: #d29922; color: #fff; }
30
+ .severity-low { background: #58a6ff; color: #fff; }
31
+ .severity-info { background: #8b949e; color: #fff; }
32
+ .finding-body { padding: 16px; }
33
+ .finding-section { margin-bottom: 12px; }
34
+ .finding-section h3 { color: #8b949e; font-size: 12px; text-transform: uppercase; margin-bottom: 4px; }
35
+ .finding-section p { margin: 0; }
36
+ .tags { display: flex; gap: 8px; flex-wrap: wrap; margin-top: 4px; }
37
+ .tag { background: #21262d; color: #8b949e; padding: 2px 8px; border-radius: 12px; font-size: 11px; }
38
+ .evidence-block { background: #0d1117; border: 1px solid #30363d; border-radius: 4px; padding: 12px; font-family: 'SFMono-Regular', Consolas, monospace; font-size: 13px; overflow-x: auto; white-space: pre-wrap; word-break: break-all; }
39
+ .evidence-header { color: #58a6ff; margin-bottom: 8px; }
40
+ .evidence-label { color: #8b949e; }
41
+ .chain { background: #161b22; border: 1px solid #30363d; border-radius: 8px; margin-bottom: 16px; padding: 16px; }
42
+ .chain h2 { color: #f0883e; margin-bottom: 12px; font-size: 18px; }
43
+ .chain-meta { color: #8b949e; margin-bottom: 12px; }
44
+ .chain-step { padding: 8px 12px; border-left: 3px solid #30363d; margin-bottom: 8px; background: #0d1117; border-radius: 0 4px 4px 0; }
45
+ .chain-step-title { font-weight: 600; color: #c9d1d9; }
46
+ .chain-step-desc { color: #8b949e; font-size: 13px; margin-top: 4px; }
47
+ .footer { text-align: center; color: #8b949e; padding: 24px; font-size: 12px; border-top: 1px solid #30363d; margin-top: 24px; }
48
+ .no-findings { text-align: center; padding: 40px; color: #3fb950; font-size: 18px; }
49
+ </style>
50
+ </head>
51
+ <body>
52
+ <div class="container">
53
+ <div class="header">
54
+ <h1>🛡️ STRYX Security Report</h1>
55
+ <div class="meta">
56
+ <p><strong>Target:</strong> {{ target }}</p>
57
+ <p><strong>Generated:</strong> {{ generated_at }}</p>
58
+ <p><strong>Tool:</strong> STRYX v0.1.0 - AI-Powered DAST</p>
59
+ </div>
60
+ </div>
61
+
62
+ <div class="summary">
63
+ <div class="summary-card">
64
+ <div class="count">{{ total_findings }}</div>
65
+ <div class="label">Total Findings</div>
66
+ </div>
67
+ <div class="summary-card">
68
+ <div class="count critical">{{ critical_count }}</div>
69
+ <div class="label">Critical</div>
70
+ </div>
71
+ <div class="summary-card">
72
+ <div class="count high">{{ high_count }}</div>
73
+ <div class="label">High</div>
74
+ </div>
75
+ <div class="summary-card">
76
+ <div class="count medium">{{ medium_count }}</div>
77
+ <div class="label">Medium</div>
78
+ </div>
79
+ <div class="summary-card">
80
+ <div class="count low">{{ low_count }}</div>
81
+ <div class="label">Low</div>
82
+ </div>
83
+ </div>
84
+
85
+ {% if findings %}
86
+ <h2 style="color: #c9d1d9; margin-bottom: 16px;">Findings ({{ findings|length }})</h2>
87
+
88
+ {% for finding in findings %}
89
+ <div class="finding">
90
+ <div class="finding-header">
91
+ <span class="finding-title">{{ finding.title }}</span>
92
+ <span class="severity-badge severity-{{ finding.severity }}">{{ finding.severity }}</span>
93
+ </div>
94
+ <div class="finding-body">
95
+ <div class="finding-section">
96
+ <h3>Endpoint</h3>
97
+ <p><code>{{ finding.endpoint }}</code></p>
98
+ </div>
99
+ <div class="finding-section">
100
+ <h3>Description</h3>
101
+ <p>{{ finding.description }}</p>
102
+ </div>
103
+ {% if finding.remediation %}
104
+ <div class="finding-section">
105
+ <h3>Remediation</h3>
106
+ <p>{{ finding.remediation }}</p>
107
+ </div>
108
+ {% endif %}
109
+ {% if finding.cwe or finding.owasp %}
110
+ <div class="finding-section">
111
+ <h3>References</h3>
112
+ <p>
113
+ {% if finding.cwe %}<strong>CWE:</strong> {{ finding.cwe }}{% endif %}
114
+ {% if finding.cwe and finding.owasp %} | {% endif %}
115
+ {% if finding.owasp %}<strong>OWASP:</strong> {{ finding.owasp }}{% endif %}
116
+ </p>
117
+ </div>
118
+ {% endif %}
119
+ <div class="finding-section">
120
+ <h3>Evidence</h3>
121
+ <div class="evidence-block"><span class="evidence-header">{{ finding.evidence.request_method }} {{ finding.evidence.request_url }}</span>
122
+ <span class="evidence-label">Status:</span> {{ finding.evidence.response_status }}
123
+ <span class="evidence-label">Confidence:</span> {{ "%.0f"|format(finding.evidence.confidence * 100) }}%
124
+ {% if finding.evidence.payload %}<span class="evidence-label">Payload:</span> {{ finding.evidence.payload }}{% endif %}
125
+
126
+ <span class="evidence-label">Response Snippet:</span>
127
+ {{ finding.evidence.response_snippet[:500] }}</div>
128
+ </div>
129
+ </div>
130
+ </div>
131
+ {% endfor %}
132
+ {% else %}
133
+ <div class="no-findings">✅ No security findings detected</div>
134
+ {% endif %}
135
+
136
+ {% if attack_chains %}
137
+ <h2 style="color: #f0883e; margin: 24px 0 16px;">⚔️ Attack Chains ({{ attack_chains|length }})</h2>
138
+
139
+ {% for chain in attack_chains %}
140
+ <div class="chain">
141
+ <h2>{{ chain.name }}</h2>
142
+ <div class="chain-meta">
143
+ <p><strong>Impact:</strong> {{ chain.total_impact }}</p>
144
+ <p><strong>Estimated Severity:</strong> {{ chain.estimated_severity }}</p>
145
+ </div>
146
+ {% for step in chain.steps %}
147
+ <div class="chain-step">
148
+ <div class="chain-step-title">Step {{ loop.index }}: {{ step.title }}</div>
149
+ <div class="chain-step-desc">{{ step.description }}</div>
150
+ </div>
151
+ {% endfor %}
152
+ </div>
153
+ {% endfor %}
154
+ {% endif %}
155
+
156
+ <div class="footer">
157
+ <p>STRYX v0.1.0 - AI-Powered Dynamic Application Security Testing</p>
158
+ <p>Built by Akhilesh Varma under Medusa Security</p>
159
+ <p>Generated {{ generated_at }}</p>
160
+ </div>
161
+ </div>
162
+ </body>
163
+ </html>
@@ -0,0 +1,138 @@
1
+ """Terminal report generator using Rich."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from rich.panel import Panel
6
+ from rich.table import Table
7
+
8
+ from stryx.attacks.attack_chain import AttackChain
9
+ from stryx.utils.evidence import Finding
10
+ from stryx.utils.logging import console as stryx_console
11
+
12
+
13
+ class TerminalReport:
14
+ """Generates rich terminal output for scan results."""
15
+
16
+ def __init__(
17
+ self,
18
+ target_url: str,
19
+ findings: list[Finding],
20
+ attack_chains: list[AttackChain] | None = None,
21
+ ):
22
+ self.target_url = target_url
23
+ self.findings = findings
24
+ self.attack_chains = attack_chains or []
25
+ self.console = stryx_console
26
+
27
+ def display(self) -> None:
28
+ """Display the terminal report."""
29
+ self._display_summary()
30
+ self._display_findings_table()
31
+ self._display_findings_detail()
32
+ self._display_attack_chains()
33
+
34
+ def _display_summary(self) -> None:
35
+ """Display summary panel."""
36
+ severity_counts: dict[str, int] = {}
37
+ for f in self.findings:
38
+ sev = f.severity.value
39
+ severity_counts[sev] = severity_counts.get(sev, 0) + 1
40
+
41
+ summary_parts = []
42
+ for sev in ["critical", "high", "medium", "low", "info"]:
43
+ count = severity_counts.get(sev, 0)
44
+ if count > 0:
45
+ summary_parts.append(f"[severity.{sev}]{sev.upper()}: {count}[/]")
46
+
47
+ summary = " | ".join(summary_parts) if summary_parts else "No findings"
48
+
49
+ self.console.print(Panel(
50
+ f"[bold]Target:[/] {self.target_url}\n"
51
+ f"[bold]Total Findings:[/] {len(self.findings)}\n"
52
+ f"[bold]Breakdown:[/] {summary}",
53
+ title="[bold]STRYX Scan Results[/]",
54
+ border_style="cyan",
55
+ ))
56
+
57
+ def _display_findings_table(self) -> None:
58
+ """Display findings in a table."""
59
+ if not self.findings:
60
+ self.console.print("[green]No findings detected.[/]")
61
+ return
62
+
63
+ table = Table(title="Findings", show_lines=True)
64
+ table.add_column("#", style="dim", width=4)
65
+ table.add_column("Severity", width=10)
66
+ table.add_column("Title", min_width=30)
67
+ table.add_column("Endpoint", min_width=20)
68
+ table.add_column("Confidence", width=10)
69
+
70
+ for i, f in enumerate(self.findings, 1):
71
+ severity_colors = {
72
+ "critical": "bold red",
73
+ "high": "red",
74
+ "medium": "yellow",
75
+ "low": "blue",
76
+ "info": "dim",
77
+ }
78
+ color = severity_colors.get(f.severity.value, "white")
79
+
80
+ table.add_row(
81
+ str(i),
82
+ f"[{color}]{f.severity.value.upper()}[/]",
83
+ f.title,
84
+ f.endpoint[:50] + ("..." if len(f.endpoint) > 50 else ""),
85
+ f"{f.evidence.confidence:.0%}",
86
+ )
87
+
88
+ self.console.print(table)
89
+
90
+ def _display_findings_detail(self) -> None:
91
+ """Display detailed findings."""
92
+ if not self.findings:
93
+ return
94
+
95
+ self.console.print("\n[bold]Detailed Findings:[/]\n")
96
+
97
+ for i, f in enumerate(self.findings, 1):
98
+ severity_colors = {
99
+ "critical": "bold red",
100
+ "high": "red",
101
+ "medium": "yellow",
102
+ "low": "blue",
103
+ "info": "dim",
104
+ }
105
+ color = severity_colors.get(f.severity.value, "white")
106
+
107
+ detail = (
108
+ f"[bold]{f.title}[/]\n"
109
+ f" Severity: [{color}]{f.severity.value.upper()}[/] | "
110
+ f"CWE: {f.cwe} | Scanner: {f.scanner}\n"
111
+ f" Endpoint: {f.endpoint}\n"
112
+ f" Description: {f.description}\n"
113
+ f" Remediation: {f.remediation}\n"
114
+ f" Evidence: {f.evidence.request_method} {f.evidence.request_url} "
115
+ f"-> {f.evidence.response_status}"
116
+ )
117
+ if f.evidence.payload:
118
+ detail += f"\n Payload: {f.evidence.payload}"
119
+ if f.evidence.response_snippet:
120
+ detail += f"\n Response: {f.evidence.response_snippet[:200]}"
121
+
122
+ self.console.print(Panel(detail, border_style=color))
123
+
124
+ def _display_attack_chains(self) -> None:
125
+ """Display attack chains."""
126
+ if not self.attack_chains:
127
+ return
128
+
129
+ self.console.print("\n[bold]Attack Chains:[/]\n")
130
+
131
+ for chain in self.attack_chains:
132
+ chain_text = f"[bold]{chain.name}[/]\n"
133
+ chain_text += f"Impact: {chain.total_impact}\n"
134
+ chain_text += f"Severity: {chain.estimated_severity.upper()}\n\n"
135
+ for j, step in enumerate(chain.steps, 1):
136
+ chain_text += f" {j}. {step.finding.title} - {step.description}\n"
137
+
138
+ self.console.print(Panel(chain_text, border_style="magenta"))
@@ -0,0 +1,17 @@
1
+ """Scanner modules for STRYX."""
2
+
3
+ from stryx.scanners.auth import AuthScanner
4
+ from stryx.scanners.authorization import AuthorizationScanner
5
+ from stryx.scanners.cors import CorsScanner
6
+ from stryx.scanners.fuzz import FuzzScanner
7
+ from stryx.scanners.graphql import GraphQLScanner
8
+ from stryx.scanners.injection import InjectionScanner
9
+
10
+ __all__ = [
11
+ "AuthScanner",
12
+ "AuthorizationScanner",
13
+ "InjectionScanner",
14
+ "FuzzScanner",
15
+ "CorsScanner",
16
+ "GraphQLScanner",
17
+ ]