Find and fix web-app vulnerabilities in one command — SAST + DAST + AI, for devs who aren't security experts.
翻譯準備中
取自儲存庫 README
正在載入完整 README。 以下為已儲存的摘錄。
AI-powered security scanner for modern web apps. SAST + DAST + LLM code review in a single scan.
Built for developers and vibe coders shipping web apps who need to know if their code is secure — without becoming security experts.
Supports: TypeScript/JavaScript (Next.js, Express, tRPC), Python (Django, FastAPI, Flask), Java/Kotlin (Spring Boot), Go (net/http, Gin, Echo, chi) — and any HTTP API for DAST.
*isitsecure scan --repo ./your-app — every scanner narrates as it runs, then you get a graded report (and a browseable HTML page) with the fixes.*檢視完整 README ↗AI-powered security scanner for modern web apps. SAST + DAST + LLM code review in a single scan.
Explore how it works → Animated, step-by-step walkthroughs of the architecture and of every scanner, with no install needed.
Built for developers and vibe coders shipping web apps who need to know if their code is secure — without becoming security experts.
Supports: TypeScript/JavaScript (Next.js, Express, tRPC), Python (Django, FastAPI, Flask), Java/Kotlin (Spring Boot), Go (net/http, Gin, Echo, chi) — and any HTTP API for DAST.
isitsecure scan --repo ./your-app — every scanner narrates as it runs, then you get a graded report (and a browseable HTML page) with the fixes.
isitsecure runs 42 rule-based scanners by default — up to 45 with --depth deep — (plus optional AI code review) against your web app in a single command. It combines four approaches that commercial tools sell separately:
The unique parts:
--output fixes produces a Markdown fix plan you can paste into Cursor or Claude Code.Code → SAST → Findings → Guide DAST → Test → Cross-Reference → LLM Triage → Report → Fixes
↑ |
└──────────────── LSP validates / suppresses false positives ───────────────────────────┘
Requirements: Python 3.11+ and git. (isitsecure isn't on PyPI — the isitsecure name there is an unrelated project, so don't pip install it.)
The installer verifies Python/git, then clones the repo, sets up an isolated environment, installs everything, and runs first-time setup:
# macOS / Linux — download it, glance at it, run it
curl -fsSLo install.sh https://raw.githubusercontent.com/jaurakunal/isitsecure/main/install.sh
bash install.sh
# Windows (PowerShell)
irm https://raw.githubusercontent.com/jaurakunal/isitsecure/main/install.ps1 -OutFile install.ps1
./install.ps1
If Python or git are missing, the installer tells you the exact command to install them for your OS and stops cleanly — it never leaves a half-finished state.
Prefer to do it by hand:
# 1. Clone the repo
git clone https://github.com/jaurakunal/isitsecure.git
cd isitsecure
# 2. Install into a virtual environment (recommended)
python3 -m venv .venv
source .venv/bin/activate # Windows: .venv\Scripts\activate
# 3. Install with all features (browser DAST, LLM review, OOB detection)
pip install -e ".[all]"
# 4. First-time setup — installs the Chromium browser, language servers, optionally saves an API key
isitsecure setup
pip install -e ".[all]" is the recommended install and enables every scanner. If you want a lighter footprint, the extras are opt-in:
| Install | What works |
|---|---|
pip install -e . | SAST / code-only scans only (--mode code-only) |
pip install -e ".[browser]" | Adds DAST / live-URL scanning (requires isitsecure setup to install Chromium) |
pip install -e ".[llm]" | Adds LLM code review, triage, and AI fixes (requires an API key) |
pip install -e ".[all]" | Everything except [taint] (see below) |
pip install -e ".[taint]" | Deterministic Semgrep taint/injection SAST for JS/TS, Python, Java, Kotlin, and Go. Install separately — semgrep's pinned dependencies don't co-resolve with [all]. The analyzer only needs the semgrep binary on PATH, so pipx install semgrep (or brew) works too; it falls back to LLM-only if absent |
URL/DAST scanning needs the [browser] extra — without it, isitsecure scan <url> exits with a message telling you to install it.
# Scan a live URL (DAST only, no API key needed)
isitsecure scan https://your-app.com --llm none
# Scan source code (SAST only)
isitsecure scan --repo https://github.com/you/your-app --mode code-only --llm none
# Full scan (SAST + DAST + LLM review)
isitsecure scan https://your-app.com --repo https://github.com/you/your-app --mode full
# One command: scan + fix everything (the magic)
isitsecure fix --repo ./your-app
# Or dry-run first to preview fixes
isitsecure fix --repo ./your-app --dry-run
# Generate a security badge for your README
isitsecure badge --repo ./your-app
# Export for GitHub Code Scanning
isitsecure scan --repo https://github.com/you/your-app --output sarif
# Open the web UI (for non-CLI users)
isitsecure launch
isitsecure is free and open source. The only cost is LLM API tokens for the AI-powered features:
| Scan Mode | API Key Needed | Estimated Cost |
|---|---|---|
| URL-only (DAST without LLM) | No | $0 |
| Code-only (SAST without LLM) | No | $0 |
| Code-only + LLM review | Yes | ~$5–8 |
| Full scan (SAST + DAST + LLM) | Yes | ~$10–15 |
Without an API key, you still get all rule-based scanners — 42 in the default quick depth (16 DAST + 8 special DAST + 18 SAST), or 45 with --depth deep (which adds 3 slower/aggressive DAST scanners). The LLM adds business logic review, semantic rule verification, injection false-positive adjudication (drops heuristic injection FPs by comparing the baseline vs. injected response), and intelligent triage — things no pattern matcher can do.
Supported LLM providers: Anthropic (Claude), Google (Gemini)
| Mode | What It Does | Requires |
|---|---|---|
url-only | DAST scanners against a live URL | Target URL |
code-only | SAST scanners against source code | GitHub repo URL |
authenticated | DAST with login credentials (IDOR, cross-user BOLA, RLS, privilege escalation) — plus the standard HTTP DAST scanners (injection, CSRF, SSRF, headers, …) now probe behind the login wall against protected endpoints | URL + credentials (add a second account for cross-user tests) |
full | Everything: SAST + DAST + authenticated + LLM review + cross-referencing | URL + repo + credentials + API key |
auto (default) | Detects mode from what you provide | Whatever you give it |
Orthogonal to mode, --depth trades speed for coverage:
| Depth | What runs | When to use |
|---|---|---|
quick (default) | Structural + config checks, error-based injection, a lightweight reflected + POST-body XSS pass, and the snapshot-based scanners (headers, CORS, RLS, source-map, SRI, client-exposure, redirects…). Fast. | Everyday scans — a solid first pass in a fraction of the time. |
deep | Everything in quick plus the slow/aggressive probes: time-based (blind) SQL injection, the full XSS pass (adds static DOM sink analysis), auth-bypass timing, rate-limit bursts, and password-reset flows. | When you want the full arsenal and can wait. |
# Fast pass (default)
isitsecure scan https://your-app.com --llm none
# Full, aggressive DAST
isitsecure scan https://your-app.com --depth deep --llm none
The scan narrates each phase and every scanner as it runs (with elapsed time), so a longer deep scan shows continuous progress rather than appearing to hang.
--probe-writes)A path in a JavaScript bundle carries no HTTP method, so every endpoint is
discovered as a GET. Any scanner that tests POST/PUT then finds nothing
to work on — which leaves stored XSS, mass assignment, CSRF and SSTI
untested, because all of them live behind a state-changing request.
--probe-writes derives those endpoints from REST shape: POST to a
collection, PUT to an item.
isitsecure scan https://your-app.com --probe-writes
Measured on OWASP Juice Shop v20.1.1, url-only:
| without | with --probe-writes | |
|---|---|---|
| recall | 22/45 (49%) | 28/45 (62%) |
| CSRF | 0/1 | 1/1 |
| SSTI | 0/1 | 0/1‡ |
| XSS | 1/7 | 4/7 |
| IDOR | 0/6 | 1/6† |
| mass assignment | 0/1 | 1/1 |
‡ SSTI and NoSQL read 0 on the deterministic scanner: their detectors were tightened to differential oracles (product-must-evaluate / operator-must-broaden), which removed ~18 false positives per scan and revealed the old credits were coincidental. Sound detection of these needs the agentic pentest path.
† --probe-writes IDOR is the mutation path (unauthenticated PUT/PATCH accepted on /api/Products, /api/Hints), not the read path; unauthenticated read-IDOR is 0 because it can't be told from public data without auth — real IDOR needs the authenticated cross-user pass.
It is off by default, and should stay off for anything you do not own. The scan writes to the target: it creates records, and an endpoint that sends email or charges a card will do so. It also takes roughly 72 minutes against 27 on that app, since the inventory doubles.
DELETE is derived and deliberately never sent — a scanner that destroys a
record to prove it could is not worth the finding.
19 total — 16 run in the default quick depth; the 3 marked ◆ (slower/aggressive probes) are added by --depth deep.
| Scanner | What It Finds |
|---|---|
| XSS Scanner | Reflected, POST body, and DOM-based cross-site scripting (quick runs the reflected + POST-body network phases; --depth deep adds the static DOM sink pass) |
| Active Injection Scanner | SQL injection (error + time-based, incl. SQLAlchemy/sqlite3/psycopg errors), command injection, NoSQL injection, XXE, SSTI — injects query, body, and path parameters |
| CSRF Scanner | Cross-site request forgery on state-changing endpoints |
| Rate Limit Scanner ◆ | Missing or bypassable rate limiting on auth endpoints |
| Session Scanner | Insecure token storage (localStorage), missing cookie flags, long-lived JWTs |
| GraphQL Scanner | Introspection enabled, no depth limits, batch query abuse |
| SSRF Scanner | Server-side request forgery (internal IPs, cloud metadata) |
| File Upload Scanner | Unrestricted file types, path traversal in filenames |
| Mass Assignment Scanner | Accepting privileged fields (role, isAdmin) in request body |
| Security Headers Scanner | Missing CSP, HSTS, X-Frame-Options; server version disclosure |
| CORS Scanner | Wildcard origins, credentials with permissive CORS |
| Open Redirect Scanner | Unvalidated redirect parameters |
| Auth Bypass Scanner ◆ | Username enumeration, default credentials, account lockout bypass |
| HTTP Probe Scanner | Method tampering, host header injection, directory listing, .env exposure |
| Password Reset Scanner ◆ | Token leakage in response body, email enumeration, no rate limiting |
| Source Map Scanner | Publicly exposed .map files leaking original source (verified, not just present) |
| Mixed Content Scanner | http:// resources loaded on an HTTPS page |
| SRI Scanner | External CDN scripts/styles loaded without Subresource Integrity hashes |
| Client Exposure Scanner | Secrets in client JS — Supabase service_role keys, internal URLs, unreplaced env placeholders |
| Scanner | What It Finds |
|---|---|
| IDOR Scanner | Insecure direct object references via ID swapping, plus authenticated cross-user (BOLA) testing with two accounts and an anonymous-access false-positive guard |
| JWT Scanner | Algorithm none bypass, weak secrets, key confusion attacks |
| RLS Deep Scanner | Supabase Row Level Security bypass via anon key and cross-user queries |
| Privilege Escalation Scanner | Admin route access, role self-elevation, object-level write bypass |
| Authenticated Crawler | Playwright-based login + BFS crawl to discover authenticated endpoints |
| Race Condition Scanner | TOCTOU bugs via concurrent mutation requests |
| DOM XSS Scanner | Playwright-based sink hooking (innerHTML, eval, location.assign) |
| Body Param Fuzzer | Prototype pollution, type confusion, injection via JSON body fields |
| Scanner | What It Finds |
|---|---|
| Semgrep Taint Analyzer | Deterministic source→sink injection for JS/TS, Python, Java, Kotlin, and Go — SQLi, XSS, SSRF, path traversal, command injection, SSTI (a reproducible floor beneath the LLM reviewer; needs the [taint] extra, no-ops without the semgrep binary) |
| Git Secret Scanner | API keys, tokens, and credentials in git history (not just HEAD) |
| Route Auth Analyzer | Next.js/Express/Django/FastAPI/Spring routes missing authentication |
| RLS Policy Analyzer | Supabase tables without Row Level Security enabled |
| Middleware Analyzer | Incomplete middleware coverage (protects pages but not API routes) |
| Express Middleware Analyzer | Express-specific auth middleware gaps |
| Drizzle Schema Analyzer | Sensitive fields (isAdmin, role) exposed to mass assignment |
| Prisma Schema Analyzer | Similar checks for Prisma schemas |
| IaC Scanner | Terraform/CloudFormation misconfigurations (public S3, no encryption) |
| Docker Scanner | Running as root, exposed ports, .env copied into image |
| Shell Script Scanner | Command injection in deploy scripts |
| Dependency Scanner (npm) | Known CVEs in package.json dependencies |
| Python Dependency Scanner | Known CVEs in requirements.txt / pyproject.toml (Django, Flask, FastAPI, PyJWT, etc.) |
| Java Dependency Scanner | Known CVEs in pom.xml / build.gradle (Log4Shell, Spring, Struts, Jackson, etc.) |
| OSV Dependency Scanner | Real-time CVE lookups against Google's OSV.dev database (200K+ vulns, extracts npm, PyPI, Maven/Gradle, and Go dependencies; other ecosystems such as Rust are not extracted) — no hardcoded CVE list, no API key |
| Firebase Rules Analyzer | Overly permissive Firestore/RTDB security rules |
| OpenAPI Scanner | Internal endpoints exposed in API specifications |
| K8s Scanner | Privileged containers, no resource limits, hostPath mounts |
| Scanner | What It Finds |
|---|---|
| LLM Code Reviewer | Business logic flaws: missing ownership checks, race conditions in payments, incorrect authorization logic |
| Semantic Rule Verifier | Logical errors in RLS policies and Firebase rules (wrong column references, tenant isolation bugs) |
| LLM Business Logic Scanner | Attack planning: price manipulation, double-spend, privilege escalation via application logic |
| LLM Triage Service | Deduplicates findings, assigns priority, generates plain-language owner summary with A–F grade |
| AI Fix Generator | Generates code patches for each finding — paste into Cursor/Claude Code, apply in place with isitsecure fix, or open per-category PRs on a remote repo |
| Feature | What It Does |
|---|---|
| Lazy-Chunk Bundle Collection | Collects JavaScript from <link rel=modulepreload/preload/prefetch> as well as <script src> — a code-split SPA ships its routes as <link> chunks, so a script-only sweep sees the shell and none of the API surface |
| OpenAPI/Swagger Discovery | Probes /openapi.json, /swagger.json, /v3/api-docs and parses the spec into testable endpoints — finds attack surface on APIs with no crawlable frontend |
| HTML Form/Link Discovery | Reads <form>/<input>/query-links from server-rendered pages (bounded url-only crawl + inside the authenticated crawler) — finds attack surface on classic MVC apps with no JS API bundle |
| Endpoint Prioritizer + Time Budget | Ranks likely-vulnerable endpoints first and tests within a per-scanner time budget, so high-risk paths get covered before the clock runs out |
| SAST→DAST Feedback Loop | SAST findings generate targeted DAST tests (6 strategies: auth bypass, IDOR, injection, mass assignment, race condition, RLS bypass) |
| Cross-Referencer | Matches DAST + SAST findings for high-confidence confirmed vulnerabilities |
| Import Graph Centrality | Identifies shared utility files imported by many risky routes for LLM review |
| LSP Auth Flow Tracing | Uses TypeScript Language Server to verify auth middleware is genuinely applied |
| Language | Route Mapping | Auth Detection | Dependency Scan | DAST |
|---|---|---|---|---|
| TypeScript/JavaScript (Next.js, Express, tRPC, GraphQL) | Yes | Yes | Yes (npm) | Yes |
| Python (Django, FastAPI, Flask) | Yes | Yes | Yes (pip) | Yes |
| Java/Kotlin (Spring Boot) | Yes | Yes | Yes (Maven, Gradle) | Yes |
| Go (net/http, Gin, Echo, chi, gorilla) | Yes | Yes | Yes (go.mod) | Yes |
| Ruby, Rust, PHP, etc. | No | No | No | Yes (DAST works against any HTTP API) |
DAST scanners test live HTTP endpoints regardless of backend language. SAST route mapping, auth detection, and dependency scanning are language-specific. Go auth detection is per-route (router/group middleware, in-handler checks) and the gopls LSP refines it — following a cross-file auth helper via go-to-definition to confirm a guard or suppress a false "missing auth". Go module dependencies (go.mod, direct and indirect) are checked against the OSV.dev vulnerability database, like npm/PyPI/Maven.
isitsecure scan URL --output table # Terminal table (default)
isitsecure scan URL --output json # Full JSON report
isitsecure scan URL --output html # Self-contained HTML report
isitsecure scan URL --output sarif # SARIF 2.1.0 for GitHub Code Scanning
isitsecure scan URL --output fixes # AI-generated fix plan (Markdown with diffs)
SARIF results carry a per-result security-severity score, each rule takes the level and score of its most severe finding, and fingerprints are emitted as isitsecure/v1 (legacy) and isitsecure/v2.
If a scanner times out or crashes, or something you asked to scan could not be read, the table shows "Scan incomplete" instead of "safe to launch", labels the grade "Provisional (scan incomplete)", and lists what did not run. The JSON report carries scanner_errors, ingestion_errors and launch_verdict.incomplete (SARIF does not). This does not change the exit code: only an ingestion failure exits 1. The same state reaches the other consumers: the MCP scan result has incomplete and coverage_gaps (and safe_to_launch is false), the CI commit status is posted as error naming what did not run instead of success, and isitsecure badge labels an incomplete scan "provisional, scan incomplete" in grey. Work a scanner skipped (files the LLM review could not read, packages the dependency check could not look up) is listed in scanner_errors too.
Probe captures in reports (the curl-reproducible request/response evidence) have their secrets redacted: cookies, Authorization/API-key/CSRF headers, any header whose name contains token/secret/key/session, token- or password-like JSON and form fields, Bearer values and JWTs are replaced with a REDACTED placeholder, and the rest of the body is kept so the evidence stays useful. JWTs quoted in auth-bypass finding text are masked to an 8-character prefix plus length. A capture's curl command will therefore not replay an authenticated request verbatim.
A finding you've reviewed and accepted (a false positive, or an accepted risk) shouldn't nag you on every scan. Each finding has a stable fingerprint — the same across runs, environments (localhost vs. prod), and edits elsewhere in the file — shown under each row in the table output (fp <hash>) and in the JSON/SARIF output.
Suppress one by adding its fingerprint to a repo-local .isitsecureignore file, which is committed and reviewable in pull requests:
# See the fingerprints (in table or JSON output)
isitsecure scan https://your-app.com --output json | jq '.findings[] | {fingerprint, title, endpoint_url}'
# Suppress a finding (appends to ./.isitsecureignore with context + reason)
isitsecure scan https://your-app.com --suppress de0e57aeb5f61708 --suppress-reason "benign: /createdb re-populate, not injection"
# Future scans hide it automatically; review what's hidden anytime:
isitsecure scan https://your-app.com --show-suppressed
A fingerprint also covers the flagged source line (SAST, line number excluded; only when the line is positively identified, otherwise the original value is kept) or the first injected parameter (DAST), so two same-titled findings in one file get different fingerprints and suppressing one does not hide the other. Fingerprints recorded in an older .isitsecureignore, baseline or report (including for --suppress and verify --fingerprint) keep matching; a legacy entry still covers every instance. SARIF results carry both isitsecure/v1 (legacy) and isitsecure/v2 (current).
The .isitsecureignore file is plain text — one fingerprint per line, # comments for context — so suppressions live with your code and are reviewed like any other change. Delete a line to un-suppress.
# .isitsecureignore
de0e57aeb5f61708 # [injection_risk] GET /createdb — SQL injection (benign: not injection)
Suppressed findings are dropped from the findings list, counts, and themes in every output format. (The AI owner summary narrative is written during the scan, so it may still mention a finding you suppressed afterward.)
For an app that already has a backlog of known findings, baseline mode lets you accept the current set once and then, on later scans, see only what's new — ideal for a CI gate that should fail on regressions but not on pre-existing debt.
# Accept the current findings as the baseline for this project (once)
isitsecure scan https://your-app.com --baseline-accept
# Later scans: show only findings new since the baseline
isitsecure scan https://your-app.com --baseline
Baselines are keyed per project (repo or target URL) and stored under ~/.isitsecure/baselines/ — machine-local state, unlike the committed .isitsecureignore. Baseline and suppression compose: --baseline shows new, not-suppressed findings. Findings match by the same stable fingerprint, so a baseline survives host/port/query changes and code edits.
In CI: the default ~/.isitsecure/ path is per-machine, so a fresh runner has no baseline and --baseline would show the whole backlog. Point --baseline-file at a path you commit to the repo (or cache between runs) so the gate compares against a shared, version-controlled baseline:
isitsecure scan "$URL" --baseline-file .isitsecure-baseline.json --baseline --output sarif
# establish/refresh it deliberately (e.g. on main):
isitsecure scan "$URL" --baseline-file .isitsecure-baseline.json --baseline-accept
After you fix something, isitsecure verify re-checks specific findings from a previous scan and tells you fixed vs. still-present — without re-scanning everything:
isitsecure scan https://your-app.com --output json -f before.json # capture findings + fingerprints
# ...apply your fix...
isitsecure verify https://your-app.com --report before.json --fingerprint de0e57aeb5f61708
fixed/still-present verdict is only given when a single-endpoint re-probe can faithfully reproduce the finding (endpoint-level checks like security headers / CORS / CSRF, and parameter-level injection where the tested parameter is recoverable). Findings that depend on crawl-derived state or multi-request flows (e.g. stored XSS) are reported unverifiable — re-verify those with a full scan.--repo via a code-only re-scan. A finding counts as fixed only when no remaining finding matches any of its fingerprints (current or legacy), so editing the flagged line without removing the bug stays still-present. If the scanner that raised a finding did not run in the re-scan (for example the optional semgrep is not installed, it timed out, or OSV was unreachable), the verdict is could_not_verify, never fixed.--fingerprint to re-verify every finding in the report. Exit codes make it a CI gate: 0 all clear (everything fixed), 1 at least one finding still present, 2 inconclusive — something couldn't be verified (unverifiable/error/could_not_verify — e.g. the target was unreachable or the re-scan couldn't read the repo; these are never reported as fixed) a requested fingerprint wasn't in the report, or the report file is malformed; a finding that disappeared from a file that no longer parses, or whose code now appears in another file, is unverifiable too, since it may have moved rather than been fixed. LLM-review findings (which need a model to reproduce) are always unverifiable.--output json includes a could_not_verify count.(This is the per-finding CLI counterpart to the MCP verify(scan_id) tool, which instead re-scans everything and reports grade movement.)
fix works two ways depending on what you point it at:
# Local: scan your code and apply AI-generated fixes in place
isitsecure fix --repo ./my-app
# Preview fixes without applying (dry run)
isitsecure fix --repo ./my-app --dry-run
# Only fix critical issues
isitsecure fix --repo ./my-app --severity critical
# See the git/backup details instead of the plain-language summary
isitsecure fix --repo ./my-app --technical
Fix a remote repo → pull requests
# Clone a GitHub repo, generate fixes, and open per-category PRs
isitsecure fix --repo https://github.com/you/your-app --github-token $GITHUB_TOKEN
# Control how fixes are grouped into PRs (default: one PR per finding category)
isitsecure fix --repo <github-url> --pr-strategy per-file --max-prs 5
Each PR groups related fixes (by category, by default), one commit per finding,
onto a feature branch — never the base branch. --max-prs caps the number
of PRs (default 8); excess low-severity categories are batched into a single PR
so nothing is silently dropped. The token is used only for the clone, push and
PR; it is never stored or logged, and it is not put in the clone URL, a command
line or the clone's .git/config (it reaches git through the process
environment, and the clone lives in a 0700 temp directory that is removed
afterwards, including on Ctrl-C). Env var GITHUB_TOKEN also works.
How the remote flow behaves:
main.isitsecure/fix-<slug>; per-file and per-finding
branches add a short stable hash (of the path, or of category + path + line +
title) so two paths like a_b.py and a-b.py never collide and the same
finding gets the same name on every re-run. A -fixes suffix is added if the
name would equal the base branch.git ls-remote) for isitsecure/fix-<slug>, -2, -3
and so on. If one of them has an OPEN pull request, that group is skipped
(noted, no duplicate PR). A name that exists with no open PR (its PR was
merged and GitHub kept the branch, or an earlier run pushed it but failed to
open the PR) is left untouched and never reused; the next free -2, -3 ...
name (up to -9) gets the freshly generated fixes. There is no force-push.
If a branch is pushed but opening its PR fails, the error and the
orphaned_branches result field name it. --dry-run does not contact the
remote beyond the scan, so leftover branches are only detected on a real run.GIT_CONFIG_GLOBAL = null device, GIT_CONFIG_NOSYSTEM=1),
so a url.<x>.insteadOf rewrite cannot send the clone or push elsewhere (for
example over ssh, around the token). Proxies must be set with HTTPS_PROXY /
NO_PROXY, not in ~/.gitconfig.isitsecure[bot] identity with hooks and gpg signing
disabled; finding titles and paths are flattened to one line, secret-looking
values (URL credentials, bearer tokens, key/password assignments, common
token shapes) are redacted, and @mentions, images, links, #123/GH-123
references, backticks and bidi overrides are neutralised in PR text. A fix
that fails to commit, makes no change, or targets a symlink is skipped and
never leaks into another finding's commit.--repo accepts https://github.com/owner/repo[.git], [email protected]:owner/repo.git,
ssh://[email protected]/owner/repo or github.com/owner/repo. URLs with credentials, a
query string, a fragment, http://, or extra path segments (such as /tree/main) are
rejected. The ssh and scp forms are accepted for parsing, but the clone and
push always go over https with the token; your ssh keys are never used.
What the local flow does:
--technical for the git diff / backup-restore details)Your original files are backed up before anything is written (a git ref, or a
file copy under ~/.isitsecure/backups/ — outside your repo, so it is never
re-scanned or committed; set ISITSECURE_BACKUP_DIR to move it); the summary
tells you how to review and restore them.
Applying never loses your work:
Exit codes (usable as a CI gate): 0 when every selected finding was fixed
(or there was nothing to fix, or a local --dry-run found nothing unfixable); 1
when any write failed or was refused, any finding could not be fixed, the re-check
did not run, or a remote run hit an error or opened no pull requests; 2 for
invalid options. Findings that may have moved to another file after the fix are
listed under "Worth a look" and counted as needing review, never as fixed. An
unknown --severity (valid: critical,high,medium,low,info), --pr-strategy
or --max-prs is rejected before any paid scan or clone.
Add a security grade badge to your README:
isitsecure badge --repo ./my-app -o badge.svg
Then add to your README:

The badge shows your grade (A–F) and total finding count, styled like a shields.io badge.
isitsecure is not a replacement for enterprise security platforms. It's designed to be the one tool a solo developer or small team needs — combining capabilities that otherwise require 4-5 separate tools.
isitsecure fix)alg=none / RS→HS confusion forging, and differential injection oracles (SSTI arithmetic evaluation, NoSQL operator-broadening, OOB SSRF) — so a finding is a demonstrated exploit, not a pattern matchbenchmarks/RESULTS.md), with an explicit anti-overfitting discipline (no app names or app-specific routes in the shipped engine). Most OSS scanners publish no recall numbers at all.| Need | Best specialized tool | How isitsecure compares |
|---|---|---|
| Deep SAST (30+ languages) | Semgrep | We embed Semgrep for deterministic JS/TS, Python, Java, Kotlin, and Go injection taint (opt-in [taint]) and add LLM review on top; Semgrep's own registry covers far more languages and rules |
| DAST with advanced exploitation | OWASP ZAP / Burp Suite | Our DAST is simpler — fewer payloads, no WAF evasion |
| Secret scanning (800+ patterns) | TruffleHog / Gitleaks | Our git scanner covers common patterns, not exhaustive |
| Container + IaC scanning | Trivy / Checkov | Our IaC/Docker scanners are basic — use Trivy for depth |
| Enterprise compliance (SOC2, PCI) | Snyk / Checkmarx | No compliance mapping (yet) |
| Template-based vuln scanning | Nuclei (28K+ stars) | Not template-based — different approach |
| AI-agent / LLM pentesting | PentestGPT / Vulnhuntr / cai |
| You are | Use this |
|---|---|
| Solo dev / vibe coder shipping a web app | isitsecure — one tool, one command |
| Team with $25K+ security budget | Snyk + GitHub Advanced Security |
| Enterprise with compliance requirements | Checkmarx / Veracode |
| Pentester doing deep exploitation | Burp Suite Pro + Nuclei |
| DevOps focused on containers/IaC | Trivy + Checkov + Gitleaks |
isitsecure works well alongside other tools. Run isitsecure scan for the combined SAST+DAST+LLM view, and use specialized tools where you need deeper coverage.
[taint]) does intra-file source→sink dataflow for JS/TS, Python, Java, Kotlin, and Go injection; cross-function/cross-file tracking (Semgrep Pro territory) still falls back to LLM reasoningSet via environment variable, .env file, or ~/.isitsecure/config.toml:
# Environment variable
export ANTHROPIC_API_KEY=sk-ant-...
# Or .env file in your project
echo "ANTHROPIC_API_KEY=sk-ant-..." > .env
# Or interactive setup
isitsecure setup
isitsecure setup stores the key in ~/.isitsecure/config.toml, created with owner-only (0600) permissions inside a 0700 directory.
isitsecure detects blind SSRF, XXE, and injection vulnerabilities using out-of-band (OOB) callbacks. By default it uses oob.isitsecure.ai — a free community server running interactsh.
What data is sent? Only the scan target makes DNS/HTTP requests to the OOB server. Your source code never leaves your machine.
Self-host your own:
# Deploy interactsh
docker run projectdiscovery/interactsh-server
# Point isitsecure to it in ~/.isitsecure/config.toml
[oob]
server = "http://oob.yourdomain.com"
Disable entirely: Add enabled = false under [oob] in config.
# Supabase auth
isitsecure scan https://your-app.com \
--auth-email [email protected] \
--auth-password yourpassword \
--auth-provider supabase
# Firebase auth
isitsecure scan https://your-app.com \
--auth-email [email protected] \
--auth-password yourpassword \
--auth-provider firebase
# Direct token
isitsecure scan https://your-app.com \
--auth-provider token \
--auth-token "eyJ..."
Cross-user IDOR / BOLA — supply a second account and isitsecure logs in as both users and checks whether one user can read or mutate another user's objects (broken object-level authorization). An anonymous-access guard suppresses false positives from endpoints that are simply public.
isitsecure scan https://your-app.com \
--auth-email [email protected] --auth-password alicepass \
--auth-email-b [email protected] --auth-password-b bobpass \
--mode authenticated
Frontend-less / plain REST APIs — use --auth-provider token, which logs in against a generic REST login endpoint (auto-discovered, or pass it explicitly with --login-url):
isitsecure scan https://api.your-app.com \
--auth-provider token \
--auth-email [email protected] --auth-password alicepass \
--auth-email-b [email protected] --auth-password-b bobpass \
--login-url https://api.your-app.com/login
isitsecure scan [URL] [OPTIONS]
Arguments:
URL Target URL to scan (DAST)
Options:
-r, --repo TEXT GitHub repo URL (SAST)
-b, --branch TEXT Git branch [default: the repo's own default branch]
-m, --mode TEXT Scan mode: auto|url-only|code-only|authenticated|full
--depth TEXT Scan depth: quick|deep [default: quick]
--probe-writes Test the mutation surface (stored XSS, mass
assignment, CSRF). WRITES to the target — only for
an app you own or are authorized to test.
--llm TEXT LLM provider: anthropic|google|none [default: anthropic]
-o, --output TEXT Output format: table|json|html|sarif|fixes [default: table]
-f, --output-file TEXT Write report to file
--auth-email TEXT Auth email/username for authenticated scanning (user A)
--auth-password TEXT Auth password (user A)
--auth-provider TEXT Auth provider: supabase|firebase|browser|token
(use token for a plain REST login)
--auth-email-b TEXT Second user's email — enables cross-user IDOR/BOLA testing
--auth-password-b TEXT Second user's password (paired with --auth-email-b)
--login-url TEXT Explicit login endpoint (else auto-discovered)
--github-token TEXT GitHub token for cloning private repos (only sent to https://github.com/owner/repo URLs)
--suppress TEXT Fingerprint to add to .isitsecureignore (repeatable)
--suppress-reason TEXT Reason recorded next to --suppress entries
--suppress-file TEXT Ignore file path [default: ./.isitsecureignore]
--show-suppressed List suppressed findings instead of hiding them
--baseline Show only findings new since the accepted baseline
--baseline-accept Record the current findings as the baseline
--baseline-file TEXT Baseline path [default: ~/.isitsecure/baselines/<project>.json]
-v, --verbose Enable debug logging
# exit 1 if something you asked to scan couldn't be read (e.g. the repo
# failed to clone) — a report that never saw your code isn't a clean scan.
# A scanner timing out or crashing is reported as "scan incomplete" but does
# not change the exit code.
isitsecure fix [OPTIONS]
-r, --repo TEXT Local repo path, OR a remote GitHub URL to open PRs against [required]
--llm TEXT LLM provider: anthropic|google [default: anthropic]
--api-key TEXT API key for the selected --llm provider (default: that provider's env var / config)
--dry-run Show fixes without applying them
--severity TEXT Severities to fix: critical,high,medium,low,info (unknown values are an error) [default: critical,high]
--technical Show git/backup details instead of the plain-language summary
--github-token TEXT GitHub token for remote-repo pull requests (env GITHUB_TOKEN; never stored)
--pr-strategy TEXT Group PRs by: per-category|per-file|per-finding|single [default: per-category]
--max-prs INT Cap on PRs; excess low-severity categories batch into one [default: 8]
-v, --verbose Enable debug logging
isitsecure verify [TARGET_URL] [OPTIONS] # exit 0 all-fixed · 1 still-present · 2 inconclusive
--report TEXT Previous scan JSON (from scan --output json) [required]
--fingerprint TEXT Only verify these fingerprints (repeatable; default: all)
-r, --repo TEXT Local repo path for re-checking SAST findings
-o, --output TEXT Output format: table|json [default: table]
isitsecure badge [OPTIONS]
-r, --repo TEXT Path to local repo to scan [required]
-o, --output TEXT Output SVG file [default: isitsecure-badge.svg]
-v, --verbose Enable debug logging
isitsecure launch [OPTIONS]
-p, --port INT Port for web UI [default: 3000]
--host TEXT Host to bind [default: 127.0.0.1]; binding elsewhere prints a warning and only
allow-listed Host names are served (see ISITSECURE_ALLOWED_HOSTS)
isitsecure mcp Run the local MCP server (stdio) for AI coding tools
isitsecure setup Interactive first-time setup
isitsecure version Show version
Prefer a GUI? isitsecure launch starts a local web interface backed by the same scan engine — no CLI flags to remember:
isitsecure launch
# Opens http://localhost:3000 in your browser
The UI provides:
isitsecure/fixes branch (-2, -3… if taken), then switches you back, so your working tree never changes. It needs a clean tree, re-checks that right before creating the branch, and never force-checks-out: if you edited or committed anything while fixes were generating, nothing is written and you get a markdown fix plan instead. Gitignored, untracked and skip-worktree files are never written. A dirty tree, a non-git folder or a remote/URL scan also gives the markdown plan (built from the real file contents; findings whose source isn't on this machine are listed as skipped, not guessed). One apply runs at a time per repo, a second click on the same scan attaches to the running job, and each job has an LLM call budgetThe web server resolves your LLM API key the same way the CLI does (ANTHROPIC_API_KEY, a .env file, or isitsecure setup), so scans and fixes work without pasting a key into the browser. You can still enter one in the scan form to override it; "Fix all" reuses the key entered with the original scan; that key is discarded once a fix-all from the scan finishes (a failed or cancelled job keeps it so a retry works), and a later fix-all then answers with a clear "key was discarded" message unless a server-side key is configured. Fix-all jobs live in memory only (the 50 most recent finished ones are kept, at most 3 run at once, DELETE /api/fix-all/{job_id} cancels one, and a job whose browser tab is gone is cancelled after a minute); they are lost on restart. API keys and GitHub tokens are scrubbed from logs, stored errors and git output.
A scan that errors or finishes without a report is shown as failed with its error (the report and export endpoints return 500 "Scan failed: ...") instead of staying "in progress".
Security model. The web server has no authentication, so it is meant for loopback only (the default --host 127.0.0.1). To block DNS-rebinding attacks it answers 421 to any request whose Host header is not localhost, 127.0.0.1, [::1] (any port) or the host you passed to --host, parses the Host header strictly (userinfo, #, ?, /, backslash, whitespace, control characters, bad ports and duplicate Host headers all get 421), and rejects state-changing requests (POST/PUT/PATCH/DELETE) and WebSocket upgrades whose Origin is not same-origin with the request (403 for requests, a close for WebSockets). Same-origin means an http/https Origin whose host and port equal the request's Host header, so another local web app on a different port is refused; null and malformed Origins are refused too. Requests with no Origin (curl, the CLI) are unaffected. To allow an additional UI origin, list it exactly in ISITSECURE_ALLOWED_ORIGINS=http://localhost:4000,https://ui.example (comma separated). The server keeps at most the 50 most recent finished scans in memory, and forgets a scan's LLM API key once no fix-all can still need it (scan failed or has no findings, or the first fix-all from it has finished). If you deliberately expose it (for example behind a reverse proxy), add the public names with ISITSECURE_ALLOWED_HOSTS=name1,name2 (comma separated). isitsecure launch prints a warning when you bind to a non-loopback address.
isitsecure ships a local MCP server so your
AI coding tool (Cursor, Claude Code, Claude Desktop) can scan your code as a
tool — right in the loop where you're writing it. Nothing is hosted: the tool
spawns isitsecure mcp as a subprocess and talks to it over stdio.
Install the extra, then add one snippet to your tool's MCP config:
pip install "isitsecure[mcp]" # or isitsecure[all]
{"mcpServers": {"isitsecure": {"command": "isitsecure", "args": ["mcp"]}}}
Now ask your agent to check your code. It calls the scan tool:
scan(path, min_severity="medium") — runs a fast code-only (SAST) scan
on a local repo and returns a security grade, a go/no-go launch verdict,
severity counts, and a trimmed list of findings. Each finding carries a
plain-English explanation (what it is, what an attacker could do, how to fix
it) — guidance the agent can act on, not jargon.explain(scan_id, finding_id) — after a scan, dig into one finding: its
specific description, the vulnerable code, and step-by-step remediation. Ask
"explain the SQL injection one" and the agent calls it for you.fix(scan_id, finding_id) — proposes a patch for one finding (a unified
diff + the fixed file). Your AI tool applies it with its own editor — the MCP
never writes files. Ask "fix the SQL injection."verify(scan_id) — after you apply fixes, re-scans and reports what
cleared and any grade movement (e.g. F → D). Ask "did that work?"export(scan_id, format) — render the scan as HTML / SARIF / JSON /
Markdown to share or attach (SARIF feeds GitHub code scanning). Ask "export the
report" — your tool saves the returned file.scan_url(url) / scan_status(job_id) — DAST-scan a running app. DAST
takes minutes, so scan_url returns a job id and scan_status polls it. Ask
"scan my app at http://localhost:3000".Code-only scans finish in seconds — the natural fit for the AI-coding loop;
scan_url/scan_status add live-URL/DAST scanning (which takes minutes) when
you need it.
The MCP is growing toward the full scan → understand → plan → fix loop. See docs/mcp.md for the design and roadmap.
The repo includes VibeTasks — an intentionally vulnerable Next.js + Supabase app with ~50 security issues. All commands below are run from the repo root.
# Scan the bundled app's source (SAST only — fast, no API key needed)
isitsecure scan --repo ./test-app --mode code-only --llm none
To also run the live-app (DAST) scanners, start the app first, then scan its URL:
# In one terminal: start the vulnerable app
cd test-app && npm install && npm run dev # serves on port 4000
# In another terminal, from the repo root: full scan (SAST + DAST)
isitsecure scan http://localhost:4000 --repo ./test-app --mode full
--repo accepts a local directory (scanned in place, including uncommitted changes) or a remote git URL like https://github.com/you/your-app. See examples/sample-report.json for what a scan produces.
isitsecure ships a repeatable benchmark harness that scores recall (of the vulnerability classes an app is known to have, how many we catch) and false positives (findings that must not appear against a hardened build) on public, deliberately-vulnerable apps.
Measured coverage — be realistic about what a scanner catches. On OWASP Juice Shop v20.1.1 — a deliberately hard benchmark of 113 challenges — isitsecure detects 49% of the 45 DAST-detectable challenge classes in a url-only scan, scored automatically against the app's own /api/Challenges list. --probe-writes takes that to 62% and an authenticated two-user pass to ~67% — ~76% when authentication and --probe-writes are combined (they find different things: writes buy the stored-XSS and CSRF challenges, authentication buys cross-user object access and the profile-image SSRF — the only sound source of IDOR, since an unauthenticated scan can't tell a leaked record from public data). The url-only pass is deterministic and reproducible in one command (python benchmarks/run_benchmarks.py juiceshop, ~27 min — identical on repeat runs); the authenticated and --probe-writes modes are heavier and vary a little run-to-run (the cross-user IDOR harvest depends on a freshly-registered user having objects to find), so treat ~67–76% as a band, not a fixed figure. It's perfect on SQL injection (7/7, including login/authentication-bypass SQLi — a tautology that logs in where a real credential is rejected), file upload (4/4) and XXE (2/2), and strong on exposed data/secrets, open redirects and misconfiguration. The remaining gaps are rate limiting, SSTI, NoSQL, the stored/header XSS variants, and two IDOR challenges on routes the scanner doesn't reach (/rest/basket/:id, /rest/products/reviews), plus challenges needing multi-step business-logic exploitation. The ~76% figure needs credentials and --probe-writes (which writes to the target); a no-config url-only scan is the 49%. NoSQL now uses a differential oracle (a Mongo operator must broaden the query vs an exact-match control), which removed the class's old false-positive spray at the cost of any coincidental recall — it reads 0 on the deterministic scanner, honestly. In other words: a solid automated first pass that catches whole classes of real bugs in one command — not a substitute for a manual pentest. Full per-class breakdown, gaps, and methodology are in benchmarks/RESULTS.md.
python benchmarks/run_benchmarks.py juiceshop # OWASP Juice Shop — the headline recall number
python benchmarks/run_benchmarks.py # VAmPI (vulnerable + secure builds) + sast-injection
python benchmarks/run_benchmarks.py sast-injection # taint recall/FP (code-only, no Docker)
python benchmarks/run_benchmarks.py --all # + NodeGoat + crAPI + Juice Shop (heavy)
Most targets spin the app up in Docker, run a DAST scan, score against a known ground truth, and tear it down (require Docker). The sast-injection target is code-only (no Docker) — it scores the deterministic Semgrep taint layer on a JS/TS + Python + Java + Kotlin injection fixture (46/46 recall, 0 FP) and needs the semgrep binary instead; it's included in the default run. Measured results are tracked in benchmarks/RESULTS.md; see benchmarks/README.md for targets and how scoring works.
--llm none to keep everything local.See docs/architecture.md for the full pipeline design, including how SAST feeds DAST, how LLM review is prioritized, and how cross-referencing works.
Prefer pictures to prose? docs/interactive/ is a site of animated, step-by-step explainers: an architecture deep dive, nine scanner-group pages, and one page for every scanner. Open docs/interactive/index.html in a browser; no build step.
See docs/scanners/ for detailed documentation on every scanner — what it detects, why it matters, real-world breach examples, and how to fix the vulnerabilities it finds. The scanners with a mechanism worth watching also have an animated page under docs/interactive/.
Language servers let the scanner trace auth flows through your code (via go-to-definition) and suppress false positives — confirming auth middleware is genuinely applied, not just imported.
Let isitsecure install them for you:
isitsecure setup --lsp # install/verify the Python, TypeScript, and Java language servers
isitsecure setup --check # report what's installed (API key, DAST browser, LSP) — installs nothing
setup --lsp installs what it can cleanly (Python via pip always; TypeScript via npm if Node is present; Java via Homebrew if available) and prints guidance for anything it can't. It's also offered as a step in the full isitsecure setup. This is optional — scans still work without it using regex-based detection. For manual setup and per-language detail, see docs/lsp-setup.md.
For TypeScript it also provisions a private TypeScript 5.x runtime under ~/.isitsecure/lsp — typescript-language-server carries none of its own and won't start without one, and a scan runs against a copy of your project with node_modules stripped, so it can't borrow yours. Your global typescript is never installed or changed. To use a specific one instead, set ISITSECURE_TSSERVER_PATH to its lib/tsserver.js. isitsecure setup --check prints the runtime a scan will use.
isitsecure is built on protocols and the strategy pattern. Adding a new scanner is straightforward:
DASTScannerProtocol or CodeScannerProtocolisitsecure/engine/factory.pytests/Set up a dev environment with pip install -e ".[all,dev]", then run the suite with pytest. Issues and pull requests welcome at github.com/jaurakunal/isitsecure.
Apache 2.0 — see LICENSE.
taskkill /T on Windows).1 if any error was reported or no PR was opened
(including a leftover branch with no open PR), else 0.fix-all and the MCP/API): mode, repo,
base_branch, fixed_count, skipped[], errors[], orphaned_branches[],
summary, and pull_requests[] (category, title, branch, url,
finding_count, is_low_batch).| Those are research-grade or single-class demos; isitsecure is a maintained, tested (~2,500 tests) and benchmarked product with the same LLM/agentic ideas wired into a deterministic scanner pipeline |