A modular web application penetration testing tool for REST and GraphQL APIs.
The name parascan is a nod to Yahoo's information security team, The Paranoids — a team that made the case that paranoia is a form of caring.
As a former Yahoo, I love the premise: that being a little paranoid isn't a flaw, it's just what happens when you actually care about what you're protecting. Good security starts with the question "okay but what if this is broken?" and doesn't stop asking until it runs out of things to poke at. That's the energy this tool tries to bring.
# install
git clone https://github.com/parascan/parascan.git
cd parascan
python3 -m venv .venv
source .venv/bin/activate
pip install -e .
# scan a target
parascan scan https://example.com
# view results
parascan dashboardPrerequisites: Python 3.11 or later.
git clone https://github.com/parascan/parascan.git
cd parascan
# create and activate virtual environment
python3 -m venv .venv
source .venv/bin/activate # on Windows: .venv\Scripts\activate
# install in development mode
pip install -e .Or install dependencies directly:
pip install -r requirements.txtVerify installation:
parascan versionPoint parascan at a URL and it handles everything — crawls the target, discovers endpoints, runs all scanners, and saves results:
parascan scan https://example.comImport an OpenAPI/Swagger spec for better endpoint coverage:
parascan scan https://api.example.com --openapi swagger.yamlMost real apps require auth. Pass credentials as CLI flags:
# bearer token (JWT, OAuth, etc.)
parascan scan https://example.com --bearer "eyJhbGciOiJIUzI1NiIs..."
# cookies
parascan scan https://example.com --cookie "session=abc123; csrf=xyz"
# API key (format: "Header-Name: value")
parascan scan https://example.com --api-key "X-API-Key: your-key-here"
# basic auth
parascan scan https://example.com --basic-auth "admin:password"Run specific scanners or exclude ones you don't need:
# only run SQL injection and XSS scanners
parascan scan https://example.com --modules sqli,xss
# run everything except IDOR and CSRF
parascan scan https://example.com --exclude-modules idor,csrf
# only GraphQL scanners
parascan scan https://example.com --modules graphql-introspection,graphql-injection,graphql-batchList all available modules:
parascan modulesControl scan speed and behavior:
# slow down for sensitive targets
parascan scan https://example.com --rate-limit 5 --concurrency 5
# speed up for local/staging environments
parascan scan https://staging.local --rate-limit 50 --concurrency 30
# route through Burp Suite or OWASP ZAP
parascan scan https://example.com --proxy http://127.0.0.1:8080Run parascan in pipelines. Returns exit code 1 if critical or high severity findings are detected:
parascan scan https://example.com --ci
# exit code 0 = no critical/high findings
# exit code 1 = critical/high findings detected
# stdout = JSON report# resume an interrupted scan
parascan scan --resume
# launch the web dashboard
parascan dashboard
# dashboard on a custom port
parascan dashboard --port 9000For complex setups, use a YAML config file instead of CLI flags. See configs/config.example.yaml for the full format.
Create one config file per project in the configs/ folder — they are gitignored by default so secrets stay out of version control:
cp configs/config.example.yaml configs/myproject.yaml
# edit configs/myproject.yaml, then:
parascan scan --config configs/myproject.yamlIf you have an OpenAPI/Swagger spec, place it in configs/specs/ and reference it from your config. Spec files are tracked by git (they contain no secrets):
configs/
├── myproject.yaml # gitignored
└── specs/
└── myproject.swagger.yaml # tracked
target:
url: https://example.com
openapi: ./configs/specs/myproject.swagger.yamlCLI flags override config file values, so you can use a base config and tweak per-run:
parascan scan --config configs/myproject.yaml --modules sqli,xss --rate-limit 5target:
url: https://example.com
openapi: ./swagger.yaml
auth:
bearer: "eyJhbG..."
# or: cookie, api_key, basic
scope:
allowed_domains:
- example.com
excluded_paths:
- /logout
- /admin/delete
scan:
modules: [] # empty = all
exclude_modules: []
concurrency: 10
rate_limit: 10
proxy:
url: http://127.0.0.1:8080| Module | What it tests | Severity range |
|---|---|---|
sqli |
SQL injection (error-based, boolean-blind, time-blind) | High |
xss |
Reflected cross-site scripting | High - Medium |
ssrf |
Server-side request forgery | Critical - Medium |
cmdi |
OS command injection | Critical |
idor |
Insecure direct object references | High |
headers |
Missing security headers, CORS misconfig | Medium - Info |
traversal |
Directory/path traversal | High |
csrf |
Missing CSRF tokens, SameSite cookie issues | Medium - Low |
jwt |
JWT alg:none bypass, weak secret brute-force | Critical |
xxe |
XML external entity injection | Critical |
redirect |
Open redirect | Medium |
graphql-introspection |
GraphQL introspection enabled | Medium |
graphql-injection |
GraphQL query injection | High - Medium |
graphql-batch |
GraphQL batch/nested query DoS | Medium - Low |
All modules run by default. Use --modules or --exclude-modules to customize.
- Legal disclaimer — parascan requires you to confirm authorization before scanning
- Fingerprinting — detects the target's tech stack, server software, and WAF
- Discovery — crawls the target, imports OpenAPI specs, or uses your endpoint list
- Scanning — runs selected vulnerability modules against each endpoint
- Reporting — saves findings to SQLite, prints a summary, and serves results via the dashboard
parascan is a detection tool, not an exploitation framework. It identifies vulnerabilities and provides evidence but does not exploit them.
All scan data is stored locally in ~/.parascan/parascan.db (SQLite). No data is sent to external services.
parascan supports both SQLite (local, default) and PostgreSQL (centralized, production).
By default, parascan uses SQLite at ~/.parascan/parascan.db. This works great for:
- Local development
- Single-user scanning
- Laptop-based testing
To enable distributed scanning (multiple workers writing to one database) or deploy the dashboard to Vercel/similar platforms, use PostgreSQL:
Option 1: Environment Variable
export DATABASE_URL="postgresql://user:pass@db.example.com:5432/parascan"
parascan scan https://example.com # writes to PostgreSQL
parascan dashboard # reads from PostgreSQLOption 2: CLI Parameter (overrides env var)
parascan scan https://example.com --database-url "postgresql://..."
parascan dashboard --database-url "postgresql://..."Install PostgreSQL support:
pip install -e ".[postgres]"Local scanning (default):
parascan scan https://example.com # uses SQLiteCI/CD + centralized dashboard:
# .env or CI secrets
DATABASE_URL=postgresql://user:pass@db.example.com:5432/parascan
# scan writes to PostgreSQL
parascan scan https://api.example.com
# deploy dashboard to Vercel (reads from PostgreSQL)
vercel deployMultiple scan workers:
# laptop 1
parascan scan https://api.prod.com --database-url "postgresql://..."
# laptop 2
parascan scan https://api.staging.com --database-url "postgresql://..."
# both write to same PostgreSQL instance
# dashboard shows all scans in one place- Neon (free tier, serverless, auto-scaling)
- Supabase (free tier, includes UI)
- Vercel Postgres (if deploying dashboard to Vercel)
- Railway (cheap, easy setup)
- AWS RDS / DigitalOcean Managed DB (production-grade)
Launch the web dashboard to browse scan results:
parascan dashboardThe dashboard shows:
- Scan history with status and endpoint counts
- Findings grouped by severity with request/response evidence
- JSON export and standalone HTML report per scan
- Served locally on
http://127.0.0.1:4242by default
parascan is designed for authorized penetration testing and security research only.
- You must have written permission from the system owner before running any scans.
- Unauthorized access to computer systems is illegal in most jurisdictions.
- The authors accept no liability for misuse of this tool.
parascan will prompt you to confirm authorization before each new target.
- Create a new file in
src/parascan/scanners/ - Inherit from
BaseScannerand implement thescan()method:
from parascan.scanners.base import BaseScanner, ScanResult
class MyScanner(BaseScanner):
module_name = "my-scanner"
description = "Description of what it tests"
async def scan(self, client, endpoint):
results = []
# your scanning logic here
return results- Register it in
src/parascan/core/engine.pyin the_get_all_scanner_classes()function - Add payload files in
src/parascan/payloads/if needed
MIT — see LICENSE.