Complete REST API reference for CiberWebScan (Beta).
ciberwebscan api
# Server runs on http://localhost:8000
# API docs: http://localhost:8000/docs
# ReDoc docs: http://localhost:8000/redocThe API requires an API key via the X-API-Key header:
curl -X POST "http://localhost:8000/api/analyze" \
-H "X-API-Key: your-api-key-here" \
-H "Content-Type: application/json" \
-d '{"url": "https://example.com"}'All responses follow a consistent structure:
{
"success": true,
"data": { ... },
"error": null,
"timestamp": "2026-01-01T00:00:00Z",
"download_token": "token-uuid",
"download_url": "/api/download/token-uuid"
}Basic health check endpoint (no authentication required).
Response:
{
"status": "healthy",
"version": "2.4.0",
"message": "CiberWebScan API is running",
"timestamp": "2026-01-01T00:00:00Z"
}Readiness check for container orchestration (no authentication required).
Response:
{
"status": "ready",
"version": "2.4.0",
"message": "CiberWebScan API is ready to accept requests",
"timestamp": "2026-01-01T00:00:00Z"
}Perform comprehensive security analysis on a URL.
Includes:
- SSL/TLS certificate analysis
- Technology fingerprinting
- Security headers evaluation
- CVE lookup and vulnerability intelligence
Request:
{
"url": "https://example.com",
"ssl": true,
"fingerprint": true,
"analyze_headers": true,
"cve": true,
"ssl_verify": true,
"timeout": 30.0,
"ssl_timeout": 10.0,
"deep_scan": false,
"cve_sources": ["nvd"],
"cve_limit": 100,
"cve_severity": null,
"headers": {},
"cookies": {}
}Parameters:
| Parameter | Type | Default | Description |
|---|---|---|---|
url |
string | required | Target URL |
ssl |
boolean | true | Perform SSL/TLS analysis |
fingerprint |
boolean | true | Detect technologies |
analyze_headers |
boolean | true | Analyze security headers |
cve |
boolean | true | Lookup CVEs |
ssl_verify |
boolean | true | Verify SSL certificates |
timeout |
float | 30.0 | Request timeout (1.0-300.0) |
ssl_timeout |
float | 10.0 | SSL analysis timeout (1.0-120.0) |
deep_scan |
boolean | false | Deeper fingerprinting |
cve_sources |
array | [] | CVE sources: nvd, vulners, circl |
cve_limit |
integer | 100 | Max CVEs to retrieve (1-1000) |
cve_severity |
string | null | Filter by severity: critical, high, medium, low, info |
headers |
object | {} | Custom HTTP headers |
cookies |
object | {} | Cookies |
proxy |
string | null | HTTP proxy URL |
user_agent |
string | null | Custom User-Agent |
check_robots |
boolean | false | Respect robots.txt |
enrich_exploits |
boolean | false | Enrich CVEs with exploit info |
export |
string | null | Export file path |
export_format |
string | json | json, jsonl, csv, or html |
Response:
{
"success": true,
"data": {
"url": "https://example.com",
"timestamp": "2026-01-01T00:00:00Z",
"ssl_analysis": {
"protocol_version": "TLSv1.3",
"certificate": {
"subject": "CN=example.com",
"issuer": "...",
"valid_from": "2026-01-01T00:00:00Z",
"valid_until": "2026-01-01T00:00:00Z"
},
"ciphers": [...],
"vulnerabilities": []
},
"fingerprint": {
"technologies": [
{
"name": "nginx",
"version": "1.24.0",
"category": "Web Servers"
}
]
},
"headers_analysis": {
"headers": {...},
"security_issues": [...]
},
"cve_results": [
{
"id": "CVE-2026-1234",
"severity": "HIGH",
"description": "...",
"cvss_score": 7.5
}
]
},
"download_token": "uuid-token",
"download_url": "/api/download/uuid-token",
"timestamp": "2026-01-01T00:00:00Z"
}Scrape a single URL with flexible extraction options.
Supports:
- Static scraping (BeautifulSoup)
- Dynamic scraping (Playwright for JavaScript-rendered content)
- CSS selector extraction
- Structured data extraction with schemas
- Pagination
Request:
{
"url": "https://example.com",
"dynamic": false,
"wait_for": null,
"selector": ".product",
"attributes": ["id", "title", "price"],
"extraction_schema": null,
"timeout": 30.0,
"headers": {},
"cookies": {}
}Parameters:
| Parameter | Type | Default | Description |
|---|---|---|---|
url |
string | required | Target URL |
dynamic |
boolean | false | Use Playwright for JS rendering |
wait_for |
string | null | CSS selector to wait for (dynamic mode) |
selector |
string | null | CSS selector for focused extraction |
attributes |
array | [] | Attributes to extract from matched elements |
extraction_schema |
object | null | Structured extraction schema |
pagination_selector |
string | null | Selector for pagination links |
pagination_limit |
integer | 1 | Max pages to traverse (1-1000) |
timeout |
float | 30.0 | Request timeout in seconds (1.0-120.0) |
headers |
object | {} | Custom HTTP headers |
cookies |
object | {} | Cookies to include |
proxy |
string | null | HTTP/HTTPS proxy URL |
user_agent |
string | null | Custom User-Agent |
check_robots |
boolean | true | Respect robots.txt |
extract_forms |
boolean | false | Extract HTML forms and their fields |
export |
string | null | Export file path |
export_format |
string | json | json, jsonl, csv, or html |
Response:
{
"success": true,
"data": {
"url": "https://example.com",
"timestamp": "2026-01-01T00:00:00Z",
"title": "Page Title",
"links": [...],
"images": [...],
"forms": [...],
"scripts": [...],
"extracted_data": [...]
},
"download_token": "uuid-token",
"download_url": "/api/download/uuid-token",
"timestamp": "2026-01-01T00:00:00Z"
}Error behavior:
Real scraping failures (HTTP 4xx/5xx responses, connection errors,
robots.txt blocks, browser timeouts) are returned as
500 Internal Server Error — never as HTTP 200 with
success: true. The detail message carries the original failure
reason (for example HTTP 404).
| Scenario | Status | detail |
|---|---|---|
| Scraping failure (404, connection, robots) | 500 | Original reason (e.g. HTTP 404) |
| SSRF-protected target (metadata/private IP) | 500 | SSRF protection reason |
| Malformed request body | 422 | FastAPI validation error |
| Missing or invalid API key | 401 | Authentication error |
Scrape multiple URLs in batch.
Request:
{
"urls": ["https://example.com/page1", "https://example.com/page2"],
"dynamic": false,
"selector": ".item",
"timeout": 30.0,
"headers": {},
"cookies": {}
}Response:
{
"success": true,
"data": {
"job_id": "uuid-string",
"results": [
{
"url": "https://example.com/page1",
"success": true,
"extracted_data": [...]
}
],
"failed_urls": [
{
"url": "https://example.com/page3",
"error": "Timeout"
}
],
"total_success": 2,
"total_failed": 1
},
"timestamp": "2026-01-01T00:00:00Z"
}Batch error behavior:
- Mixed batches (some URLs succeed, some fail) return HTTP 200 with
success: true; failed URLs are listed individually infailed_urls. - If every URL fails, the endpoint returns HTTP 500 and
detailsummarizes the per-URL errors.
Perform controlled security attack simulations.
IMPORTANT: Only test systems you own or have explicit written authorization to test. Unauthorized security testing is illegal.
Supports:
- XSS (Cross-Site Scripting) detection
- SQL Injection testing
- Path Traversal vulnerability testing
- Directory Enumeration
- CSRF (Cross-Site Request Forgery) detection
- Subdomain enumeration (DNS brute force)
- OS Command Injection detection (GET, POST forms, and POST/JSON bodies)
- Custom payloads and wordlists
Request:
{
"url": "https://testsite.example.com",
"xss": true,
"sqli": true,
"traversal": true,
"enumeration": true,
"csrf": true,
"subdomain": true,
"command_injection": true,
"json_body": { "cmd": "id" },
"all_attacks": false,
"intensity": "medium",
"max_payloads": 50,
"timeout": 10.0,
"delay_between_requests": 0.1,
"concurrent_requests": 1,
"skip_dangerous_payloads": true,
"user_consent": true
}Parameters:
| Parameter | Type | Default | Description |
|---|---|---|---|
url |
string | required | Target URL |
xss |
boolean | null | Test XSS vulnerabilities |
sqli |
boolean | null | Test SQL injection |
traversal |
boolean | null | Test path traversal |
enumeration |
boolean | null | Directory enumeration |
csrf |
boolean | null | Test CSRF vulnerabilities |
subdomain |
boolean | null | Enumerate active subdomains |
command_injection |
boolean | null | Test OS command injection |
json_body |
object | null | JSON body template for POST/JSON injection testing (each key is tested one at a time) |
all_attacks |
boolean | false | Enable all attack types |
intensity |
string | medium | low, medium, or high |
max_payloads |
integer | null | Max payloads per attack (1-1000) |
custom_payloads_file |
string | null | Path to custom payloads |
custom_wordlist |
string | null | Custom wordlist for enumeration |
timeout |
float | 10.0 | Request timeout (1.0-300.0) |
delay_between_requests |
float | 0.1 | Delay between requests (seconds) |
concurrent_requests |
integer | 1 | Concurrent requests (1-10) |
skip_dangerous_payloads |
boolean | true | Skip dangerous payloads |
scope_urls |
array | [] | URLs to scope attack to |
export |
string | null | Export file path |
export_format |
string | json | json, jsonl, csv, or html |
headers |
object | {} | Custom HTTP headers |
cookies |
object | {} | Cookies |
proxy |
string | null | HTTP proxy URL |
user_agent |
string | null | Custom User-Agent |
verbose |
boolean | false | Verbose output |
user_consent |
boolean | required | Must be true to proceed |
Response:
{
"success": true,
"data": {
"url": "https://testsite.example.com",
"timestamp": "2026-01-01T00:00:00Z",
"attack_results": {
"xss": {
"vulnerabilities_found": 2,
"payloads_tested": 50,
"results": [
{
"type": "XSS",
"parameter": "search",
"payload": "<script>alert(1)</script>",
"status": "vulnerable",
"severity": "HIGH"
}
]
},
"sqli": {
"vulnerabilities_found": 0,
"payloads_tested": 50,
"results": []
},
"traversal": {
"vulnerabilities_found": 1,
"payloads_tested": 30,
"results": [...]
},
"enumeration": {
"paths_found": 15,
"common_paths_tested": 1000,
"results": [...]
}
},
"total_vulnerabilities": 3,
"risk_score": 72
},
"download_token": "uuid-token",
"download_url": "/api/download/uuid-token",
"timestamp": "2026-01-01T00:00:00Z"
}Error Response (Missing Consent):
{
"success": false,
"error": "Attack simulation requires user_consent=true. Only test systems you own or have explicit permission to test.",
"error_code": "VALIDATION_ERROR",
"timestamp": "2026-01-01T00:00:00Z"
}Perform a combined scan using presets: analysis + attacks + scraping in a single request.
Presets:
| Preset | Analysis | Attacks | Consent Required |
|---|---|---|---|
low |
SSL, fingerprint, headers | None | No |
medium |
+ CVE lookup | XSS, SQLi (medium intensity) | Yes |
high |
+ exploit enrichment, deeper | All types (high intensity) | Yes |
Request:
{
"url": "https://example.com",
"preset": "low",
"user_consent": false,
"timeout": 30.0,
"selector": null,
"dynamic": false,
"headers": {},
"cookies": {},
"proxy": null,
"user_agent": null,
"export": null,
"output_format": "json"
}Parameters:
| Parameter | Type | Default | Description |
|---|---|---|---|
url |
string | required | Target URL |
preset |
string | low | Scan preset: low, medium, high |
user_consent |
boolean | false | Must be true for medium/high presets |
timeout |
float | null | Request timeout in seconds (overrides preset default) |
selector |
string | null | CSS selector to extract (enables scraping) |
dynamic |
boolean | false | Use Playwright for JS rendering |
headers |
object | {} | Custom HTTP headers |
cookies |
object | {} | Cookies |
proxy |
string | null | HTTP/HTTPS proxy URL |
user_agent |
string | null | Custom User-Agent |
export |
string | null | Optional output file path (enables download token) |
output_format |
string | json | Export format: json, jsonl, csv, html |
Response:
{
"success": true,
"data": {
"url": "https://example.com",
"timestamp": "2026-01-01T00:00:00Z",
"ssl_analysis": { ... },
"fingerprint": { ... },
"headers_analysis": { ... },
"cve_results": [ ... ],
"attack_results": { ... },
"scrape_result": { ... },
"risk_score": 40,
"summary": {
"critical": 0,
"high": 0,
"medium": 4,
"low": 0
}
},
"preset": "low",
"duration_seconds": 5.89,
"warnings": [],
"download_token": "uuid-token",
"download_url": "/api/download/uuid-token",
"timestamp": "2026-01-01T00:00:00Z"
}Error Response (Missing Consent):
{
"success": false,
"error": "User consent is required for 'medium' preset. Set user_consent=true to confirm you have permission to test this system.",
"error_code": "CONSENT_REQUIRED",
"timestamp": "2026-01-01T00:00:00Z"
}List available scan presets and their configurations (no authentication required).
Response:
{
"presets": {
"low": {
"analyze": {
"ssl": true,
"fingerprint": true,
"cve": false,
"analyze_headers": true
},
"has_attacks": false,
"attack_types": [],
"intensity": null,
"scrape_dynamic": false
},
"medium": {
"analyze": {
"ssl": true,
"fingerprint": true,
"cve": true,
"analyze_headers": true
},
"has_attacks": true,
"attack_types": ["xss", "sqli"],
"intensity": "medium",
"scrape_dynamic": false
},
"high": {
"analyze": {
"ssl": true,
"fingerprint": true,
"cve": true,
"analyze_headers": true,
"enrich_exploits": true
},
"has_attacks": true,
"attack_types": ["xss", "sqli", "traversal", "enumeration", "csrf"],
"intensity": "high",
"scrape_dynamic": true
}
}
}Download previously exported results using a time-limited token.
URL Parameters:
| Parameter | Type | Description |
|---|---|---|
token |
string | Download token from a POST response |
Headers:
X-API-Key: your-api-key-here
Response: File stream (application/octet-stream). The Content-Disposition header carries a filename matching the export format: attachment; filename="export.json" (also .jsonl, .csv or .html).
Status Codes:
| Code | Description |
|---|---|
| 200 | File downloaded successfully |
| 400 | Invalid token |
| 401 | Unauthorized (different user or max retries exceeded) |
| 404 | Token not found |
| 410 | Token expired |
| 429 | Too many retry attempts |
| 503 | Download service unavailable |
Attempt and token semantics: every request consumes one of the token's retry
attempts (download.max_retries, default 3) when the token passes validation —
including requests that later fail. What happens next depends on the outcome:
- Completed download (200): the token is deleted; it cannot be downloaded again.
- Server-side failure before streaming (500): the consumed attempt is returned and the token remains usable, so the same request can be retried.
- Client disconnect mid-download: the server stops streaming, the consumed attempt is not returned, and the token is kept until it expires — later retries are allowed while attempts remain.
Two limits of this contract: the server cannot verify that the client stored every byte (a disconnect that happens after the last chunk was sent is indistinguishable from a completed download), and a disconnect is detected asynchronously, so retry-safe cleanup is guaranteed by the attempt accounting rather than by an immediate disconnect signal.
Same-key requirement: by default a token can only be redeemed with the same X-API-Key
that issued it. The 401 case above (token belongs to different user) is controlled by
download.require_same_user (default true) — keep it true unless you intentionally want to
share tokens across API keys, because false disables that access control entirely. See
CONFIGURATION.md — Download.
When no token is issued: export-enabled POST responses return 200 with
download_token: null (and no download_url) while the export itself stays available on
disk. This happens when downloads are disabled (download.enabled=false) or when the
registry budget is exhausted (download.max_tokens / download.max_total_bytes — see
CONFIGURATION.md — Download). A GET /api/download/{token}
against a disabled service returns 503.
Example:
curl -H "X-API-Key: your-key" \
"http://localhost:8000/api/download/uuid-token" \
-o results.jsonThe API uses API key authentication via the X-API-Key header.
API keys can be configured via the config file or environment variables:
Config file (~/.ciberwebscan/config.yaml):
api:
auth:
api_keys:
- "my-secret-key-1"
- "my-secret-key-2"Environment variable:
CIBERWEBSCAN_API_AUTH_API_KEYS="my-secret-key-1,my-secret-key-2"Optional server_secret (log obfuscation only):
api:
auth:
server_secret: "" # default; auto-generated once per process, never persistedCIBERWEBSCAN_API_AUTH_SERVER_SECRET=""api.auth.server_secret is used only to obfuscate API keys in logs (HMAC-SHA256). It does not
take part in authentication or download-token authorization: identity is derived from the API key
itself, and changing the secret only changes the identifiers shown in logs. Full details:
CONFIGURATION.md — api.auth.server_secret.
Get information about the current authenticated user.
Response:
{
"identifier": "user-id",
"auth_method": "api_key",
"scopes": ["full_access"],
"authenticated": true
}Identity model: for API-key requests, identifier is apikey: followed by the first 8
characters of the key that authenticated the request. It is stable across requests — the same
key always yields the same identifier — and it does not depend on api.auth.server_secret.
Download tokens are bound to this identifier, which is why redeeming a token requires the same
X-API-Key that issued it (see Downloads).
Generate a new API key (requires admin/full_access scope).
Response:
{
"api_key": "new-key-uuid",
"message": "Store this key securely. It cannot be retrieved again."
}Retrieve all configuration settings.
Response:
{
"success": true,
"data": {
"api": {...},
"scraping": {...},
"analysis": {...},
"attacks": {...}
},
"timestamp": "2026-01-01T00:00:00Z"
}Retrieve a specific configuration section.
URL Parameters:
| Parameter | Type | Description |
|---|---|---|
section |
string | Section name (api, scraping, analysis, attacks) |
Response:
{
"success": true,
"data": {
"key1": "value1",
"key2": "value2"
},
"timestamp": "2026-01-01T00:00:00Z"
}Update a single configuration value.
Request:
{
"path": "scraping.timeout",
"value": 45.0,
"save": false
}| Field | Type | Description |
|---|---|---|
path |
string | Configuration key in dot-notation |
value |
any | New value (string, number, boolean, list, or object) |
save |
boolean | Persist the change to the config file immediately (default: false) |
Response:
{
"success": true,
"data": {
"key": "scraping.timeout",
"value": 45.0,
"default": 30.0,
"source": "runtime"
},
"timestamp": "2026-01-01T00:00:00Z"
}Export the current configuration to a file inside the configuration base
directory (~/.ciberwebscan).
Request:
{
"path": "backup.yaml",
"format": "yaml"
}| Field | Type | Description |
|---|---|---|
path |
string | Output file path (resolved inside ~/.ciberwebscan) |
format |
string | yaml (default) or json |
Response:
{
"success": true,
"data": {
"file_path": "/home/user/.ciberwebscan/backup.yaml",
"operation": "export",
"format": "yaml"
},
"timestamp": "2026-01-01T00:00:00Z"
}Load configuration from a file (values are merged into the running configuration; sensitive values are masked in the response).
Request:
{
"path": "backup.yaml"
}| Field | Type | Description |
|---|---|---|
path |
string | Input file path (resolved inside ~/.ciberwebscan) |
A missing file returns 404; invalid paths return 400.
Reset to default configuration.
Request:
{
"path": "scraping",
"save": false
}| Field | Type | Description |
|---|---|---|
path |
string | Key or section to reset; omit the field to reset everything |
save |
boolean | Persist the reset to the config file immediately (default: false) |
Save the current configuration to a file.
Request (body optional):
{
"path": "snapshot.yaml"
}| Field | Type | Description |
|---|---|---|
path |
string | Destination inside ~/.ciberwebscan; omit to use the default file |
Response:
{
"success": true,
"data": {
"file_path": "/home/user/.ciberwebscan/snapshot.yaml",
"operation": "save",
"format": null
},
"timestamp": "2026-01-01T00:00:00Z"
}Every per-call file path accepted by POST /api/config/export, POST /api/config/load, and POST /api/config/save is resolved against the
configuration base directory ~/.ciberwebscan:
~is expanded first, then the result must still lie inside the base (~/.ciberwebscan/x.yamlsucceeds,~/x.yamlis rejected).- Relative paths such as
backup.yamlresolve to~/.ciberwebscan/backup.yaml, never to the server's working directory. - Parent traversal (
../), absolute paths outside the base, and symlinks that resolve outside the base are rejected with400(Invalid configuration path). - Only
.yaml,.yml, and.jsonextensions are accepted; a missing file on load returns404.
The CLI uses different rules (relative to the current working directory, no sandbox) — see CLI.md.
PUT /api/config, POST /api/config/reset, and POST /api/config/load modify the in-process
configuration object in place (when the request sets "save": true, the change is also persisted
to the config file). Changes take effect immediately for components that read the configuration on
each use:
- API authentication (
api.auth.api_keys): every request re-reads the key list, so adding or revoking a key applies to the very next request, without restarting the server. - Download access control (
download.require_same_user): checked on everyGET /api/download/{token}, so toggling it applies to the next download request. - API auth
server_secret(api.auth.server_secret): read per request but used only for log identifiers; the auto-generated default is fixed for the lifetime of the process. - Trusted proxies (
api.trusted_proxies): re-read on every request when resolving the client IP for rate limiting and auth log attribution, so proxies can be declared or removed at runtime. - HTTP client settings (timeouts, retries, rate limits): apply to HTTP clients created after the change; operations already in flight keep the settings they started with.
- Scan defaults (scraping, analysis, attack options): apply to operations started after the change.
The following settings are read once at startup and require a server restart to take effect:
| Setting | Read when |
|---|---|
api.cors.* |
App creation (create_app()) |
api.rate_limit.enabled, requests_per_minute |
Middleware registration at app creation |
download.cleanup_interval_seconds |
Cleanup scheduler loop start |
logging.* (handlers, level, file) |
Logging setup at process start |
uvicorn host / port / workers |
Server launch |
A restart re-reads the config file and environment variables (environment variables keep higher precedence than the file).
All error responses include standardized error information:
{
"success": false,
"error": "Validation error",
"error_code": "VALIDATION_ERROR",
"details": [
{
"field": "url",
"message": "Invalid URL format",
"value": "not-a-url"
}
],
"timestamp": "2026-01-01T00:00:00Z"
}{
"success": false,
"error": "Internal server error",
"error_code": "INTERNAL_ERROR",
"timestamp": "2026-01-01T00:00:00Z"
}| Status | Meaning |
|---|---|
| 200 | Success |
| 400 | Bad request (validation error) |
| 401 | Unauthorized (missing/invalid API key) |
| 403 | Forbidden (insufficient permissions) |
| 404 | Not found |
| 429 | Too many requests (rate limited) |
| 500 | Internal server error |
| 503 | Service unavailable |
Configure the API via environment variables:
# Server
CIBERWEBSCAN_API_HOST=0.0.0.0
CIBERWEBSCAN_API_PORT=8000
# Authentication
CIBERWEBSCAN_API_AUTH_API_KEYS="key1,key2,key3"
CIBERWEBSCAN_API_AUTH_SERVER_SECRET=""
# Rate Limiting
CIBERWEBSCAN_API_RATE_LIMIT_ENABLED=true
CIBERWEBSCAN_API_RATE_LIMIT_REQUESTS_PER_MINUTE=60
# Client identification (reverse proxies allowed to set X-Forwarded-For)
CIBERWEBSCAN_API_TRUSTED_PROXIES="127.0.0.1"
# CORS
CIBERWEBSCAN_API_CORS_ORIGINS="http://localhost:3000,https://example.com"
CIBERWEBSCAN_API_CORS_ALLOW_CREDENTIALS=true
CIBERWEBSCAN_API_CORS_ALLOW_METHODS="GET,POST"
CIBERWEBSCAN_API_CORS_ALLOW_HEADERS="Authorization,Content-Type,X-API-Key"
CIBERWEBSCAN_API_CORS_EXPOSE_HEADERS="X-Custom-Header"
# Downloads
CIBERWEBSCAN_DOWNLOAD_ENABLED=true
CIBERWEBSCAN_DOWNLOAD_RETENTION_SECONDS=1800
CIBERWEBSCAN_DOWNLOAD_MAX_RETRIES=3
CIBERWEBSCAN_DOWNLOAD_REQUIRE_SAME_USER=true
CIBERWEBSCAN_DOWNLOAD_MAX_TOKENS=100
CIBERWEBSCAN_DOWNLOAD_MAX_TOTAL_BYTES=1073741824The API includes rate limiting to prevent abuse:
- Default: 60 requests per minute
- Configurable via
CIBERWEBSCAN_API_RATE_LIMIT_REQUESTS_PER_MINUTE - 429 status code returned when exceeded
- Retry-After header included in rate limit responses
- Per-client counter: each client identity has its own bucket (see Client identification below)
Clients are identified by their connection peer address. The
X-Forwarded-For header is ignored by default (secure default) and is only
honoured when the peer address is declared in api.trusted_proxies:
CIBERWEBSCAN_API_TRUSTED_PROXIES="127.0.0.1" # single proxy
CIBERWEBSCAN_API_TRUSTED_PROXIES="10.0.0.0/8,::1" # CIDR and IPv6 acceptedRules applied (ciberwebscan.api.client_ip.resolve_client_ip, shared by the
rate limiter and auth log attribution):
- Peer missing or not parseable as an IP (e.g. unix socket) → that fallback
identity is used and
X-Forwarded-Foris never consulted. api.trusted_proxiesempty (default) → the header is ignored.- Peer outside the trusted list → the header is ignored, even when the list is non-empty.
- Peer is a trusted proxy → the chain is walked from right to left; the
first hop not vouching for by a trusted proxy is the client. Every hop is
validated with
ipaddress(IPv4, IPv6 and IPv4-mapped forms are normalized); a malformed hop stops the walk and the last trusted hop wins. Entries are validated as IP addresses or CIDR networks at configuration time and stored in canonical network form.
Reverse proxy requirements for correct attribution:
- The proxy must overwrite
X-Forwarded-Foror append (rightmost) the address it observed. Transparent/pass-through proxies that do not touch the header must not be declared. - Declare only the proxy's own endpoint addresses (
/32//128), never a range that also contains end clients — a client inside a trusted range could forge a chain that the walk would believe.
Effect of not configuring it behind a reverse proxy: all clients share the proxy's peer address, so they share a single rate-limit bucket (60 rpm global) and logs show the proxy IP. Spoofing is not possible either way.
Interaction with Uvicorn's proxy handling: both launch paths of this API
(ciberwebscan api and python -m ciberwebscan.api.app) start Uvicorn with
proxy_headers=False, so Uvicorn never rewrites request.client from
forwarded headers and FORWARDED_ALLOW_IPS is irrelevant — the resolver
above is the single authority for client identity. Deployments that start
the app under a different ASGI server (or a custom Uvicorn invocation) with
forwarded-header trust enabled — especially FORWARDED_ALLOW_IPS=*, which
installs the leftmost, client-controlled header value as the peer — void
this policy and are not supported. The Forwarded header (RFC 7239) is not
honoured by this API.
Invalid values: api.trusted_proxies entries must be valid IP addresses or
CIDR networks. Catch-all networks (0.0.0.0/0, ::/0, and IPv4-mapped
ranges covering the whole mapped IPv4 space), IPv4-mapped networks wider
than /96, and IPv6 zone identifiers (fe80::1%eth0) are rejected: they
would either trust every peer as a proxy or over-match silently. Any
invalid value at startup is reported loudly and, like any
invalid configuration value, makes the loader fall back to the default
configuration (see Configuration); PUT /api/config and
ciberwebscan config set validate without mutating.
Cross-Origin Resource Sharing is fully configurable via config.yaml or environment variables:
| Setting | Default | Description |
|---|---|---|
cors_origins |
[] |
Allowed origins (empty = no cross-origin) |
cors_allow_credentials |
false |
Allow credentials (API keys, cookies) |
cors_allow_methods |
GET, POST, PUT, DELETE, PATCH |
Allowed HTTP methods |
cors_allow_headers |
Authorization, Content-Type, X-API-Key |
Allowed headers |
cors_expose_headers |
[] |
Headers exposed to the browser |
Example — allow only your frontend:
# config.yaml
api:
cors_origins:
- "https://app.example.com"
cors_allow_credentials: true
cors_allow_methods:
- "GET"
- "POST"
cors_allow_headers:
- "Authorization"
- "X-API-Key"# Or via environment variables
CIBERWEBSCAN_API_CORS_ORIGINS="https://app.example.com"
CIBERWEBSCAN_API_CORS_ALLOW_CREDENTIALS=trueSecurity notes:
- Default
cors_origins: []blocks all cross-origin requests (secure by default) ["*"]withcredentials: trueis rejected by browsers (CORS spec violation)- Use specific production domains, not wildcards
Once the API server is running, visit:
- Swagger UI: http://localhost:8000/docs
- ReDoc: http://localhost:8000/redoc
- OpenAPI JSON: http://localhost:8000/openapi.json
Health Check:
curl http://localhost:8000/healthAnalyze URL:
curl -X POST "http://localhost:8000/api/analyze" \
-H "X-API-Key: your-api-key" \
-H "Content-Type: application/json" \
-d '{
"url": "https://example.com",
"ssl": true,
"fingerprint": true,
"analyze_headers": true
}'Scrape URL:
curl -X POST "http://localhost:8000/api/scrape" \
-H "X-API-Key: your-api-key" \
-H "Content-Type: application/json" \
-d '{
"url": "https://example.com",
"selector": ".product"
}'Attack Test:
curl -X POST "http://localhost:8000/api/attack" \
-H "X-API-Key: your-api-key" \
-H "Content-Type: application/json" \
-d '{
"url": "https://testsite.example.com",
"xss": true,
"user_consent": true
}'Quick Scan:
curl -X POST "http://localhost:8000/api/quick/scan" \
-H "X-API-Key: your-api-key" \
-H "Content-Type: application/json" \
-d '{
"url": "https://example.com",
"preset": "low"
}'List Quick Scan Presets:
curl "http://localhost:8000/api/quick/presets"- Never commit API keys to version control
- Store keys securely using environment variables or secrets manager
- Rotate keys regularly for accounts with multiple keys
- Use HTTPS only in production
- Use IP whitelisting if behind a firewall
- Use timeouts appropriately (30-60 seconds for most operations)
- Implement exponential backoff for retries
- Respect rate limits and implement queuing on your side
- Use batch endpoints for multiple URLs when available
- Cache results when appropriate
- Check HTTP status codes before parsing response
- Log all errors for debugging
- Implement retry logic with exponential backoff
- Handle timeouts gracefully
- Validate all responses before processing
- Only test authorized targets - obtain written permission
- Follow responsible disclosure for vulnerabilities
- Comply with local laws regarding security testing
- Use appropriate delays between requests
- Respect robots.txt when configured
401 Unauthorized: Verify your API key is correct and included in X-API-Key header
429 Too Many Requests: Rate limit exceeded. Implement exponential backoff
503 Service Unavailable: Download service disabled. Check configuration
Connection Refused: Ensure API server is running on the correct host/port
- Check API Documentation
- Review Configuration Guide
- See CLI Reference
- Report issues on GitHub