GitTrace MCP server
Commit Oct 30, 2026Analyzes Python web projects to find reachable security vulnerabilities in FastAPI and Flask routes and provides path-to-bug explanations.
Also known as codingwinterz/GitTrace
- Stars
- 0
- Tools
- —
- Downloads, 30 days
- —
- Latest version
- —
- Last commit
- Oct 30, 2026
- License
- MIT
What is GitTrace MCP?
GitTrace analyzes Python web projects to find reachable security vulnerabilities in FastAPI and Flask routes and provides path-to-bug explanations.
Summary generated from the project's README
About GitTrace MCP server
Security researchers and developers working on Python web applications can use GitTrace to identify reachable vulnerabilities within FastAPI and Flask projects. Unlike standard scanners that only flag dangerous patterns, this tool maps the actual path from an HTTP route to a specific bug, providing context on which endpoints can reach the code and who last modified it via git blame. It focuses on five specific categories: SQL injection from string building, eval/exec usage, shell commands from non-constant input, unsafe deserialization, and hardcoded secrets.
The system operates locally by building a code graph and using deterministic rules to detect issues. It integrates with Ollama to run local models like llama3.1:8b for triaging findings and qwen2.5-coder:7b for generating plain-language code explanations. Users can run scans to find attack paths and blast radius or use the explain command to get summaries of files and functions. The tool requires Python 3.12 through 3.14 and works with local model setups to ensure code privacy.
What you can do with it
- 1Trace vulnerability reachabilityIdentify if a specific security flaw in a Python function can actually be triggered by an external HTTP request.
- 2Analyze code ownership and impactDetermine which developer last changed a vulnerable line and how many other endpoints depend on that code.
Written from the project's README. Every use case is checked against a quote from it.
Configuration
GITTRACE_GATEWAYGITTRACE_MODELGITTRACE_SHOW_EMAILOPENAI_API_KEY
| Name | Description | |
|---|---|---|
| GITTRACE_GATEWAY | — | |
| GITTRACE_MODEL | — | |
| GITTRACE_SHOW_EMAIL | — | |
| OPENAI_API_KEY | — |
README
View on GitHubGitTrace
Point it at a Python web project. It finds the dangerous code an HTTP route can actually reach, shows the path from the route to the bug, who calls it, who last changed it, and asks a local model whether it is real.
- Scope, honestly: Python only; routes from FastAPI, Flask and Flask-RESTful/RESTX; five kinds of finding (SQL built from strings,
eval/exec, shell commands from non-constant input, unsafe deserialization, hardcoded secrets). - Rules detect, the model only comments. Every finding comes from a deterministic rule. A local model (via Ollama) gives an opinion on each one; it cannot add or remove findings or change the CI gate.
- Within those five categories it is comparable to Bandit, with added reachability and ownership context (measured below, on small samples).
Built for Graph Hacks, Track 01 (team SUDO SAIYAN, Avaneesh S). This is the v2 command-line rebuild; the earlier web demo is tagged v1.0.0.
Install
pipx install "git+https://github.com/codingwinterz/GitTrace@v2.1.0" # Python 3.12-3.14; or, from a clone: pipx install .
gittrace --version
Python 3.12-3.14. pipx puts gittrace (and gittrace-mcp, the MCP server) on your PATH in their own environment; pip install . in a virtualenv works too.
Use Python 3.12, 3.13 or 3.14 for now. Python 3.15 is new, and several dependencies (pydantic-core, tiktoken, tree-sitter, ...) have not published prebuilt wheels for it yet, so GitTrace declares requires-python = ">=3.12,<3.15": on 3.15 the installer stops with a clear "requires a different Python" message instead of trying to compile them. If your default Python is 3.15, choose the version explicitly:
pipx install --python 3.14 "git+https://github.com/codingwinterz/GitTrace@v2.1.0"
uv tool install --python 3.14 "git+https://github.com/codingwinterz/GitTrace@v2.1.0" # the same with uv
First run: Ollama
GitTrace needs no database server: the code graph is built in the GitTrace process and cached in <project>/.gittrace/graph.json. The only other program a full run uses is Ollama, for the local model. Install it and start it, then:
gittrace up --pull # checks Ollama and pulls both models (about 9.6 GB the first time)
gittrace doctor # checks Ollama, the models, free GPU memory, power and sleep settings
The same by hand, if you prefer:
ollama pull llama3.1:8b # 4.9 GB, used by scan to triage findings
ollama pull qwen2.5-coder:7b # 4.7 GB, used by explain to summarise code
- Ollama: https://ollama.com/download
- The models run best on a GPU with about 6 GB of free memory (llama3.1:8b takes 5.6 GiB, qwen2.5-coder:7b 4.7 GiB); otherwise Ollama runs them on the CPU, much more slowly.
- Without Ollama:
gittrace scan <path> --no-llmandgittrace explain <path> --no-llmmake no model call. The rules, reachability, the graph and the facts all work; only the model's opinion is missing. - No configuration file is needed: GitTrace defaults to local Ollama. See Settings to change that.
Quick start
gittrace scan path/to/project # findings, attack paths, blast radius, owner, the model's triage
gittrace explain path/to/project # a plain-language tour of the code
gittrace # the interactive mode (in a terminal)
To try it on the bundled sample, a small FastAPI app with planted bugs, clone the repository and run gittrace scan tests/fixtures/sample_app.
Results at a glance
All numbers come from eval/ (scripts, labels and per-finding results are committed). The samples are small: read them as indications, not benchmarks. Ground truth and reviews were written before either tool or model ran on a target; "after tuning" rows are optimistic because the tuning looked at the same targets; held-out targets were run once, after tuning, and nothing was changed because of them.
Detection (the rules) against Bandit 1.9.4, in the same five categories only. 36 known vulnerabilities in 5 deliberately vulnerable apps, plus every finding on 3 real projects reviewed by hand:
| Set | Size | GitTrace precision · recall | Bandit precision · recall |
|---|---|---|---|
| Before any tuning (vulpy bad/good, Vulnerable-Flask-App, vulnpy) | 31 vulnerabilities | 88% · 74% | 83% · 81% |
| After tuning on those same apps (optimistic) | 31 | 97% · 94% | 83% · 81% |
| Held out (DVGA) | 5 | 100% · 80% (4 of 5) | 80% · 80% (4 of 5) |
| Real projects after tuning (CTFd, mealie; open-webui held out) | 3 + 15 + 29 findings | 4 real issues among 47 findings (CTFd 0, mealie 2, open-webui 2) | not reviewed |
So: comparable to Bandit in these categories, with added reachability and ownership context: which route reaches each finding, whether that route needs authentication, the path, how many callers and endpoints depend on it, and who last changed the line. On real projects most findings are real patterns that are not exploitable (SQL built from trusted table names); that context, and the model's triage, are how GitTrace helps you sort them. Details: Evaluation.
Reachability (is the finding reached from a route?). Of the 20 true positives on the vulnerable apps that a route truly reaches (per the ground truth), GitTrace marks them reachable: untouched: 7 marked, 0 wrong; after-reach-fixes: 7 marked, 0 wrong. Marked reachable without auth: untouched: 7 marked, 1 wrong; after-reach-fixes: 6 marked, 0 wrong. The one wrong mark in untouched is VF-1 (POST /search), whose JWT is checked inside the handler; from after-reachability an auth check at the top of a handler's body counts, so it is marked as needing auth. The 13 it misses are all in vulnpy: 11 are reached through getattr dispatch (a function looked up by name, returned, and called later), which GitTrace does not follow, and 2 through Falcon routes: GitTrace detects Falcon routes now, but vulnpy's Falcon app is rebound under a condition (app = ContrastMiddleware(app)), so GitTrace does not resolve which object the routes hang off. The same 13 in every run; every run's counts, and the check that these sentences match them (eval/claims.json, tests/test_claims.py), in eval/README.md.
Known weakness: many false positives on real projects. On CTFd and mealie most findings are dangerous-looking patterns that are not vulnerabilities: before tuning, 10 of 10 (CTFd) and 63 of 65 (mealie) were false positives; after tuning, 3 of 3 and 13 of 15 (open-webui, held out: 27 of 29).
Triage (the model's opinion on each finding). "Real issues marked harmless" is the number that matters: a real problem the model would push into the "likely harmless" section (it is still reported there).
| Set | Model | Real issues marked harmless | False alarms marked harmless |
|---|---|---|---|
Tuning set, prompt triage-v4 (35 real, 17 false) |
llama3.1:8b (default) | 1 of 35 | 14 of 17 |
| qwen2.5-coder:7b | 9 of 35 | 11 of 17 | |
Held out, open-webui, prompt triage-v3 (2 real, 27 false) |
llama3.1:8b | 0 of 2 | 11 of 27 |
| qwen2.5-coder:7b | 0 of 2 | 12 of 27 |
Every real issue a model ever hid was a hardcoded secret. The held-out set has only 2 real issues and no secrets, so it says little about how often one is hidden. Details: Model triage.
Explain (the model's summaries), rated correct / partly / wrong against notes written before any summary existed. The ratings were made by an AI (Claude), with spot checks by the owner:
| Set | Model | Files | Functions |
|---|---|---|---|
| After tuning: sample_app + microblog (47 files, 30 sampled functions) | qwen2.5-coder:7b (default) | 45 / 2 / 0 | 29 / 1 / 0 |
| llama3.1:8b | 38 / 8 / 1 | 26 / 3 / 1 | |
| Held out: fastapi-tdd-docker (18 files, 7 functions) | qwen2.5-coder:7b | 16 / 2 / 0 | 3 / 4 / 0 |
| llama3.1:8b | 14 / 4 / 0 | 4 / 3 / 0 |
The held-out function summaries are weak because GitTrace's own facts missed that project's database library (the Tortoise ORM), and the models repeated "no side effects". Details: Explain results.
Which model does what, and why
| Task | Default model | Why |
|---|---|---|
scan triage |
ollama/llama3.1:8b |
It hid far fewer real issues (1 of 35, against qwen2.5-coder:7b's 10 with the earlier prompt and 9 with the current one) and ignored an injected "mark this harmless" comment (0 flips against 2). Cost: about 0.9 GiB more GPU memory. |
explain summaries |
ollama/qwen2.5-coder:7b |
Its summaries were rated higher in 5 of 6 comparisons and never "wrong", and it needs less GPU memory. Summarising is a different job from triage, so each task keeps the model that measured better at it. |
Speed, on a laptop RTX 5050 (8 GB) on AC power: about 2.1 s per summary call for either model, and about 2.6 s per triaged finding. Any LiteLLM model name works with --model (or GITTRACE_MODEL / GITTRACE_EXPLAIN_MODEL); only the two defaults were measured.
What it does
gittrace scan path/to/project
- Reads the real code with tree-sitter: every function, method and nested function, the calls between them, and the FastAPI, Flask and Flask-RESTful/RESTX routes with their auth dependencies.
- Builds a graph of it in memory (NetworkX), one graph per project, cached in
<project>/.gittrace/graph.json. - Runs deterministic rules for SQL built from strings,
eval/exec, shell commands from non-constant input, unsafe deserialization and hardcoded secrets — one finding per dangerous call site. - Asks the graph who can reach each finding: the shortest path from every endpoint (unauthenticated first), the upstream callers (blast radius), and
git blamefor the owner. - Scores severity deterministically from the rule, the reachability and the blast radius, and prints it with Rich (or JSON for CI).
- Asks a local model to triage each finding (Ollama through LiteLLM,
llama3.1:8bby default): likely real, likely harmless or uncertain, a one-line reason, a severity adjustment of −2..+1 shown next to the rules' score, and, with--fix, a fix as a diff that GitTrace checks withgit apply --checkand never applies.
gittrace explain path/to/project
- Explains the project in plain language, for someone who built it with an AI assistant and does not fully understand it: entry points, the endpoints table, modules by folder, libraries, data stores and outside services, side effects per function, a request-flow tree per endpoint, and model-written summaries of the project, every file and the important functions, with each function the summaries mention checked against the call graph. See Explain.
Rules detect; nothing else decides what is a finding. The model only comments on findings the rules produced: it cannot add or remove one, its adjustment never changes the --fail-on gate, and a finding it calls harmless is still reported, in its own section. --no-llm turns it off.
See it work
tests/fixtures/sample_app is a small FastAPI app with planted vulnerabilities and planted non-findings. The fixtures and tests contain deliberate fake secrets and vulnerabilities: key-shaped strings (some look like Stripe or API keys), passwords and injectable code, written for testing and never real credentials. Secret scanners will flag them; do not copy them into real code. Every value below is asserted by the test suite, offline and through the real graph store:
| Finding | Severity | Why |
|---|---|---|
cursor.execute(f"... '{name}'") in app/db.py:17 |
critical (11) | reached from GET /api/users/{name} without auth (and from GET /me with auth); name is a parameter; blast radius 5 |
os.system("logger " + msg) in app/utils/shell.py:8 |
critical (10) | GET /api/users/{name}/files → list_files → run_command → run_command.log, no auth |
os.popen(cmd) in app/routers/debug.py:16 |
critical (10), uncertain | the debug router is never included, so its route prefix is not resolved |
eval(expr) in app/routers/admin.py:11 |
high (8) | reachable only through the admin router, which requires Depends(get_current_user) |
"... %s" % days → conn.execute in app/services/maintenance.py:19 |
medium (5) | no route reaches it (only the cron job nightly) |
API_TOKEN = "s3c…" in app/auth.py:4 |
medium (4) | hardcoded secret; not adjusted for reachability |
Not reported, on purpose: constant SQL, a parameterised query, eval("1 + 1"), yaml.load(..., Loader=yaml.SafeLoader), secrets read with os.environ/os.getenv, a PASSWORD_FIELD = "password" name, a "changeme" placeholder.
Commands
gittrace scan <path> [--format rich|json] [--min-severity LEVEL] [--depth N]
[--fail-on LEVEL|none] [--offline] [--no-blame] [--include-tests]
[--model NAME] [--no-llm] [--fix] [--show-email]
gittrace explain <path> [--format rich|md|json] [--out FILE] [--file PATH | --function ID]
[--all] [--min-callers N] [--depth N] [--model NAME] [--no-llm] [--yes]
gittrace trace <file.py:line | finding-id> [--project PATH] [--depth N] [--format ...]
gittrace blast-radius <function-id> [--project PATH] [--depth N] [--format ...]
gittrace doctor [--format json] # is this machine ready? (changes nothing)
gittrace up [--pull] # check (and pull) the models
gittrace --version
python -m gittrace ... works the same, e.g. from a source checkout.
scanwrites the project's graph cache (<project>/.gittrace/graph.json) and prints a headline panel, a findings table (severity, rule, file:line, the endpoint that reaches it, whether that endpoint needs auth), then for each finding the code with the line highlighted, the attack paths as a tree, the blast radius, the owner and the severity reasons.--fail-on(defaulthigh) sets the exit code: 1 if any finding is at or above it, soscancan gate CI;nonenever fails. It reads the rules' severity only, never the model's adjustment.--min-severityonly filters what is shown (and what the model is asked about).--depth(default 10, max 50) bounds the CALLS hops followed from a route handler to the vulnerable function, and the upstream callers counted in the blast radius.--offlinecomputes reachability from the analysis directly and writes no graph cache (same rules and scores);traceandblast-radiusneed the cache. The cache records the sha256 of every source file and is never used once any file is changed, added or removed: the graph tools then ask for a newscan.--no-blameskipsgit blame. Outside a git repository the owner lookup is skipped quietly. The owner is shown by name only;--show-emailadds the author's email (in the JSON output too).- Findings in test code (a
tests/testdirectory,test_*.py,*_test.py,conftest.py) are listed in a separate section atinfoand never trip--fail-on;--include-teststreats them like any other code. --modelpicks the triage model (any LiteLLM model name; default$GITTRACE_MODEL, elseollama/llama3.1:8b);--no-llmmakes no model call at all. Test-code findings are not triaged unless--include-tests.--fixalso asks the model for a fix for each finding, shown as a diff checked withgit apply --check(never applied). Off by default: writing fixes is the slow part of a reply, and a fix is only checked to apply, not to be correct.--fixcannot be combined with--no-llm.doctorchecks Python, Ollama and both models, free GPU memory against what the models need, and on Windows the power source, power plan, sleep and display timers and Modern Standby (in GitTrace's own evaluation, standby paused single model calls for up to 25 minutes). It exits 1 when a check fails.upchecks Ollama and the models;--pullpulls the missing ones.- Exit code 2 means an error: bad arguments, an unscanned project (or one changed since its scan), the graph cache cannot be written, the model cannot be called (Ollama not running, model not pulled).
Interactive mode
gittrace # in a terminal: the interactive mode
gittrace --no-anim # the same, with a still logo
Run with no arguments in a terminal, GitTrace opens a full-screen session (built with Textual). A header shows a turning graph logo, the version, the current project and the model(s). Below it is a scrollable output area drawn exactly as the commands above draw, and a prompt at the bottom:
| Command | Does |
|---|---|
/scan [path] [--offline] [--no-llm] [--fix] [--include-tests] [--no-blame] [--show-email] |
scan with its default options |
/explain [path] [--no-llm] [--all] [--yes] |
explain of the whole project |
/trace <file:line> |
trace in the current project |
/blast-radius <function-id> |
blast-radius in the current project |
/model [name | default] |
show the models, or use one model for both /scan and /explain |
/help, /quit |
list the commands; leave (also ctrl+q) |
- The current project is the last
/scanor/explainpath, else the working directory. Up and down recall earlier commands, and the right arrow accepts a suggested command name. An empty session suggests a first command. - One command runs at a time, on a background thread, with its progress on a status line. Errors are shown in the output instead of ending the session, and log warnings go to the output instead of stderr.
/explaindoes not ask a question: when its estimate is over 10 minutes it stops and asks you to add--yes. - The logo (a cluster of connected nodes, ray-cast and shaded in
.:-=+*#%@) turns once in 2 s at 20 fps, then rests for 5 s. It holds still while a command runs, and it is a still frame with--no-anim, whenNO_COLORis set, or in a terminal smaller than 80×24. All 40 frames are computed once at start; at rest the timer is paused. - Scripts and CI are unaffected: any arguments, or no terminal on stdin and stdout, give the CLI exactly as before (no arguments without a terminal still prints the help).
Settings
Nothing needs configuring for the defaults (local Ollama). To change something, set environment variables, or put them in a .env file: GitTrace reads the first of $GITTRACE_ENV_FILE, .env in a source checkout, and .env in your user config directory (e.g. ~/.config/gittrace/.env, %LOCALAPPDATA%\gittrace\.env; gittrace doctor shows which). It never reads the .env of the directory you are in, which is usually the project being scanned. Environment variables win over the file. See .env.example.
| Variable | Default | Purpose |
|---|---|---|
GITTRACE_MODEL |
ollama/llama3.1:8b |
Triage model; --model wins |
GITTRACE_EXPLAIN_MODEL |
ollama/qwen2.5-coder:7b |
explain model; --model wins; GITTRACE_MODEL does not apply to explain |
GITTRACE_LLM_TIMEOUT |
120 | Seconds to wait for one reply |
GITTRACE_SHOW_EMAIL |
off | 1 adds the git blame email to the graph tools' results (MCP, trace, blast-radius); scan uses --show-email |
GITTRACE_ENV_FILE |
none | A .env file to read first |
OLLAMA_API_BASE |
http://localhost:11434 |
Where Ollama listens |
GITTRACE_GATEWAY |
off | 1 sends the model calls through an in-process Respan Gateway instead of LiteLLM (see below) |
GITTRACE_GATEWAY_CONFIG |
none | A gateway.toml for that gateway; without it, it is built from the provider keys in the environment |
The graph stays on this machine, in the scanned project's .gittrace/ folder (which gets a * .gitignore). The cache leaves out function source code, so hardcoded secrets in the project are not copied into it; finding snippets are masked as everywhere else. Tracebacks never print local variables. See SECURITY.md for what GitTrace trusts and what it does not.
Model layer through the Respan Gateway
GITTRACE_GATEWAY=1 sends every model call (scan triage, explain summaries, the --fix request) through the open-source Respan Gateway, in the same process, instead of LiteLLM calling the provider directly. Nothing else about the call changes: the same prompt, temperature, seed, reply-token limit, JSON-schema reply and timeout, validated and retried the same way, and a reply that is invalid or unavailable fails the same way. The gateway is the Rust gateway of respanai/respan-gateway (its python/README.md documents the Python API), installed from PyPI as respan-gateway==0.3.0 by the optional gateway extra, which a plain install leaves out. Install GitTrace with it (the extra is in v2.1.0 and later; v2.0.0 has no gateway):
pipx install --force "gittrace[gateway] @ git+https://github.com/codingwinterz/GitTrace@v2.1.0"
uv tool install --force --python 3.14 "gittrace[gateway] @ git+https://github.com/codingwinterz/GitTrace@v2.1.0" # the same with uv
From a clone, pipx install --force ".[gateway]". It spends your own provider keys; a Respan account, credits and a tracing export are separate, optional features and no part of this.
GitTrace MCP questions
What Python frameworks does GitTrace support?
It supports routes from FastAPI, Flask, and Flask-RESTful/RESTX.
Can I use GitTrace without a local LLM?
Yes, you can use the --no-llm flag to run scans and explanations without making model calls.