ما الجديد
سجل إصدارًا بإصدار لتطوير Fendix. صدر الإصدار v3.4.1 في سبتمبر 2026 — تُبنى صور المحرك الآن بلغة Go 1.27 وتشحن بها، فيرتفع سقف الوحدات المدعومة في govulncheck من 1.25؛ ولا تغييرات في الفاحصات أو البصمات أو رموز الخروج، ويبقى الحد الأدنى لإصدار وحدات Go عند 1.25. وفي v3.4.0 — لم يعد بالإمكان أن يبدو المسح مكتملًا وهو ليس كذلك: يسجّل كل محلل ما إذا كان قد عمل ولماذا لم يعمل، ويحمل التقرير كتلة تغطية تسمّي الفجوات، ويكتب مسح URL الذي لا يجد نقاط نهاية تقريره بدل أن يختفي، وتحوّل رايتان اختياريتان فجوة التغطية إلى بناء مُخفق. وفي v3.3.0 — لم تعد الترقية تقسم النتيجة التي تتابعها إلى مغلقة وجديدة، وصارت قائمة الفحوص تُبنى مما اكتمل لا مما جرى إعداده. وفي v3.2.0 — وفيه صار كل قرار حظر يشرح نفسه. تحمل نتيجة BLOCK الآن السياسة التي اتُّخذت بموجبها وصنف الدليل الذي برّرها، عبر تخزين Fendix نفسه وحتى ملف SARIF الذي تصدّره، فيصير خط الإنتاج المخفق قابلًا للتفسير من التقرير وحده. وتذكر النتائج المحجوزة سبب حجزها، وتأتي درجات الثقة مصحوبة بالقواعد التي أنتجتها، ويُوسم صراحةً أي بناء حُظر لمجرد أن فرض الثقة كان مُعطَّلًا. وأوقف الإصدار v3.1.0 ادعاء النتائج بأكثر مما أثبتته — من CORS بحرف بدل مع بيانات الاعتماد، إلى ترتيب أولويات تحديد المعدّل، وتقييم الترويسات بحسب نوع الاستجابة، وصياغة اجتياز المسارات، وبيانات الاعتماد الاختبارية، وقابلية تطبيق الاعتماديات. ومنح الإصدار v3.0 النتائج هوية مستقرة مبنية على القاعدة والملف والرمز والعملية بدل رقم السطر؛ أعد حفظ خطوط الأساس مرة واحدة بعد الترقية من إصدار أقدم منه.
Trust fixes — pip-audit naming, OSV batch queries, verify scope
May 14, 2026
- pip-audit naming gap fixed. The Python dep-CVE scanner was marking every finding from the in-process pip-audit path with the tool's internal name instead of the human-facing advisory title. Findings now surface as
<package>@<version>: <CVE-ID> — <advisory title>matching what users see inpip auditoutput. New--no-pip-audit-fallbackflag disables the local-list fallback for strict environments. - OSV batch queries for pip and npm. The PyPI and npm dep-CVE scanners now send a single
POST /v1/querybatchrequest with all (package, version) pairs instead of N sequential single-package queries. Typical reduction: 30 packages × 1 query → 1 batch query. Both scanners respect a new--osv-concurrency <n>flag (default 4) to cap parallel batch requests when the project has many lockfiles. - `fendix verify` scope fixed.
fendix verify --id SEC-042now exits 1 when the finding is confirmed (vulnerability still present) and exits 0 when it is resolved — matching the documented intent. Previously the exit codes were inverted. New explicit--confirmed-okflag for the rare case where an operator wants the CI job to pass even on confirmed findings.
Engine evaluation — 3-track real-world accuracy scorecard
May 13, 2026
- Three independent evaluation tracks against v0.11.0 — synthetic precision/recall against canonical patterns, real-world DAST against OWASP Juice Shop, real-world SAST against PyGoat's Django OWASP Top 10 demo. All three say the engine catches what it claims at the latency the benchmark publishes on the breadth real-world codebases need. Headline numbers and the methodology now live on a dedicated <Link href="/accuracy">/accuracy</Link> page.
- Track 1 — Synthetic labeled corpus (56 cases, 7 categories): F1 = 1.000, precision = 1.000, recall = 1.000. 38 true-positives / 0 false-positives / 0 false-negatives across sqli / cmdi / path-traversal / ssrf / open-redirect / xss / secrets. The corpus exists in
scripts/accuracy/corpus/; ground truth inscripts/accuracy/manifest.json; harness inscripts/accuracy/run.py. - Track 2 — OWASP Juice Shop refresh vs v0.6.1 baseline: 12 findings (5 CRITICAL / 4 MEDIUM / 2 LOW / 1 INFO) in 27 s. +5 CRITICALs vs v0.6.1, scan duration −35 %. All 5 new CRITICALs are exposed-config-file detections (CWE-538):
.DS_Store,.envfamily (3 paths),.git/*(3 paths),.htaccess,.htpasswd. Caveat: juice-shop's SPA returns 200 for unknown paths so these could be SPA-fallback responses — still a real security issue (cache poisoning + WAF confusion). - Track 3 — PyGoat real-world SAST: 147 findings in 17.1 s on a 52-Python-file Django app. 1 CRITICAL (unsafe pickle deserialization), 146 HIGH. Categories: 135 deps (real CVE-tagged: certifi / cryptography / django), 9 injection (eval / subprocess(shell) / SSRF / innerHTML XSS / yaml.unsafe_load / open-redirect at 9 sites / pickle), 3 secrets. Every OWASP Top 10 category PyGoat advertises was detected.
- 5 real engine improvements shipped during the evaluation arc: (1)
_is_open_redirectupgraded to taint-chain posture parity — pre-fix the detector only matched directredirect(request.args.get('x')); multi-hop assignments were silently missed; the other 6 reachable sinks already had the chain treatment. Open-redirect recall: 0/3 → 3/3. (2) cmd-injection posture aligned with the other reachable sinks via new_cmdi_arg_is_dangeroushelper —os.system('echo hello')no longer fires HIGH on literal-string args. Precision: 0.833 → 1.000. (3) OrchestratorrunWhiteboxScannow resolvescode_pathandspecto absolute paths before sending the ScanRequest — pre-fix the Python subprocess silently saw 0 files on every real codebase using--python-enginewithoutFENDIX_ENGINEset. (4)run-juice-shop.shportability fix: replaced GNUtimeout(not on macOS) with--max-duration. (5) 3 cmdi unit tests updated to assert the new no-emit-on-literal posture. - Caveats documented honestly. The synthetic 1.000 means fendix never misses these 56 canonical patterns — not that it never misses anything. PyGoat lacks a machine-readable ground-truth manifest so we report category coverage rather than precision/recall. Juice Shop's SPA-fallback means the CRITICALs could be SPA-fallback responses (still a real issue, but remediation may be server-config not secret-rotation). The full caveat list + follow-up backlog lives in
docs/accuracy.md.
FP discipline + path-traversal reachability
May 13, 2026
- The engine kept its fast cold-start while adding three new detection paths. This release ships false-positive round 2 plus path-traversal detection, and the engine held its v0.8.0 cold-start (6.1 ms p50 default) while adding three new detection paths on top.
- Native-Go exposed-config-file scanner. New
internal/scanner/configleak.go(~280 LOC). 32 patterns (27 basename + 5 directory prefix) cover.env/.env.local/.env.production, web-server config (.htaccess,.htpasswd,web.config), package-manager creds (.npmrc,.pypirc,.netrc), Docker (docker-compose.override.yml), IDE/OS leftovers (.DS_Store), and directory-style leaks (.git/,.aws/,.ssh/). Fires CRITICAL with CWE-538 on any 2xx response to a known config-file path. Body sample (capped 512 bytes) gets[REDACTED]masking of common secret-shape tokens before landing in evidence — no engine leak of leaked credentials. Inverts the prior FP shape where the engine would emit noisy MEDIUMmissing-CSP-on-/.envfindings into one CRITICALexposed config filefinding. 9 race-clean unit tests. - Path-traversal as the 7th reachable taint-chain sink class. New
SEC-PY_PATH_TRAVERSALfinding (CWE-22, HIGH severity, MEDIUM confidence, category=injection). Four filesystem-path sinks recognised:open(x)(stdlib),Path(x)/pathlib.Path(x),send_file(x)(Flask),send_from_directory(safe_dir, x)(Flask — the user-controlled arg is at index 1, handled by_path_traversal_arg_index). When user input flows to the path arg,_collect_taint_chainrecords the chain and setsreachable: true; both the correlator and orchestrator severity escalations apply. Brings the engine's reachable sink categories to 7: SQLi / SSRF / open-redirect + XSS + cmd-injection + path-traversal. 9 new Python tests. - `fendix ignore` subcommand tree (
internal/ignorecmd/, ~300 LOC). Three subcommands close the suppression-bookkeeping gap:fendix ignore listrenders a tabular view with EXPIRED / expiring-soon (within 30 days) / active / no-expiry / INVALID-DATE status;fendix ignore validatereports schema and date errors with non-zero exit for CI gating;fendix ignore prune [--dry-run]removes expired rules and rewrites the file (preserves rules with invalid dates —validatesurfaces those). All three default to.fendix-ignorein cwd;--file <path>targets a different file. 14 race-clean unit tests. - Cold-start benchmark refreshed. Default v0.11 = 6.1 ms p50 (+0.5 ms vs v0.9, within noise — configleak doesn't fire with zero discovered endpoints on the secrets-only fixture).
--python-engineopt-in v0.11 = 40.7 ms p50 (+16.3 ms vs v0.9 — real cost of the new path-traversal sink in the AST analyzer). Net: default v0.11 is still 16 % faster than v0.8.0 while adding three new detection paths on top. - Spawner bug fix.
internal/engine/spawner.gocould composepython/python/engine.pyfor users on the local-fallback path because bothengineDirand the relative script path were used together ascmd.Dir+ arg. Fixed by resolving viafilepath.Absbefore composing. Closes a silent exit-2 for every--python-engineinvocation withoutFENDIX_ENGINEset. - FP corpus re-triaged. No new shipping artifact, but the false-positive corpus catalog now records which of the 35 catalogued FPs are addressed, which the new config-file scanner picks up (dotfile inversion), and which is deferred until real post-launch user data exists (SPA-fallback dedup).
Plugin ecosystem polish
May 13, 2026
- The plugin ecosystem is genuinely usable now. The plugin wire contract from v0.7 is unchanged; what changed is the surface around it. An external author can ship a plugin against an installed binary in 60 seconds — copy-paste quickstart in the rewritten author docs, two new reference plugins in Node + Ruby prove the contract is language-agnostic,
fendix pluginssubcommand tree handles install + list, CI smoke test catches wire-contract regressions automatically. - `docs/plugins.md` rewritten for external authors. ~580 LOC restructured around the outside-contributor audience. 60-second copy-paste quickstart (TODO-comment finder, ~30 LOC) before any conceptual content; new 'How plugins fit into a scan' pipeline diagram; Python / Node / Bash skeleton entrypoints; 'Testing your plugin locally' section with inside-engine and outside-engine smoke-test recipes; 'Common errors' 8-row diagnosis table; 'Distributing your plugin' covering both
git cloneandfendix plugins install; tightened security model with explicit auth-token handling guidance; authoring checklist expanded 8 → 13 items. - Two new reference plugins in non-Go languages.
examples/plugins/license-header-check/(Node, stdlib-only, ~150 LOC) walks the source tree and flags files lacking anSPDX-License-Identifierheader.examples/plugins/dockerfile-best-practices/(Ruby, stdlib-only, ~210 LOC) walks forDockerfile/Dockerfile.*and emits up to 5 distinct findings per file (:latesttag,curl | sh,ADD <url>, root-by-default, missing HEALTHCHECK). Both ship with a README. Reference-plugin shelf is now 5 plugins covering Python ×2, Bash ×1, Node ×1, Ruby ×1. - `fendix plugins` CLI subcommand tree.
fendix plugins listenumerates discovered plugins (NAME / VERSION / MODE / DIR) using the same discovery roots a real scan uses — what's printed is exactly what would run.fendix plugins install <git-url>is a thin wrapper overgit clone --depth=1: derives the on-disk name from the URL (handles.gitsuffix, scp-stylegit@host:org/repo, query strings, trailing slashes), refuses to overwrite preexisting directories, validates the cloned tree'splugin.yamlafter clone, and removes the directory on validation failure so users never end up with a half-installed plugin that WARNs every scan. Also includes a symlink-discovery fix: plugin discovery nowos.Stats each entry beforeIsDir()-checking, so symlinked plugin dirs work too. - Plugin smoke test in CI. New
internal/e2e/refplugins_test.go(build-tage2e, ~330 LOC) under the existingmake e2eumbrella. One subtest per reference plugin (5 total) that copies the plugin into a temp scan root, runsfendix scanagainst a deterministic fixture, and asserts the expected findings flow through with the engine-attachedfendix-plugin:<name>provenance tag. Each subtest skips cleanly when its required runtime (node, ruby, python3, bash + jq) isn't installed on the CI runner — so the test still passes on minimal images while exercising every plugin on full ones. Catches wire-contract regressions before plugin authors hit them.
Cold start under 6 ms, no Python required
May 13, 2026
- Secrets and semgrep checks now run as native Go in-process scanners; the embedded Python distribution is no longer bundled in the binary. Default cold start = 5.6 ms p50 (was 7.3 ms on v0.8 — 23 % faster). fendix no longer carries a Python interpreter requirement at all in the default scan path. New
--python-engineopt-in flag re-enables the Pythonauth/injection/depschecks for users who still want them — costs ~24.4 ms p50 (4.4× the default; that gap is the actual cost of Python interpreter startup + engine extraction we removed). All numbers reproduced viascripts/bench/coldstart.py. - Native Go secrets scanner. New
internal/scanner/secrets/package ports the Python secrets analyzer to in-process Go: all 15 patterns (7 generic + 8 provider-specific — GitHubghp_*/ghs_*, Stripesk_live_*, Slackxox*, GoogleAIza*, Anthropicsk-ant-*, OpenAIsk-*/sk-proj-*/sk-svcacct-*, npmnpm_*, GCP service-account JSON) plus the.env-onlyENV_SECRETregex, walker with skip-dirs / extension gate /.env-name match / 1 MB cap / minified-JS-line skip, and evidence truncation. SameSEC-<PATTERN_ID>finding IDs as the Python implementation so any overlap (user explicitly passes--checks secrets) dedupes cleanly. Go RE2 doesn't support(?<![A-Za-z0-9])lookbehinds, so the 9 provider-prefix patterns carry aboundaryOKpost-match validator instead. Real-world parity verified against the Python fixture suite: 30 unique (title, endpoint) tuples emitted by both engines, set-diff empty in both directions. 24 race-clean unit tests. - Semgrep shelled-out, not embedded. New
internal/scanner/semgrep/package wraps the host's installedsemgrepbinary instead of running it through the embedded Python engine. The fendix rule pack (auth.yaml/injection.yaml/secrets.yaml) is bundled into the Go binary via//go:embedand extracted to a per-process temp dir on first scan. Result mapping mirrors the Python wrapper byte-for-byte:SEC-<RULE_ID>IDs,metadata.fendix_severitypreferred over Semgrep's ERROR / WARNING / INFO mapping, evidence truncated at 200 chars, title at 120. Graceful absence:exec.LookPath("semgrep")failure returnsErrSemgrepUnavailableand the orchestrator logs an install hint and continues. Non-fatal exit codes (1 for matches, 2 / 5 / 7 for rule-parse errors that still emit valid JSON) are absorbed in parity with the Python wrapper. 28 race-clean unit tests cover mapping, graceful absence, ctx cancellation, fake-semgrep happy path, and rule embedding extraction. - Embedded Python distribution dropped from binary + redundant Python wrappers deleted. The
Makefile'sembed-enginetarget no longer copiespython/into the embedded engine tree; the binary's//go:embeddirective bundles only a placeholder. The now-redundant Python secrets and semgrep-runner wrappers and their test files were deleted (215 → 159 Python tests; the 56 deleted tests were strictly the redundant wrapper coverage). Python whitebox spawning is now opt-in via the new--python-engineCLI flag, requires a localpython/source tree (or explicitFENDIX_ENGINEenv var), and is silently skipped when no Python engine is resolvable. Newscripts/bench/coldstart.pyreproduction harness;docs/benchmarks.mdadds a cold-start latency section with the table, methodology, binary-size delta (-99 KB), and re-run instructions. - Plugin wire-contract compatibility audit. All three reference plugins (
custom-secret-pattern,custom-blackbox-check,custom-semgrep-pack) re-verified end-to-end against the new binary: discovery works, NDJSON in/out works, findings flow through correlation + dedup unchanged. Plugins do not depend on the embedded engine, the extracted~/.fendix/engine/tree, or--python-enginebeing set. Pre-existing limitation surfaced and documented:os.ReadDir().IsDir()returns false for symlinked plugin directories, so plugins must be installed as real directories (cp -R/git clone, notln -s). Documented indocs/plugins.md. - No backend / frontend schema delta. The Go secrets and Go semgrep scanners emit findings with the same
SEC-*IDs and the same Finding fields as the prior Python path. Dropping the embedded Python distribution doesn't touch the wire shape — only the runtime topology and the cold-start cost. Plugin findings still flow through the same Finding shape. So this release ships frontend-side as version-literal bumps + this changelog entry, and backend-side as a no-op (the existing_finding_defaultsingest path already absorbed all the relevant fields).
Detection depth + FP discipline
May 12, 2026
- Native in-process dep-CVE scanners across all three ecosystems. Go (
internal/scanner/deps/govulncheck/) — uses the upstreamgolang.org/x/vuln/scanAPI in-process; inherits the same call-graph reachability filter as the upstreamgovulncheckbinary but without requiring the binary on the user's PATH. PyPI (internal/scanner/deps/pip/) — parsesrequirements.txt(== pins only; ranges deliberately skipped, matching pip-audit's posture), POSTs each (package, version) to OSV.dev/v1/query, caches responses at~/.fendix/cache/osv-pypi/<pkg>@<ver>.jsonwith 24h TTL. npm (internal/scanner/deps/npm/) — parsespackage-lock.jsonv2/v3 (full transitive tree, dedup on (name,version)), OSV.dev queries against thenpmecosystem, scoped-package aware (@scope/namehandled correctly in path parsing + cache keys), 24h cache. All three behind a single--no-native-depsescape hatch. Toolchain bump 1.21 → 1.22 required (x/tools v0.17.0 has a constant-folding bug under modern Go; v1.1.4 of x/vuln is the earliest version that picks up a fixed x/tools, and v1.1.4 needs go 1.22). - Reachable XSS taint chains. New
SEC-PY_XSS_HTML_SINKfinding (CWE-79, HIGH severity, MEDIUM confidence). Three HTML-render sinks detected:Markup(x)/flask.Markup(x)/markupsafe.Markup(x)(bypasses Jinja2 auto-escaping),mark_safe(x)/django.utils.safestring.mark_safe(x)(bypasses Django's{{ }}escaping),render_template_string(x)(Flask/Jinja2 SSTI + reflective XSS when the template body is user-controlled). When intra-function dataflow proves a request source reaches the sink, the finding carriestaint_chain+reachable: true— the correlator then applies a second severity bump for correlated-reachable pairs. - Reachable command-injection taint chains. Extended existing
PY_OS_SYSTEMandPY_SUBPROCESS_SHELLsinks with_collect_taint_chaincapture; added newPY_OS_POPENsink (deprecated form with the same shell-injection surface). Brings the engine's reachable sink categories to 6 across SQLi/SSRF/open-redirect, XSS, and cmd-injection. - FP corpus + targeted gates. New
scripts/fp-corpus/run.shrunner plus a false-positive catalog: 35 false-positives across 4 distinct root-cause patterns (test fixtures flagged as production findings, header/CORS check fires on 4xx, rate-limit check on static-file path, metrics-endpoint headers). Two math-shaped levers shipped: 4xx-response gate on header + CORS checks (a missing CSP/HSTS or CORS misconfig on a 404 page isn't actionable — early-return) and static-file path regex on rate-limit check (skips .DS_Store, favicon.ico, robots.txt, .css, .js, .map, .woff2, etc. before sending any probe requests). - One-click suppression snippet in PR comments. Every top finding in the GitHub-App-posted PR comment now ships with a fenced
yamlblock under the bullet — copy-paste straight into.fendix-ignoreto suppress. Keyed on a stable(title, category, endpoint)SHA-256 hash (truncated to 8 hex chars in a trailing# fp-<hash>comment) so the suppression survives SEC-NNN reassignment across scans. Addresses the dominant FP corpus pattern: 31 of 35 FPs come from test fixtures being flagged as production findings; the snippet lets a developer suppress the whole cluster in one paste. - Severity scoring refresh — reachable_code multiplier first-class. New
ReachableMult = 1.5constant ininternal/models/scoring.go+ newCalculateSeverityReachable()function (existingCalculateSeverityis a backwards-compatible wrapper). A new orchestrator step bumps severity by one level on pure-whitebox findings withreachable: true(the correlator already bumps the correlated-reachable case inmergeFindings). The existing confidence cap still applies — the bump amplifies what the confidence allows; it can't override the cap. EPSS / KEV multipliers are explicitly deferred. - Cross-repo sync: backend persists taint_chain + reachable + affected_endpoints. The engine had been emitting these reachability fields on
Findingsince v0.7.0, but the backend's_finding_defaultssilently stripped them on ingest. New migration0006_finding_reachabilityadds 3 columns toScanFinding(affected_endpoints+taint_chainJSONField,reachableBool) with safe defaults._coerce_taint_chaindefensive parser drops malformed links rather than failing the whole finding. FrontendFindinginterface extended with newTaintLinktype + optionaltaint_chain?/reachable?fields;app/types/api.tsregenerated vianpm run codegen. No UI surfacing yet — deferred until a future findings-detail iteration when there's enough reachable-pattern volume to earn its design cost. - ADR-008: read-only AI permitted, auto-remediation permanently forbidden. New
docs/adr/ADR-008-readonly-ai.mdformalises the strategic decision. The boundary: read-only AI (finding explanation + fix suggestion as text, delivered by the cloud product) is permitted and planned. Auto-PR generation, auto-merge, and any LLM calls from the OSS engine binary are permanently forbidden. Rationale covers three options, trust-story constraints, regulatory trajectory, and four constraints on future PRs (noimport anthropicingo/orpython/).
Open & Extensible — the wedge is now defensible
May 1, 2026
- The wedge is now defensible. The correlator now distinguishes 'DAST + SAST agreed' from 'DAST + SAST agreed AND we can prove the exploit path' — the latter gets a double severity escalation, which is exactly what makes the wedge defensible against vendor noise. This release closes out the GitHub App business logic and ships open-source ratification, the plugin system, and reachability/dataflow correlation.
- Open-source posture ratified via ADR-007. What was tactical in v0.1.0 (an MIT
LICENSEshipped because the project needed *a* license) is now a deliberate strategic decision: MIT, single repo, no open-core split planned. ADR-007 records the rejected alternatives (Apache 2.0, AGPL 3.0, dual-license, open-core) and why each was the wrong fit. README hero gains a fourth bullet 'Open source under MIT — read the source, audit the wedge, fork it, ship plugins.' CONTRIBUTING.md gains a 'Licensing of contributions' section: by submitting a PR, you agree to MIT for your work; no CLA, no copyright assignment. Out-of-tree plugins choose their own license — only plugins shipped inside this repo (underexamples/plugins/) are required to be MIT to match the rest of the tree. - Plugin system: out-of-tree extension via NDJSON IPC. New
internal/pluginpackage:Discoverwalks<repo>/.fendix/plugins/(repo-local, takes precedence) +~/.fendix/plugins/(user-global), parses each child'splugin.yamlwith strictKnownFields(true)parsing (typos likeentry_point→ loud error, not silent drop), and dedups by name.(*Plugin).Runinvokes the entrypoint with a JSONScanRequeston stdin and reads NDJSON Findings on stdout — same wire contract as the embedded Python engine (ADR-002), so plugin authors writing in Python can reuse most ofengine.py. Per-plugin timeout (default 30s, max 5m) bounds wall-clock; partial findings before a kill are preserved; plugins inheritFENDIX_PLUGIN_NAME+FENDIX_PLUGIN_DIRenv vars; every emitted finding getsfendix-plugin:<name>appended to References for provenance. Plugin findings flow through the same Correlate / Dedup / Sort / ID-assignment pipeline as embedded engine findings — a custom-secret-pattern plugin correlates against blackbox auth checks identically to the built-in secrets analyzer. New `--no-plugins` CLI flag disables discovery for sandboxed CI or debugging. Three reference plugins underexamples/plugins/:custom-secret-pattern(Python, regex-based custom secret detection),custom-blackbox-check(Python, custom HTTP-response assertion),custom-semgrep-pack(shell, wraps a custom Semgrep rule pack). Author guide:docs/plugins.mdcovers discovery, IPC schema, security model, and an authoring checklist. - Reachability/dataflow correlation: `correlated:reachable` for proven exploit paths. The Python AST analyzer now records taint chains for SQLi, SSRF, and open-redirect findings: when
_collect_taint_chainproves intra-function dataflow from a request source (request.args/POST/form/data/json/headersplus the Flask handler-arg formreq) through one or more variable assignments to a dangerous sink, the emitted finding carriestaint_chain: [{file, line, expr}, …]plusreachable: true. The chain walks recursively through scope assignments —q = request.args.get('q'); sql = '...' + q; cursor.execute(sql)resolves three links without false positives on literal-only chains. Correlator escalation: when the whitebox half of a correlated pair carries a chain, the merged finding inherits the chain plusreachable: trueAND gets a *second* severity escalation. So MEDIUM blackbox + MEDIUM whitebox + reachable jumps to CRITICAL (vs. HIGH without reachability). HTML reporter renders the chain as an ordered list under finding details ('Reachable dataflow (N steps)'). - GitHub App business logic wired end-to-end. Replaced the v0.6.1 scaffold's stub handlers with the full PR workflow on top of the credentials/auth layer. On every
pull_request.{opened,synchronize,reopened}, the webhook handler now: (1) fetches an installation token from the cachedTokenSource; (2) clones the PR head SHA viagit init+ shallowfetch --depth=1 origin <sha>+checkout FETCH_HEAD(only the exact commit, no history; auth viax-access-token:<token>@…userinfo on the HTTPS clone URL); (3) runsfendix scan --code <tmp> --format json; (4) re-renders SARIF viafendix report --format sarifso the PR comment + Code Scanning tab describe identical findings; (5) renders a Markdown PR comment matching theexamples/github-actions/fendix-scan.ymlgithub-script template byte-for-byte modulo whitespace; (6) POSTs the comment to/repos/{o}/{r}/issues/{n}/comments; (7) gzip+base64-encodes the SARIF and uploads to/repos/{o}/{r}/code-scanning/sarifsagainstrefs/pull/<n>/head. SARIF upload is best-effort (Code Scanning disabled orsecurity_events: writemissing → log warning, comment still posts). `check_run.action == "rerequested"` re-runs the scan against the recorded head SHA. Tempdir always cleaned up viadefer os.RemoveAll. Tokens redacted from any error surfaced for git-step failures. Per-scan timeout: 15 minutes wall-clock. Distribution: newDockerfile.app(multi-stage; bundlesfendix+fendix-app+ Python engine +git+ tini in ~250 MiB Debian-slim). 26 new ghapp tests under-race. - Vulnerable-app benchmark numbers captured. Stock
fendix scan --url http://localhost:3000againstbkimminich/juice-shop:v17.1.1on Fendix v0.6.1 produced 97 endpoints discovered / 7 deduped findings (4 MEDIUM + 2 LOW + 1 INFO) / 41.5s scan time / 0 correlated (passive-only run; juice-shop's intentional SQLi/XSS/IDOR vulns need--enable-activeand/or--codeto surface). Pre-dedup the engine emitted 391 raw findings that deduplication collapsed to 7. Numbers published indocs/benchmarks.md'Latest results' table with a 'Reading the row' explanation that documents what the row does NOT measure. - GitHub App scaffold. New
cmd/fendix-appbinary (separate from thefendixCLI; long-running webhook server) plusinternal/ghapppackage andapp/manifest.ymlfor one-click App registration via GitHub's manifest flow. Webhook layer: HMAC-SHA256 signature verification (legacysha1=rejected), event router, 4 MiB body cap. Auth layer: pure-stdlib RS256 App-JWT signing (nogolang-jwtdep added — preserves the project's zero-runtime-deps posture),/app/installations/{id}/access_tokensexchange, single-flight installation-token cache. Setup guide:docs/github-app.md. 28 unit tests under-race. Marketplace listing is an operator step distinct from the code deliverable. - `fendix demo` command. New cobra subcommand spins up
bkimminich/juice-shop:v17.1.1in Docker onlocalhost:3000, runs a stock scan, renders an HTML report, and (with--open) opens it in the user's default browser. Container always cleaned up on exit. Flags:--open,--port,--output,--image. Removes the cold-start 'what does a real scan look like?' question for first-time evaluators. - `.fendix.yaml` repo-committed policy. New
internal/policypackage + new--config <path>flag onfendix scan. Teams commit a.fendix.yamlat repo root encoding scan posture (severity threshold, scan budgets, auth profile reference, crawler defaults, format) and invokefendix scanwith one CLI flag instead of the prior six-flag-and-growing wall. Precedence matchesgit config: cobra defaults <.fendix.yamlvalues < explicit CLI flags. Strict YAML parsing (yaml.KnownFields(true)) rejects typos. Schema versioned; future v2 forward-rejected. `fendix init` now writes 3 files (workflow +.fendix.yaml+.fendix-ignore). - Backend not extended for plugins, `--config`, or the GitHub App at v0.7.0 release time. Plugins run on the host filesystem (backend container can't see them);
--configis a host-filesystem flag (the API itself accepts every policy field directly);fendix-appis a separate deployable on the GitHub-event side of the pipeline. - Reachability fields persisted backend-side and typed frontend-side. v0.7.0 originally landed
reachable+taint_chain+affected_endpointson the engine'sFindingJSON output, but the backend's_finding_defaultssilently stripped them on ingest, soGET /api/findingsserved lossy rows. Closed in a follow-up: backend migration0006_finding_reachabilityadds 3 columns toScanFinding(affected_endpoints+taint_chainJSONField,reachableBool) with safe defaults;_coerce_taint_chaindefensive parser drops malformed links rather than failing the whole finding; serializer exposes all three;openapi.jsonregenerated; 3 new tests. Frontend gains aTaintLinkinterface andFinding.taint_chain?+Finding.reachable?;app/types/api.tsregenerated vianpm run codegen. No UI surfacing yet — deferred until the engine ships XSS and command-injection reachability patterns; a real findings-detail iteration earns its design cost against 5 reachable categories, not 1.
Patch — install.sh `mkdir -p` fix
May 1, 2026
- Critical install-pipe fix.
scripts/install.shnowmkdir -pthe install directory before themv. Previously,curl -fsSL https://get.fendix.dev/install.sh | FENDIX_DIR=$HOME/.local/bin shfailed on any system where$HOME/.local/bindidn't pre-exist withmv: cannot move 'fendix' to '/home/runner/.local/bin/fendix': No such file or directory. Blocked the benchmark CI on every run and broke any first-time user who setFENDIX_DIRto a non-existent dir. The fix triesmkdir -pnon-sudo first, falling back tosudo mkdir -ponly when a parent up the chain isn't writable — POSIX-sh clean. Mirrored toget.fendix.dev/install.shautomatically by the release pipeline's mirror-sync job - `fendix init` zero-config workflow generator. New
fendix initsubcommand detects the project's stack (Go viago.mod, Python viapyproject.toml/requirements.txt/setup.py/Pipfile, Node.js, Ruby, Rust, Java/Kotlin, PHP) plus a colocated OpenAPI/Swagger spec at any of 14 conventional paths. Writes.github/workflows/fendix.yml(drop-in PR-gated DAST + SAST scan, embedded viago:embed) plus.fendix-ignore(commented starter for finding-level suppressions). Refuses to overwrite by default;--forceoverrides;--printdry-runs to stdout. Note:fendix initwas later extended to also write.fendix.yaml— see the v0.7.0 entry - README repositioned around correlated evidence. Hero moved beyond generic scanner positioning to explain how Fendix combines DAST and SAST evidence in one PR check. The release also added clearer trust signals for the single binary, signed releases, and telemetry-free execution.
- 'What Fendix sends to the network' section at top of README. Five-row table covering default scan / active probing / white-box / no-flags / telemetry. Explicit 'no telemetry code; verify with tcpdump' claim. Plus a 'Verifying signed releases' section with the full cosign keyless verify recipe
- Vulnerable-app benchmark scaffold. New
scripts/benchmark/run-juice-shop.sh+make benchmark+.github/workflows/benchmark.yml(workflow_dispatchonly) +docs/benchmarks.md. CI workflow installs Fendix viahttps://get.fendix.dev/install.sh(doubles as install-pipe smoke test). Real juice-shop numbers captured in the v0.7.0 entry
صدر الإصدار v3.4.1 في سبتمبر 2026 — تُبنى صور المحرك الآن بلغة Go 1.27 وتشحن بها، فيرتفع سقف الوحدات المدعومة في govulncheck من 1.25؛ ولا تغييرات في الفاحصات أو البصمات أو رموز الخروج، ويبقى الحد الأدنى لإصدار وحدات Go عند 1.25. صدر الإصدار v3.4.0 في سبتمبر 2026 — لم يعد بالإمكان أن يبدو المسح مكتملًا وهو ليس كذلك: يسجّل كل محلل ما إذا كان قد عمل ولماذا لم يعمل، ويحمل التقرير كتلة تغطية تسمّي الفجوات، ويكتب مسح URL الذي لا يجد نقاط نهاية تقريره بدل أن يختفي، وتحوّل رايتان اختياريتان فجوة التغطية إلى بناء مُخفق. صدر الإصدار v3.3.0 في سبتمبر 2026 — ولم تعد الترقية تقسم النتيجة التي تتابعها إلى مغلقة وجديدة، وصارت قائمة الفحوص تُبنى مما اكتمل لا مما جرى إعداده. وفي v3.2.0 — وفيه صار كل قرار حظر يشرح نفسه. تحمل نتيجة BLOCK الآن السياسة التي اتُّخذت بموجبها وصنف الدليل الذي برّرها، عبر تخزين Fendix نفسه وحتى ملف SARIF الذي تصدّره، فيصير خط الإنتاج المخفق قابلًا للتفسير من التقرير وحده. وتذكر النتائج المحجوزة سبب حجزها، وتأتي درجات الثقة مصحوبة بالقواعد التي أنتجتها، ويُوسم صراحةً أي بناء حُظر لمجرد أن فرض الثقة كان مُعطَّلًا. وأوقف الإصدار v3.1.0 ادعاء النتائج بأكثر مما أثبتته — من CORS بحرف بدل مع بيانات الاعتماد، إلى ترتيب أولويات تحديد المعدّل، وتقييم الترويسات بحسب نوع الاستجابة، وصياغة اجتياز المسارات، وبيانات الاعتماد الاختبارية، وقابلية تطبيق الاعتماديات. ومنح الإصدار v3.0 النتائج هوية مستقرة مبنية على القاعدة التي أطلقت النتيجة، وفي أي ملف، وداخل أي دالة، وحول أي عملية. أعد حفظ خطوط الأساس مرة واحدة بعد الترقية من إصدار أقدم من v3.0، وأعد كتابة أي قاعدة .fendix-ignore تثبّت قيمة fingerprint: — أما القواعد التي تطابق بالمسار أو الفئة أو معرّف القاعدة فلا تتأثر.