
BianryNinja plugin for identifying vulnerabilities in decompiled binaries with both programmatic scans and LLM support.
LLM-assisted vulnerability research for Binary Ninja.
VulnFanatic-NG adds a sidebar panel that scans the current binary and asks an LLM — a locally hosted OpenAI-compatible model by default, or Anthropic Claude, Google Gemini, or Azure OpenAI (see LLM backends) — to judge whether suspicious code is actually vulnerable. It works primarily from Binary Ninja's decompiler output (HLIL), falling back to assembly when needed, and reports only confirmed issues with clickable references back to the code.
A scan runs in up to three phases (Phase 3 is opt-in and online-only):
Finds call sites of dangerous functions defined in
rules/phase1_rules.json — strcpy,
memcpy, sprintf/format strings, system, alloca, scanf, command/exec
APIs, weak RNG, the free/delete family (use-after-free / double-free),
reads of untrusted input into fixed buffers (recv/read/fread/ReadFile),
SQL injection (sqlite3_exec/mysql_query/PQexec), disabled TLS
certificate verification (SSL_CTX_set_verify/curl), SSRF, and improper
privilege management (setuid/setresgid), the memset/bzero family, and
comparisons with an attacker-controlled length (memcmp/strncmp →
authentication bypass), across C/C++, Win32, and (best-effort) Rust FFI. Coverage
includes fortified _chk (FORTIFY) and Annex-K _s variants. Bounded
formatted-output functions (snprintf and variants) have their own default-safe
rule so a correct size argument is not reported as an overflow. Call sites are
found three ways: direct calls to the named symbols; calls routed through
forwarding thunks / PLT stubs (the real callers are recovered, so an import
reached only through a stub is not missed); and — unless
vulnfanatic.scanIndirectCalls is off — indirect calls dispatched through a
function pointer or vtable that Binary Ninja resolved to a dangerous function.
For each call site it builds an interprocedural, decompiler-centric context,
budgeted to a token limit (default 100k):
__*_chk and bounds-checked *_s variants take extra leading
arguments, shifting the format/size/destination position,s->buf
resolves to the field's real array size rather than the pointer size of s;
struct definitions in the types section also carry per-field byte sizes,0x40
or bounded to [0, 0xff]), which the model uses as ground truth when comparing a
size against a buffer capacity instead of guessing,vulnfanatic.includeStackLayout),if/loop/switch conditions guarding the call),That context plus a rule-specific prompt is sent to the model, which returns a structured verdict. Non-issues are dropped. The prompts are tuned for a strong local code model (e.g. Qwen2.5-Coder) and instruct it to analyze the entire flow and emit JSON only.
The model is told to favor recall — report plausible, security-relevant issues and express uncertainty through a Confidence rather than dropping anything it can't fully prove. It shows its work in a scratchpad that quotes the verbatim code snippets it relied on (the input source, each guard, the size/length, the relevant type, and the sink), which is stored on the finding so you can audit the reasoning.
Each finding carries a Confidence (high/medium/low): high = the whole chain is
shown in the context; medium = likely, with a link or two inferred; low = a lead
worth manual review. This is the headline metric (the model's severity estimate is
a secondary field). Set vulnfanatic.minConfidence to drop anything below a threshold.
By default VulnFanatic-NG favors recall (catching real issues). If you get too many false positives, tighten with any of:
vulnfanatic.validationPass (default off) — runs a second LLM pass that
double-checks each flagged issue against the same context (verifying the
scratchpad snippets and re-tracing the flow) and can correct the verdict or
confidence. Doubles LLM calls for flagged candidates.
vulnfanatic.validatorModel (plus validatorProvider / validatorBaseUrl /
validatorApiKey) to run the second pass on a different model. A second
opinion is far more useful from an independent model — it shares fewer blind
spots and is much less likely to rubber-stamp the first verdict (models tend to
prefer their own answers). A good pattern is a cascade: a fast model as the
analyst (broad recall) and your strongest model as the validator, which only runs
on flagged candidates. Leave the validator model blank to validate with the
analyst model. The validator should be at least as capable as the analyst — a
weaker one mostly adds false rejections. Everything except provider/base URL/key/
model is inherited from the analyst connection settings; a blank validator key
reuses the analyst key; and if the validator endpoint is unreachable the first
verdict is kept (the finding is never lost to a validator outage).vulnfanatic.minConfidence (default low) — raise to medium/ to report
only stronger findings.Speed. Most per-call latency is the written reasoning, so
vulnfanatic.verdictReasoning controls how much the model writes:
concise (default) — a brief 1–3 sentence rationale, no verbatim code. Much
faster than full with little accuracy loss; you can also lower
vulnfanatic.maxResponseTokens.full — the detailed scratchpad with quoted snippets (most auditable, slowest).none — verdict only. Fastest; pair it with a reasoning-capable backend
(vulnfanatic.reasoningEffort) so the model's internal thinking does the work.
On a plain local model none loses accuracy (no chain-of-thought at all).Supporting precision features that are always on (they inform the model without suppressing findings):
_s (Annex K) and
_chk (FORTIFY) variants and length-bounded APIs as safe unless the size
argument itself is wrong.The Scan Offline button runs Phase 1 with no model — purely programmatic
heuristics declared in the offline block of each rule in phase1_rules.json. It
flags dangerous call sites and eliminates the obviously-safe ones, assigning a
heuristic Confidence:
memcpy/memmove with a constant length, a
strcpy from a constant string, a printf with a constant format, a system
with a constant command, etc. — calls whose governing argument is a compile-time
constant and so can't be attacker-controlled. "Constant" includes values that
Binary Ninja's value-set analysis pinned to a fixed number upstream, not just
literal arguments.strlen/size
comparison, if (len < …)) was found somewhere on the flow — including in the
functions called along the path — so it may already be handled. (A branch that
merely mentions the variable without comparing it no longer counts, removing a
source of spurious downgrades.)The heuristics use a small declarative vocabulary in the rules
(constant_safe_args, eliminate_if_all_args_constant, format_arg_lookup,
length_guard_vars, base_confidence, skip) evaluated by Python predicates —
no embedded code to exec. Most rules have an offline definition (overflow,
format-string, command-exec, scanf, path handling, weak-RNG, weak numeric parsing,
privilege changes, allocation size, …). Only the two categories that genuinely need
semantic analysis are skipped offline and left for the LLM: the free/delete family
(use-after-free / double-free, which needs pointer-lifetime tracking) and TLS
verification (the bug is a specific constant value like SSL_VERIFY_NONE). The
offline summary reports how many sites were flagged / eliminated / skipped (need the
LLM) / failed, so the counts add up. This is a fast triage; for real judgment — and
for the skipped categories — run the full LLM scan.
Offline findings still build the same full interprocedural context an online
scan would send (only for the flagged sites) and store it, so once you triage them
they can be exported as fine-tuning data just like online findings. Disable with
vulnfanatic.offlineBuildContext if you want maximum offline speed.
Only runs when the binary appears to have real symbols / variable names. Locates
security-sensitive functions defined in
rules/phase2_rules.json —
authentication, cryptography (incl. weak algorithms), signature/certificate
verification, session/token handling, access control, secret/key handling,
input validation, non-constant-time secret comparison, and insecure
deserialization — matched by function name and referenced strings, then audited
by the model.
A firmware hardening audit against fault-injection (voltage/clock/EM glitching)
and side-channel (timing/power) attacks, based on hardware-attack mitigation
guidance. Unlike Phases 1–2 (which find bugs), Phase 3 reports a missing or
violated hardening control on a security-critical function — for example:
default-fail branches, double-checked security decisions, post-loop counter
validation, high-Hamming-distance state constants (vs plain 0/1), constant-time
full-length secret comparison, randomized-offset secret access/clearing,
encrypt-then-verify (anti-DFA), control-flow integrity counters, avoiding user-land
crypto, and not handling raw key material directly
(rules/phase3_rules.json).
Because compiler optimizations can strip source-level protections, these controls are best verified on the compiled binary — exactly what this checks. Phase 3 is LLM-only (online), symbol-gated, and disabled by default; enable it per scan with the Phase 3 checkbox on the New Scan tab (it never runs in offline mode).
Findings are listed in a table (status, confidence, phase, CWE, function, address, title) with a detail pane that shows the explanation, the analysis scratchpad, and the validation notes. Double-click a row to navigate the binary view to the code.
Every finding starts Untriaged. Right-click a row to set its status — Mark as Real Issue, Mark as False Positive, or Mark as Untriaged. Each status change pops up a "Provide reason:" text box (the reason is stored with the finding). The table makes status obvious: Real Issues are green/bold and sort to the top, False Positives are grey/struck-through and sort to the bottom, Untriaged sit in between with their confidence colour. A summary line shows the counts.
Each result tab has an Export triaged (fine-tuning)… button that exports only
the triaged findings (Real Issue + False Positive) as OpenAI chat-format JSONL
for fine-tuning: each example pairs the original system+user prompt with the
human-corrected verdict as the assistant target (a False Positive teaches
is_vulnerable=false with your reason; a Real Issue reinforces is_vulnerable=true),
so you can iteratively improve the model's accuracy on your binaries.
The per-finding context shown in the detail pane (and used to reconstruct the
fine-tuning prompts) is, by default, kept in full — controlled by
vulnfanatic.storedContextChars (0 = unlimited; set a positive cap, e.g. 4000,
to limit BNDB growth at the cost of context fidelity).
The panel is tabbed. The first tab is always New Scan, where you set:
<timestamp> <mode>, e.g.
2026-06-15 14:03:50 offline),then press Start Scan or Scan Offline. Each run opens its own result tab and findings stream into it live. All scans are stored in the BNDB, so you can e.g. keep an offline scan and later add an online scan, or compare runs with different rule sets, side by side — they reappear as tabs when you reopen the database. Closing a tab permanently deletes that scan from the BNDB — to prevent accidents it pops up a confirmation that requires ticking "I confirm that I will lose the results from forever." before the Delete results forever button activates. Export current scan… writes the selected tab to Markdown/JSON.
Each open binary has its own independent panel state — its own scan tabs and running scan. Starting a scan in one binary and switching to another shows the second binary's results (and lets you scan it separately); the first binary's scan keeps running in the background and is intact when you switch back.
This plugin's package folder is named vulnfanatic_ng (a valid Python identifier —
Binary Ninja imports the plugin folder name as a module, so a hyphenated name
like VulnFanatic-NG would not load).
(Optional) Install accurate token counting into Binary Ninja's Python:
pip install tiktoken
Symlink or copy the vulnfanatic_ng folder into your Binary Ninja user plugins
directory:
~/Library/Application Support/Binary Ninja/plugins/~/.binaryninja/plugins/%APPDATA%\Binary Ninja\plugins\For example, on macOS:
ln -s "$(pwd)/vulnfanatic_ng" "$HOME/Library/Application Support/Binary Ninja/plugins/vulnfanatic_ng"
Restart Binary Ninja (or run Reload Plugins). A VF icon appears in the right sidebar.
Open Settings (the gear / Edit ▸ Preferences ▸ Settings) and search for
vulnfanatic. Set at minimum:
vulnfanatic.apiProvider selects how requests are formed and authenticated. The
verdict contract (and all rule prompts) are identical across providers.
AWS Bedrock can be used through the
openaiprovider via its OpenAI-compatible endpoint, so it does not need a dedicated backend.
Other useful settings: vulnfanatic.maxContextTokens (default 100000),
vulnfanatic.maxResponseTokens, vulnfanatic.temperature,
vulnfanatic.reasoningEffort (off/low/medium/high; default high — asks
the model to think before answering where supported, mapped per provider:
openai/azure reasoning_effort, anthropic adaptive thinking + output_config.effort,
google dynamic thinkingConfig; auto-stripped and retried if a model rejects it),
vulnfanatic.requestTimeoutSec,
vulnfanatic.callPathMaxDepth / ,
(include the decompiled bodies of functions along
the call path; default on) / (cap, default 12),
(also include other functions called along the
path, which may hold the bounds/validation checks; default on) /
(cap, default 12),
(include struct/union/enum definitions; default on) /
(cap, default 24),
(trace call arguments back through their producers
/consumers and include those bodies; default on) /
(cap, default 8),
(include the calling function's stack-variable layout
when it has a fixed-size buffer; default on),
(also match dangerous calls dispatched through a resolved
function pointer/vtable; default on — turn off for a faster scan on very large binaries),
(run the second double-check pass; default off) /
/ /
/ (run the validation pass on
a separate, independent model — blank = same model as the analyst) /
(//; drop findings below this; default
), (report sites the model couldn't score as
-confidence "Unscored" leads instead of dropping them; default on),
(skip all-constant overflow call sites;
default off), (//; how much
reasoning the model writes per verdict — the main speed lever; default ),
/
/ (enable each phase; Phase 3 is
online-only and usually toggled per-scan via the New Scan checkbox rather than here),
/ ,
(tiktoken encoding for token estimates; falls back to
a character heuristic if tiktoken is not installed),
(build full context for offline findings so they can
be exported for fine-tuning; default on),
(verbose pipeline trace to the console; default off) /
(redact all binary-identifying details so the log can be
shared — see below),
, (verify HTTPS
certificates; default on) / (CA bundle for HTTPS — see
Troubleshooting if you hit ), and
/ /
(point these at your own rule files to customize
detections and prompts).
Security note: the API key is stored in Binary Ninja's settings in plaintext. Prefer the environment-variable override for sensitive keys.
Set vulnfanatic.apiBaseUrl to the literal value TEST to run without any LLM:
/tmp/vulnfanatic_ng/<binary>-<timestamp>/.Use this to inspect and validate exactly what VulnFanatic-NG would send to the model, and to iterate on the rule prompts/context without spending model time.
Turn on vulnfanatic.debugLogging to print a verbose, step-by-step trace of the
scan pipeline (both online and offline) to the Binary Ninja log/console: each call
site, every skip/elimination decision, context build (size only), each LLM request
(provider/model/endpoint, retries, fallbacks), every verdict, and each reported
finding. API keys are never logged.
While debug logging is on, an online scan keeps every candidate in the results table instead of dropping the ones that don't become confirmed issues, each tagged with a debug-only status (dimmed, sorted to the bottom):
So a debug scan shows one row per candidate in the /N total, and the summary reports
issues vs. rejected/skipped/error counts separately. You can right-click any of these
rows to re-triage it as a Real Issue or False Positive (which makes it eligible for
fine-tuning export). (Offline scans are unaffected — they never call the LLM.)
Independently of debug mode, when a model returns an unparseable response — a stray
token like Gemma's <unused…>, prose instead of JSON, or an empty message (only a
role, no content) — the client makes one corrective retry, re-asking for JSON
only with the structured-output format disabled; if that succeeds it keeps the format off
for the rest of the scan. The client also reads the reasoning channel
(reasoning_content / reasoning) when content is empty, so reasoning models that put
their answer there still work.
The empty-message case is common with reasoning models such as GPT-OSS / o1 served
over an OpenAI-compatible API (e.g. mlx-community/gpt-oss-20b): with
response_format=json_object set, the harmony "final" answer channel is often suppressed
and the server returns {"role": "assistant"} with no content. These models can also burn
their whole output budget on the reasoning channel and get truncated mid-thought,
returning prose with no JSON at all. The auto-retry recovers the format-related cases; if
it persists, turn off vulnfanatic.sendJsonResponseFormat, lower
vulnfanatic.reasoningEffort (so less budget goes to thinking), and/or raise
vulnfanatic.maxResponseTokens. A persistent <unused…>/garbage reply instead usually
means the prompt exceeds the model's context window (set vulnfanatic.modelContextWindow
and/or raise the server's context length), or that the model is a poor fit for strict JSON
output (a code model such as Qwen2.5-Coder behaves far better than Gemma here).
Recall-preserving fallback. When a candidate still can't be scored after the retry,
vulnfanatic.flagUnparseableResponses (default on) reports it anyway as an
"Unscored" finding with UNKNOWN confidence — a value distinct from low (the
model never produced a verdict, so it isn't a low-confidence judgement) that sorts to
the bottom — keeping the model's partial output as the explanation, so you don't lose the
site, you just review it manually. Turn it off to drop such sites instead (they then
surface only as analysis errors, or debug ERROR rows).
Also enable vulnfanatic.debugAnonymous to make the log safe to share: it
redacts everything that could identify the analyzed file — symbol/variable names and
addresses become per-run salted hashes (still consistent within a run so the flow is
followable), the file name is hidden, finding text is replaced with <redacted>, the
LLM endpoint host is hashed, and decompiled code / prompts / context are logged as
sizes only (never the content). So you can send a debug log to report an issue without
disclosing anything about your binary.
Findings — including their false-positive status — are stored in the Binary Ninja
database. They are written into the .bndb when you save the database (and
flushed immediately if a .bndb already exists), so they survive reopening.
The scan analyzes every matched call site (no cap), which is appropriate for local models. For a hosted/paid endpoint, be mindful of volume on large binaries.
Both rule files share an envelope with a shared system_prompt and
output_schema, plus a list of rules. Copy a bundled file, edit the
functions/keywords/prompts, and point vulnfanatic.rulesPhase1Path /
vulnfanatic.rulesPhase2Path at your copy. Phase 1 rules match by functions (exact)
and name_regex; Phase 2 rules match by name_keywords, name_regex, and
string_keywords. Each rule's prompt may use the {function} placeholder.
The triaged exports are designed to be fed straight back into the model. After
triaging findings across several binaries and clicking Export triaged
(fine-tuning)… on each (collecting the .jsonl files into one folder),
scripts/finetune_mlx.py runs an MLX LoRA
fine-tune on them.
pip install mlx-lm # Apple Silicon / macOS
# Fine-tune a local 4-bit model on every *.jsonl under ./exports
python scripts/finetune_mlx.py ./exports \
--model mlx-community/Qwen2.5-Coder-7B-Instruct-4bit \
--adapter-path ./vf-adapters --iters 800
# ...then fuse the adapters into a standalone model
python scripts/finetune_mlx.py ./exports --model <base> \
--fuse --fused-path ./vf-qwen-coder-vuln
The script takes the training-data folder as its positional argument and the
base --model (local path or MLX/HF repo id); other parameters are optional:
--adapter-path, --valid-split (0.1), --iters, --batch-size (auto-clamped to
fit a tiny split), --num-layers, --learning-rate, --max-seq-length
(0 = auto-fit to the longest example, capped at 16384; set a positive value to
force it), --fine-tune-type (lora/dora/full), --seed, /,
and (prepare data + print the command without training). Anything after a
literal is forwarded verbatim to . It recursively merges every
in the folder, validates and the chat examples, makes the
/ split MLX expects, then launches
(and with ).
Serve the result with an OpenAI-compatible server (mlx_lm.server --model <path>)
and point vulnfanatic.apiBaseUrl back at it to scan with your tuned model.
VulnFanatic-NG contexts are large, so by default the script auto-fits
--max-seq-lengthto your longest example (rounded up, capped at 16384 tokens). Long sequences dominate training memory, so a large model near this cap can run a smaller Mac out of memory. If your examples exceed the cap they're truncated — pass a higher--max-seq-length(more memory) or lowervulnfanatic.storedContextCharsbefore exporting. If training is killed by a signal (e.g.exit -10/ SIGBUS), that's an out-of-memory crash: lower--max-seq-length, add-- --grad-checkpoint, or use a smaller model.
The plugin has zero required third-party dependencies. Pure modules
(rules, tokens, llm, findings, settings, prototypes) are covered by an
offline test suite that needs neither Binary Ninja nor a network. The tests/
suite lives in the project's source repository (it is not shipped inside the
published plugin); run it from there. From the package directory you can still
syntax-check every module:
python3 -m py_compile *.py ui/*.py
python3 -m unittest discover -s tests # from the source repository
The Binary Ninja-facing modules (context_builder, phase1, phase2) import
cleanly without Binary Ninja (their API access is guarded) but require a running
Binary Ninja to exercise.
strcpys argv[1] into a
fixed stack buffer and calls system() on input). Compile with symbols
to also exercise Phase 2.vulnfanatic.apiBaseUrl,
vulnfanatic.apiKey, and vulnfanatic.model.SSL: CERTIFICATE_VERIFY_FAILED ... unable to get local issuer certificate —
the HTTPS endpoint's certificate is fine, but Binary Ninja's bundled Python has no
CA bundle to verify it against (common on macOS and in embedded Pythons; you'll see
this with hosted endpoints like AWS Bedrock, Anthropic, Google, Azure). Fix with one
of, in order of preference:
pip install certifi.
VulnFanatic-NG picks it up automatically.vulnfanatic.caBundlePath to a bundle file (or
directory) — e.g. the path printed by python3 -m certifi, or /etc/ssl/cert.pem.vulnfanatic.tlsVerify (only for a trusted/internal
endpoint or a self-signed local server — this disables certificate checking).HTTP 400 ... tokenizer.chat_template is not set — the model you are serving
has no chat template, so the /chat/completions endpoint cannot format the
messages. VulnFanatic-NG automatically falls back to the /completions endpoint for
the rest of the scan when it sees this error, so scanning continues. To avoid the
first failed request entirely, set vulnfanatic.apiMode to completions. Alternatively,
fix it server-side by serving a model that ships a chat template, or pass one to
your server — e.g. for vLLM: --chat-template <template.jinja> (or use an
-Instruct/-Chat model variant). The dedicated chat template usually gives
better results than the flattened completions prompt.
No JSON object found ... response looks truncated — the model's reply was cut
off before the JSON finished. Two causes:
vulnfanatic.maxResponseTokens.maxResponseTokens is. Local servers often have a small window (ollama defaults
to num_ctx=2048!). Fix it by setting vulnfanatic.modelContextWindow to your
server's window (e.g. ollama num_ctx, llama.cpp -c, vLLM --max-model-len) —
VulnFanatic-NG then automatically caps the context it sends so prompt + response fit.
Also keep vulnfanatic.maxResponseTokens reasonable (≈8192, not 65535) and/or raise the
server's window. Tiny windows (≤8k) cannot hold the full interprocedural context;
use a model/server configured for 32k+.IncompleteRead / Could not complete request ... after N attempt(s) — the
server accepted the request but closed the connection before sending the full
response. This almost always means the model server died or stalled mid-generation:
out-of-memory (large context + long output), an internal/worker timeout, or a proxy
resetting the connection. VulnFanatic-NG retries once automatically and then skips that
site. Check the model server's own logs for the real cause; reducing
vulnfanatic.maxContextTokens and/or vulnfanatic.maxResponseTokens, or giving the server more
memory / a larger context window, usually resolves it.
vulnfanatic.phase2ForceEnable to also audit name-based matches, or
vulnfanatic.phase2RequireSymbols=off. The symbol gate is a heuristic.response_format=json_object; the client
tolerates that and still extracts JSON. Disable vulnfanatic.sendJsonResponseFormat
if your server rejects the parameter outright.Apache-2.0 (© Martin Petran) — see plugin.json.
MAIN→ABCD→strcpy, also the functions MAIN and ABCD call elsewhere), since
they may hold the bounds/validation checks that gate the dangerous value
(vulnfanatic.includeCallPathSiblings, filled while budget allows), andrecv/read/getenv called in
the same function).highvulnfanatic.skipConstantArgCalls (default off) — skip overflow-class call
sites whose arguments are all compile-time constants.| Setting | Meaning |
|---|
vulnfanatic.apiProvider | Which LLM backend to call: openai (default), anthropic, google, or azure. See LLM backends below. All providers are reached over the Python standard library — nothing to pip install. |
vulnfanatic.apiBaseUrl | Endpoint base for the selected provider (see the table below). Default http://localhost:8080/v1. Set to the literal TEST to enable test mode (see below). |
vulnfanatic.apiKey | API key / bearer token. May be blank for local servers. Overridden by the VULNFANATIC_API_KEY or OPENAI_API_KEY environment variables. |
vulnfanatic.model | Required (except in test mode). The model identifier (for azure, the deployment name). |
vulnfanatic.apiMode | openai only: chat (default, /chat/completions) vs completions (single flattened prompt — for base/instruct models served without a chat template). |
vulnfanatic.azureApiVersion | azure only: the api-version query parameter (default 2024-10-21). |
| Provider | apiBaseUrl | Auth | Notes |
|---|
openai | your server, e.g. http://localhost:8080/v1 | Authorization: Bearer | OpenAI-compatible Chat/Completions: local llama.cpp / ollama / vLLM, OpenAI, and AWS Bedrock's OpenAI-compatible endpoint. |
anthropic | blank → https://api.anthropic.com | x-api-key + anthropic-version | Claude Messages API (POST <base>/v1/messages). temperature is not sent (current Claude models reject it). |
google | blank → https://generativelanguage.googleapis.com | API key in the URL | Gemini generateContent (<base>/v1beta/models/<model>:generateContent). |
azure | https://<resource>.openai.azure.com | api-key header | Azure OpenAI; set model to the deployment name and azureApiVersion to your API version. |
vulnfanatic.callPathMaxPathsvulnfanatic.callPathIncludeBodiesvulnfanatic.callPathMaxBodiesvulnfanatic.includeCallPathSiblingsvulnfanatic.callPathSiblingMaxBodiesvulnfanatic.includeDataTypesvulnfanatic.maxTypeDefsvulnfanatic.includeVariableDataflowvulnfanatic.dataflowMaxFunctionsvulnfanatic.includeStackLayoutvulnfanatic.scanIndirectCallsvulnfanatic.validationPassvulnfanatic.validatorModelvulnfanatic.validatorProvidervulnfanatic.validatorBaseUrlvulnfanatic.validatorApiKeyvulnfanatic.minConfidencelowmediumhighlowvulnfanatic.flagUnparseableResponsesUNKNOWNvulnfanatic.skipConstantArgCallsvulnfanatic.verdictReasoningconcisefullnoneconcisevulnfanatic.runPhase1vulnfanatic.runPhase2vulnfanatic.runPhase3vulnfanatic.phase2RequireSymbolsvulnfanatic.phase2ForceEnablevulnfanatic.tokenizerEncodingvulnfanatic.offlineBuildContextvulnfanatic.debugLoggingvulnfanatic.debugAnonymousvulnfanatic.sendJsonResponseFormatvulnfanatic.tlsVerifyvulnfanatic.caBundlePathCERTIFICATE_VERIFY_FAILEDvulnfanatic.rulesPhase1Pathvulnfanatic.rulesPhase2Pathvulnfanatic.rulesPhase3Path--fuse--fused-path--dry-run--mlx_lm lora*.jsonltrain.jsonlvalid.jsonlpython -m mlx_lm loramlx_lm fuse--fuse