Skip to content
03RELEASE NOTES

What's new

A phase-by-phase history of Fendix development. v0.19.0 shipped June 2026 — scan straight from an uploaded OpenAPI/Swagger spec file (JSON or YAML, size-capped and billion-laughs-safe), plus a false-positive precision pass: the FastAPI missing-auth check is now scoped to real FastAPI routes (no more Django/DRF/Celery/Pydantic false positives) and the password-exposure guard ignores i18n label tables while still catching real plaintext leaks. It builds on v0.18.0's DAST module upgrade: the black-box scanner runs 15 checks (was 8), adding cookie-flags, open-redirect, reflected XSS, in-band SSRF, host-header injection, GraphQL introspection, and HTTP method tampering, with 3 critical fixes and 45 false-positive reductions. It builds on the 90-Day Cut (v0.16.1): diff-aware scans on every commit (~18ms on a 200-file monorepo), a pre-commit hook, Proven Path SARIF codeFlows, and transitive Python SCA via poetry.lock / Pipfile.lock — and on the enterprise-readiness roadmap (through v0.14.1): unified Go/JS/IaC textscan SAST, PDF executive reports, Arabic HTML i18n, air-gapped offline CVE mode, Jira + Slack/Teams integrations, and a 24-rule Semgrep pack. The engine's sub-7 ms cold-start (Phase 17b) is maintained throughout.

v0.19.0 Complete

Spec-file upload + false-positive precision — scan from an uploaded OpenAPI spec

June 2026

  • **Scan from an uploaded OpenAPI/Swagger spec file.** You can now start an API scan by uploading a spec file directly, instead of only pointing at a hosted `/schema/` URL — for internal or pre-prod APIs whose schema isn't publicly reachable, or specs exported from other tools. JSON and YAML are accepted; the upload is size-capped and parsed with YAML aliases disabled (billion-laughs-safe) before the engine reads it.
  • **FastAPI auth check scoped to real FastAPI routes.** The "route missing authentication dependency" check matched any `@object.method(...)` decorator with no framework check, so on Django/DRF projects it flagged Celery tasks (`@shared_task`), DRF actions (`@action`), drf-spectacular getters (`@extend_schema_field`) and even Pydantic validators (`@field_validator`) as unauthenticated FastAPI routes. It now fires only inside files that actually use FastAPI, only on real HTTP-verb routes, and recognises `Depends` / `Security` / `Annotated[...]` auth — eliminating that false-positive class (a real Django monorepo dropped from 256 to 10 findings, the 10 being genuine unauthenticated routes in embedded FastAPI services).
  • **Sharper password-exposure guard.** The black-box password-field check no longer fires on i18n/label dictionaries (where `"password"` is just the translated UI label), while genuine plaintext-credential leaks — verbose user records, combined payloads, and non-Latin credentials — are still flagged. The guard now keys off the matched value's shape instead of a blunt body-wide heuristic.
  • **Taint-analysis false positives suppressed.** A batch of whitebox taint false positives surfaced by real-world scans were closed, each backed by a regression case, tightening precision without dropping recall.
v0.18.1 Complete

CI-integration hardening — container fix, fewer false positives, stable suppressions

June 2026

  • **Whitebox taint engine now loads in containers.** The runtime image set `FENDIX_PYTHON_ENGINE`, but the binary resolves the engine via `FENDIX_ENGINE` — so in every Docker run the Python taint engine silently fell back to a missing path and disabled itself, leaving only the native Go scanners. The Dockerfile now exports the variable the engine actually reads, so interprocedural auth/injection taint analysis runs in CI and any other container.
  • **Fewer secret false positives, at the source.** The secrets scanner now recognises a *reference* to a secret (an AWS Secrets Manager ARN, an `rds!db-` managed-secret name, a GCP `projects/*/secrets/*` resource) versus an actual credential value, and filters unambiguous placeholders (`YOUR_TOKEN_HERE`, `<...>`, `${VAR}`, `changeme`). Deliberately narrow — it never suppresses on the bare substring `example`, so AWS's documented example keys are still flagged. These two classes previously needed hand-written per-line suppressions.
  • **Stable finding fingerprints for durable suppressions.** Every finding now carries a content-derived `fingerprint` (hash of category + location + title) that doesn't change between runs, unlike the positional `SEC-NNN` id. `.fendix-ignore` gains a `fingerprint:` rule (and `fendix ignore validate` recognises it) so a suppression keeps matching across scans and code moves instead of silently drifting onto the wrong finding. The baseline diff shares the same identity.
  • **Coverage is no longer silent + baseline fails closed.** Reports now include `endpoints_discovered` and `endpoints_truncated` so a scan capped by `--max-endpoints` is distinguishable from a complete one (no more invisible partial coverage). And a corrupt `--baseline` file now fails the run (exit 2) instead of silently scanning with no diff — a missing baseline still fail-opens for the legitimate first run.
v0.18.0 Complete

DAST module upgrade — 15 black-box checks, 3 critical fixes

June 2026

  • **Black-box DAST expanded 8 → 15 checks.** Seven new check types ship in this release: **cookie-flags** (passive, CWE-1004/614/1275 — missing `Secure` / `HttpOnly` / `SameSite`), **open-redirect** (active, CWE-601), **reflected XSS** (active, CWE-79), **in-band SSRF** (active, CWE-918), **host-header injection** (active, CWE-644/601), **GraphQL introspection** (active, CWE-200), and **HTTP method tampering** (active, CWE-650/693/285). The full registered set is now configleak, headers, cors, exposure, ratelimit, cookie-flags, auth, idor, injection, open-redirect, xss, ssrf, host-header, graphql, and method-tamper.
  • **3 criticals fixed.** `addAuth` double-prefix (auth header value getting the scheme prepended twice on retry), an SSRF-guard bypass that affected 6 active checks (the guard ran before payload mutation on those paths), and a redirect-follow path that re-issued requests without re-validating the resolved IP against the private/loopback denylist.
  • **45 verified accuracy fixes.** A batch of false-positive reductions across the active and passive probes, each backed by a regression case, sharpening precision without dropping recall on the labeled corpus.
  • **New finding categories.** The report schema now emits `xss`, `cookie`, `redirect`, `host_header`, `graphql`, and `method_tamper` alongside the existing categories, so the dashboard and SARIF output classify the new check types correctly.
v0.16.1 Complete

The 90-Day Cut — runs on every commit

June 13, 2026

  • **Diff-aware scan — `fendix scan --code . --diff --staged --fast`.** Resolves changed files via `git diff` and threads a file-allowlist through every whitebox scanner; SCA runs only when a manifest/lockfile changed. Scopes a scan to just the files a commit touches — **~18ms on a 200-file monorepo**, fast enough to run on every commit instead of only in CI. (Distinct from the existing `--baseline` finding-suppression mechanism.)
  • **Pre-commit hook — `fendix hook install`.** Drops a pre-commit script that runs the diff-aware scan (secrets + textscan, sub-second budget) and blocks the commit the moment a secret or HIGH+ finding is staged. Honours `core.hooksPath`, refuses to clobber a foreign hook, and `git commit --no-verify` is the escape hatch.
  • **Proven Path v1 (SAST taint chains).** For Python/Django/Flask/FastAPI, route-table extraction binds a request route to its handler and exports the full **route → handler → source → sink** taint chain as SARIF `codeFlows`/`threadFlows`, so GitHub renders the step-through inline in the Security tab. A new `source_tier` provenance tag (`native_go` / `tree_sitter_sidecar` / `semgrep_shim`) travels end-to-end so a regex-tier finding can never ride correlation up to CRITICAL.
  • **Transitive Python SCA — `poetry.lock` + `Pipfile.lock`.** Both lockfiles are now parsed as the full resolved dependency closure, so a CVE three dependencies deep is caught — closing the `requirements.txt`-only direct-deps gap. (Separate from the npm `package-lock.json` transitive tree shipped in v0.8.0.)
  • **Released, signed, and shipped to production.** v0.16.1 publishes a cosign-signed multi-arch Docker image with CycloneDX SBOM + SLSA provenance, and is live on the hosted platform. The GitHub Action is pinned via `uses: abdel-rahmansaied/fendix@v1`.
v0.14.1 Complete

Enterprise-readiness complete — audit pass + CI stability

May 18, 2026

  • **Enterprise-readiness plan complete (18 sprints, all ✅).** All six phases of the enterprise-readiness roadmap shipped: Phase 1 trust fixes (v0.11.1), Phase 2 unified SAST engine (v0.12.0), Phase 3 REST API via the backend SaaS layer (v0.12.1 — served by fendix-backend), Phase 4 offline + i18n + PDF (v0.13.0), Phase 5 integrations (v0.13.1), Phase 6 benchmark + CI templates + rule pack (v0.14.0). This patch closes the CI loop and locks in a stable state for the next feature cycle.
  • **CI red-state sweep (Sprint 18 audit pass).** Resolved three pre-existing CI flakies: context-cancellation tests widened to 8s budget + EPIPE tolerance on plugin stdin writes (macOS-specific pipe close sequence); two hybrid-correlator E2E tests fixed by honouring the `FENDIX_ENGINE` env var so they opt into `--python-engine` correctly on CI runners; gofmt drift corrected across 21 files that had accumulated whitespace / import-ordering skew. All 21 Go packages race-clean, 180 Python tests green, 14 E2E tests green.
  • **Pre-existing YAML quoting fix in `auth.yaml`.** The `python-jwt-decode-no-verification` rule embedded `{"verify_signature": False}` unquoted — valid to Python's ruamel-yaml but rejected by `gopkg.in/yaml.v3`. Sprint 18's new YAML catalog test surfaced it; single-quoted in this patch. Behaviour unchanged at scan time.
  • **Backend (fendix-backend) and frontend (fendix_frontend) synced to v0.14.1.** Backend absorbed all new CLI flags / schema fields via the v0.14 drift-reconciliation PR. Frontend updated with version literals, changelog entries, CLI reference (new subcommands + flags), checks page (textscan + expanded Semgrep), and integrations page (Jira + Slack/Teams + GitLab/CircleCI).
v0.14.0 Complete

Enterprise benchmark harness + GitLab/CircleCI templates + Semgrep rule pack (Phase 6)

May 18, 2026

  • **Enterprise SAST comparison harness (Sprint 16).** `scripts/benchmark-enterprise/` provides an apples-to-apples comparison of fendix vs. semgrep vs. bandit on a shared ~100-LOC fixture with 5 labeled true positives and 5 false-positive probes. Measures wall-clock, peak RSS, TP count, and FP count. Tools not on PATH are honestly reported as 'skipped' — no silent zeros. A new `benchmark-enterprise.yml` GitHub Actions workflow runs on release tags and `workflow_dispatch`, installs both competitors, and posts results as a job summary.
  • **GitLab CI + CircleCI templates via `fendix init --ci` (Sprint 17).** The init command now auto-detects the CI system from project root files (`.github/`, `.gitlab-ci.yml`, `.circleci/`) and emits a native CI template. `--ci github` (unchanged), `--ci gitlab` (`.gitlab-ci.fendix.yml` + SAST report + `NEXT-STEPS.md`), `--ci circleci` (inline snippet + `NEXT-STEPS.md`). Without `--ci`, auto-detect falls back to `github`. Every emitted YAML is parse-validated at test time so typos can't ship in a release.
  • **Semgrep rule pack expanded 9 → 24 rules (Sprint 18).** New file `go/internal/scanner/semgrep/rules/crypto.yaml` adds 4 cryptography rules. Additions across existing files: 2 new auth rules (Django function-based view missing decorator, Flask route missing auth decorator), 5 new injection rules (Django ORM raw SQL, Flask `render_template_string` SSTI, `subprocess(shell=True)` high-precision variant, `pickle.loads`, `yaml.load` without SafeLoader), 4 new secrets rules (GCP service-account JSON inline, AWS access-key ID, Slack webhook URL, PEM private key). Every rule carries `metadata.category`, `metadata.fendix_severity`, `metadata.confidence`, `metadata.cwe`. A new YAML-only catalog test enforces these invariants for every current and future rule.
v0.13.1 Complete

Integrations: GitHub App + Jira + Slack/Teams (Phase 5)

May 18, 2026

  • **GitHub App handler doc cleanup (Sprint 13).** The `internal/ghapp/webhook.go` package comment had described the scan-and-comment workflow as 'stubbed pending a follow-up commit' since TASK-107b wired the full PR lifecycle. Updated to describe what's actually there: `HandlePullRequest` (clone → scan → comment → SARIF upload), `HandleCheckRun` (re-run button), `HandlePush` (no-op baseline placeholder).
  • **Jira integration via `fendix jira` (Sprint 14).** Idempotent Jira sync: each finding above `FENDIX_JIRA_MIN_SEVERITY` (default HIGH) gets exactly one Jira issue. Idempotency key is `fendix-id:<finding.ID>` on the issue's label field — re-running the command on the same findings is safe. Severity → priority mapping: CRITICAL→Highest, HIGH→High, MEDIUM→Medium, LOW/INFO→Low. Configured via four environment variables (`FENDIX_JIRA_URL`, `FENDIX_JIRA_PROJECT_KEY`, `FENDIX_JIRA_EMAIL`, `FENDIX_JIRA_API_TOKEN`). Works against both Jira Cloud and Server tiers (plaintext description format, ADF rendering is server-side).
  • **Slack + Teams webhook alerts via `fendix notify` (Sprint 15).** Post Slack Block Kit and Teams Adaptive Card alerts for findings above a configurable severity floor. Both sinks are optional — set `FENDIX_SLACK_WEBHOOK_URL` and/or `FENDIX_TEAMS_WEBHOOK_URL`; whichever are set receive alerts. `FENDIX_NOTIFY_MIN_SEVERITY` controls the floor (default: CRITICAL). `FENDIX_NOTIFY_DEDUP_WINDOW` (Go duration, default 1h) prevents re-alerting the same finding ID within the window (in-memory; restart re-arms). Per-sink errors are isolated so a failing Teams endpoint doesn't block Slack delivery.
v0.13.0 Complete

Offline mode + Arabic HTML + PDF executive report (Phase 4)

May 18, 2026

  • **Air-gapped CVE database + `fendix db` (Sprint 09).** New `internal/offline/` package implements a JSON snapshot format (schema v1) for OSV advisory exports. Three management subcommands: `fendix db update --source <osv-export.json>` ingests an OSV export into a local snapshot; `fendix db list [--path]` prints snapshot metadata; `fendix db verify [--path]` prints the SHA-256 for integrity checking. New `--offline` and `--offline-db <path>` flags on `fendix scan` enable fully air-gapped operation. Designed for government / enterprise environments with no outbound internet access.
  • **Arabic HTML report + i18n foundation (Sprint 10).** `fendix scan` and `fendix report` accept `--lang ar` to render an HTML report right-to-left with Arabic strings. New `internal/reporters/i18n/` package provides a `Strings` struct, `English()` / `Arabic()` / `Get(lang)` constructors, and `IsRTL(lang)` detection. Adding a new language is one new constructor + a switch case. JSON, SARIF, and PDF outputs stay English (machine-consumed; localisation would break downstream tooling). Unknown `--lang` values fall back to English with a stderr warning.
  • **PDF executive report via `--format pdf` (Sprint 11).** New PDF output format via `fendix scan --format pdf` and `fendix report --format pdf`. Structure: cover page → executive summary with severity-counts table and top-3 findings → paginated findings table with severity-coloured cells → remediation plan (CRITICAL + HIGH only) → metadata appendix. New `--classification <text>` flag (default `INTERNAL`) renders a red classification banner at the top-right of every page; empty string disables the banner. Uses `github.com/go-pdf/fpdf` (MIT, pure Go, no CGo). Arabic PDF is deferred — fpdf's built-in fonts do not render Arabic glyphs.
v0.12.0 Complete

Unified Go/JS/IaC textscan SAST engine (Phase 2)

May 18, 2026

  • **`internal/scanner/textscan` package — unified regex SAST engine (Sprints 04 + 05 + 06).** A single codebase drives Go, JavaScript/TypeScript, Dockerfile, and Kubernetes YAML rules using shared scanner scaffolding. 16 rules total across 4 language targets: Go rules (SQL injection via string concat, `exec.Command` shell invocation, weak hash for passwords, hardcoded AWS key ID); JS/TS rules (eval with non-literal arg, innerHTML from non-literal, child_process.exec, document.write, require with non-literal path, hardcoded AWS key); IaC rules (Dockerfile `FROM` without privilege drop, `ADD` vs `COPY`, `:latest` tag; Kubernetes `privileged: true`, `hostNetwork: true`, `allowPrivilegeEscalation: true`, `runAsUser: 0`).
  • **Extension-based routing.** Filename extension determines which rule set applies: `.go` → Go rules; `.js` / `.ts` / `.jsx` / `.tsx` → JS rules; `Dockerfile` / `*.dockerfile` → Docker rules; `.yaml` / `.yml` → Kubernetes rules (heuristic: skips files that look like GitLab/CircleCI CI configs). Skips noisy build directories: `node_modules`, `vendor`, `.git`, `build`, `dist`.
  • **Pure stdlib — no new deps, no CGo.** The textscan engine is wired into the orchestrator between the Semgrep and Python passes; runs whenever `--code` is set. Sprint 04.5 (Go XXE + insecure rand), Sprint 05.5 (JS prototype pollution + insecure RNG), and Sprint 06.5 (Terraform HCL) are deferred follow-ups requiring either AST context or MPL-2.0 dep acceptance.
v0.11.1 Complete

Phase 1 trust fixes — pip-audit naming, OSV batch queries, verify scope

May 14, 2026

  • **pip-audit naming gap fixed (Sprint 01).** 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 in `pip audit` output. New `--no-pip-audit-fallback` flag disables the local-list fallback for strict environments.
  • **OSV batch queries for pip and npm (Sprints 02 + 02.5).** The PyPI and npm dep-CVE scanners now send a single `POST /v1/querybatch` request 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 (Sprint 03).** `fendix verify --id SEC-042` now 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-ok` flag for the rare case where an operator wants the CI job to pass even on confirmed findings.
Engine evaluation Complete

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 in `scripts/accuracy/manifest.json`; harness in `scripts/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 TASK-133 exposed-config-file detections (CWE-538): `.DS_Store`, `.env` family (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_redirect` upgraded to taint-chain posture parity — pre-fix the detector only matched direct `redirect(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_dangerous` helper — `os.system('echo hello')` no longer fires HIGH on literal-string args. Precision: 0.833 → 1.000. (3) Orchestrator `runWhiteboxScan` now resolves `code_path` and `spec` to absolute paths before sending the ScanRequest — pre-fix the Python subprocess silently saw 0 files on every real codebase using `--python-engine` without `FENDIX_ENGINE` set. (4) `run-juice-shop.sh` portability fix: replaced GNU `timeout` (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`.
v0.11.0 Complete

FP discipline + path-traversal reachability — Phase 17d complete (engine-first roadmap closed)

May 13, 2026

  • **Headline: the engine-first roadmap is done.** Phase 17a (v0.8 — detection depth + FP discipline), 17b (v0.9 — cold-start, no embedded Python), 17c (v0.10 — plugin ecosystem polish), and now 17d (v0.11 — FP round 2 + path-traversal) all shipped over the past ~3 days. Next-planned work is Cloud Q1 (Stripe + AI explanation) from the original `docs/example_plan.md` — the cloud quarter that was deferred 5 months ago to widen the engine moat. The engine kept its v0.8.0 cold-start (6.1 ms p50 default; 82× under Phase 17b's 500 ms exit gate) while adding **three new detection paths** on top.
  • **TASK-133 — 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 MEDIUM `missing-CSP-on-/.env` findings into one CRITICAL `exposed config file` finding. 9 race-clean unit tests.
  • **TASK-134 — path-traversal as the 7th reachable taint-chain sink class.** New `SEC-PY_PATH_TRAVERSAL` finding (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_chain` records the chain and sets `reachable: true`; the correlator's TASK-114 escalation and orchestrator step 5.4's TASK-125 escalation both apply. Brings the engine's reachable sink categories to **7**: SQLi / SSRF / open-redirect (TASK-114) + XSS (TASK-120) + cmd-injection (TASK-121) + path-traversal (TASK-134). 9 new Python tests.
  • **TASK-135 — `fendix ignore` subcommand tree** (`internal/ignorecmd/`, ~300 LOC). Three subcommands close the suppression-bookkeeping gap: `fendix ignore list` renders a tabular view with EXPIRED / expiring-soon (within 30 days) / active / no-expiry / INVALID-DATE status; `fendix ignore validate` reports 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 — `validate` surfaces those). All three default to `.fendix-ignore` in cwd; `--file <path>` targets a different file. 14 race-clean unit tests.
  • **Cold-start benchmark refreshed (TASK-136).** 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-engine` opt-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). Phase 17b's <500 ms exit gate still cleared by 82× on default / 12× on opt-in. Net: default v0.11 is still 16 % faster than v0.8.0 (pre-Phase-17b) while adding three new detection paths on top.
  • **Spawner bug fix (latent since TASK-118).** `internal/engine/spawner.go` could compose `python/python/engine.py` for users on the local-fallback path because both `engineDir` and the relative script path were used together as `cmd.Dir` + arg. Surfaced during TASK-134's end-to-end validation; fixed by resolving via `filepath.Abs` before composing. Closes a silent exit-2 for every `--python-engine` invocation without `FENDIX_ENGINE` set.
  • **TASK-132 — FP corpus re-triaged.** No new shipping artifact, but `tasks/FP_CORPUS.md` now records which of the 35 catalogued FPs are addressed (P1/P2/P3/P4 all closed by Phase 17a's TASK-105/123/124), which TASK-133 picks up (D2 dotfile inversion), and which is deferred until real post-launch user data exists (D1 SPA-fallback dedup).
v0.10.0 Complete

Plugin ecosystem polish — Phase 17c complete

May 13, 2026

  • **Headline: the plugin ecosystem is genuinely usable now.** The wire contract from TASK-113 (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 plugins` subcommand tree handles install + list, CI smoke test catches wire-contract regressions automatically.
  • **TASK-128 — `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 clone` and `fendix plugins install`; tightened security model with explicit auth-token handling guidance; authoring checklist expanded 8 → 13 items.
  • **TASK-129 — 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 an `SPDX-License-Identifier` header. `examples/plugins/dockerfile-best-practices/` (Ruby, stdlib-only, ~210 LOC) walks for `Dockerfile` / `Dockerfile.*` and emits up to 5 distinct findings per file (`:latest` tag, `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.
  • **TASK-130 — `fendix plugins` CLI subcommand tree.** `fendix plugins list` enumerates 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 over `git clone --depth=1`: derives the on-disk name from the URL (handles `.git` suffix, scp-style `git@host:org/repo`, query strings, trailing slashes), refuses to overwrite preexisting directories, validates the cloned tree's `plugin.yaml` after clone, and removes the directory on validation failure so users never end up with a half-installed plugin that WARNs every scan. **Folded in the symlink-discovery fix** from TASK-127's audit: plugin discovery now `os.Stat`s each entry before `IsDir()`-checking, so symlinked plugin dirs work too.
  • **TASK-131 — plugin smoke test in CI.** New `internal/e2e/refplugins_test.go` (build-tag `e2e`, ~330 LOC) under the existing `make e2e` umbrella. One subtest per reference plugin (5 total) that copies the plugin into a temp scan root, runs `fendix scan` against a deterministic fixture, and asserts the expected findings flow through with the engine-attached `fendix-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 — Phase 17b complete (engine-first roadmap)

May 13, 2026

  • **Headline: 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); Phase 17b exit gate (<500 ms p50) cleared by ~89×. fendix no longer carries a Python interpreter requirement at all in the default scan path. New `--python-engine` opt-in flag re-enables the Python `auth` / `injection` / `deps` checks 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 via `scripts/bench/coldstart.py`.
  • **Native Go secrets scanner (TASK-115).** New `internal/scanner/secrets/` package ports `python/analyzers/secrets.py` to in-process Go: all 15 patterns (7 generic + 8 provider-specific — GitHub `ghp_*` / `ghs_*`, Stripe `sk_live_*`, Slack `xox*`, Google `AIza*`, Anthropic `sk-ant-*`, OpenAI `sk-*` / `sk-proj-*` / `sk-svcacct-*`, npm `npm_*`, GCP service-account JSON) plus the `.env`-only `ENV_SECRET` regex, walker with skip-dirs / extension gate / `.env`-name match / 1 MB cap / minified-JS-line skip, and evidence truncation. Same `SEC-<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 a `boundaryOK` post-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 (TASK-116).** New `internal/scanner/semgrep/` package wraps the host's installed `semgrep` binary instead of running it through the embedded Python engine. The fendix rule pack (`auth.yaml` / `injection.yaml` / `secrets.yaml`, identical bytes to `python/rules/`) is bundled into the Go binary via `//go:embed` and 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_severity` preferred over Semgrep's ERROR / WARNING / INFO mapping, evidence truncated at 200 chars, title at 120. **Graceful absence:** `exec.LookPath("semgrep")` failure returns `ErrSemgrepUnavailable` and 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. Wired into orchestrator step 3.7 (after secrets, before Python spawn). 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 (TASK-118).** The `Makefile`'s `embed-engine` target no longer copies `python/` into `go/internal/embedded/engine/`; the binary's `//go:embed` directive bundles only a placeholder. Deleted `python/analyzers/secrets.py` + `python/analyzers/semgrep_runner.py` + their test files (215 → 159 Python tests; the 56 deleted tests were strictly the now-redundant wrapper coverage). Python whitebox spawning is now opt-in via the new `--python-engine` CLI flag, requires a local `python/` source tree (or explicit `FENDIX_ENGINE` env var), and is silently skipped when no Python engine is resolvable. New `scripts/bench/coldstart.py` reproduction harness; `docs/benchmarks.md` adds a 'Cold-start latency (TASK-118 / v0.9)' section with the table, methodology, binary-size delta (-99 KB), and re-run instructions. **Phase 17b exit gate (<500 ms p50) cleared by ~89×.**
  • **Plugin wire-contract compatibility audit (TASK-127).** All three reference plugins (`custom-secret-pattern`, `custom-blackbox-check`, `custom-semgrep-pack`) re-verified end-to-end against the TASK-118 binary: discovery works, NDJSON in/out works, findings flow through correlation + dedup unchanged. Plugins do not depend on `embedded.HasEngine()`, the extracted `~/.fendix/engine/` tree, or `--python-engine` being 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`, not `ln -s`). Documented in `docs/plugins.md`. Phase 17b is 4/4 complete.
  • **No backend / frontend schema delta.** TASK-115 (Go secrets) emits findings with the same `SEC-*` IDs and the same Finding fields as the prior Python path. TASK-116 (Go semgrep) ditto. TASK-118 (embedded Python drop) doesn't touch the wire shape — only the runtime topology and the cold-start cost. TASK-127 confirms 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_defaults` ingest path absorbed all the relevant fields back in the pre-Phase-17a sync).

Detection depth + FP discipline — Phase 17a complete (engine-first roadmap)

May 12, 2026

  • **Native in-process dep-CVE scanners across all three ecosystems (TASK-119).** Go (`internal/scanner/deps/govulncheck/`) — uses the upstream `golang.org/x/vuln/scan` API in-process; inherits the same call-graph reachability filter as the upstream `govulncheck` binary but without requiring the binary on the user's PATH. PyPI (`internal/scanner/deps/pip/`) — parses `requirements.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>.json` with 24h TTL. npm (`internal/scanner/deps/npm/`) — parses `package-lock.json` v2/v3 (full transitive tree, dedup on (name,version)), OSV.dev queries against the `npm` ecosystem, scoped-package aware (`@scope/name` handled correctly in path parsing + cache keys), 24h cache. All three behind a single `--no-native-deps` escape hatch. Phase 17b (TASK-118) can drop the Python `deps.py` path entirely without losing coverage. **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 (TASK-120).** New `SEC-PY_XSS_HTML_SINK` finding (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 carries `taint_chain` + `reachable: true` — the correlator's existing TASK-114 escalation then applies a second severity bump for correlated-reachable pairs.
  • **Reachable command-injection taint chains (TASK-121).** Extended existing `PY_OS_SYSTEM` and `PY_SUBPROCESS_SHELL` sinks with `_collect_taint_chain` capture; added new `PY_OS_POPEN` sink (deprecated form with the same shell-injection surface). Brings the engine's reachable sink categories to 6 across TASK-114 (SQLi/SSRF/open-redirect), TASK-120 (XSS), and TASK-121 (cmd-injection).
  • **FP corpus + targeted gates (TASK-122 + TASK-123).** New `scripts/fp-corpus/run.sh` runner + `tasks/FP_CORPUS.md` 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). Phase 17a exit gate (≥15 FPs) comfortably exceeded. 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 (TASK-124).** Every top finding in the GitHub-App-posted PR comment now ships with a fenced `yaml` block under the bullet — copy-paste straight into `.fendix-ignore` to 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 (TASK-125).** New `ReachableMult = 1.5` constant in `internal/models/scoring.go` + new `CalculateSeverityReachable()` function (existing `CalculateSeverity` is a backwards-compatible wrapper). New orchestrator step 5.4 `escalateNonCorrelatedReachable` bumps severity by one level on pure-whitebox findings with `reachable: true` (the correlator already bumps the correlated-reachable case in `mergeFindings`). Confidence cap from TASK-092 still applies — the bump amplifies what the confidence allows; it can't override the cap. EPSS / KEV multipliers from `docs/example_plan.md` §3.5 explicitly deferred to the cloud quarter.
  • **Cross-repo sync (earlier 2026-05-11 session): backend persists taint_chain + reachable + affected_endpoints.** Engine had been emitting these Phase 11 + Phase 15 fields on `Finding` since v0.7.0, but the backend's `_finding_defaults` silently stripped them on ingest. New migration `0006_finding_reachability` adds 3 columns to `ScanFinding` (`affected_endpoints` + `taint_chain` JSONField, `reachable` Bool) with safe defaults. `_coerce_taint_chain` defensive parser drops malformed links rather than failing the whole finding. Frontend `Finding` interface extended with new `TaintLink` type + optional `taint_chain?` / `reachable?` fields; `app/types/api.ts` regenerated via `npm 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 (TASK-126).** New `docs/adr/ADR-008-readonly-ai.md` formalises the 2026-05-11 strategic decision that partially supersedes BACKLOG-017. The boundary: read-only AI (finding explanation + fix suggestion as text, delivered by the `fendix-backend` cloud product) is permitted and planned for Cloud Q1. 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 (no `import anthropic` in `go/` or `python/`). Phase 17a is 8/8 complete.

Open & Extensible — Phase 14 closeout + Phase 15 (the wedge is now defensible)

May 1, 2026

  • **Headline: 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. v0.7.0 folds 8 commits since v0.6.1 across two phase closures: Phase 14 (P4 External Wedge) closing end-to-end with the GitHub App business logic, and Phase 15 (P5 Open & Extensible) shipping open-source ratification + plugin system + reachability/dataflow correlation
  • **Open-source posture ratified via ADR-007 (TASK-112).** What was tactical in v0.1.0 (an MIT `LICENSE` shipped 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 (under `examples/plugins/`) are required to be MIT to match the rest of the tree.
  • **Plugin system: out-of-tree extension via NDJSON IPC (TASK-113).** New `internal/plugin` package: `Discover` walks `<repo>/.fendix/plugins/` (repo-local, takes precedence) + `~/.fendix/plugins/` (user-global), parses each child's `plugin.yaml` with strict `KnownFields(true)` parsing (typos like `entry_point` → loud error, not silent drop), and dedups by name. `(*Plugin).Run` invokes the entrypoint with a JSON `ScanRequest` on 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 of `engine.py`. Per-plugin timeout (default 30s, max 5m) bounds wall-clock; partial findings before a kill are preserved; plugins inherit `FENDIX_PLUGIN_NAME` + `FENDIX_PLUGIN_DIR` env vars; every emitted finding gets `fendix-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 under `examples/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.md` covers discovery, IPC schema, security model, and an authoring checklist.
  • **Reachability/dataflow correlation: `correlated:reachable` for proven exploit paths (TASK-114).** The Python AST analyzer now records taint chains for SQLi, SSRF, and open-redirect findings: when `_collect_taint_chain` proves intra-function dataflow from a request source (`request.args/POST/form/data/json/headers` plus the Flask handler-arg form `req`) through one or more variable assignments to a dangerous sink, the emitted finding carries `taint_chain: [{file, line, expr}, …]` plus `reachable: 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 plus `reachable: true` AND 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 (TASK-107b).** 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 cached `TokenSource`; (2) clones the PR head SHA via `git init` + shallow `fetch --depth=1 origin <sha>` + `checkout FETCH_HEAD` (only the exact commit, no history; auth via `x-access-token:<token>@…` userinfo on the HTTPS clone URL); (3) runs `fendix scan --code <tmp> --format json`; (4) re-renders SARIF via `fendix report --format sarif` so the PR comment + Code Scanning tab describe identical findings; (5) renders a Markdown PR comment matching the `examples/github-actions/fendix-scan.yml` github-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/sarifs` against `refs/pull/<n>/head`. SARIF upload is best-effort (Code Scanning disabled or `security_events: write` missing → log warning, comment still posts). **`check_run.action == "rerequested"`** re-runs the scan against the recorded head SHA. **Tempdir always cleaned up** via `defer os.RemoveAll`. **Tokens redacted** from any error surfaced for git-step failures. **Per-scan timeout:** 15 minutes wall-clock. **Distribution:** new `Dockerfile.app` (multi-stage; bundles `fendix` + `fendix-app` + Python engine + `git` + tini in ~250 MiB Debian-slim). 26 new ghapp tests under `-race`.
  • **Vulnerable-app benchmark numbers captured (TASK-106).** Stock `fendix scan --url http://localhost:3000` against `bkimminich/juice-shop:v17.1.1` on 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-active` and/or `--code` to surface). Pre-dedup the engine emitted **391 raw findings** that TASK-088 deduplication collapsed to 7. Numbers published in `docs/benchmarks.md` 'Latest results' table with a 'Reading the row' explanation that documents what the row does NOT measure.
  • **GitHub App scaffold (TASK-107).** New `cmd/fendix-app` binary (separate from the `fendix` CLI; long-running webhook server) plus `internal/ghapp` package and `app/manifest.yml` for one-click App registration via GitHub's manifest flow. Webhook layer: HMAC-SHA256 signature verification (legacy `sha1=` rejected), event router, 4 MiB body cap. Auth layer: pure-stdlib RS256 App-JWT signing (no `golang-jwt` dep added — preserves the project's zero-runtime-deps posture), `/app/installations/{id}/access_tokens` exchange, 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 (TASK-108).** New cobra subcommand spins up `bkimminich/juice-shop:v17.1.1` in Docker on `localhost: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 (TASK-109).** New `internal/policy` package + new `--config <path>` flag on `fendix scan`. Teams commit a `.fendix.yaml` at repo root encoding scan posture (severity threshold, scan budgets, auth profile reference, crawler defaults, format) and invoke `fendix scan` with one CLI flag instead of the prior six-flag-and-growing wall. **Precedence** matches `git config`: cobra defaults < `.fendix.yaml` values < 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); `--config` is a host-filesystem flag (the API itself accepts every policy field directly); `fendix-app` is a separate deployable on the GitHub-event side of the pipeline.
  • **Reachability fields persisted backend-side and typed frontend-side (2026-05-11 cross-repo sync).** v0.7.0 originally landed `reachable` + `taint_chain` + `affected_endpoints` on the engine's `Finding` JSON output, but the backend's `_finding_defaults` silently stripped them on ingest, so `GET /api/findings` served lossy rows. Closed pre-Phase-17a: backend migration `0006_finding_reachability` adds 3 columns to `ScanFinding` (`affected_endpoints` + `taint_chain` JSONField, `reachable` Bool) with safe defaults; `_coerce_taint_chain` defensive parser drops malformed links rather than failing the whole finding; serializer exposes all three; `openapi.json` regenerated; 3 new tests. Frontend gains a `TaintLink` interface and `Finding.taint_chain?` + `Finding.reachable?`; `app/types/api.ts` regenerated via `npm run codegen`. **No UI surfacing yet** — deferred until engine ships TASK-120 + TASK-121 (XSS + 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 + Phase 14 partial

May 1, 2026

  • **Critical install-pipe fix.** `scripts/install.sh` now `mkdir -p` the install directory before the `mv`. Previously, `curl -fsSL https://get.fendix.dev/install.sh | FENDIX_DIR=$HOME/.local/bin sh` failed on any system where `$HOME/.local/bin` didn't pre-exist with `mv: 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 set `FENDIX_DIR` to a non-existent dir. The fix tries `mkdir -p` non-sudo first, falling back to `sudo mkdir -p` only when a parent up the chain isn't writable — POSIX-sh clean. Mirrored to `get.fendix.dev/install.sh` automatically by the release pipeline's mirror-sync job
  • **`fendix init` zero-config workflow generator (TASK-105, folded into v0.6.1).** New `fendix init` subcommand detects the project's stack (Go via `go.mod`, Python via `pyproject.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 via `go:embed`) plus `.fendix-ignore` (commented starter for finding-level suppressions). Refuses to overwrite by default; `--force` overrides; `--print` dry-runs to stdout. **Note:** post-v0.6.1, init was extended to also write `.fendix.yaml` per TASK-109 — see Unreleased entry above
  • **README repositioned around the wedge (TASK-110, folded).** Hero rewritten from generic 'Find vulnerabilities before attackers do.' to 'DAST + SAST in one PR check. Fails only when both engines confirm.' Three-bullet trust block matches the get.fendix.dev landing page (confirmed findings, single binary, signed + silent)
  • **'What Fendix sends to the network' section at top of README (TASK-111, folded).** Five-row table covering default scan / active probing / white-box / no-flags / telemetry. Explicit 'no telemetry code; verify with tcpdump or read go/internal/' claim. Plus a 'Verifying signed releases' section with the full cosign keyless verify recipe
  • **Vulnerable-app benchmark scaffold (TASK-106 partial, folded).** New `scripts/benchmark/run-juice-shop.sh` + `make benchmark` + `.github/workflows/benchmark.yml` (`workflow_dispatch` only) + `docs/benchmarks.md`. CI workflow installs Fendix via `https://get.fendix.dev/install.sh` (doubles as install-pipe smoke test). Real juice-shop numbers captured in the post-v0.6.1 Unreleased entry above

First Stable Signed Release (Phase 13 complete)

April 30, 2026

  • **First stable signed release.** Every binary, `.deb`, `.rpm`, and Docker manifest ships with `.crt` + `.sig` cosign sidecars verifiable against the build's GitHub Actions OIDC identity — no static public key, no rotation surface, no key-loss recovery story
  • Cosign keyless signing fully active on the release pipeline (Sigstore Fulcio + GitHub Actions OIDC). `COSIGN_ENABLED=true` flipped on the engine repo on 2026-04-30T14:07Z; rc2 was the first tag to exercise the full signed-release path end-to-end, then promoted to v0.6.0 final after pipeline validation. Cosign steps hard-fail the release job, so a broken signing path can't silently ship unsigned artifacts
  • `https://get.fendix.dev/install.sh` is live — DNS CNAME at the registrar, GitHub Pages on the homebrew-fendix mirror, auto-provisioned Let's Encrypt cert. End-to-end smoke test verified the install pipe end-to-end. Mirror URL retained as a documented fallback. Engine repo is now the single source of truth for everything served at get.fendix.dev — auto-synced by `release.yml` on every `v*` tag
  • Linux arm64 release binary added — `brew install fendix` and `curl -fsSL https://get.fendix.dev/install.sh | sh` now serve native ARM builds for Graviton, Ampere, Raspberry Pi, and ARM Linux laptops
  • Multi-arch Docker image at `ghcr.io/abdel-rahmansaied/fendix:vX.Y.Z` — manifest list covers `linux/amd64` + `linux/arm64`; `docker pull` picks the right arch automatically
  • Linux `.deb` and `.rpm` packages built via nfpm — install with `sudo dpkg -i fendix-*.deb && sudo apt-get install -f` or `sudo dnf install ./fendix-*.rpm`. Declares `python3` as required and `semgrep` as recommended
  • Documentation pass for external evaluators: 5-minute juice-shop walkthrough (`docs/walkthrough-juice-shop.md`), Semgrep rule-author guide (`docs/semgrep-rules.md`), triage workflow (`docs/triage-workflow.md`), JSON schema reference (`docs/schema.md`), all cross-linked from a top-level Documentation index in the README
  • New `--debug-bundle <path>` flag writes a redacted diagnostic `.tar.gz` for attaching to bug reports — auth values masked as `[REDACTED]`, full DEBUG slog stream tee'd into the bundle, probe audit log on `--enable-active`. Auth credentials never leak (e2e regression locks this in)
  • `SECURITY.md` + active-scanner threat model (`docs/threat-model.md`): vulnerability disclosure policy, supported-versions, cosign verification instructions, and the 7-threat safety envelope every active probe must maintain
  • Performance benchmark suite: scan time vs endpoint count, peak goroutine count, allocation profile — published in README. Reference: 1000 endpoints in 31.7 ms / 24.7 MB / 166 peak goroutines (Apple M1, Go 1.21)
v0.6.0-rc1RC Complete

External Release Readiness (Phase 13 release candidate)

April 30, 2026

  • Linux arm64 release binary added — `brew install fendix` and `curl -fsSL .../install.sh | sh` now serve native ARM builds for Graviton, Ampere, Raspberry Pi, and ARM Linux laptops
  • Multi-arch Docker image at `ghcr.io/abdel-rahmansaied/fendix:vX.Y.Z` — manifest list covers `linux/amd64` + `linux/arm64`; `docker pull` picks the right arch automatically
  • Cosign keyless signing wired (Sigstore Fulcio + GitHub Actions OIDC; opt-in via the `COSIGN_ENABLED=true` repo variable). When enabled, every binary ships with `.sig` + `.crt` sidecar files
  • Linux `.deb` and `.rpm` packages built via nfpm — install with `sudo dpkg -i fendix-*.deb && sudo apt-get install -f` or `sudo dnf install ./fendix-*.rpm`. Declares `python3` as required and `semgrep` as recommended
  • New `docs/install.md` install reference covers every install path with cosign verification one-liners; `get.fendix.dev` short-URL installer planned (operator-action: domain registration + GitHub Pages CNAME)
  • Documentation pass for external evaluators: 5-minute juice-shop walkthrough (`docs/walkthrough-juice-shop.md`), Semgrep rule-author guide (`docs/semgrep-rules.md`), triage workflow (`docs/triage-workflow.md`), JSON schema reference (`docs/schema.md`), all cross-linked from a top-level Documentation index in the README
  • New `--debug-bundle <path>` flag writes a redacted diagnostic `.tar.gz` for attaching to bug reports — auth values masked as `[REDACTED]`, full DEBUG slog stream tee'd into the bundle, probe audit log on `--enable-active`. Auth credentials never leak (e2e regression locks this in)
  • `SECURITY.md` + active-scanner threat model (`docs/threat-model.md`): vulnerability disclosure policy, supported-versions, cosign verification instructions, and the 7-threat safety envelope every active probe must maintain
  • Performance benchmark suite: scan time vs endpoint count, peak goroutine count, allocation profile — published in README. Reference: 1000 endpoints in 31.7 ms / 24.7 MB / 166 peak goroutines (Apple M1, Go 1.21)
  • Release-candidate cut to validate the new pipeline (cosign + nfpm + ghcr) end-to-end before tagging clean v0.6.0; signed-release validation pending `COSIGN_ENABLED=true`

Quality & Ops (Phase 12 complete)

April 30, 2026

  • Public JSON output schema published (`docs/schema.md` + `docs/schema.json` draft-07); validator runs in tests against every emitted report; `findings: []` instead of `null` when empty
  • Path-parameter substitution: templated endpoints like `/users/{id}` now scan against `/users/1` (or schema-derived sample). Resolution order: `schema.example` → `schema.enum[0]` → type-driven default → name heuristic → fallback `1`. Endpoint.Path stays templated for reports; FullURL is concrete
  • Logging hygiene: aggregated WARN volume — max 3 per check key per scan, rest downgraded to DEBUG. New `INFO warning summary` line at scan end. Real-world: 30 WARN lines → 9 + 1 summary on a 10-endpoint scan against an unreachable target
  • Scan budgets: new `--max-requests` (soft cap on total HTTP requests; discovery exempt), `--max-duration` (Go duration string, e.g. `5m`), `--respect-robots` (treat robots.txt Disallow as hard restriction across all discovery sources)
  • New auth profile: `--auth-type apikey-query` puts the credential in the URL query string instead of a header — common for legacy/sensor APIs that prefer query placement
  • Auth profiles end-to-end: bearer / apikey-header / apikey-query / basic / cookie all covered by e2e tests that record what reaches the server
  • Concurrency review: race-clean proof at 1000 endpoints × 32 workers under `go test -race`; new `FuzzWorkerPool_CancelTiming` fuzzer (4455 execs / zero failures over 15s of fuzzing)
  • Severity↔confidence consistency enforced — LOW confidence caps severity at MEDIUM, MEDIUM caps at HIGH (matches the scoring formula's implicit max). Inconsistent findings get severity downgraded with an aggregated WARN line
  • Drop-in GitHub Actions workflow: `examples/github-actions/fendix-scan.yml` does scan → `actions/cache` baseline → SARIF upload → PR summary comment via `actions/github-script@v7`

Coverage Parity (Phase 11 complete)

April 29, 2026

  • Correlator finalized: HTTP method-prefix stripping + path-suffix matching (handles base-path skew like spec `/pet/findByStatus` ↔ live `/api/v3/pet/findByStatus`); debug instrumentation with `match_kind=exact|suffix|fuzzy`; blackbox findings consumed at most once
  • Real CVE coverage: pip-audit (PyPI) + npm audit (JS) + govulncheck (Go) as primary paths; hardcoded list as offline fallback. Real-world: badcode/requirements.txt = 6 deps findings (offline) → 97 with pip-audit installed (16× coverage)
  • Go module support — `go.mod` files now scanned by govulncheck; only emits findings on actually-called code paths (vendored-but-uncalled noise dropped)
  • Crawler upgrade: robots.txt + sitemap.xml + HTML link parsing with recursive depth. Real-world: httpbin.org discovery went from 1 endpoint to 3 (Disallow /deny + linked /forms/post + brute-force /robots.txt)
  • New flags: `--wordlist`, `--crawl-depth` (default 1), `--max-endpoints` (default 500); built-in `CommonPaths` expanded ~50 → 117 with admin/dashboard/source-control/DevOps tooling paths
  • Findings deduplication: identical issues across N endpoints collapse into one finding with `affected_endpoints` (real-world: petstore 160 → 10, 16× reduction)
  • Static analyzer: 6 new patterns — pickle.load, yaml.load without SafeLoader, MD5/SHA1 for passwords, open redirect, SSRF, auth-header trust
  • Multi-step SQLi detection via intra-function scope tracking (`sql = '...' + var; cursor.execute(sql)`)
  • Active scanner: body & header param probing; error-based + boolean-based SQLi; SQLite + Oracle time-based payloads (5 DB types total)
  • New `--max-probes-per-endpoint` flag (default 20) for active scan budget control
  • Secrets analyzer: 8 new provider patterns — GitHub, Stripe, Slack, Google, Anthropic, OpenAI, npm, GCP service-account JSON (15 total, was 7)
  • .env file scanning fixed (dotfile walker now yields env-files; unquoted KEY=value pattern gated to .env*)
  • v0.4.0 folds the planned v0.3.0 batch — single tag covering TASK-085..091
v0.2.0 Complete

P0 Flag Wiring

April 29, 2026

  • `--save-baseline` now actually writes a file (was previously a silent no-op at the CLI)
  • `--code`-only scans run successfully (orchestrator no longer early-exits when only --code is given)
  • Active scanner now uses spec-defined query/path parameters instead of hardcoded `id`
  • `--spec` now accepts `https://...` URLs in addition to local file paths (fetched with content-type detection, 50 MB cap)
  • SARIF rule IDs are now stable per check type (`fendix.<category>.<title-slug>`) — breaking change for v0.1 SARIF baseline consumers
  • End-to-end test infrastructure: every CLI flag now has a test that runs the binary and asserts observable effect
v0.1.0 Complete

Initial Release

April 2026

  • All 9 development phases complete — 78 tasks, ~594 tests
  • MIT License, CHANGELOG, .fendix-ignore.example template
  • Ready for production use with full documentation
Phase 9 Complete

Hardening

April 2026

  • Performance benchmark suite across all critical paths
  • Go native fuzz testing (362k+ executions, 0 panics)
  • Python hypothesis fuzz testing — found and fixed 3 real bugs
  • Self-audit: 0 production code vulnerabilities
  • 29 resilience tests: garbage responses, timeouts, crashes, malformed streams
  • Memory profiling: 2.3KB/finding, 15MB/1000 correlations
  • Error message audit — 7 messages improved with actionable guidance
Phase 8 Complete

Documentation

April 2026

  • Complete README.md with all 10 required sections
  • CONTRIBUTING.md with development setup and check-writing guides
  • 11 individual check documentation pages in docs/checks/
  • 6 Architecture Decision Records (ADR-001 through ADR-006)
  • CHANGELOG.md following Keep a Changelog format
  • Full godoc (92 Go symbols) and docstring (16 Python symbols) coverage
Phase 7 Complete

Distribution

April 2026

  • Python engine embedded into Go binary via go:embed
  • Auto-extraction to ~/.fendix/engine/ on first run with version stamp
  • Graceful fallback if Python not installed (whitebox skipped with clear message)
  • GitHub Actions release workflow: linux/amd64, darwin/amd64, darwin/arm64
  • Multi-stage Dockerfile with non-root user
  • curl-pipe installer with platform detection and sha256 verification
  • Homebrew formula (brew tap fendix/tap && brew install fendix)
Phase 6 Complete

Reporting & CI/CD

April 2026

  • JSON reporter with full scan metadata (mode, endpoints, source counts)
  • Self-contained HTML report with JS sorting, expand/collapse, print CSS
  • SARIF 2.1.0 reporter — GitHub Code Scanning compatible
  • fendix report command — re-render JSON to HTML/SARIF without re-scanning
  • Baseline diff mode: --baseline and --save-baseline for PR workflows
  • CI/CD integration docs with GitHub Actions examples
Phase 5 Complete

Active Scanner

March 2026

  • Safe probe framework with audit log and legal disclaimer
  • Time-based blind SQL injection detection (MySQL, PostgreSQL, MSSQL)
  • Command injection canary detection with safe echo payloads
  • CRLF header injection probes
  • Per-endpoint probe rate limiter (max 20 probes/endpoint)
  • --enable-active gate ensures probes never run by default
Phase 4 Complete

Hybrid Engine & Orchestration

March 2026

  • Go subprocess spawner for Python engine with full IPC
  • Streaming Finding reader with malformed-line resilience
  • Correlator: endpoint normalization, fuzzy matching, severity escalation
  • .fendix-ignore suppression with YAML rules and expiry dates
  • Baseline diff for tracking new vs. existing findings
  • --fail-on exit code logic for CI/CD gates
Phase 3 Complete

Auth Scanner

March 2026

  • AuthContext model with multi-source credential resolution
  • Unauthenticated access detection on protected endpoints
  • JWT bypass checks: none algorithm, expired token, signature strip
  • IDOR two-account access control verification
  • Credential masking ([REDACTED]) in all report formats
  • ~/.fendix/profiles/ persistent config system
Phase 2 Complete

White-Box Engine

March 2026

  • Python engine.py with full IPC contract and error handling
  • Secrets analyzer: 7 pattern types (AWS, PEM, API key, JWT, DB URI, etc.)
  • OpenAPI spec parser for 2.0 and 3.x with 4 auth checks
  • Semgrep rules for auth, injection, and secrets
  • AST analyzer for Python and JavaScript code patterns
  • Dependency CVE checker via pip-audit and npm audit
Phase 1 Complete

Passive Scanner & Reporting

February 2026

  • Endpoint crawler with spec parsing, JS discovery, and brute-force
  • Security headers, CORS, data exposure, and rate limit checks
  • Worker pool concurrency model
  • JSON reporter with scan metadata
  • Self-contained HTML reporter with color-coded severity
  • Orchestrator wiring for all passive checks
Phase 0 Complete

Foundation

February 2026

  • Go module and Python package initialization
  • Finding, ScanConfig, and severity scoring models
  • Cobra CLI skeleton with version command
  • GitHub Actions CI workflow
  • Makefile for build, test, lint, and clean
  • ADR-001 and ADR-002 architecture decision records

v0.19.0 shipped June 2026 — scan from an uploaded OpenAPI/Swagger spec file (JSON or YAML, size-capped, YAML aliases disabled), plus a false-positive precision pass: the FastAPI missing-auth check now fires only on real FastAPI routes (no more Django/DRF/Celery/Pydantic false positives) and recognises Depends/Security/Annotated auth, while the black-box password-field check ignores i18n label dictionaries but still flags genuine plaintext-credential leaks. It builds on v0.18.0's DAST module upgrade, which takes the black-box scanner from 8 to 15 checks (cookie-flags, open-redirect, reflected XSS, in-band SSRF, host-header injection, GraphQL introspection, HTTP method tampering), with 3 critical fixes and 45 false-positive reductions. It builds on the 90-Day Cut (v0.16.1): diff-aware staged scans (~18ms on a 200-file monorepo), a `fendix hook install` pre-commit gate, Proven Path v1 taint chains exported as SARIF codeFlows, and transitive Python SCA over poetry.lock / Pipfile.lock — and on the engine-first and enterprise-readiness roadmaps through v0.14.1's unified Go/JS/IaC SAST, PDF reports, offline CVE mode, integrations, the Phase 17 plugin ecosystem, and the 7 reachable taint-chain sink classes. Default cold-start stays under 7 ms p50. Every artifact still carries cosign keyless signatures verifiable against the build's GitHub Actions OIDC identity.