synapse-hub 2.0.1 → 2.1.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.
@@ -13,6 +13,7 @@
13
13
  | **Comprehensive Health & AST Analysis ("analyze project")** | `tools/analyze_project.py` | `python tools/analyze_project.py [target]` (or `agent-qa analyze`).<br/>*Post-Audit:* Stages, commits, and pushes `PROJECT_HEALTH_REPORT.md` & `repo_map.txt` to project Git. |
14
14
  | **Action Safety Firewall (Environment Protection)** | `tools/safety_firewall.py` | `python tools/safety_firewall.py --text "Delete" --action click` (or `agent-qa safety-check`) |
15
15
  | **API Contract Drift (Breaking Change Detection)** | `tools/api_drift_sentinel.py` | `python tools/api_drift_sentinel.py --old base.json --new curr.json` (or `agent-qa contract-drift`) |
16
+ | **Autonomous API Fuzzing & Chaos Breaker** | `tools/api_fuzzer.py` | `agent-qa fuzz-api --base-url <url> --spec <swagger.json>` (Catches 500, DB leaks, SLA breach) |
16
17
  | **FastMCP JSON-RPC Server (Cursor/Claude/Antigravity)** | `tools/mcp_server.py` | `python tools/mcp_server.py` (or `agent-qa mcp-serve`) |
17
18
  | **AST Micro-Mutation (Assertion Quality Verification)** | `tools/micro_mutator.py` | `python tools/micro_mutator.py [target]` (or `agent-qa mutate`) |
18
19
  | **Self-Healing PR Automator (Trace & Visual Diff Proof)** | `tools/heal_pr_automator.py` | `python tools/heal_pr_automator.py --page P.groovy --old "//a" --new "//b" --trace-zip trace.zip` (or `agent-qa heal-pr`) |
@@ -33,6 +34,8 @@
33
34
  | **After healing a test failure** | `tools/cache_resolver.py` | `python tools/cache_resolver.py record-lesson --context "<page>" --error "<sig>" --lesson "<summary>"` |
34
35
  | **Generating test scripts from Natural Language Story** | `tools/generate_tc_from_story.py`| `python tools/generate_tc_from_story.py "<story>"` |
35
36
  | **Generating E2E matrix from OpenAPI/Swagger** | `tools/generate_api_matrix.py` | `python tools/generate_api_matrix.py --spec <swagger.json>` |
37
+ | **Exporting API tests to Postman Collection v2.1** | `tools/export_postman_collection.py` | `agent-qa export-postman --spec <swagger.json> -o col.json` |
38
+ | **Generating JMeter Performance Test Plan (.jmx)** | `tools/generate_jmx_plan.py` | `agent-qa generate-jmx --spec <swagger.json> --vus 100 -o plan.jmx` |
36
39
  | **Auditing Web Accessibility (WCAG 2.2)** | `tools/wcag_audit.py` | `python tools/wcag_audit.py <path_to_html_or_url>` |
37
40
  | **0-Token Polyglot Test Transpilation** | `tools/omni_transpiler.py` | `agent-qa transpile <file> --to <target>` (Playwright TS, Python, Java, Katalon, Cypress) |
38
41
  | **0-Token Repository Polyglot AST Scan** | `tools/omni_transpiler.py` | `agent-qa omni-scan [path]` (Instant inventory of Pages, Locators, Tests across languages) |
@@ -233,14 +236,30 @@
233
236
  ---
234
237
 
235
238
  ### 15. `tools/visual_som_helper.py`
236
- - **Purpose:** Multimodal Vision Grounding and Set-of-Marks (SoM) fallback engine. When standard DOM locators fail on HTML5 Canvas, WebGL, or deeply nested Shadow DOM, it overlays numbered bounding box badges on screenshots, resolves target coordinates `(x, y)`, and dispatches physical Playwright mouse clicks (`await page.mouse.click(x, y)`).
239
+ - **Purpose:** Multimodal Vision Grounding and Set-of-Marks (SoM) fallback engine. When standard DOM locators fail on HTML5 Canvas, GIS maps, WebGL charts, or deeply nested Shadow DOM, it autonomously detects interactive visual contours using PIL edge clustering, overlays numbered bounding box badges on screenshots, resolves target coordinates `(x, y)`, caches coordinates via 64-bit dHash in SQLite, and dispatches physical Playwright mouse clicks (`await page.mouse.click(x, y)`).
237
240
  - **CLI Syntax:**
238
241
  ```bash
242
+ # Autonomous Canvas contour detection without DOM
243
+ python tools/visual_som_helper.py --image canvas_map.png --detect-only
244
+
239
245
  # Generate Set-of-Marks overlay and resolve target coordinates
240
246
  python tools/visual_som_helper.py --image screenshot.png --label "Execute Trade"
241
247
 
242
- # Resolve specific Mark ID
243
- python tools/visual_som_helper.py --image screenshot.png --mark-id 3 --json
248
+ # Zero-Token Visual Cache lookup (Similarity > 98%)
249
+ python tools/visual_som_helper.py --image screenshot.png --component "TradeButton" --cache-lookup
250
+ ```
251
+
252
+ ---
253
+
254
+ ### 16. `tools/api_fuzzer.py`
255
+ - **Purpose:** Autonomous runtime API fuzzer and chaos breaker. Ingests OpenAPI 3.x / Swagger 2.0 schemas, generates dynamic boundary, type-inverted, and adversarial fuzzing payloads from `tools/fuzz_payloads.py`, executes live HTTP/S requests against staging/test APIs, and evaluates three-alarm defect Oracle: HTTP 500 crashes, database stacktrace leaks, and SLA latency timeouts (> 3000ms).
256
+ - **CLI Syntax:**
257
+ ```bash
258
+ # Execute active fuzzing against staging API
259
+ agent-qa fuzz-api --base-url "https://staging.api.enterprise.com" --spec swagger.json --token "JWT_SECRET"
260
+
261
+ # Air-gapped / deterministic dry-run mode for CI/CD
262
+ agent-qa fuzz-api --dry-run
244
263
  ```
245
264
 
246
265
  ---
@@ -296,3 +315,35 @@
296
315
  ```bash
297
316
  agent-qa ci-hook --file LoginPage.ts --old-loc "//button[@id='old']" --new-loc "//button[@id='new']"
298
317
  ```
318
+
319
+ ---
320
+
321
+ ### 21. `tools/export_postman_collection.py`
322
+ - **Purpose:** Enterprise OpenAPI/Swagger & HAR to Postman Collection v2.1.0 Exporter. Generates fully structured Postman collections with dynamic pre-request scripts, SLA latency tests, response code assertions, and autonomous bearer token / created entity ID extraction for Newman and CI/CD pipelines.
323
+ - **CLI Syntax:**
324
+ ```bash
325
+ # Ingest OpenAPI spec and export Postman collection + companion environment
326
+ agent-qa export-postman --spec swagger.json -o collection.json --env-output env.json
327
+
328
+ # Replay production HAR traffic as Postman requests
329
+ agent-qa export-postman --har session.har -o har_collection.json
330
+
331
+ # Generate sample collection from built-in enterprise schema
332
+ agent-qa export-postman --sample -o sample_collection.json
333
+ ```
334
+
335
+ ---
336
+
337
+ ### 22. `tools/generate_jmx_plan.py`
338
+ - **Purpose:** Apache JMeter 5.x Performance Test Plan XML (.jmx) Generator. Ingests OpenAPI specifications, Postman Collections, or HAR recordings to output production-ready JMeter load, stress, and spike test plans with configurable VUs, ramp-up schedules, SLA assertions (< 500ms), and dynamic session token propagation.
339
+ - **CLI Syntax:**
340
+ ```bash
341
+ # Generate JMeter plan from OpenAPI with 100 Virtual Users for 10 minutes
342
+ agent-qa generate-jmx --spec swagger.json -o load_plan.jmx --vus 100 --duration 600
343
+
344
+ # Convert existing Postman collection into JMeter performance test plan
345
+ agent-qa generate-jmx --postman collection.json -o perf_plan.jmx --vus 50 --ramp-up 20
346
+
347
+ # Run headless JMeter CLI
348
+ jmeter -n -t load_plan.jmx -l results.jtl -e -o ./html_report
349
+ ```
@@ -0,0 +1,468 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ """
4
+ Enterprise Autonomous API Fuzzing & Chaos Breaker
5
+ -------------------------------------------------
6
+ Zero-dependency, active runtime API mutator and resilience tester.
7
+ Ingests OpenAPI 3.x / Swagger 2.0 specifications (or endpoint catalogs),
8
+ generates dynamic boundary, type-inverted, and adversarial fuzzing payloads,
9
+ and executes live HTTP/S requests against staging/test APIs.
10
+
11
+ Three-Alarm Oracle Detection:
12
+ 1. HTTP 500 / 502 / 503 Internal Server Error (Backend crash / unhandled fault)
13
+ 2. Database & Framework Stacktrace Leaks (SQL injection reflection, Django/Spring/Postgres traces)
14
+ 3. SLA Latency Breach (> 3000ms response time - potential ReDoS or lock contention)
15
+ """
16
+
17
+ import os
18
+ import re
19
+ import sys
20
+ import json
21
+ import time
22
+ import ssl
23
+ import urllib.request
24
+ import urllib.error
25
+ import urllib.parse
26
+ import argparse
27
+ from typing import Dict, Any, List, Optional, Tuple
28
+
29
+ # Enforce UTF-8 stdout encoding for Windows/macOS/Linux compatibility
30
+ if hasattr(sys.stdout, 'reconfigure'):
31
+ try:
32
+ sys.stdout.reconfigure(encoding='utf-8')
33
+ sys.stderr.reconfigure(encoding='utf-8')
34
+ except Exception:
35
+ pass
36
+
37
+ SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
38
+ HUB_ROOT = os.path.abspath(os.path.join(SCRIPT_DIR, ".."))
39
+ REPORTS_DIR = os.path.join(HUB_ROOT, "reports")
40
+
41
+ if SCRIPT_DIR not in sys.path:
42
+ sys.path.insert(0, SCRIPT_DIR)
43
+
44
+ try:
45
+ from fuzz_payloads import get_fuzz_payloads, get_field_fuzz_scenarios
46
+ except ImportError:
47
+ get_fuzz_payloads = None
48
+ get_field_fuzz_scenarios = None
49
+
50
+ # Stacktrace and DB leak pattern signatures
51
+ STACKTRACE_LEAK_PATTERNS = [
52
+ re.compile(r'Traceback \(most recent call last\):', re.IGNORECASE),
53
+ re.compile(r'Exception in thread "[^"]+"', re.IGNORECASE),
54
+ re.compile(r'\b(org\.springframework|org\.hibernate|java\.lang\.NullPointerException)\b', re.IGNORECASE),
55
+ re.compile(r'\b(psycopg2\.(OperationalError|ProgrammingError)|PSQLException)\b', re.IGNORECASE),
56
+ re.compile(r'\b(mysql_error|mysqli_sql_exception|pymysql\.err)\b', re.IGNORECASE),
57
+ re.compile(r'\b(sqlite3\.OperationalError|SQLiteException)\b', re.IGNORECASE),
58
+ re.compile(r'\b(django\.db\.utils|sqlalchemy\.exc)\b', re.IGNORECASE),
59
+ re.compile(r'SQLSTATE\[[0-9A-Z]{5}\]', re.IGNORECASE),
60
+ re.compile(r'syntax error at or near\s+"', re.IGNORECASE),
61
+ re.compile(r'ODBC SQL Server Driver', re.IGNORECASE),
62
+ ]
63
+
64
+ GREEN = "\033[92m"
65
+ RED = "\033[91m"
66
+ YELLOW = "\033[93m"
67
+ CYAN = "\033[96m"
68
+ BOLD = "\033[1m"
69
+ RESET = "\033[0m"
70
+
71
+
72
+ class ApiFuzzer:
73
+ """Autonomous runtime API fuzzer with three-alarm defect Oracle."""
74
+
75
+ def __init__(
76
+ self,
77
+ base_url: str,
78
+ spec_path_or_url: Optional[str] = None,
79
+ headers: Optional[Dict[str, str]] = None,
80
+ timeout_sec: float = 10.0,
81
+ sla_ms: float = 3000.0,
82
+ verify_ssl: bool = False
83
+ ):
84
+ self.base_url = base_url.rstrip("/")
85
+ self.spec_path = spec_path_or_url
86
+ self.custom_headers = headers or {"Content-Type": "application/json", "User-Agent": "AgentQA-Fuzzer/2.0"}
87
+ self.timeout_sec = timeout_sec
88
+ self.sla_ms = sla_ms
89
+ self.verify_ssl = verify_ssl
90
+
91
+ # Prepare SSL context (support self-signed staging certificates)
92
+ self.ssl_context = ssl.create_default_context()
93
+ if not verify_ssl:
94
+ self.ssl_context.check_hostname = False
95
+ self.ssl_context.verify_mode = ssl.CERT_NONE
96
+
97
+ def load_spec(self) -> Dict[str, Any]:
98
+ """Loads OpenAPI or Swagger specification from file or URL."""
99
+ if not self.spec_path:
100
+ # Fallback default health and mock endpoints
101
+ return {
102
+ "paths": {
103
+ "/api/v1/users": {
104
+ "post": {
105
+ "summary": "Create user",
106
+ "requestBody": {
107
+ "content": {
108
+ "application/json": {
109
+ "schema": {
110
+ "type": "object",
111
+ "properties": {
112
+ "username": {"type": "string", "maxLength": 50},
113
+ "age": {"type": "integer", "minimum": 1, "maximum": 120},
114
+ "email": {"type": "string"}
115
+ },
116
+ "required": ["username"]
117
+ }
118
+ }
119
+ }
120
+ }
121
+ }
122
+ }
123
+ }
124
+ }
125
+
126
+ if self.spec_path.startswith(("http://", "https://")):
127
+ req = urllib.request.Request(self.spec_path, headers={"User-Agent": "AgentQA-Fuzzer/2.0"})
128
+ with urllib.request.urlopen(req, context=self.ssl_context, timeout=10.0) as resp:
129
+ raw = resp.read().decode("utf-8")
130
+ return json.loads(raw)
131
+ else:
132
+ if not os.path.exists(self.spec_path):
133
+ raise FileNotFoundError(f"OpenAPI spec not found: {self.spec_path}")
134
+ with open(self.spec_path, "r", encoding="utf-8") as f:
135
+ return json.load(f)
136
+
137
+ def extract_fuzz_targets(self, spec: Dict[str, Any]) -> List[Dict[str, Any]]:
138
+ """Extracts actionable API routes and property schemas from spec."""
139
+ targets = []
140
+ paths = spec.get("paths", {})
141
+ for path, path_item in paths.items():
142
+ for method, details in path_item.items():
143
+ if method.lower() not in ("get", "post", "put", "patch", "delete"):
144
+ continue
145
+
146
+ req_body = details.get("requestBody", {}).get("content", {}).get("application/json", {}).get("schema", {})
147
+ props = req_body.get("properties", {})
148
+ required = req_body.get("required", [])
149
+
150
+ targets.append({
151
+ "path": path,
152
+ "method": method.upper(),
153
+ "summary": details.get("summary", f"{method.upper()} {path}"),
154
+ "parameters": details.get("parameters", []),
155
+ "properties": props,
156
+ "required": required
157
+ })
158
+ return targets
159
+
160
+ def generate_mutations(self, target: Dict[str, Any]) -> List[Dict[str, Any]]:
161
+ """Generates dynamic mutation vectors for query params and request body."""
162
+ mutations = []
163
+ props = target.get("properties", {})
164
+ method = target["method"]
165
+ path = target["path"]
166
+
167
+ # Base valid skeleton
168
+ valid_body: Dict[str, Any] = {}
169
+ for p_name, p_schema in props.items():
170
+ t = p_schema.get("type", "string")
171
+ if t == "string":
172
+ valid_body[p_name] = f"valid_{p_name}"
173
+ elif t in ("integer", "number"):
174
+ valid_body[p_name] = 10
175
+ elif t == "boolean":
176
+ valid_body[p_name] = True
177
+ else:
178
+ valid_body[p_name] = "test"
179
+
180
+ # 1. Baseline Happy-Path
181
+ mutations.append({
182
+ "name": f"{method} {path} - Baseline Happy-Path",
183
+ "type": "BASELINE",
184
+ "path": path,
185
+ "method": method,
186
+ "body": valid_body,
187
+ "headers": self.custom_headers
188
+ })
189
+
190
+ # 2. Field Fuzzing Payloads
191
+ for p_name, p_schema in props.items():
192
+ p_type = p_schema.get("type", "string")
193
+
194
+ # A. Extreme Buffer Stress (10,000 chars)
195
+ mut_body = dict(valid_body)
196
+ mut_body[p_name] = "A" * 10000
197
+ mutations.append({
198
+ "name": f"{method} {path} - Buffer Stress on '{p_name}'",
199
+ "type": "BUFFER_STRESS",
200
+ "path": path,
201
+ "method": method,
202
+ "body": mut_body,
203
+ "headers": self.custom_headers
204
+ })
205
+
206
+ # B. SQLi Polyglot Probe
207
+ mut_body = dict(valid_body)
208
+ mut_body[p_name] = "' OR '1'='1' -- /* injected */"
209
+ mutations.append({
210
+ "name": f"{method} {path} - SQLi Polyglot Probe on '{p_name}'",
211
+ "type": "SQLI_INJECTION",
212
+ "path": path,
213
+ "method": method,
214
+ "body": mut_body,
215
+ "headers": self.custom_headers
216
+ })
217
+
218
+ # C. Unicode BiDi / RTL Trojan Source
219
+ mut_body = dict(valid_body)
220
+ mut_body[p_name] = "\u202E\u0041\u0042\u0043_AUTO_TX"
221
+ mutations.append({
222
+ "name": f"{method} {path} - Unicode BiDi RTL Override on '{p_name}'",
223
+ "type": "BIDI_UNICODE",
224
+ "path": path,
225
+ "method": method,
226
+ "body": mut_body,
227
+ "headers": self.custom_headers
228
+ })
229
+
230
+ # D. Null Byte Injection
231
+ mut_body = dict(valid_body)
232
+ mut_body[p_name] = f"data\x00{p_name}.json"
233
+ mutations.append({
234
+ "name": f"{method} {path} - Null Byte Injection on '{p_name}'",
235
+ "type": "NULL_BYTE",
236
+ "path": path,
237
+ "method": method,
238
+ "body": mut_body,
239
+ "headers": self.custom_headers
240
+ })
241
+
242
+ # E. Numeric Boundaries (if integer)
243
+ if p_type in ("integer", "number"):
244
+ mut_body = dict(valid_body)
245
+ mut_body[p_name] = 2147483648 # 32-bit overflow
246
+ mutations.append({
247
+ "name": f"{method} {path} - 32-bit Integer Overflow on '{p_name}'",
248
+ "type": "NUMERIC_OVERFLOW",
249
+ "path": path,
250
+ "method": method,
251
+ "body": mut_body,
252
+ "headers": self.custom_headers
253
+ })
254
+
255
+ # F. Type Inversion (String where integer expected or vice versa)
256
+ mut_body = dict(valid_body)
257
+ mut_body[p_name] = 99999 if p_type == "string" else "NOT_A_NUMBER"
258
+ mutations.append({
259
+ "name": f"{method} {path} - Type Mutation on '{p_name}'",
260
+ "type": "TYPE_MUTATION",
261
+ "path": path,
262
+ "method": method,
263
+ "body": mut_body,
264
+ "headers": self.custom_headers
265
+ })
266
+
267
+ # 3. Auth Bypass Vector (Remove Authorization Header)
268
+ mut_headers = {k: v for k, v in self.custom_headers.items() if k.lower() != "authorization"}
269
+ mutations.append({
270
+ "name": f"{method} {path} - Auth Token Omission Probe",
271
+ "type": "AUTH_OMISSION",
272
+ "path": path,
273
+ "method": method,
274
+ "body": valid_body,
275
+ "headers": mut_headers
276
+ })
277
+
278
+ return mutations
279
+
280
+ def execute_request(self, mutation: Dict[str, Any], dry_run: bool = False) -> Dict[str, Any]:
281
+ """Dispatches HTTP mutation and evaluates the three-alarm Oracle."""
282
+ url = f"{self.base_url}{mutation['path']}"
283
+ method = mutation["method"]
284
+ body_data = None
285
+ if method in ("POST", "PUT", "PATCH") and mutation.get("body") is not None:
286
+ body_data = json.dumps(mutation["body"]).encode("utf-8")
287
+
288
+ if dry_run:
289
+ # Deterministic offline mock for CI & local checks
290
+ return {
291
+ "name": mutation["name"],
292
+ "type": mutation["type"],
293
+ "url": url,
294
+ "method": method,
295
+ "status_code": 400 if mutation["type"] != "BASELINE" else 200,
296
+ "duration_ms": 42.5,
297
+ "verdict": "PASS",
298
+ "alarms": [],
299
+ "response_preview": '{"status": "handled_by_dry_run"}'
300
+ }
301
+
302
+ req = urllib.request.Request(
303
+ url=url,
304
+ data=body_data,
305
+ headers=mutation["headers"],
306
+ method=method
307
+ )
308
+
309
+ start_time = time.time()
310
+ status_code = 0
311
+ response_body = ""
312
+ duration_ms = 0.0
313
+
314
+ try:
315
+ with urllib.request.urlopen(req, context=self.ssl_context, timeout=self.timeout_sec) as resp:
316
+ status_code = resp.getcode()
317
+ response_body = resp.read().decode("utf-8", errors="replace")
318
+ except urllib.error.HTTPError as he:
319
+ status_code = he.code
320
+ try:
321
+ response_body = he.read().decode("utf-8", errors="replace")
322
+ except Exception:
323
+ response_body = str(he)
324
+ except urllib.error.URLError as ue:
325
+ status_code = -1
326
+ response_body = str(ue.reason)
327
+ except Exception as e:
328
+ status_code = -2
329
+ response_body = str(e)
330
+
331
+ duration_ms = round((time.time() - start_time) * 1000, 2)
332
+
333
+ # ----------------------------------------------------
334
+ # THREE-ALARM DEFECT ORACLE EVALUATION
335
+ # ----------------------------------------------------
336
+ alarms = []
337
+
338
+ # Alarm 1: HTTP 500 / 502 / 503 Internal Server Error
339
+ if status_code in (500, 502, 503):
340
+ alarms.append(f"HTTP_{status_code}_SERVER_CRASH: Unhandled backend exception / server panic")
341
+
342
+ # Alarm 2: Database / Framework Stacktrace Leak
343
+ for pattern in STACKTRACE_LEAK_PATTERNS:
344
+ if pattern.search(response_body):
345
+ matched = pattern.pattern
346
+ alarms.append(f"STACKTRACE_LEAK: Response leaks backend internals matching '{matched}'")
347
+ break
348
+
349
+ # Alarm 3: SLA Latency Breach (> 3000ms)
350
+ if duration_ms > self.sla_ms:
351
+ alarms.append(f"SLA_TIMEOUT: Response took {duration_ms}ms (threshold: {self.sla_ms}ms), possible ReDoS or DB lock")
352
+
353
+ # Alarm 4: Auth Bypass (OMISSION succeeded with 200/201)
354
+ if mutation["type"] == "AUTH_OMISSION" and status_code in (200, 201):
355
+ alarms.append("AUTH_BYPASS_VULNERABILITY: Endpoint returned 200 OK without Authorization header")
356
+
357
+ verdict = "FAIL" if alarms else "PASS"
358
+
359
+ return {
360
+ "name": mutation["name"],
361
+ "type": mutation["type"],
362
+ "url": url,
363
+ "method": method,
364
+ "status_code": status_code,
365
+ "duration_ms": duration_ms,
366
+ "verdict": verdict,
367
+ "alarms": alarms,
368
+ "response_preview": response_body[:300]
369
+ }
370
+
371
+ def run_fuzz_suite(self, dry_run: bool = False) -> Dict[str, Any]:
372
+ """Runs the complete fuzzing suite against all extracted targets."""
373
+ spec = self.load_spec()
374
+ targets = self.extract_fuzz_targets(spec)
375
+
376
+ all_mutations = []
377
+ for t in targets:
378
+ all_mutations.extend(self.generate_mutations(t))
379
+
380
+ results = []
381
+ total_failures = 0
382
+
383
+ print(f"\n{BOLD}{CYAN}======================================================================{RESET}")
384
+ print(f"{BOLD}{CYAN} ENTERPRISE AUTONOMOUS API FUZZING & CHAOS BREAKER {RESET}")
385
+ print(f"{BOLD}{CYAN}======================================================================{RESET}")
386
+ print(f" Target Base URL : {self.base_url}")
387
+ print(f" Spec Source : {self.spec_path or 'Default Archetype'}")
388
+ print(f" Total Endpoints : {len(targets)}")
389
+ print(f" Total Mutations : {len(all_mutations)}")
390
+ print(f" SLA Threshold : {self.sla_ms} ms")
391
+ print(f" Mode : {'DRY-RUN (Air-Gapped / Deterministic)' if dry_run else 'LIVE STAGING ATTACK'}\n")
392
+
393
+ for idx, m in enumerate(all_mutations, start=1):
394
+ res = self.execute_request(m, dry_run=dry_run)
395
+ results.append(res)
396
+
397
+ status_str = f"{GREEN}PASS{RESET}" if res["verdict"] == "PASS" else f"{RED}ALARM{RESET}"
398
+ sys.stdout.write(f" [{idx:03d}/{len(all_mutations):03d}] {status_str} | HTTP {res['status_code']} ({res['duration_ms']}ms) | {m['name']}\n")
399
+ if res["alarms"]:
400
+ total_failures += 1
401
+ for alarm in res["alarms"]:
402
+ print(f" 👉 {RED}{BOLD}[DEFECT]{RESET} {alarm}")
403
+
404
+ scorecard = {
405
+ "base_url": self.base_url,
406
+ "timestamp": time.strftime("%Y-%m-%d %H:%M:%S"),
407
+ "total_mutations": len(all_mutations),
408
+ "passed": len(all_mutations) - total_failures,
409
+ "failed": total_failures,
410
+ "sla_ms": self.sla_ms,
411
+ "dry_run": dry_run,
412
+ "results": results
413
+ }
414
+
415
+ # Print Summary Scorecard
416
+ print(f"\n{BOLD}======================================================================{RESET}")
417
+ print(f"{BOLD} API FUZZING ORACLE SCORECARD {RESET}")
418
+ print(f"{BOLD}======================================================================{RESET}")
419
+ print(f" Mutations Executed: {scorecard['total_mutations']}")
420
+ print(f" Clean Handled : {GREEN}{scorecard['passed']}{RESET}")
421
+ print(f" Defects Detected : {RED if total_failures > 0 else GREEN}{scorecard['failed']}{RESET}")
422
+ if total_failures == 0:
423
+ print(f" Verdict : {GREEN}[PASS] API resilient against input fuzzing & chaos.{RESET}")
424
+ else:
425
+ print(f" Verdict : {RED}[FAIL] {total_failures} defect alarms detected. Backend fixes required!{RESET}")
426
+ print(f"======================================================================\n")
427
+
428
+ # Save Report
429
+ os.makedirs(REPORTS_DIR, exist_ok=True)
430
+ report_path = os.path.join(REPORTS_DIR, "api_fuzz_report.json")
431
+ with open(report_path, "w", encoding="utf-8") as f:
432
+ json.dump(scorecard, f, indent=2)
433
+ print(f"Full telemetry report saved to: {report_path}")
434
+
435
+ return scorecard
436
+
437
+
438
+ def main():
439
+ parser = argparse.ArgumentParser(description="Enterprise Autonomous API Fuzzer & Chaos Breaker")
440
+ parser.add_argument("--base-url", default="http://localhost:8080", help="Base URL of target API server")
441
+ parser.add_argument("--spec", default=None, help="Path or URL to OpenAPI/Swagger JSON specification")
442
+ parser.add_argument("--token", default=None, help="Bearer token for authenticated endpoints")
443
+ parser.add_argument("--sla-ms", type=float, default=3000.0, help="SLA latency ceiling in ms (default: 3000ms)")
444
+ parser.add_argument("--dry-run", action="store_true", help="Simulate mutation generation without live requests")
445
+ parser.add_argument("--json", action="store_true", help="Output summary in JSON format")
446
+
447
+ args = parser.parse_args()
448
+
449
+ headers = {"Content-Type": "application/json", "User-Agent": "AgentQA-Fuzzer/2.0"}
450
+ if args.token:
451
+ headers["Authorization"] = f"Bearer {args.token}"
452
+
453
+ fuzzer = ApiFuzzer(
454
+ base_url=args.base_url,
455
+ spec_path_or_url=args.spec,
456
+ headers=headers,
457
+ sla_ms=args.sla_ms
458
+ )
459
+
460
+ scorecard = fuzzer.run_fuzz_suite(dry_run=args.dry_run)
461
+ if args.json:
462
+ print(json.dumps(scorecard, indent=2))
463
+
464
+ return 1 if scorecard["failed"] > 0 else 0
465
+
466
+
467
+ if __name__ == "__main__":
468
+ sys.exit(main())
@@ -13,7 +13,7 @@
13
13
  (function () {
14
14
  'use strict';
15
15
 
16
- if (window.__qa && window.__qa.__version === '2.0.1') {
16
+ if (window.__qa && window.__qa.__version === '2.1.0') {
17
17
  return window.__qa;
18
18
  }
19
19
 
@@ -47,7 +47,7 @@
47
47
  }
48
48
 
49
49
  const qa = {
50
- __version: '2.0.1',
50
+ __version: '2.1.0',
51
51
 
52
52
  /**
53
53
  * 1. Smart Action Batching