Performance
Cold-start latency, binary size, and the methodology behind every published number. Current --code scans attempt the Python engine automatically. --fast skips Semgrep and native SCA, but does not suppress that attempt; add --python-engine=false for a native-only fast path.
Current execution scope
- Runtime
- Prebuilt Go binaries do not require a Go compiler for basic scanning. Building from source requires Go 1.25+. Optional analyzers have their own runtime requirements; inspect coverage to confirm they actually ran.
- DAST
- URL and API probing inspect observable runtime behavior. Intrusive probes require --enable-active. An HTTP 200 response alone does not prove access to sensitive data.
- SAST
- Native static rules cover supported Go, JavaScript, Java and infrastructure patterns. Semgrep and the Python engine attempted by code scans extend applicable checks when available. Language support does not imply complete framework or path coverage.
- SCA
- Go analysis uses govulncheck. Python and npm dependency scanners use supported manifests and lockfiles, including poetry.lock, Pipfile.lock and package-lock.json. Advisory queries can transmit package names and versions.
- Evidence and policy
- Correlation connects supported matching observations; not every finding has multiple sources. Strong single-source evidence can be sufficient. Severity, confidence, finding disposition and release recommendation are distinct.
- Coverage and human review
- An analyzer failure or missing required evidence can leave the release incomplete. A PASS is a result under the declared policy and coverage, not a guarantee of security or a human approval.
The latency and binary-size tables below are historical measurements, each labelled with the engine version that produced it. They are not current-release figures or a promise for your hardware.
Read deployment-specific data handlingCold-start latency
Time from fendix scan exec to JSON-on-stdout exit. Includes process spawn, argv parse, scan setup, scan execution, JSON render, and exit. Cache wiped between every run so each invocation pays full cold-start cost.
| Configuration | p50 | p95 | mean | CI exit gate |
|---|---|---|---|---|
| v1.0.0— historical v1.0 default measurement with the expanded white-box and dependency phase | 43 ms | 44 ms | 43 ms | ✅ 11× under 500 ms |
v1.0.0 + --python-engine— historical v1.0 explicit Python-engine measurement; approximately on par | 43 ms | 46 ms | 44 ms | ✅ 11× under 500 ms |
| v0.11.0 default— what v0.11 ships | 6.1 ms | 7.0 ms | 6.1 ms | ✅ 82× under 500 ms |
v0.11.0 + --python-engine— opt-in legacy whitebox path (including its path-traversal check) | 40.7 ms | 45.1 ms | 41.3 ms | ✅ 12× under 500 ms |
| v0.9.0 default— baseline after the embedded Python runtime was removed | 5.6 ms | 6.3 ms | 5.6 ms | ✅ 89× under 500 ms |
| v0.8.0— pre-Phase-17b, embedded Python | 7.3 ms | 8.1 ms | 7.2 ms | ✅ 68× under 500 ms |
What the extra milliseconds pay for.
A --code scan does more than grep for secrets: native analyzers cover text patterns, Semgrep, and dependency CVEs, while the Python engine adds AST and Proven-Path analysis when available. The historical expanded-suite benchmark sits near ~43 ms and still clears the 500 ms CI gate 11× over. --fast skips Semgrep and native SCA; add --python-engine=false when the native-only fast path is required.
v1.0.0 default vs explicit Python
≈ on par
43 ms vs 43 ms p50 — the Python-engine startup cost that dominated earlier versions is now a rounding error next to the native scanners.
Where the time goes
whitebox phase
The historical v1.0 trace adds a white-box and dependency phase after secrets. Current --fast skips Semgrep and native SCA, but code scans can still attempt Python; add --python-engine=false for the native-only fast path.
CI exit gate
~11× under
Target is <500 ms p50 for code-only scans. v1.0.0 default = 43 ms p50 (min 42 ms), comfortably inside the gate.
Methodology
We measure the user-perceived cost — wall-clock time from typing fendix scan to receiving the JSON. Go-internal benchmarks would only capture the middle of that path; the harness uses Python's time.monotonic() around subprocess.run so process spawn, argv parse, scan setup, scan execution, JSON render, and exit are all included.
Fixture
python/tests/fixtures/secrets_target/ — 5 small files, 30 secrets findings. A tiny fixture isolates the fixed cost of cold start from the variable cost of scanning a large codebase. Bigger codebases will show bigger absolute numbers; the delta between PRE/POST stays the same (it's the cost of Python startup we removed).
Sample size
N=30 runs per configuration. The ~/.fendix/engine directory is wiped before every run so each invocation pays the full cold-start cost (no warm cache).
Hardware
Apple M-series, both binaries built with go build -ldflags="-s -w". Other architectures were not measured.
Binary size
| Build | Bytes | Δ |
|---|---|---|
| Before: embedded Python runtime | 19,002,482 | — |
| After: no embedded Python runtime | 18,903,282 | −99,200 (−0.5 %) |
The size delta is small because Go's binary build compresses embedded text aggressively. Standalone release binaries no longer embed the Python tree. Native analyzers still run without Python; a code scan records an unavailable implicit engine as a coverage gap unless that attempt is disabled.
Reproduce locally
Every number on this page is reproducible. Clone the engine repo and run the harness — same methodology, same fixture, same harness as the published numbers.
# Build the binary then run the cold-start benchmark
make build
python3 scripts/bench/coldstart.py
# Output (v1.0.0, Apple M-series arm64, 2026-07-01; 30 runs, ~/.fendix/engine wiped per run):
#
# binary: ./bin/fendix
# fixture: python/tests/fixtures/secrets_target
# runs: 30
#
# [historical v1.0 default] N=30 min=41.7 p50=42.9 p95=44.4 mean=43.3 (ms)
# [v1.0 + --python-engine] N=30 min=42.1 p50=43.2 p95=45.7 mean=43.5 (ms)
#
# Note: the default --code path runs the full native check suite (secrets,
# semgrep, deps, textscan, + DAST checks), so cold-start is higher than the
# v0.11 secrets-only path. The explicit Python run was on par; both
# stay an order of magnitude under the 500 ms CI gate.Require the Python path
Code scans attempt the Python checks automatically. To make a missing engine a hard error, add --python-engine — requires a local python/ tree or FENDIX_ENGINE pointing at one.
# Current --code scans attempt the Python engine automatically
fendix scan --code ./src --format json
# Require Python (missing engine exits 2)
fendix scan --code ./src --python-engine --format json
# Native-only path for a standalone binary without the Python tree
fendix scan --code ./src --python-engine=false --format jsonGoing deeper
- /accuracy — current v3.6.3 synthetic SAST measurements and historical v0.11.0 observations, with reproduction steps and limits. These are not production accuracy estimates.
- Changelog — the v0.9→v0.11 cold-start work (native Go secrets, Semgrep wrapper, no embedded Python).
- CLI reference — every flag, including
--python-engineand--no-native-deps. - Architecture — ADR-001 + the native-engine evolution note.
- docs/benchmarks.md — upstream source of the numbers on this page (kept in sync).