REST API Reference
REST API Reference
The following files were used as context for generating this wiki page:
- .github/workflows/ghcr-publish.yml
- README.md
- RELEASE_3.0.0.md
- RELEASE_SUMMARY.md
- docs/V3_COMPLETE_GUIDE.md
Purpose and Scope
This document provides complete technical reference for the WSHawk Web Management Dashboard REST API. The API enables programmatic control of the scanner, including initiating scans, retrieving results, and managing scan history. It is designed for integration into CI/CD pipelines, security orchestration platforms, and custom automation tools.
For information about launching the dashboard and configuring the web server, see Dashboard Overview and Launch. For authentication setup and security considerations, see Authentication and API Keys. For understanding the persistence layer and scan storage, see Scan History and Persistence.
API Architecture Overview
The REST API is built on Flask and backed by SQLite with WAL (Write-Ahead Logging) mode for crash-resistant persistence. All scan operations are asynchronous, with the API returning scan IDs immediately and allowing clients to poll for status updates.
High-Level API Architecture
graph TB
subgraph "Client Layer"
CLI["CLI Tools<br/>curl, httpie"]
Script["Python Scripts<br/>requests library"]
CICD["CI/CD Pipelines<br/>GitHub Actions"]
end
subgraph "Flask Application"
Routes["API Routes<br/>/api/scans<br/>/api/stats<br/>/login"]
Auth["Authentication Middleware<br/>SHA-256 verification<br/>API Key validation"]
Scanner["WSHawkV2 Integration<br/>Async scan orchestration"]
end
subgraph "Persistence Layer"
DB[("SQLite Database<br/>~/.wshawk/scans.db<br/>WAL Mode")]
Reports["Report Files<br/>HTML/JSON/CSV/SARIF<br/>reports/"]
end
CLI --> Auth
Script --> Auth
CICD --> Auth
Auth --> Routes
Routes --> Scanner
Routes --> DB
Scanner --> DB
Scanner --> Reports
DB --> Routes
Sources: README.md:106-136, RELEASE_SUMMARY.md:15-19, docs/V3_COMPLETE_GUIDE.md:289-310
Authentication
The API supports two authentication methods:
Password Authentication
Used for interactive sessions via the web dashboard. Credentials are validated against a SHA-256 hashed password stored in the environment variable WSHAWK_WEB_PASSWORD.
POST /login
Content-Type: application/json
{
"password": "your-strong-password"
}
Returns a session cookie valid for the browser session.
API Key Authentication
Used for programmatic access. Include the API key in the X-API-Key header:
GET /api/scans
X-API-Key: your-api-key-here
Set the API key via the WSHAWK_API_KEY environment variable when launching the dashboard.
Authentication Flow Diagram
sequenceDiagram
participant Client
participant AuthMiddleware
participant PasswordValidator
participant APIKeyValidator
participant Routes
Client->>AuthMiddleware: "Request with credentials"
alt "Session Cookie Present"
AuthMiddleware->>PasswordValidator: "Validate session"
PasswordValidator-->>AuthMiddleware: "Valid/Invalid"
else "X-API-Key Header Present"
AuthMiddleware->>APIKeyValidator: "Validate API key"
APIKeyValidator-->>AuthMiddleware: "Valid/Invalid"
else "No Authentication"
AuthMiddleware-->>Client: "401 Unauthorized"
end
alt "Authentication Success"
AuthMiddleware->>Routes: "Forward request"
Routes-->>Client: "200 OK + Response"
else "Authentication Failure"
AuthMiddleware-->>Client: "401 Unauthorized"
end
Sources: README.md:121-127, README.md:135, RELEASE_SUMMARY.md:18, docs/V3_COMPLETE_GUIDE.md:299-302, docs/V3_COMPLETE_GUIDE.md:313-315
Base URL and Headers
Base URL
When running locally with default settings:
http://localhost:5000
When deployed with custom host/port:
http://<host>:<port>
Required Headers
| Header | Value | Required For |
|--------|-------|--------------|
| Content-Type | application/json | POST requests with body |
| X-API-Key | Your API key | API key authentication |
| Accept | application/json | JSON responses (optional) |
Sources: README.md:118
Endpoint Reference
Core Scan Management Endpoints
graph LR
subgraph "Scan Lifecycle Endpoints"
POST_scans["POST /api/scans<br/>Initiate new scan"]
GET_scans["GET /api/scans<br/>List all scans"]
GET_scan_id["GET /api/scans/{id}<br/>Get scan details"]
GET_report["GET /api/scans/{id}/report<br/>Download report"]
DELETE_scan["DELETE /api/scans/{id}<br/>Delete scan"]
end
subgraph "Monitoring Endpoints"
GET_stats["GET /api/stats<br/>System statistics"]
GET_status["GET /api/scans/{id}/status<br/>Real-time progress"]
end
POST_scans --> GET_status
GET_status --> GET_scan_id
GET_scan_id --> GET_report
Sources: docs/V3_COMPLETE_GUIDE.md:317-330
POST /api/scans
Initiates a new WebSocket security scan. Returns immediately with a scan ID, allowing the client to poll for status updates.
Request Body
{
"target": "ws://target.com/socket",
"options": {
"smart_payloads": true,
"playwright": false,
"use_oast": true,
"rate_limit": 10,
"full_scan": false,
"session_tests": true
}
}
Request Parameters
| Field | Type | Required | Description |
|-------|------|----------|-------------|
| target | string | Yes | WebSocket URL (ws:// or wss://) |
| options.smart_payloads | boolean | No | Enable Smart Payload Evolution engine (default: false) |
| options.playwright | boolean | No | Enable Playwright browser verification (default: false) |
| options.use_oast | boolean | No | Enable OAST for blind vulnerabilities (default: true) |
| options.rate_limit | integer | No | Max requests per second (default: 10) |
| options.full_scan | boolean | No | Enable all advanced features (default: false) |
| options.session_tests | boolean | No | Enable session hijacking tests (default: true) |
Response (201 Created)
{
"scan_id": "550e8400-e29b-41d4-a716-446655440000",
"status": "queued",
"created_at": "2024-01-15T10:30:00Z",
"target": "ws://target.com/socket"
}
Error Responses
| Status Code | Reason | |-------------|--------| | 400 Bad Request | Invalid target URL or malformed options | | 401 Unauthorized | Missing or invalid authentication | | 429 Too Many Requests | Rate limit exceeded |
Sources: docs/V3_COMPLETE_GUIDE.md:319-328
GET /api/scans
Retrieves a list of all historical scans stored in the database. Supports pagination and filtering.
Query Parameters
| Parameter | Type | Description | Default |
|-----------|------|-------------|---------|
| limit | integer | Maximum number of results | 50 |
| offset | integer | Number of results to skip | 0 |
| status | string | Filter by status (queued, running, completed, failed) | all |
| target | string | Filter by target URL (partial match) | all |
| sort | string | Sort field (created_at, target, status) | created_at |
| order | string | Sort order (asc, desc) | desc |
Response (200 OK)
{
"scans": [
{
"scan_id": "550e8400-e29b-41d4-a716-446655440000",
"target": "ws://target.com/socket",
"status": "completed",
"created_at": "2024-01-15T10:30:00Z",
"completed_at": "2024-01-15T10:35:23Z",
"vulnerabilities_found": 12,
"severity": {
"critical": 2,
"high": 5,
"medium": 3,
"low": 2
}
}
],
"total": 145,
"limit": 50,
"offset": 0
}
Sources: docs/V3_COMPLETE_GUIDE.md:318
GET /api/scans/{id}
Retrieves detailed information about a specific scan, including all detected vulnerabilities.
Path Parameters
| Parameter | Type | Description |
|-----------|------|-------------|
| id | string (UUID) | Scan identifier |
Response (200 OK)
{
"scan_id": "550e8400-e29b-41d4-a716-446655440000",
"target": "ws://target.com/socket",
"status": "completed",
"created_at": "2024-01-15T10:30:00Z",
"completed_at": "2024-01-15T10:35:23Z",
"duration_seconds": 323,
"options": {
"smart_payloads": true,
"playwright": false,
"rate_limit": 10
},
"statistics": {
"payloads_sent": 22847,
"messages_received": 22891,
"average_response_time_ms": 45.3,
"vulnerabilities_found": 12
},
"vulnerabilities": [
{
"id": "vuln-001",
"type": "SQL_INJECTION",
"severity": "critical",
"cvss_score": 9.8,
"cvss_vector": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H",
"confidence": "HIGH",
"payload": "1' OR '1'='1",
"evidence": {
"request": "{\"id\": \"1' OR '1'='1\"}",
"response": "{\"users\": [...]}",
"timestamp": "2024-01-15T10:32:15Z"
},
"remediation": "Use parameterized queries..."
}
],
"server_fingerprint": {
"framework": "Node.js/Socket.io",
"version": "4.5.1",
"waf_detected": false
}
}
Error Responses
| Status Code | Reason | |-------------|--------| | 404 Not Found | Scan ID does not exist | | 401 Unauthorized | Missing or invalid authentication |
Sources: docs/V3_COMPLETE_GUIDE.md:318-330
GET /api/scans/{id}/status
Retrieves real-time status and progress information for an ongoing scan. Designed for polling during scan execution.
Response (200 OK)
{
"scan_id": "550e8400-e29b-41d4-a716-446655440000",
"status": "running",
"progress": {
"current_phase": "mutation_engine",
"percent_complete": 67,
"payloads_sent": 15234,
"estimated_time_remaining_seconds": 180
},
"vulnerabilities_found": 8,
"current_severity_counts": {
"critical": 1,
"high": 3,
"medium": 2,
"low": 2
}
}
Status Values
| Status | Description |
|--------|-------------|
| queued | Scan is waiting to start |
| initializing | Establishing connection and fingerprinting |
| running | Active payload injection phase |
| mutation_engine | Smart Payload Evolution in progress |
| verification | Playwright/OAST verification phase |
| reporting | Generating final reports |
| completed | Scan finished successfully |
| failed | Scan encountered an error |
Sources: README.md:133
GET /api/scans/{id}/report
Downloads the complete scan report in the requested format.
Query Parameters
| Parameter | Type | Description | Default |
|-----------|------|-------------|---------|
| format | string | Report format (html, json, csv, sarif) | json |
Response (200 OK)
For JSON format:
{
"scan_metadata": { /* ... */ },
"vulnerabilities": [ /* ... */ ],
"traffic_logs": [ /* ... */ ],
"recommendations": [ /* ... */ ]
}
For HTML format: Returns rendered HTML report.
For SARIF format: Returns SARIF-compliant JSON for GitHub Security integration.
Headers
| Format | Content-Type |
|--------|--------------|
| json | application/json |
| html | text/html |
| csv | text/csv |
| sarif | application/sarif+json |
Sources: docs/V3_COMPLETE_GUIDE.md:329, README.md:175-184
DELETE /api/scans/{id}
Deletes a scan and all associated data from the database and filesystem.
Response (204 No Content)
No response body on success.
Error Responses
| Status Code | Reason | |-------------|--------| | 404 Not Found | Scan ID does not exist | | 401 Unauthorized | Missing or invalid authentication | | 409 Conflict | Cannot delete running scan |
GET /api/stats
Retrieves system-wide statistics and metrics.
Response (200 OK)
{
"total_scans": 1453,
"scans_last_24h": 23,
"total_vulnerabilities": 8912,
"average_scan_duration_seconds": 287,
"database_size_mb": 245.8,
"vulnerabilities_by_type": {
"SQL_INJECTION": 1234,
"XSS": 2345,
"COMMAND_INJECTION": 567,
"XXE": 123,
"SSRF": 89
},
"severity_distribution": {
"critical": 1203,
"high": 3456,
"medium": 2789,
"low": 1464
}
}
Sources: docs/V3_COMPLETE_GUIDE.md:317
Request/Response Flow
Complete Scan Workflow
sequenceDiagram
participant Client
participant API["Flask API<br/>/api/scans"]
participant Scanner["WSHawkV2<br/>scanner_v2.py"]
participant DB["SQLite<br/>scans.db"]
participant Reports["Report Generator"]
Client->>API: "POST /api/scans"
API->>DB: "INSERT scan record<br/>status='queued'"
DB-->>API: "scan_id"
API-->>Client: "201 Created {scan_id}"
Note over Scanner: "Async execution starts"
API->>Scanner: "Trigger scan with options"
Scanner->>DB: "UPDATE status='initializing'"
Scanner->>Scanner: "Connection + Fingerprinting"
Scanner->>DB: "UPDATE status='running'"
loop "Payload Injection"
Scanner->>Scanner: "Send payloads"
Scanner->>DB: "INSERT vulnerabilities"
end
Scanner->>DB: "UPDATE status='verification'"
Scanner->>Scanner: "Playwright/OAST verification"
Scanner->>Reports: "Generate reports"
Reports->>DB: "UPDATE report_path"
Scanner->>DB: "UPDATE status='completed'"
Client->>API: "GET /api/scans/{id}/status"
API->>DB: "SELECT scan"
DB-->>API: "scan data"
API-->>Client: "200 OK {status='completed'}"
Client->>API: "GET /api/scans/{id}/report"
API->>Reports: "Read report file"
Reports-->>API: "report content"
API-->>Client: "200 OK + report"
Sources: RELEASE_SUMMARY.md:15-19, docs/V3_COMPLETE_GUIDE.md:113-119
Error Handling
Standard Error Response Format
All error responses follow a consistent JSON structure:
{
"error": {
"code": "INVALID_TARGET",
"message": "Target URL must start with ws:// or wss://",
"details": {
"provided": "http://example.com",
"expected_format": "ws://example.com or wss://example.com"
}
}
}
HTTP Status Codes
| Status Code | Meaning | Common Causes | |-------------|---------|---------------| | 200 OK | Successful request | - | | 201 Created | Resource created successfully | Scan initiated | | 204 No Content | Successful deletion | Scan deleted | | 400 Bad Request | Invalid request data | Malformed JSON, invalid URL | | 401 Unauthorized | Authentication failed | Missing/invalid credentials | | 404 Not Found | Resource not found | Invalid scan ID | | 409 Conflict | Resource state conflict | Deleting running scan | | 422 Unprocessable Entity | Validation failed | Invalid option combination | | 429 Too Many Requests | Rate limit exceeded | Too many concurrent scans | | 500 Internal Server Error | Server error | Database corruption, scanner crash | | 503 Service Unavailable | Service temporarily unavailable | Database locked, maintenance mode |
Sources: RELEASE_SUMMARY.md:12-13
API Client Examples
Python Using requests Library
import requests
import time
# Configuration
BASE_URL = "http://localhost:5000"
API_KEY = "your-api-key-here"
HEADERS = {
"X-API-Key": API_KEY,
"Content-Type": "application/json"
}
# Initiate scan
response = requests.post(
f"{BASE_URL}/api/scans",
headers=HEADERS,
json={
"target": "ws://target.com/socket",
"options": {
"smart_payloads": True,
"playwright": False,
"rate_limit": 10
}
}
)
scan_id = response.json()["scan_id"]
print(f"Scan initiated: {scan_id}")
# Poll for completion
while True:
status_response = requests.get(
f"{BASE_URL}/api/scans/{scan_id}/status",
headers=HEADERS
)
status_data = status_response.json()
if status_data["status"] == "completed":
print(f"Scan completed: {status_data['vulnerabilities_found']} vulnerabilities")
break
elif status_data["status"] == "failed":
print("Scan failed")
break
print(f"Progress: {status_data['progress']['percent_complete']}%")
time.sleep(5)
# Download report
report_response = requests.get(
f"{BASE_URL}/api/scans/{scan_id}/report",
headers=HEADERS,
params={"format": "json"}
)
report = report_response.json()
print(f"Critical vulnerabilities: {report['statistics']['severity']['critical']}")
Sources: README.md:252-263
curl Command Line Examples
Initiate a scan:
curl -X POST http://localhost:5000/api/scans \
-H "X-API-Key: your-api-key" \
-H "Content-Type: application/json" \
-d '{
"target": "ws://target.com/socket",
"options": {
"smart_payloads": true,
"rate_limit": 10
}
}'
Check scan status:
curl -X GET http://localhost:5000/api/scans/{scan_id}/status \
-H "X-API-Key: your-api-key"
List all scans:
curl -X GET "http://localhost:5000/api/scans?limit=10&status=completed" \
-H "X-API-Key: your-api-key"
Download JSON report:
curl -X GET "http://localhost:5000/api/scans/{scan_id}/report?format=json" \
-H "X-API-Key: your-api-key" \
-o report.json
Download HTML report:
curl -X GET "http://localhost:5000/api/scans/{scan_id}/report?format=html" \
-H "X-API-Key: your-api-key" \
-o report.html
Get system statistics:
curl -X GET http://localhost:5000/api/stats \
-H "X-API-Key: your-api-key"
CI/CD Integration Patterns
GitHub Actions Workflow
name: WebSocket Security Scan
on:
schedule:
- cron: '0 2 * * *' # Daily at 2 AM
workflow_dispatch:
jobs:
wshawk_scan:
runs-on: ubuntu-latest
steps:
- name: Start WSHawk Dashboard
run: |
docker run -d \
--name wshawk \
-p 5000:5000 \
-e WSHAWK_API_KEY=${{ secrets.WSHAWK_API_KEY }} \
rothackers/wshawk wshawk --web --host 0.0.0.0
# Wait for service to be ready
sleep 10
- name: Trigger Scan
id: scan
run: |
RESPONSE=$(curl -s -X POST http://localhost:5000/api/scans \
-H "X-API-Key: ${{ secrets.WSHAWK_API_KEY }}" \
-H "Content-Type: application/json" \
-d '{
"target": "wss://api.example.com/socket",
"options": {"smart_payloads": true}
}')
SCAN_ID=$(echo $RESPONSE | jq -r '.scan_id')
echo "scan_id=$SCAN_ID" >> $GITHUB_OUTPUT
- name: Wait for Completion
run: |
while true; do
STATUS=$(curl -s http://localhost:5000/api/scans/${{ steps.scan.outputs.scan_id }}/status \
-H "X-API-Key: ${{ secrets.WSHAWK_API_KEY }}" \
| jq -r '.status')
if [ "$STATUS" = "completed" ]; then
break
elif [ "$STATUS" = "failed" ]; then
echo "Scan failed"
exit 1
fi
sleep 30
done
- name: Download SARIF Report
run: |
curl -X GET "http://localhost:5000/api/scans/${{ steps.scan.outputs.scan_id }}/report?format=sarif" \
-H "X-API-Key: ${{ secrets.WSHAWK_API_KEY }}" \
-o wshawk.sarif
- name: Upload to GitHub Security
uses: github/codeql-action/upload-sarif@v2
with:
sarif_file: wshawk.sarif
Sources: docs/V3_COMPLETE_GUIDE.md:362-376, README.md:194
Rate Limiting and Throttling
The API implements per-client rate limiting to prevent resource exhaustion:
| Endpoint | Rate Limit | Window |
|----------|-----------|--------|
| POST /api/scans | 10 requests | per hour |
| GET /api/scans | 100 requests | per minute |
| GET /api/scans/{id} | 100 requests | per minute |
| GET /api/scans/{id}/status | 120 requests | per minute |
| GET /api/stats | 60 requests | per minute |
When rate limited, the API returns:
HTTP/1.1 429 Too Many Requests
Retry-After: 3600
{
"error": {
"code": "RATE_LIMIT_EXCEEDED",
"message": "Too many scan requests",
"retry_after_seconds": 3600
}
}
Sources: RELEASE_SUMMARY.md:12
Database Schema Reference
The API interacts with the SQLite database stored at ~/.wshawk/scans.db. Understanding the schema is helpful for advanced use cases.
Key Tables
erDiagram
scans ||--o{ vulnerabilities : contains
scans ||--o{ traffic_logs : records
scans {
string scan_id PK
string target
string status
datetime created_at
datetime completed_at
integer duration_seconds
json options
json statistics
}
vulnerabilities {
string id PK
string scan_id FK
string type
string severity
float cvss_score
string cvss_vector
string confidence
string payload
json evidence
text remediation
}
traffic_logs {
integer id PK
string scan_id FK
datetime timestamp
string direction
text message
integer size_bytes
}
Sources: RELEASE_SUMMARY.md:17, docs/V3_COMPLETE_GUIDE.md:293-297
Advanced Usage Patterns
Concurrent Scan Management
The API supports running multiple scans concurrently. Clients can initiate multiple scans and track them independently:
import asyncio
import aiohttp
async def scan_target(session, target):
async with session.post(
f"{BASE_URL}/api/scans",
json={"target": target, "options": {"rate_limit": 5}}
) as response:
scan_data = await response.json()
return scan_data["scan_id"]
async def main():
targets = [
"ws://app1.example.com/socket",
"ws://app2.example.com/socket",
"ws://app3.example.com/socket"
]
async with aiohttp.ClientSession(headers={"X-API-Key": API_KEY}) as session:
# Initiate all scans
scan_ids = await asyncio.gather(*[
scan_target(session, target) for target in targets
])
print(f"Initiated {len(scan_ids)} scans")
# Monitor all scans
# ... (polling logic)
asyncio.run(main())
Filtering and Analytics
Use the /api/scans endpoint with query parameters for analytics:
# Get all critical findings from last week
curl -X GET "http://localhost:5000/api/scans?status=completed&sort=created_at&order=desc" \
-H "X-API-Key: your-api-key" \
| jq '[.scans[] | select(.severity.critical > 0)] | length'
Sources: docs/V3_COMPLETE_GUIDE.md:317-330
Security Considerations
API Key Management
- Never commit API keys to version control
- Store API keys in secure vaults (HashiCorp Vault, AWS Secrets Manager)
- Rotate API keys regularly
- Use different keys for different environments (dev, staging, prod)
Network Security
When deploying in production:
- Use TLS: Place the Flask app behind nginx/Apache with HTTPS
- Firewall rules: Restrict API access to known IP ranges
- VPN/Bastion: Require VPN connection for API access
- Authentication: Always enable password or API key authentication
Example nginx configuration:
server {
listen 443 ssl;
server_name wshawk.example.com;
ssl_certificate /path/to/cert.pem;
ssl_certificate_key /path/to/key.pem;
location / {
proxy_pass http://localhost:5000;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}
Sources: README.md:121-127, docs/V3_COMPLETE_GUIDE.md:299-302
Sources: README.md:106-136, RELEASE_SUMMARY.md:15-19, RELEASE_3.0.0.md:15-19, docs/V3_COMPLETE_GUIDE.md:49-62, docs/V3_COMPLETE_GUIDE.md:289-331, .github/workflows/ghcr-publish.yml:1-50