Skip to content
Back to Docs

Performance

Cold-start latency, binary size, and the methodology behind every published number. The default --code scan adds a whitebox + dependency-scan phase (govulncheck / dep-CVE) on top of the secrets pass — broader coverage out of the box, at a flat ~40 ms cold-start cost that the v0.11 secrets-only path didn't pay. Use --fast to skip it.

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 opt-in Python engine extend applicable checks. 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.

Benchmark tables below are historical, scoped observations. Counts and synthetic regression scores are not production precision/recall. No replacement measurements are claimed here.

Read deployment-specific data handling

Cold-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.

Configurationp50p95meanCI exit gate
v1.0.0— current default; adds a whitebox + dep-scan phase after secrets (~40 ms, mostly fixed)43 ms44 ms43 ms✅ 11× under 500 ms
v1.0.0 + --python-engine— opt-in whitebox engine; ~on par with default, since the default already pays the whitebox-phase cost43 ms46 ms44 ms✅ 11× under 500 ms
v0.11.0 default— what v0.11 ships6.1 ms7.0 ms6.1 ms✅ 82× under 500 ms
v0.11.0 + --python-engine— opt-in legacy whitebox path (+ historical change path-traversal)40.7 ms45.1 ms41.3 ms✅ 12× under 500 ms
v0.9.0 default— historical change baseline5.6 ms6.3 ms5.6 ms✅ 89× under 500 ms
v0.8.0— pre-Phase-17b, embedded Python7.3 ms8.1 ms7.2 ms✅ 68× under 500 ms

The extra milliseconds buy a lot more scanning.

The default --code scan does more than grep for secrets: it runs a full whitebox + dependency-scan phase — an AST taint analyzer (interprocedural data-flow with Proven-Path route binding), transitive dependency-CVE (govulncheck + OSV), and a DAST module spanning cookie flags, open-redirect, host-header, GraphQL, method-tampering, and rate-limiting. That deeper coverage is why cold-start sits near ~43 ms rather than a few — and it still clears the 500 ms CI gate 11× over. Need the old speed for a pre-commit hook? --fast drops the heavy phase.

v1.0.0 default vs opt-in

≈ 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

v0.11 default ran secrets only and finished in ~2 ms. The current default adds a whitebox + dependency-scan phase (the ~40 ms between 'secrets complete' and 'whitebox complete' in the trace). It's a flat cost, not proportional to repo size, and not semgrep (skipped if absent) — pass --fast to drop it for pre-commit speed.

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". Numbers will scale predictably on x86_64 — the relative deltas between PRE/POST and default/opt-in hold across architectures.

Binary size

BuildBytesΔ
PRE-historical change (with embedded Python)19,002,482
POST-historical change (no embedded Python)18,903,282−99,200 (−0.5 %)

The size delta is small because Go's binary build compresses embedded text aggressively. The real win is the dependency posture — fendix no longer carries a Python interpreter requirement at all in the default path; users can now run scans on machines without Python installed (CI runners, distroless containers, embedded systems).

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.

terminal
# 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
#
#   [default (no Python whitebox)] N=30  min=41.7 p50=42.9 p95=44.4 mean=43.3 (ms)
#   [--python-engine (opt-in)]     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. With --python-engine ~on par with default, both
# stay an order of magnitude under the 500 ms CI gate.

Try the opt-in path

Want the legacy Python whitebox checks (auth, injection, AST analysis)? Add --python-engine — requires a local python/ tree or FENDIX_ENGINE pointing at one.

terminal
# Default — no Python required, fastest path
fendix scan --code ./src --format json

# Opt back into the Python whitebox engine
# (requires a local python/ tree or FENDIX_ENGINE pointing at one)
fendix scan --code ./src --python-engine --format json

Going deeper

  • /accuracy — precision/recall scorecard: F1 = 1.000 on the labeled synthetic corpus, plus the real-world DAST and SAST target results.
  • v0.9.0 changelog — the v0.9→v0.11 cold-start work (native Go secrets, Semgrep wrapper, no embedded Python).
  • CLI reference — every flag, including --python-engine and --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).