From df0a607732a236979382e4b814e83c018cae28ce Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 21 Oct 2026 00:55:14 +0000 Subject: [PATCH 1/3] docs(changelog): restore the entries the wave-3 merge dropped Merging ako/mxcli#1517 resolved CHANGELOG.md with the stale side of its Unreleased section: - the three Security entries for the v1 BSON driver removal (ako/mxcli#1497) and the "go mod tidy runs again" Fixed entry were dropped; - thirteen Fixed entries and the klauspost/compress Security block were duplicated. upstream/main's copy was intact, so the next sync would have carried the damage upstream. The file is now upstream's, plus the two entries #1517 legitimately adds. `search --exclude-marketplace` moves from the released [0.27.0] section to Unreleased: the contributor's branch predates that release, and the feature has not shipped. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01AeAyg3qx2my26Q8cvGyW5z --- CHANGELOG.md | 21 +++++---------------- 1 file changed, 5 insertions(+), 16 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index faf6bdce92..ae43a4d9aa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,24 +8,13 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). ### Fixed -- **`describe module` prints an entity's grants after the associations they name** (mendixlabs/mxcli#1409) — an entity's grant naming one of its associations was printed with the entity, ahead of the association, so replaying the output stopped with "entity ModA.EntA has no member(s) EntA_EntB", leaving the entity without its association or access. `check` passed the same script; it now reports a grant naming an association created further down the script, with the reorder. -- **`import from mapping` over an XML schema or web service mapping imports XML** (mendixlabs/mxcli#1232) — the activity was always written with content type JSON, so re-executing a microflow whose import uses a mapping from a web service operation failed `mx check` with "The selected import mapping cannot handle content type JSON." until the activity was opened in Studio Pro. The content type now follows the mapping's source, and a stored one is kept on rewrite; a REST call's mapping result handling gets the same. -- **Workflow user tasks keep "Auto-assign when targeting results in one user"** (mendixlabs/mxcli#1547) — every single- and multi-user task was written with `AutoAssignSingleTargetUser = false`, MDL had no spelling for it and `describe workflow` did not print it, so `create or modify workflow` turned off a box ticked in Studio Pro. When the targeting then yielded one user, the task was not assigned to them and completing it from a microflow failed at runtime with "You can't complete this user task, it is not assigned to you"; `check` and `mx check` were silent. The new clause `auto assign single user` (on `user task` and `multi user task`) writes it, `describe` prints it when set, and a `create or modify` or `replace activity` that does not restate a stored one is refused, like `await all users`. Omitted, it is off, which is what Studio Pro 11.14 stores for a new task. The MCP backend sent `false` too and now sends the task's value. -- **`check` without a project no longer reports a Data grid 2's own settings as dropped, or refuses an expression on one** (mendixlabs/mxcli#1573) — `exec -p` stores `configurationStorageType`, `configurationAttribute`, `storeFiltersInPersonalization`, `refreshInterval`, `columnsHidable`, `rowClass`, a column's `minWidth` and the rest of the widget's own keys, and `check -p` accepts them, but `check` alone warned MDL-WIDGET07 "not recognized and will be silently dropped on write" for each, and refused an Expression-typed one (`rowClass`, a column's `columnClass`) as an MDL-WIDGET42 error, so a script `exec` takes failed `make check-mdl`. Data grid 2 has no embedded definition (it is generated from the project's package, and an embedded one would take precedence over that package on every write), so the check judged its keys against the built-in widgets' vocabulary. It now reads the property list of the Data grid 2 template mxcli embeds: a key the template declares is accepted and its declared type decides MDL-WIDGET42. A key it does not declare still warns — a misspelled `configurationStorageTyp` too, with the spelling it probably meant — but says what is true without a project: that the key cannot be verified, and is dropped on write unless the installed widget declares it. With `-p`, and for every other widget, nothing changes. -- **`describe page` prints a Data grid 2's and a Gallery's settings that MDL has no keyword for** (mendixlabs/mxcli#1573) — a pluggable widget takes every setting by its own key (`configurationStorageType: localStorage`, `itemSelectionMethod: rowClick`, `refreshInterval: 30`, a column's `minWidth`), and `exec` stores it with `mx check` clean, but `describe page` printed only the keyword properties (`PageSize`, `Pagination`, `Selection`, `onClick`). Executing the description therefore wrote the widget's default over each of them, silently: measured on Mendix 11.12.5, a grid with 22 settings changed came back with the 4 that have keywords, and a Studio Pro-authored grid lost its `onClickTrigger: double`. `describe` now prints each setting whose stored value differs from the default, under its key — enumerations and plain values, text templates (with their `{N}` parameters), expressions, attributes (bare inside a data container, qualified where a bare name would not resolve), a column's own settings and the empty-state placeholder slot. The default is read from the widget's own stored definition, not a list, and a text template counts as default when it holds the text the widget ships for it, so a grid that sets none of them describes exactly as before. A setting with no spelling (an attribute reached over associations) is named in a `-- NOT re-executable` comment. A Gallery had the same loss and takes the same fix. -- **`describe page` names the entity an association data source reaches, not the association** (mendixlabs/mxcli#1571) — under a data view, list view, grid or gallery bound over an association, the context comment read `-- Context: $currentObject (Shop.Order_Customer)`: the association, where `(Shop.Customer)` belongs. The page was right (`mx check` clean); the comment misled anyone reading `describe` to learn what a nested widget binds, and the same context decides how an association name inside the container is spelled. The entity is the destination stored on the source's last hop, so it is correct from either end of the association, for a reference set, and for a specialization of the association's end. The `DataSource:` text is unchanged. A source with no stored destination prints what it did before. -- **`check -p` and `exec` refuse a navigation profile or menu document that names a page or flow that does not exist** (mendixlabs/mxcli#1569) — `create or modify navigation` with a home page, role home, not-found page or menu item (at any depth, in both the deprecated and the `mdl 1` spelling) pointing at a missing page, microflow or nanoflow passed `check -p` ("Check passed!") and `exec` ("Navigation profile 'Responsive' updated."), and only `mx check` objected: CE1613 "The selected page 'Shop.NoSuchHome' no longer exists.", once for the home page and once per menu item. A page's own `show page` and `call microflow` and an icon name were already resolved; a navigation statement was not, and it rewrites the whole profile. `check -p` now reports each unresolved target with the profile or menu document, the menu item path (`menu item 'Admin' > 'Users'`) and the missing name, and `exec` refuses the statement before it writes anything. A target the script creates resolves, wherever it stands in a run that rolls back (plain `exec`, `check`), as a call to a flow created further down does (mendixlabs/mxcli#1405); `exec --continue-on-error`, the REPL and `-c` refuse one created further down. A standalone `create menu` document is covered, and one stored as excluded only warns, as an excluded page does. A login page is not resolved: the sign-in page may be the theme's static `login.html` rather than a page document. -- **A bare `returns Module.Name` that names an enumeration is accepted by `exec`, as `check` already said** (mendixlabs/mxcli#1562) — `create microflow M.F () returns M.Variant as $V` (and the nanoflow and rule forms) passed `check -p --references` and was then refused by `exec` with "entity 'M.Variant' not found for return type". The parser cannot tell an entity from an enumeration for a bare qualified name; `exec` resolved that for parameters (the entity first, then an enumeration) but not for return types. All three kinds now do, and store exactly what `returns enum M.Variant` stores. A rule's `check` refused the bare form too, with a misleading "a rule must return a Boolean or an enumeration, not Entity". A name that is both an entity and an enumeration stays the entity, as for a parameter, and a name that is neither now says "entity or enumeration" in `check` and in `exec`, for return types and parameters alike, where it used to say only "entity". `returns enum M.Variant` and `Enumeration(M.Variant)` always worked and are what `describe` prints. -- **A JavaScript action's parameters are written with the names mxbuild generates** (mendixlabs/mxcli#1560) — `create javascript action … (Url: String, ReadOnly: Boolean)` wrote `export async function JS_Open(Url, ReadOnly)`, while mxbuild and Studio Pro regenerate the signature with the first character of each parameter lower-cased, `JS_Open(url, readOnly)`, and keep the body. A body using the declared `Url` then threw `ReferenceError` the first time the action ran, with `check`, `mx check` and the build all clean. The signature and the JSDoc `@param` names now carry that name (only the first character changes: `URL` is `uRL`, `MyID` is `myID`, `_Lead` stays), measured against mxbuild 11.12.5, so what `exec` writes is what a build leaves. The same rewrite used to break a Studio Pro-authored action on its first edit through MDL: `Geocode(address, …)` became `Geocode(Address, …)` over a body that still read `address`. The model keeps the declared parameter names, `describe` prints them as declared, and bodies are not rewritten, so a body reads `readOnly`, not `ReadOnly`. -- **A quoted `Editable: ''` is stored as a conditional editability instead of being dropped** (mendixlabs/mxcli#1567) — `CHECKBOX cb (Attribute: Flag, Editable: '$currentObject/Locked = false')` passed `check`, `exec` said "Created page", and the widget was stored `Editable: Always` with no condition, so a field meant to be locked stayed editable (`mx check` also reported 0 errors). `Visible: ''` beside it was stored as a conditional visibility, and the unquoted `Editable: $currentObject/Locked = false` worked: the page builder read `Editable` only as one of `Always`/`Never`/`Conditional` and skipped every other string without a word. A string that is not one of those is now the condition, exactly as for `Visible`, and `describe` prints it back bare, so a describe → exec round trip is stable. `Editable: false` / `true` on an input widget were dropped the same way and now store `Never` / `Always` (a list view and a grid column keep their own plain boolean, and a data view keeps its existing handling). `check` refuses what still cannot be stored — a misspelt `Editable: Neever`, a quoted text that is not an expression, `Editable: Conditional` with no condition — as MDL-WIDGET19 naming the accepted spellings, and a quoted condition now gets the checks the bare one gets (MDL-WIDGET13 association steps, MDL-BUTTON02, MDL-WIDGET41 on a pluggable widget that declares no Editability, MDL-WIDGET20, and MDL-WIDGET31 no longer warns about an input marked `editable: false`). `alter page … set (Editable: '')` still refuses a quoted condition with its existing message. -- **`mxcli run --local --detach` no longer opens a console window per child process on Windows** (mendixlabs/mxcli#1484) — a detached run on Windows made several terminal windows pop up during a boot, one for each console program it started (Java, MxBuild, Gradle). The run was created without a console (`DETACHED_PROCESS`), so Windows gave each console child a new, visible one. It is now created with a console of its own that has no window (`CREATE_NO_WINDOW`), which its children share. Measured on Mendix 11.12.5: v0.26.0 showed 4 new visible windows during a boot, this build 0. Closing the launching terminal still does not end the run, and `run stop` is unchanged. -- **`mxcli test tests` finds test files in subfolders** (mendixlabs/mxcli#1363) — `mxcli test tests --list` printed `Found 0 test(s)` when the suites live in `tests//*.test.mdl`, while `mxcli test tests/Sales --list` found them: a directory argument only read the files directly inside it. It now reads `*.test.mdl` and `*.test.md` at any depth, in path order, skipping hidden folders (`.git`, `.mxcli`) and folders starting with `_` (the Go convention, so a positive-control suite can sit in `tests/unit/_control` outside the default run; the folder you name is always read, so `mxcli test tests/unit/_control` runs it). Skipped `_` folders are named after the `Found N test(s)` line of a run and at the end of a `--list`, so they do not disappear silently, and `--list` over a tree with no tests now says why on stderr (it still exits 0; a run with no tests still exits 1). A file that cannot be parsed is still reported on its own and does not stop the others; neither does a subfolder that cannot be read (a locked folder, a root-owned one in a mounted volume), which is reported by name and turns the run red while the tests in the other folders still run. The "no tests found" hint now also names misnamed `.mdl` / `.md` files in subfolders and the `_` folders that were skipped. -- **`change` through an association is refused with the working idiom instead of shipping a project mxbuild rejects** — `change $a (A_B.Title = 'x')` (a member whose dotted qualifier is an association, not a module) passed `mxcli check`, `exec` reported success, and the next build failed with CE1613 "The selected association 'A_B.Title' no longer exists": Mendix's Change object activity cannot set an attribute through an association at all (probe matrix in [the bug report](docs/12-bug-reports/2026-10-16-change-member-through-association.md) — every encoding, including the ones an older mxcli wrote, is rejected or unloadable), and the writer's one-dot heuristic categorized the name as an association-set verbatim. `check -p` now reports the form and `exec` refuses it, both naming the retrieve-then-change replacement (`retrieve $obj from $a/A_B;` then `change $obj (Title = …)`); the legal shapes — `Module.Assoc` association-sets and `Module.Entity.Attribute` qualified attributes — are untouched. Example: `mdl-examples/bug-tests/change-member-through-association.mdl`. +- **`go mod tidy` runs again, and the expression round-trip test runs for the first time** — `mdl/exprcheck/roundtrip_test.go` imported `mdl/backend/mpr` (the legacy engine, deleted) plus `executor.GetHierarchyForMining` and `executor.DescribeMicroflowToString` (both removed). It never broke a build because it sat behind `//go:build roundtrip`, a tag no Makefile target and no CI job has ever passed — the only file in the tree using it. `go mod tidy` considers every build tag, so it was the one command that still saw the file, and it failed with "no matching versions for query latest" on the deleted package, which reads as a dependency problem rather than a dead test. The test is ported to `mdl/executor/exprcheck_roundtrip_test.go` under the `integration` tag, where `setupTestEnv`/`describeMDL` already provide the real-project harness and `make test-integration-executor` actually runs it; the removed hierarchy helper is replaced by building the container-to-module map from `backend.ListModules()`. Its assertion is worth keeping: describing every microflow and re-parsing each expression with the robust parser catches grammar gaps — a missing `parsePrimary` branch, a missing `funcTable` entry, a missing slot constraint — that the unit tests cannot, since those only feed the parser expressions someone thought to write down. It now also fails rather than passing silently when the fixture project yields no microflows or no expression slots. ### Security -- **Dependency scan follow-up: `klauspost/compress` and `go-mssqldb` raised to their fixed versions** — `github.com/klauspost/compress` v1.19.2 -> v1.20.3 and `github.com/microsoft/go-mssqldb` v1.10.0 -> v1.11.2. A second scan of the published `.exe`, run after the mongo-driver upgrade, still blocked the release. `klauspost/compress` v1.19.2 predates GHSA-fpmh-frq8-7rj8 (a zstd `DecodeAll` crash on crafted input, fixed in v1.20.2) — the earlier upgrade had only raised it to whatever `mongo-driver/v2` declared, which is not a security floor; it is now an explicit requirement. As before, that module is compiled into no mxcli build (no `go.sum` entry, no `go version -m` line), so the bump moves only the requirement graph the scanner reads. `go-mssqldb` is a blank import in `sql/drivers.go` for driver registration and mxcli calls none of its API, so the bump has no API surface; `TestExternalDriversRegistered` now pins that contract, which had no coverage, and fails with `driver "sqlserver" is not registered` when the import is dropped. -- **`go.mongodb.org/mongo-driver` v1 remains flagged and cannot be upgraded out** — CVE-2026-107325 has no fix on the v1 line (v1.17.10 is its last release and the module is deprecated). mxcli is not exposed: the panic is in `bsoncore.Array.Validate`, which mxcli imports in no file, every `.Validate()` call site in the tree resolves to the v2 driver, and v1's `bson.Unmarshal` — the API mxcli does use, on `.mpr` bytes — does not panic on the malformed payloads tried. Measured on v1.17.10: `bsoncore.Array.Validate([]byte{0,0,0,0})` panics with `index out of range [-1]`, while `bson.Raw.Validate` on CVE-2026-107324's `math.MaxInt32` payload does not, so that second CVE's code path is v2-only and the v1 report overstates it by one. Clearing the finding at source needs the remaining v1 `bson` imports moved to v2, which is tracked separately. +- **The deprecated v1 BSON driver is gone: mxcli runs on `mongo-driver/v2` alone** — `go.mongodb.org/mongo-driver` v1.17.10 is removed from `go.mod`, and `go version -m bin/mxcli` now lists only `mongo-driver/v2 v2.9.2`. It was the last component left on a customer's blocking dependency scan, flagged for CVE-2026-107325, which **has no fix on the v1 line and never will** — v1.17.10 is that line's final release and the module is marked deprecated, so no upgrade could ever have cleared it. mxcli was never exposed (the panic is in `bsoncore.Array.Validate`, which no file imported), but the finding would have recurred on every scan. +- **New `modelsdk/bsoncompat` — the decode shims that make v2 read like v1** — the two drivers decode nested documents into *different Go types*, and mxcli's type assertions were written against v1's. Measured on identical bytes: a `bson.D` destination nests `primitive.D` under v1 and `bson.D` under v2 (equivalent); a `bson.M` destination nests `primitive.M` under v1 but `bson.D` under v2; a `map[string]any` destination nests `map[string]any` under v1 but `bson.D` under v2. The last two silently break a `doc["Key"].(bson.M)` read — a failed type assertion is not an error, so the value is dropped and the next write emits a document with a property missing, which Studio Pro refuses to merge (the `canon.CompletePropertySets` class, mendixlabs/mxcli#1373). `UnmarshalDocM` and `UnmarshalDocMap` restore v1's shape at every depth, arrays included; `DMap` replaces the `bson.D.Map()` method v2 removed. The two Unmarshal helpers are **not** interchangeable — `bson.M` is a named type, so a `bson.M` value does not satisfy `.(map[string]any)` — and a `bson.D` destination needs neither, since applying one there re-introduces the bug inverted. All three shapes are pinned by tests that fail loudly if a future driver release makes a helper unnecessary, rather than passing vacuously. +- **A guard keeps v1 from coming back** — `TestNoV1DriverImports` walks the tree for the v1 import path and `TestGoModDoesNotRequireV1Driver` reads `go.mod`, because re-adding either is not a build error and nothing else would notice until the next dependency scan. Verified by control: both name the offending file and the offending `go.mod` line. ### Security @@ -37,6 +26,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). - **`layoutheader` and native layout parts — every Atlas layout copies whole** — `describe layout` → `create layout` of Atlas's Phone_ and Tablet_ layouts dropped their header bar (`Forms$Header`, printed as NOT re-executable), so pages bound to its `HeaderLeft` / `HeaderRight` placeholders failed CE1613 and a copy of Phone_Sidebar or Tablet_Sidebar failed CE0611 "A sidebar toggle button is required if a region can toggle". `layoutheader header1 { region left { … } region right { … } }` writes it. A native layout's copy silently lost its header placeholder, bottom bar and sidebar — pages bound to `Header` or a sidebar placeholder then failed CE1613; they are `region header { placeholder Header }`, `showbottombar: true` and `region sidebar { … }`, and the native content is written with the eight keys Studio Pro stores. Measured on a new 11.13.0 app: all 22 Atlas_Core layouts copied, with pages bound to the copied header and sidebar placeholders, build with 0 errors. - **`sidebartoggle` — Atlas's responsive sidebar toggle (`Forms$SidebarToggleButton`) in MDL** (mendixlabs/mxcli#1220) — copying `Atlas_Core.Atlas_Default` with `describe layout` → `create layout` dropped the button that opens the sidebar on phones and tablets: `describe` printed it as a NOT re-executable comment, and `exec` gave no warning. `sidebartoggle sidebarToggle3 (Tooltip: 'Toggle Menu', ButtonStyle: Primary, Icon: 'Atlas_Core.Atlas_Filled.navigation-menu', Class: 'toggle-btn')` takes an action button's caption, tooltip, style and icon, and `RenderMode: Link`; it has no action. The written widget has the keys of Atlas's own, including its caption under `CaptionTemplate` rather than the `Caption` key the vendored metamodel bound. A scroll container region's `ToggleMode` (`ToggleMode: 'ShrinkContentInitiallyClosed'`), which the toggle opens, is described and written too — without it the copy failed CE0628. - **`mxcli marketplace package` — export a module as a `.mpk`** (mendixlabs/mxcli#1382) — `mxcli marketplace package MyModule -p app.mpr [-o dir] [--all-libs] [--exclude REGEX] [--exclude-managed-dependencies] [--json]` is the reverse of `marketplace install --file`: it wraps Mendix's own `mx create-module-package` (resolved for the project's Mendix version), so the package is what Studio Pro's *Export module package* makes, and it installs back with `install --file`. By default only the module's own required `userlib/` JARs go in (`mx -l`; measured 12.5 KB against 24 MB with Business Events installed); `--all-libs` bundles every JAR. `-l` filters only JARs — mx still exports the app's whole `resources/` folder (measured: another module's `resources/EventLog/` came along) — so after the export mxcli opens the package, warns on stderr about each `resources//`, `javasource//` or `themesource//` file of another module, and prints the `--exclude` pattern that drops them in the one form mx accepts (`^resources.EventLog.`; a character class or backslash is rejected by mx with exit 1 and a usage message, and an exit 0 that wrote no package is reported as an error too). A module name that is not in the project lists the project's modules instead of failing with mx's exit 3, and mx's exit codes 1–4 are reported with what they mean and what mx printed. `mx create-module-package` writes into the project it is given — measured on a blank 11.12.5 app: `theme-cache/web/theme.compiled.css` and its map rewritten, `deployment/sass/main.scss` added, the module's `themesource/` folder created, model files untouched — so it runs on a temporary copy, as `docker check` does; a before/after listing of the project (sizes and modification times) was identical. The `mx` that runs must be the project's Mendix version: `ResolveMxForVersion` prefers an `mx` on PATH and falls back to any cached one, so mxcli reads the version of the `mx` it found (its `mx.deps.json`, else its version-named folder) and refuses a different major.minor.patch, or one it cannot identify, naming both versions and the fix (`mxcli setup mxbuild --version X`); `--mxbuild-path` is the override, with a warning on stderr. mx's own output on a successful export goes to stderr, cancelling the command stops mx, and an output directory inside the project is not copied into the temporary copy. Round trip on 11.12.5: export, `install --file` into a second fresh app, `mx check` — 0 errors. +- **`mxcli search --exclude-marketplace` leaves out hits inside Marketplace modules** (mendixlabs/mxcli#1477) — an audit of the app's own text (a leftover `TODO`, a word that must not ship) reported a vendor module's text beside the project's own. The flag drops hits whose module was downloaded from the Marketplace, including the module's own documentation and domain model documentation; it is off by default, because `search` is read-only and the default must not change what existing callers see (`layout` skips Marketplace modules because it writes). A module counts as a Marketplace module by the current snapshot of the catalog only, so one that was a Marketplace module in an older snapshot is not left out of every later search. The `SEARCH` statement is unchanged. Rules: docs-site "Full-Text Search". ### Fixed @@ -62,7 +52,6 @@ Headline: **`exec` is all or nothing: when a statement fails at write time, the - **`mxcli run --local --listen-addr` — reach a local app from another machine** (mendixlabs/mxcli#1248) — the app always bound `127.0.0.1`, so a phone or a second laptop on the LAN could not open it. `--listen-addr 0.0.0.0` (or `::`) binds every interface and prints the URLs the app is reachable at; the default stays loopback. The M2EE admin API is unaffected and stays on `127.0.0.1` whatever the flag says — before, the one option set both binds, so widening the app would have put the admin API on the network too. mxcli itself still dials the app on loopback under a wildcard (health check, screenshots, "App is running at"), and brackets an IPv6 address. The value must be an IP address and is refused before the build otherwise. The runtime does not derive an application root URL from the bind address (measured on 11.12.1: unset stays `null` under `0.0.0.0`), so a wildcard bind with none warns at boot and names the `--runtime-setting ApplicationRootUrl=…` to pass. - **`mxcli search --fail-on-match` turns the search into a gate** — a pipeline that must refuse a change when a forbidden word turns up in the project's text had no exit status to read: `search` exited 0 whether it found something or not. With the flag it exits 1 when there is at least one hit and 0 when there is none, printing the results as before (a JSON array under `--json`). A search that cannot run, such as an unbalanced `"` or an unknown project, exits 2 so that 1 always means a match; the `SEARCH` statement still answers an unrunnable query with "No matches found.". Without the flag a hit exits 0 as before. Rules: docs-site "Full-Text Search". -- **`mxcli search --exclude-marketplace` leaves out hits inside Marketplace modules** (mendixlabs/mxcli#1477) — an audit of the app's own text (a leftover `TODO`, a word that must not ship) reported a vendor module's text beside the project's own. The flag drops hits whose module was downloaded from the Marketplace, including the module's own documentation and domain model documentation; it is off by default, because `search` is read-only and the default must not change what existing callers see (`layout` skips Marketplace modules because it writes). A module counts as a Marketplace module by the current snapshot of the catalog only, so one that was a Marketplace module in an older snapshot is not left out of every later search. The `SEARCH` statement is unchanged. Rules: docs-site "Full-Text Search". - **`filemanager` — the classic file manager (`Forms$FileManager`) in MDL** (mendixlabs/mxcli#151) — `filemanager fileManager1 (Label: 'File', Type: Upload, MaxFileSize: 10, AllowedExtensions: '.xlsx', ShowFileInBrowser: false)` inside a data view on a `System.FileDocument` specialization. Before, there was no way to author one: `pluggablewidget 'Forms$FileManager'` had no definition, the bare keyword was refused as MDL-WIDGET25, and `describe page` printed a stored one as a NOT re-executable comment, so describe → exec dropped it. Omitted properties take Studio Pro's defaults (`Both`, 5 MB, any file, false); the written BSON has the property set of a Studio Pro-authored file manager. `alter page … set` changes all four properties. A file manager in no data container is MDL-WIDGET45 at `check`, and one in a data view on an entity that is not a FileDocument is refused by `exec` — both were mxbuild's CE6810 after a clean `exec`. - **A module's domain model as SVG, in the companion and on the command line** — `mxcli describe --format svg domainmodel ` writes a finished SVG: entities at the positions the model stores, coloured by kind, with attributes, associations, multiplicities and generalizations. Boxes are sized to fit every name, pulling the stored layout apart just enough instead of clipping. In the Claude Code companion, selecting a module's Domain Model node in the desktop app or VS Code shows it at actual size, with pan, Fit and a full-width view; the terminal lists the entities as before. - **`Visible: roles (Mod.A, …)` — role-based widget visibility** (mendixlabs/mxcli#1460) — Studio Pro's "visible for module roles" was never read by `describe`, had no MDL spelling, was written empty and was dropped by `alter … set Visible`. It is now read, written and combinable with an expression or attribute condition, on widgets, tab pages and navigation list items. A tab page's (mendixlabs/mxcli#1465) and a layout grid row's `Visible:` (mendixlabs/mxcli#1461) are now written too; `describe` → `exec` used to make a Studio Pro row always visible. From d32fb5e7c2adce9ed1358884bf55cdafd0fac99b Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 21 Oct 2026 01:05:45 +0000 Subject: [PATCH 2/3] fix(deps): keep the klauspost/compress floor across go mod tidy The v1 BSON driver removal (ako/mxcli#1497) ran `go mod tidy`, which deleted the `github.com/klauspost/compress v1.20.3` requirement from ako/mxcli#1493. No package mxcli builds imports that module, and tidy removes a requirement nothing needs. The module graph fell back to the v1.19.2 that mongo-driver/v2 declares. That version is flagged for GHSA-fpmh-frq8-7rj8, so the customer's dependency scan would block the release again. No build or test noticed. internal/depfloor/floor.go blank-imports klauspost/compress/zstd behind a `depfloor` build tag that no build sets. Tidy considers every build tag, so the requirement is now direct and survives tidy. The binary is unchanged: `go version -m bin/mxcli` lists no klauspost/compress. TestGoModKeepsSecurityFloors fails when go.mod does not require a floored module, or requires it below its floor. It also pins mongo-driver/v2 at v2.9.2. Measured: - on main, the test fails with "go.mod does not require github.com/klauspost/compress"; - a hand-added require line is gone after one `go mod tidy`; - with floor.go deleted, tidy drops the line and the test fails; - with floor.go present but no version pinned, tidy re-adds v1.19.2 and the test fails "below the floor". Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01AeAyg3qx2my26Q8cvGyW5z --- ...1-go-mod-tidy-dropped-klauspost-floor.json | 1 + CHANGELOG.md | 1 + go.mod | 1 + go.sum | 2 + internal/depfloor/floor.go | 25 +++++ internal/depfloor/floor_test.go | 98 +++++++++++++++++++ 6 files changed, 128 insertions(+) create mode 100644 .claude/skills/fix-issue/findings/other/2026-10-21-go-mod-tidy-dropped-klauspost-floor.json create mode 100644 internal/depfloor/floor.go create mode 100644 internal/depfloor/floor_test.go diff --git a/.claude/skills/fix-issue/findings/other/2026-10-21-go-mod-tidy-dropped-klauspost-floor.json b/.claude/skills/fix-issue/findings/other/2026-10-21-go-mod-tidy-dropped-klauspost-floor.json new file mode 100644 index 0000000000..4cd9ceed54 --- /dev/null +++ b/.claude/skills/fix-issue/findings/other/2026-10-21-go-mod-tidy-dropped-klauspost-floor.json @@ -0,0 +1 @@ +{"area": "go.mod", "date": "2026-10-21", "symptom": "After the v1 BSON driver removal (ako/mxcli#1497), go.mod no longer named github.com/klauspost/compress and `go list -m github.com/klauspost/compress` reported v1.19.2, the version a customer's Sonatype scan had blocked on for GHSA-fpmh-frq8-7rj8. No build, test or CI job failed", "cause": "The v1.20.3 floor from ako/mxcli#1493 was a hand-written `// indirect` require line for a module no mxcli package imports (it arrives through mongo-driver/v2, used only for its BSON packages). `go mod tidy` removes any requirement that no package in the module's import graph needs, so the first tidy after it, the one #1497 ran to drop v1, deleted the line and MVS fell back to the v1.19.2 mongo-driver/v2@v2.9.2 declares", "file": "`internal/depfloor/floor.go` (new, `//go:build depfloor`, blank-imports github.com/klauspost/compress/zstd); `internal/depfloor/floor_test.go` (new, TestGoModKeepsSecurityFloors); `go.mod`/`go.sum` (klauspost/compress v1.20.3, now direct)", "insight": "A security floor on a module the program never imports is not a go.mod edit, it is an import: tidy treats every build tag except `ignore` as enabled, so a blank import behind a tag no build sets keeps the requirement through tidy while compiling nothing (`go version -m bin/mxcli` still lists no klauspost/compress). Measured both ways: a hand-added require line is gone after one `go mod tidy`; with floor.go deleted tidy drops the line and the test fails 'go.mod does not require'; with floor.go present but no version pinned tidy re-adds it at the graph's v1.19.2 and the test fails 'below the floor'. The guard reads go.mod, which is what the scanner's graph is built from, and a direct requirement at or above the floor guarantees MVS selects at least that version", "refs": ["GHSA-fpmh-frq8-7rj8", "ako/mxcli#1493", "ako/mxcli#1497"]} diff --git a/CHANGELOG.md b/CHANGELOG.md index ae43a4d9aa..86f6971661 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). ### Security +- **The `klauspost/compress` v1.20.3 floor survives `go mod tidy`** — the v1 BSON driver removal ran `go mod tidy`, which deleted the hand-written `github.com/klauspost/compress v1.20.3` requirement, because no package mxcli builds imports it. The module graph fell back to the v1.19.2 that `mongo-driver/v2` declares, which is flagged for GHSA-fpmh-frq8-7rj8, so the dependency scan would have blocked the release again. A new `internal/depfloor/floor.go` imports `github.com/klauspost/compress/zstd` behind a `depfloor` build tag that no build sets. `go mod tidy` considers every tag, so the requirement is now direct and kept, while `go version -m bin/mxcli` still lists no `klauspost/compress`. `TestGoModKeepsSecurityFloors` fails when go.mod lacks a floored module or requires it below its floor, and it also pins `mongo-driver/v2` at v2.9.2. - **Dependency scan follow-up: `klauspost/compress` and `go-mssqldb` raised to their fixed versions** — `github.com/klauspost/compress` v1.19.2 -> v1.20.3 and `github.com/microsoft/go-mssqldb` v1.10.0 -> v1.11.2. A second scan of the published `.exe`, run after the mongo-driver upgrade, still blocked the release. `klauspost/compress` v1.19.2 predates GHSA-fpmh-frq8-7rj8 (a zstd `DecodeAll` crash on crafted input, fixed in v1.20.2) — the earlier upgrade had only raised it to whatever `mongo-driver/v2` declared, which is not a security floor; it is now an explicit requirement. As before, that module is compiled into no mxcli build (no `go.sum` entry, no `go version -m` line), so the bump moves only the requirement graph the scanner reads. `go-mssqldb` is a blank import in `sql/drivers.go` for driver registration and mxcli calls none of its API, so the bump has no API surface; `TestExternalDriversRegistered` now pins that contract, which had no coverage, and fails with `driver "sqlserver" is not registered` when the import is dropped. - **`go.mongodb.org/mongo-driver` v1 remains flagged and cannot be upgraded out** — CVE-2026-107325 has no fix on the v1 line (v1.17.10 is its last release and the module is deprecated). mxcli is not exposed: the panic is in `bsoncore.Array.Validate`, which mxcli imports in no file, every `.Validate()` call site in the tree resolves to the v2 driver, and v1's `bson.Unmarshal` — the API mxcli does use, on `.mpr` bytes — does not panic on the malformed payloads tried. Measured on v1.17.10: `bsoncore.Array.Validate([]byte{0,0,0,0})` panics with `index out of range [-1]`, while `bson.Raw.Validate` on CVE-2026-107324's `math.MaxInt32` payload does not, so that second CVE's code path is v2-only and the v1 report overstates it by one. Clearing the finding at source needs the remaining v1 `bson` imports moved to v2, which is tracked separately. diff --git a/go.mod b/go.mod index 48d47e5da9..df949ac424 100644 --- a/go.mod +++ b/go.mod @@ -14,6 +14,7 @@ require ( github.com/fsnotify/fsnotify v1.10.1 github.com/jackc/pgx/v5 v5.9.2 github.com/jpillora/chisel v1.11.8 + github.com/klauspost/compress v1.20.3 github.com/mattn/go-runewidth v0.0.24 github.com/microsoft/go-mssqldb v1.11.2 github.com/pmezard/go-difflib v1.0.0 diff --git a/go.sum b/go.sum index f1181e690a..31520db476 100644 --- a/go.sum +++ b/go.sum @@ -107,6 +107,8 @@ github.com/jpillora/requestlog v1.0.0 h1:bg++eJ74T7DYL3DlIpiwknrtfdUA9oP/M4fL+Pp github.com/jpillora/requestlog v1.0.0/go.mod h1:HTWQb7QfDc2jtHnWe2XEIEeJB7gJPnVdpNn52HXPvy8= github.com/jpillora/sizestr v1.0.0 h1:4tr0FLxs1Mtq3TnsLDV+GYUWG7Q26a6s+tV5Zfw2ygw= github.com/jpillora/sizestr v1.0.0/go.mod h1:bUhLv4ctkknatr6gR42qPxirmd5+ds1u7mzD+MZ33f0= +github.com/klauspost/compress v1.20.3 h1:zWNoFqq/lSuYQRim3dUXlXeA0IqwuZGicdG5ZTfY5j4= +github.com/klauspost/compress v1.20.3/go.mod h1:LUdAzn7YLVvxLpc7y3V1m40wESHTgc1422pwwBSKYuI= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= diff --git a/internal/depfloor/floor.go b/internal/depfloor/floor.go new file mode 100644 index 0000000000..8556531fd7 --- /dev/null +++ b/internal/depfloor/floor.go @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: Apache-2.0 + +//go:build depfloor + +// Package depfloor keeps security floors in go.mod for modules mxcli requires +// but never compiles. +// +// A customer's dependency scan reads the module graph, so a vulnerable version +// there blocks a release even when no mxcli build contains the code. Raising +// such a module needs a require line in go.mod, but `go mod tidy` removes a +// requirement no package imports. Tidy considers every build tag, so the +// imports below keep each requirement alive. Nothing sets the depfloor tag, +// so no build compiles this file and the binary is unchanged. +// +// TestGoModKeepsSecurityFloors checks the versions. To raise a floor, run +// `go get @` and then `go mod tidy`. To drop one, remove its +// import here and its row in the test. +package depfloor + +import ( + // GHSA-fpmh-frq8-7rj8: zstd DecodeAll crash on crafted input, fixed in + // v1.20.2. Arrives through go.mongodb.org/mongo-driver/v2, which declares + // v1.19.2 and which mxcli uses only for its BSON packages. + _ "github.com/klauspost/compress/zstd" +) diff --git a/internal/depfloor/floor_test.go b/internal/depfloor/floor_test.go new file mode 100644 index 0000000000..8540ada9ad --- /dev/null +++ b/internal/depfloor/floor_test.go @@ -0,0 +1,98 @@ +// SPDX-License-Identifier: Apache-2.0 + +package depfloor_test + +import ( + "os" + "path/filepath" + "strconv" + "strings" + "testing" +) + +// floors are the lowest versions go.mod may require. Each one is a version a +// customer's dependency scan blocked a release on. The scan reads the module +// graph, not the binary, so a module counts even when no mxcli build compiles +// it. +var floors = []struct { + module, min, why string +}{ + {"github.com/klauspost/compress", "v1.20.2", "GHSA-fpmh-frq8-7rj8 (zstd DecodeAll crash), fixed in v1.20.2"}, + {"go.mongodb.org/mongo-driver/v2", "v2.9.2", "CVE-2026-81521, fixed in v2.8.2, regressed in v2.9.0 and v2.9.1, restored in v2.9.2"}, +} + +// TestGoModKeepsSecurityFloors fails if go.mod no longer requires a floored +// module at or above its floor. +// +// klauspost/compress is the case this exists for. mxcli imports none of its +// packages; it arrives through mongo-driver/v2, which declares v1.19.2. A +// hand-written require line is removed by the next `go mod tidy`, which is how +// the v1.20.3 floor from ako/mxcli#1493 was lost in ako/mxcli#1497 without any +// build or test noticing. floor.go keeps the requirement alive across tidy. +func TestGoModKeepsSecurityFloors(t *testing.T) { + b, err := os.ReadFile(filepath.Join("..", "..", "go.mod")) + if err != nil { + t.Fatalf("read go.mod: %v", err) + } + required := map[string]string{} + for _, line := range strings.Split(string(b), "\n") { + f := strings.Fields(strings.TrimPrefix(strings.TrimSpace(line), "require ")) + if len(f) >= 2 && strings.HasPrefix(f[1], "v") { + required[f[0]] = f[1] + } + } + for _, fl := range floors { + got, ok := required[fl.module] + switch { + case !ok: + t.Errorf("go.mod does not require %s, so the module graph falls back to whatever a dependency declares.\n"+ + "Floor %s: %s.\n"+ + "Keep its import in internal/depfloor/floor.go and run `go mod tidy`; a hand-written require line does not survive tidy.", + fl.module, fl.min, fl.why) + case less(got, fl.min): + t.Errorf("go.mod requires %s %s, below the floor %s: %s", fl.module, got, fl.min, fl.why) + } + } +} + +// less reports whether semantic version a sorts before b. It compares only +// vMAJOR.MINOR.PATCH, which is all the floors use; a pre-release suffix sorts +// as its release. +func less(a, b string) bool { + pa, pb := parts(a), parts(b) + for i := range pa { + if pa[i] != pb[i] { + return pa[i] < pb[i] + } + } + return false +} + +func parts(v string) [3]int { + var p [3]int + v = strings.TrimPrefix(v, "v") + if i := strings.IndexAny(v, "-+"); i >= 0 { + v = v[:i] + } + for i, s := range strings.SplitN(v, ".", 3) { + p[i], _ = strconv.Atoi(s) + } + return p +} + +func TestLess(t *testing.T) { + for _, c := range []struct { + a, b string + want bool + }{ + {"v1.19.2", "v1.20.2", true}, + {"v1.20.2", "v1.20.2", false}, + {"v1.20.3", "v1.20.2", false}, + {"v1.9.0", "v1.10.0", true}, + {"v2.9.2-rc1", "v2.9.2", false}, + } { + if got := less(c.a, c.b); got != c.want { + t.Errorf("less(%s, %s) = %v, want %v", c.a, c.b, got, c.want) + } + } +} From 21be71b6f53838b1481370da13ed1dca1020fee3 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 21 Oct 2026 05:07:40 +0000 Subject: [PATCH 3/3] =?UTF-8?q?ci(deps):=20bump=20Go=20toolchain=201.27.2?= =?UTF-8?q?=20=E2=86=92=201.27.3=20for=20GO-2026-6809?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit govulncheck flagged GO-2026-6809 (distrust compromised intermediate certificates in crypto/x509), present in crypto/x509@go1.27.2 and fixed in go1.27.3. mxcli reaches the vulnerable code through marketplace.Client.Download (io.Copy over TLS). This is a new advisory against the standard library: build-and-test is red on main for the same reason. Bump the toolchain in go.mod and the go-version in every workflow, so CI installs the fixed standard library. Locally, govulncheck ./... reports GO-2026-6809 on go1.27.2 (exit 3) and "No vulnerabilities found" on go1.27.3. make build, make test and make vet pass on go1.27.3. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01AeAyg3qx2my26Q8cvGyW5z --- .github/workflows/nightly.yml | 8 ++++---- .github/workflows/push-test.yml | 8 ++++---- .github/workflows/release.yml | 2 +- go.mod | 2 +- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml index 5bdbdda112..da3b5719ea 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -40,7 +40,7 @@ jobs: - uses: actions/setup-go@v7 with: - go-version: '1.27.2' + go-version: '1.27.3' - name: Cache ANTLR4 JAR uses: actions/cache@v6 @@ -89,7 +89,7 @@ jobs: - uses: actions/setup-go@v7 with: - go-version: '1.27.2' + go-version: '1.27.3' # mxbuild 9 draws through System.Drawing; without libgdiplus every mx call # dies with "The type initializer for 'Gdip' threw an exception." @@ -144,7 +144,7 @@ jobs: - uses: actions/checkout@v7 - uses: actions/setup-go@v7 with: - go-version: '1.27.2' + go-version: '1.27.3' - name: Cache ANTLR4 JAR uses: actions/cache@v6 with: @@ -175,7 +175,7 @@ jobs: - uses: actions/setup-go@v7 with: - go-version: '1.27.2' + go-version: '1.27.3' - uses: oven-sh/setup-bun@v2 diff --git a/.github/workflows/push-test.yml b/.github/workflows/push-test.yml index 1b876f965d..f9feb55789 100644 --- a/.github/workflows/push-test.yml +++ b/.github/workflows/push-test.yml @@ -23,7 +23,7 @@ jobs: - uses: actions/checkout@v7 - uses: actions/setup-go@v7 with: - go-version: '1.27.2' + go-version: '1.27.3' - name: Test the tunnel seam shell: bash # Scoped with -run to the seam's own tests. The full test binaries are still @@ -78,7 +78,7 @@ jobs: - uses: actions/checkout@v7 - uses: actions/setup-go@v7 with: - go-version: '1.27.2' + go-version: '1.27.3' - name: Test the Windows process helpers shell: bash # Scoped with -run: the rest of the docker package's tests assert POSIX @@ -122,7 +122,7 @@ jobs: submodules: true - uses: actions/setup-go@v7 with: - go-version: '1.27.2' + go-version: '1.27.3' - name: Cache ANTLR4 JAR uses: actions/cache@v6 with: @@ -256,7 +256,7 @@ jobs: submodules: ${{ matrix.suite == 'roundtrip' || matrix.suite == 'parity' }} - uses: actions/setup-go@v7 with: - go-version: '1.27.2' + go-version: '1.27.3' - name: Cache ANTLR4 JAR uses: actions/cache@v6 with: diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 2f0b8d4021..5bdcf8eb98 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -17,7 +17,7 @@ jobs: fetch-depth: 0 - uses: actions/setup-go@v7 with: - go-version: '1.27.2' + go-version: '1.27.3' - uses: oven-sh/setup-bun@v2 - name: Cache ANTLR4 JAR uses: actions/cache@v6 diff --git a/go.mod b/go.mod index df949ac424..77d136b3bd 100644 --- a/go.mod +++ b/go.mod @@ -2,7 +2,7 @@ module github.com/mendixlabs/mxcli go 1.26.0 -toolchain go1.27.2 +toolchain go1.27.3 require ( github.com/alecthomas/chroma/v2 v2.26.1