What's new
A version-by-version history of Fendix development. 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 — 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 — 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 state why they were held back, 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 rule, file, symbol and operation rather than line number; re-save your baselines once after upgrading from before it.
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.coverageblock 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 afailedentry should readcoverage.configured_completeinstead: 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-gapexits 2 when something the run was configured to execute was unavailable or failed;--require-analyzers a,bexits 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
gocommand it depends on was not there. Module resolution uses Go's default proxy; setGOPRIVATEfor private modules. - Two report semantics were corrected.
pipwith no manifest isskipped/not_applicable, notok; an npmpackage.jsonwithout a lockfile isskipped/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-ignorefingerprint 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_runwhile, 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.
A finding keeps its identity when your code moves
August 2026
- Adding a comment above a vulnerability used to turn it into a different vulnerability. A finding was identified partly by the line it sat on, so any edit above it gave it a new identity: your baseline stopped recognising it, your
.fendix-ignorerule stopped suppressing it, and something you had already triaged came back as new work. Findings are now identified by what they are — which rule fired, in which file, in which function, about which operation — and not by where they happen to sit today. Scanned before and after an edit that moved every line of the file, every finding was still recognised; the old scheme lost roughly a quarter of them and reported the same issues over again as brand-new. - Findings no longer claim more than they proved. A path-traversal finding said "user input flows to filesystem path" even when Fendix had only seen the filesystem call and could not trace any user input reaching it. A fully proven SSRF, meanwhile, still described itself as "potential". The wording now follows the evidence: "Potential" when only the dangerous operation was observed, and a plain statement when the path from user input was actually traced. The finding does not change identity when its wording strengthens — it is the same issue, better understood.
- GitHub now shows what Fendix actually believes about a finding. A placeholder API key in a test fixture — scored low-confidence, held as a warning, never going to fail your build — was still handed to GitHub ranked as High with nothing to say otherwise. An alert now carries the rule's inherent severity, Fendix's confidence in that specific instance, and the two combined, each stated separately. Where they disagree, the alert says so in the first line you read.
- Rate limiting is tested with the method it reports. Fendix reported "no rate limiting observed" against
POST /loginhaving only ever sent a GET — a claim about a test that never happened. It now probes the operation's own method where that is safe: a passive scan will not fire twenty writes at your production API, and a DELETE is never burst-probed at any setting. Where an operation cannot be probed safely it is reported as not tested, rather than quietly counted as passing. - A rate-limit sweep reads as one finding instead of eight hundred locations. A sweep across a large API produced a single result carrying a separate location for every operation it touched, which no tool renders legibly. You now get a readable sample with the true total beside it, and the complete list is still attached.
- Separate scan types stop clearing each other's GitHub alerts. A code scan and a live scan of the same repository were filed under one category, so whichever ran last marked everything the other had found as fixed. Each scan type now keeps its own set of alerts.
- Before you upgrade, re-save your baselines. Because findings are identified differently now, a baseline written by an older version matches nothing — the first scan after upgrading would report every finding as new. Run
--save-baselineonce to regenerate it, and rewrite any.fendix-ignorerule that pins afingerprint:value. Rules that match by path, category or rule id are unaffected. GitHub code-scanning alerts also re-file themselves once, under their new rule names.
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 matching by path, category or rule id are unaffected.