Skip to content

What's new

A version-by-version history of Fendix. v3.7.0 shipped October 2026: the GitHub Action fails the job when Semgrep cannot run, and a fatal Semgrep run is recorded as failed instead of passing as a clean scan. Each entry below lists what changed in that release.

A threshold you ask for is a threshold you get

October 2026

  • `--fail-on` fails closed. It accepts a severity in any case — high, High, HIGH — and refuses anything else, INFO included, with exit 2 before scanning. v3.4.1 ran an unrecognised value as no threshold at all, so --fail-on high warned on a HIGH finding and exited 0: the gate you asked for was off. A pipeline that passed an invalid value now fails with exit 2; the valid thresholds are CRITICAL, HIGH, MEDIUM and LOW.
  • `fendix verify` no longer reads missing input as a fix. A source file or dependency manifest that is absent or unreadable, or a header finding re-checked against an error or login page, now returns unknown instead of resolved.
  • govulncheck says why a Go module could not be loaded — the go command's own diagnosis instead of a misleading "no go.mod file" — and release binaries are built with Go 1.27.1, the toolchain the images ship.
  • Official distribution. Images publish to docker.io/fendixapp/fendix, the Homebrew tap is Fendix-app/fendix, and release signatures carry the Fendix-app/Fendix workflow identity.
  • No fingerprint, report-schema or decision-policy changes.

Go 1.27 in the images

September 2026

  • The engine images build with and ship Go 1.27, so govulncheck loads any module whose go.mod requires Go 1.27 or older; the earlier ceiling was 1.25.
  • No scanner, fingerprint or exit-code changes. The Go module floor stays 1.25.
  • Hosted scans: release policy 2.0.0 is live — every whitebox analyzer is required, coverage gaps are owned and refunded once, and the verdict wording is the same in the app, the PR check, email and Slack.

A scan can no longer look complete when it wasn't

September 2026

  • Every analyzer now says whether it ran, and if not, why. Endpoint discovery, the spec parser, active probes, secrets, textscan, semgrep, govulncheck, pip, npm, the Python engine and each of its checks, and plugins are recorded once per scan in a fixed order with a closed, machine-readable reason: not applicable to this target, disabled by a flag you passed, a missing dependency in the environment, a target the analyzer does not support, or a failure with its cause. A scanner that was quietly absent from a report can no longer be mistaken for one that ran clean.
  • The report states its own coverage. A new metadata.coverage block says whether everything the run was configured to execute actually ran, names the gaps, lists the limitations, and records anything that was retried. Consumers that used to check only for a failed entry should read coverage.configured_complete instead: a missing dependency is a skip, and it is a gap.
  • A URL scan that finds zero endpoints writes its report instead of vanishing. It still exits 2, so scripts see the same code, but the report now exists with discovery recorded as failed/no_endpoints — Fendix Cloud used to store that case as a clean scan.
  • Two opt-in flags turn a coverage gap into a failing build. --fail-on-coverage-gap exits 2 when something the run was configured to execute was unavailable or failed; --require-analyzers a,b exits 2 when a named analyzer was not delivered, and a required analyzer disabled by another flag is reported as the contradiction it is. Both are off by default; nothing about the default exit codes changed.
  • A transient network failure in a dependency scanner is retried once, in process. govulncheck, pip and npm get one re-invocation on a network error or timeout, and the entry says attempts: 2. Nothing else is retried and the engine never re-runs itself.
  • Coverage is visible in every format. SARIF lists every analyzer that did not run as a tool notification and carries the coverage block; HTML and PDF reports show a coverage table under the verdict; the scan-end summary on the terminal is now that same table.
  • The Docker image ships a Go toolchain, so govulncheck runs inside it. Every earlier image recorded govulncheck as failed on any Go repository because the go command it depends on was not there. Module resolution uses Go's default proxy; set GOPRIVATE for private modules.
  • Two report semantics were corrected. pip with no manifest is skipped/not_applicable, not ok; an npm package.json without a lockfile is skipped/unsupported_target. Neither is a coverage gap; both stop confusing "nothing to scan" with "scanned clean".

An upgrade stops splitting your findings in two

September 2026

  • A finding you were already tracking is recognised after the identity change, instead of arriving as one closed issue and one new one. When finding identity moved in v3.0, anything tracking Fendix findings had no way to tell that the finding it knew and the finding it now saw were the same one — so one vulnerability could become two records, with two ids and two histories. Reports now carry the retired identity alongside the current one, purely so a consumer can join the two. It is a migration bridge and nothing scores or filters on it; the current identity is unchanged, so saved baselines and .fendix-ignore fingerprint rules keep matching exactly as before.
  • The list of checks a scan ran no longer includes checks it skipped. A report could name a scanner under checks_run while, in the same document, recording that the scanner never ran — because the list was built from what the scan was configured to do rather than from what it did. It is now built from what actually completed. You may see fewer entries than before on scans where a scanner was unavailable; that is the list becoming accurate, not coverage shrinking. What a scan actually covered, and every release decision made from it, are unchanged.

Every blocking decision explains itself

August 2026

  • A blocked build now tells you why, in the report itself. A BLOCK used to arrive in your SARIF as little more than the word BLOCK once it had been through Fendix's own storage and re-rendering — the reasoning existed, but not everywhere you could read it. Every finding now carries the decision, the policy it was made under, and the specific evidence that justified it, and the JSON and SARIF reports say exactly the same thing. You can answer "why did this fail my pipeline?" without opening Fendix.
  • You can see which evidence class allowed a block. A dependency blocked on a deterministic match in production code, a vulnerability blocked because user input provably reaches the dangerous call, and a finding blocked because two independent signals agreed are three different claims — and they now read as three different claims rather than one undifferentiated verdict.
  • A finding held back says why it was held back. "Held at WARN because nothing corroborates it", "the vulnerable component is never imported", "this credential is test fixture data" — each is now a stable machine-readable reason, not a sentence you would have had to parse.
  • Confidence scores are explainable line by line. Each score comes with the rules that produced it and what each contributed, so a 45 and a 75 are no longer just numbers.
  • A relaxed gate can no longer be mistaken for a normal one. If a build was blocked only because confidence enforcement was switched off, the report says so. Findings that do not block are no longer mislabelled as though they did.

Findings claim only what they proved

August 2026

  • A wildcard CORS policy with credentials is reported as a misconfiguration, not an exploit. Browsers reject that combination outright, so it can never produce the authenticated cross-origin read the CRITICAL rating was claiming. It is now MEDIUM and no longer fails a build. Origin reflection with credentials — the shape that genuinely is account-takeover grade — is unchanged.
  • Missing rate limiting is ranked by what abusing the endpoint is worth. One flat severity across a large API asserted that an unlimited login and an unlimited list read deserved equal urgency. Login, identity and expensive unauthenticated operations stay prominent; ordinary reads drop to informational.
  • Browser security headers are judged against the response they are on. A JSON API response is not a browser document, so a missing Content-Security-Policy on one is no longer graded as though it were.
  • A dynamic file path is no longer reported as a proven path traversal. Building a path from a variable is worth flagging; it is not the same claim as demonstrating traversal, and the wording now reflects that.
  • Obvious test credentials stop being treated as leaks. When both the location and the value agree that a string is fixture data, it is reported as informational. A real provider-issued secret is never de-escalated, wherever it sits.
  • An unresolved import is no longer read as proof a dependency is unused. Not being able to tell is different from knowing it is safe, and only positive evidence of non-use now lowers a dependency finding.

Semgrep findings hold still

August 2026

  • Every Semgrep finding was getting a brand-new identity on each scan. Its identity carried an internal temporary path that changed every single run, so a baseline or a suppression never matched it again — even when nothing in your code had changed at all. Semgrep findings now keep the same identity from one scan to the next, like every other finding.
  • Every unprotected route is its own finding again. Five Flask routes missing an auth decorator were collapsing into one finding, so suppressing the one you had reviewed silently suppressed the four you had not. Separately, these findings were showing "requires login" as their evidence — a placeholder Semgrep returns when it is not signed in — instead of the line of code that actually matched. You now see the code.

Older reports keep their own identity scheme

August 2026

  • Re-exporting a report saved before v3.0.0 labelled it with the new identity scheme while it still carried the old values. A tool reading that export could match those identities against the wrong things. A report now always declares which scheme its own identities were produced under, so old and new exports can never be confused for one another.

v3.7.0 shipped October 2026 — the GitHub Action requires Semgrep for code scans and fails the job when it cannot run, installs wheels only and uploads SARIF only for a finished scan, and a fatal Semgrep run is recorded as failed; no report-schema, SARIF, fingerprint or decision-policy changes, and every exit code keeps its meaning. v3.6.5 shipped October 2026 — Semgrep 1.182.1, MCP 1.29.0 and golang.org/x/mod 0.40.0 fix the last fixable HIGH advisories in the official image, Semgrep no longer contacts semgrep.dev, and self-hosted Linux CI runners need glibc 2.34 or later for Semgrep; no scanner, fingerprint, report-schema, decision-policy or exit-code changes. v3.6.4 shipped October 2026 — rebuilt on Go 1.27.3 and certifi 2026.10.23, so TLS no longer trusts the eight compromised emSign intermediate CAs and a hostile HTTP/2 scan target can no longer burn the scanner's CPU; no scanner, fingerprint, report-schema, decision-policy or exit-code changes. v3.6.3 shipped October 2026 — four bundled Semgrep rules that never matched now report, WARN by default under the confidence-gated --fail-on; the Flask auth advice says the decorator goes below @app.route(...); eleven checks have static catalog copy; no finding-identity change. v3.6.2 shipped October 2026 — diff-aware scans with a relative --code scan the changed files again, and a diff scope that cannot be represented safely falls back to a full scan. v3.6.1 shipped October 2026 — SARIF for a scan with no findings is valid, and the GitHub Action verifies the release signature before it runs the binary. v3.6.0 shipped October 2026 — baselines, .fendix-ignore rules and SARIF act on each security occurrence, so a baselined or ignored test fixture can no longer hide a new production credential grouped with it; baseline format v2, one SARIF result per occurrence, and fendix jira and fendix verify track every occurrence. v3.5.1 shipped October 2026 — fendix init writes CI that installs a pinned, signature-verified Fendix, brew upgrade reaches installs older Homebrew recorded as version 64, and the installer can require a signature or a pinned SHA-256; no scanner, fingerprint, report-schema, decision-policy or exit-code changes. v3.5.0 shipped October 2026 — --fail-on refuses an invalid threshold instead of silently scanning with none, fendix verify no longer reads missing input as a fix, and images publish to Docker Hub; no fingerprint, report-schema or decision-policy changes. v3.4.1 shipped September 2026 — the engine images now ship Go 1.27, raising govulncheck's module ceiling from 1.25; no scanner, fingerprint or exit-code changes, and the Go module floor stays 1.25. v3.4.0 shipped September 2026 — a scan can no longer look complete when it wasn't: every analyzer records whether it ran and why not, the report carries a coverage block that names the gaps, a URL scan with zero endpoints writes its report instead of vanishing, and two opt-in flags turn a coverage gap into a failing build. v3.3.0 shipped September 2026 — an upgrade no longer splits a finding you were already tracking into a closed one and a new one, and the list of checks a scan ran is built from what completed rather than what was configured. v3.2.0 — every blocking decision explains itself. A BLOCK now carries the policy it was made under and the evidence class that justified it, all the way through Fendix's own storage into the SARIF you export, so a failed pipeline is answerable from the report alone. Findings held back say why, confidence scores come with the rules that produced them, and a build blocked only because confidence enforcement was switched off is labelled as such. v3.1.0 stopped findings claiming more than they proved — wildcard CORS with credentials, rate-limit prioritisation, response-aware header grading, path-traversal wording, test-fixture credentials and dependency applicability all became more precise. v3.0 gave findings a stable identity based on which rule fired, in which file, in which function, about which operation. Re-save your baselines once after upgrading from before v3.0, and rewrite any .fendix-ignore rule that pins a fingerprint: value — rules using the supported id, endpoint or category selectors are unaffected by the fingerprint change, although positional SEC-NNN ids are suitable only for short-lived triage.