kwaro
A free, open-source security scanner that finds, proves, fixes, and verifies vulnerabilities on your machine.
pipx install kwaro · runs on a local model (Ollama) with no API key and no internet, ever.
kwaro helps you find real vulnerabilities in your code by chatting with a local AI agent. No paid APIs, no Docker stacks, no lock-in. It runs anywhere: Windows, macOS, Linux, and WSL.
Most scanners stop at "here is a bug." kwaro goes further, all locally, all for free: it finds a vulnerability, proves it with a runnable proof-of-concept, fixes it with a codebase-aware patch, and verifies the fix by re-scanning. You watch a finding go from "reported" to "proven" to "fixed and verified" in one session, on your own hardware.
kwaro is fully built and released (v0.6.0). Architecture, research, and all design decisions live in
docs/. This README is the user-facing entry point.
Why kwaro
- 100% free by default. Runs on a local model via Ollama. No credit card, no account, fully offline. Paid models (OpenAI, Groq, OpenRouter, and any OpenAI-compatible API) are opt-in via bring-your-own-key.
- Privacy as a feature, not a footnote. Your code never leaves your machine. With the default local provider, analysis is fully offline. There is no telemetry, no crash reporting, no analytics. This is a hard requirement for fintech, banking, and blockchain teams blocked from cloud scanners.
- Cross-platform, zero runtime dependencies. Pure Python 3.10+, standard library only for the CLI. One codebase on Windows, macOS, Linux, and WSL.
- Proof, not opinion. Every finding shows its evidence chain: static rule hit? model confirmation? PoC verified? Confidence is derived from the loop, not asserted by the model.
- Any codebase, any domain. Generic scanning plus community domain profiles (fintech, blockchain, AI apps). Profiles are plain files anyone can write and submit.
- Two interfaces, one engine. Talk to it in your terminal (
kwaro chat) or in a browser UI (kwaro serve). Same agent, same findings. - Open source, AGPL-3.0. Free for everyone, forever. Forkers must stay open.
The loop that makes it different
Find -> Prove -> Fix -> Verify, all on your machine, for free.
- Find via static analyzers and model triage.
- Prove by generating and (optionally) running a PoC that shows the actual crash.
- Fix via the chat agent that edits the file with a real remediation.
- Verify by re-scanning the changed file to confirm the bug is gone.
The math (why you can trust it)
kwaro's differentiator is that confidence and termination are derived from the
find/prove/fix/verify loop, not asserted by the model. Four primitives make this
explicit and honest. All are pure stdlib Python, zero runtime deps, offline-friendly.
Full detail and verified examples are in docs/math.md.
- Bayesian confidence. Each finding starts from a low base rate (candidate flags are
mostly noise,
prior = 0.05). Evidence collected during prove/verify updates the posterior with Bayes rule. A finding is only reported if its posterior clears the bar. The model's own "I'm 90% sure" number is ignored. - Loop-variant termination. The run state has a measure
V = unproven + unfixed + unverified. Each pass strictly decreasesVuntil it reaches 0 (or hits a safety cap), so the loop provably terminates. The trace is visible in output. - Pipeline graph + trace validator. Find/Prove/Fix/Verify is a directed graph. A completed run must be a legal walk, so prove can never be silently skipped and every finding carries full lineage.
- SPRT stop rule. Wald's Sequential Probability Ratio Test sets error-rate budgets
(
alpha= false-positive rate,beta= missed rate) and stops as soon as the accumulated log-likelihood ratio crosses a bound. This controls both error rates by design instead of relying on an arbitrary threshold.
Example from docs/math.md: a real SQLi climbs from posterior 0.05 to
0.89 as static and PoC evidence arrives; a false alarm the model also rated 0.90
collapses to 0.02 because the verify stage found no real exposure. The math decides,
not the model.
Features
| Feature | Status | Notes |
|---|---|---|
| Static analyzers (secrets, SQLi, XSS, path traversal, weak crypto) | shipped | pure-Python, zero-dep, CWE-mapped |
| Domain profiles (generic, fintech, blockchain, ai_app) | shipped | select which analyzers run |
| Math spine (Bayes, variant, graph, SPRT) | shipped | drives confidence, ranking, termination |
| Pipeline ranking (L3 severity bands + composite confidence) | shipped | static > model, PoC-verified > unverified |
| De-duplication by root cause (L4) | shipped | merges by root cause, not tool output |
| SARIF 2.1.0 + JSON export | shipped | carries the math fields under properties |
| Diff-aware rescan (L9) | shipped | analyze changed files only |
Browser UI via serve extra |
shipped | hand-written bundle, no React build |
CI/CD guard (--diff + SARIF) |
planned | GitHub code scanning output |
| Tree-sitter AST + intraprocedural taint | shipped (kwaro[ast] extra) |
Rust (Solana rules) + Python/JS taint; see docs/coverage.md |
Sandbox PoC execution (--execute-pocs) |
shipped (opt-in) | no network, timeout, VERIFIED only on explicit confirm marker |
Coverage and honesty
kwaro has two analysis layers. The base layer is pure-Python regex over 8 extensions
(secrets, SQLi, XSS, path traversal, weak crypto): fast and zero-dependency. The
kwaro[ast] extra adds tree-sitter AST depth: Rust/Solana rules (missing signer check,
missing ownership check, unchecked arithmetic) and intraprocedural taint for Python and
JS/TS (untrusted input reaching SQL/shell/eval/DOM sinks, with sanitizer awareness).
A language is only claimed "covered" once its seeded fixture passes the eval.
The per-language matrix lives in docs/coverage.md. We target free,
local, Semgrep-Community-Edition-class breadth plus the find/prove/fix/verify loop,
not "beat CodeQL" on deep multi-step taint in v1. Taint scope is intraprocedural only;
the PoC sandbox is process-level containment, not a VM.
Install
The CLI has zero third-party dependencies and one pure-Python wheel covers
Windows, macOS, and Linux. On Debian/Ubuntu and some macOS setups, system Python is
externally managed (PEP 668), so pip install system-wide is blocked. Use pipx
(recommended for a CLI app), pip install --user, a venv, or a package manager.
# Recommended (isolates the app, exposes the `kwaro` command globally)
pipx install kwaro
# PyPI, into a venv or user site
pip install kwaro # in a venv, or: pip install --user kwaro
uv tool install kwaro # if you use uv
# macOS / Linux
brew install kwaro # after: brew tap vip-ultr/kwaro
# Windows
scoop install kwaro # after: scoop bucket add kwaro https://github.com/vip-ultr/scoop-kwaro
kwaro init # detect Ollama, write config (free/offline default)
If you only need the CLI (scan/chat), the plain install above is enough. The
browser UI needs the serve extra (see below).
If you used pip install --user, make sure ~/.local/bin is on your PATH
(export PATH="$HOME/.local/bin:$PATH").
The browser UI is an optional extra. If you installed the plain package, add it with
pipx inject kwaro fastapi uvicorn websockets (or reinstall with the extra):
pipx install "kwaro[serve]" # or, in a venv: pip install "kwaro[serve]"
# already have plain kwaro via pipx? inject the deps instead:
pipx inject kwaro fastapi uvicorn websockets
Without the extra, kwaro serve prints an install hint instead of failing.
Providers (chat only)
Scanning is fully static and offline. The chat agent needs a model, and kwaro supports two kinds with zero extra dependencies:
- Local (default, Ollama): free, no API key, fully offline. Install Ollama, pull a
code model, then
kwaro init --provider ollama. - Groq (free API, no model pull): if you don't want to download models, set a free Groq key and kwaro uses it. No local setup.
# Groq (free tier, no local model needed)
export GROQ_API_KEY=gsk_xxx
kwaro init --provider groq # writes provider=groq, key from env
kwaro chat ./my-repo
# Or set it in the environment only (no config write) and run:
export GROQ_API_KEY=gsk_xxx
kwaro chat ./my-repo # auto-resolves groq from GROQ_API_KEY
# Other OpenAI-compatible hosts work the same way (BYOK):
# openai, openrouter, together, deepseek - set the matching *_API_KEY env var,
# or put base_url + api_key in ~/.kwaro/config.toml.
The default model for Groq is llama-3.3-70b-versatile (a strong, free code model);
override by editing config.toml (model = "qwen-2.5-coder-32b" for coding tasks).
Local-first stays the documented default; Groq is the easy opt-in for users who don't
want to manage local models.
For development from source:
git clone https://github.com/vip-ultr/kwaro
cd kwaro
python -m venv .venv && source .venv/bin/activate
pip install -e ".[dev,serve]"
pytest # runs the suite + the in-repo fixture repo
Quickstart
kwaro init # detect Ollama, recommend a local model
kwaro scan ./my-repo # static-first scan, free, offline
kwaro chat ./my-repo # conversationally find, prove, fix, verify
kwaro serve # open the browser UI at http://127.0.0.1:8080
CLI reference
kwaro init # first run: detect Ollama, write config
kwaro scan ./my-repo # static scan + math spine, ranked report
kwaro scan ./my-repo --profile fintech # domain-tuned analyzers
kwaro scan ./my-repo --rescan # diff-aware: only changed files (L9)
kwaro scan ./my-repo --format sarif # SARIF 2.1.0 export (math in properties)
kwaro scan ./my-repo --format json # JSON export (L7 fields + math)
kwaro scan ./my-repo --pocs # generate PoC stubs per finding (offline)
kwaro chat ./my-repo # interactive loop: model uses tools, then reports
kwaro serve [--port 8080] # browser UI (needs the serve extra)
kwaro chat needs a model. With no model configured it falls back to the static
analyzer; point it at local Ollama (no key) or a hosted bring-your-own-key provider.
Example scan output
kwaro: scanning ./my-repo (profile: generic, 5 analyzers, math spine on)
5 unique findings (from 5 raw), 0 kept after prove/verify
loop variant trace: 5 -> 5
pipeline graph valid: True (ok)
de-duplicated: 5 raw -> 5 unique
severity | rule | file:line | confidence
---------|----------------------|------------------|-----------
HIGH | secrets.hardcoded | app.py:1 | static
HIGH | injection.sql-concat | app.py:3 | static
MEDIUM | xss.innerhtml | static/bundle.js:5| static
MEDIUM | traversal.open-userpath| server.go:9 | static
MEDIUM | auth.weak-hash | app.py:7 | static
Each finding carries its posterior, SPRT verdict, composite confidence, and PoC state, exposed in the terminal report, the browser UI, and the SARIF/JSON export.
Results (eval, L13)
The seeded fixture tests/fixtures/vuln-repo/ contains one
instance of each detector. tests/test_eval.py asserts recall
(every seeded rule fires) and flags unexpected rule ids as possible false positives.
Current numbers on the fixture: 100% recall on the 5 seeded rule families (secrets,
SQLi, XSS, traversal, weak crypto), 0 unexpected findings. The math spine keeps
0 of 5 static candidates in the "kept" set because offline static evidence alone does
not clear the posterior bar or the SPRT REAL verdict, which is the honest behavior
until a PoC verifies. As the loop gains prove/verify evidence (see
docs/math.md), that SQLi posterior rises to 0.89 and the finding is
kept; a false alarm collapses and is dropped. Precision/recall tracking per scan is the
product's headline quality metric, not raw "findings found."
Privacy and security posture
kwaro is a local scanner. The SECURITY.md policy is explicit:
- With the default local provider (Ollama), nothing leaves your machine. No code, snippets, or findings are uploaded anywhere.
- The only network traffic is what you opt into: a non-local provider, or an explicit version check (off by default).
- Proof-of-concept execution is off by default. When enabled, it runs in a separate process with a temp directory, no network, and resource/time limits. It is a soft boundary, not a hard sandbox. Run untrusted PoCs in a container or VM you can discard.
- kwaro complements, not replaces, expert review and mature SAST tools. Model output can
be wrong; findings tagged
model-onlyorunverifiedare lower-confidence by design.
Architecture at a glance
kwaro/
core/ models, storage (SQLite), workspace, verify (Bayes + SPRT),
graph (trace validator), loop (variant termination),
rank (L3 bands + confidence), pipeline (FIND/PROVE/FIX/VERIFY),
profiles, export (SARIF + JSON)
analyzers/ base + registry, secrets, injection, xss, traversal, auth, prover
providers/ base, ollama (offline default), openai-compat (BYOK)
chat/ agent (tool-calling loop, deterministic validation)
web/ static bundle (index.html, style.css, app.js), no React build
serve.py FastAPI app (lazy import; CLI stays zero-dep)
The CLI stays zero-dependency. FastAPI/uvicorn/websockets load only under kwaro serve.
Detailed design and locked decisions: docs/architecture.md,
docs/locked-decisions.md.
Development status
Phases 0-6 are shipped and verified (pytest green, 35/35). The CI matrix runs on
ubuntu/macos/windows across Python 3.10-3.12. The only remaining pre-1.0 items are a
launch demo and the optional PyPI publish step (a maintainer action). See
docs/roadmap.md and BUILD.md for the phase history.
Documentation
All planning, research, and locked decisions are in docs/:
docs/math.md- the math spine (Bayes, variant, graph, SPRT) with verified examplesdocs/locked-decisions.md- the locked engineering decisions (L1-L14)docs/game-changers.md- the differentiators that define the productdocs/architecture.md- module layout and data flowdocs/profiles.md- how to write a domain profiledocs/release.md- versioning and release processBUILD.md- build handoff and resume point
Community guidelines: CONTRIBUTING.md, SECURITY.md,
CODE_OF_CONDUCT.md.
Contributing
Contributions of all kinds are welcome: code, domain profiles, docs, bug reports, and
ideas. Start with CONTRIBUTING.md. The easiest high-value
contribution is a domain profile (see docs/profiles.md). All
commits are Developer Certificate of Origin (DCO) signed-off (git commit -s).
License
AGPL-3.0. See LICENSE. Free for everyone, forever; forkers must stay open.
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file kwaro-0.7.1.tar.gz.
File metadata
- Download URL: kwaro-0.7.1.tar.gz
- Upload date:
- Size: 159.6 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via:
twine/7.0.0 CPython/3.12.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
f9b7cbee80a77f67f9a8fb5c80ec1fb87de9e52be5548511e0694eb8783c3339
|
|
| MD5 |
b03b9ba164ed17934cb607a59884dd23
|
|
| BLAKE2b-256 |
e528b26624feb1a1d6987ad9eb30a67e113daedb88fb9da26412c8bc334193f4
|
File details
Details for the file kwaro-0.7.1-py3-none-any.whl.
File metadata
- Download URL: kwaro-0.7.1-py3-none-any.whl
- Upload date:
- Size: 73.6 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via:
twine/7.0.0 CPython/3.12.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
406fe0a7df1cc2a49cbd5094c2430f8f8de2f606e20b56451c3a70afc207fa5c
|
|
| MD5 |
ad8e78029467e975e9df92196031b31e
|
|
| BLAKE2b-256 |
b65f00c3df666cabf993c9d5bfe90a42decd165c5af61174fe341cd2661f899a
|