[0.2.0] - 2026-07-20
A repo-wide correctness and hardening pass from an internal audit. Most of it is straightforward bug fixing, but a few changes alter results or reject input that previously passed — read Breaking changes before upgrading.
Breaking changes
groupByAggregaterejects aggregations with no exact polars equivalent (sem,skew,kurt,size,mad). These previously ran on the pandas backend and failed or silently differed elsewhere. Migration: switch to a supported aggregation, or compute the statistic downstream in apythonTransformnode.- polars
first/last/nuniquenow skip nulls, matching pandas. Groups containing nulls produce different (now correct and engine-consistent) values. Migration: re-run affected flows; polars results now agree with the pandas backend and with the exported code. concatRowson polars unions mismatched columns and null-fills, instead of raising. Flows that previously errored now succeed. Migration: none.GET /api/datasetsandGET /api/flowsreturn at most 500 rows by default. Migration: none for the UI; API consumers that relied on an unbounded list should expect truncation untillimit/offsetquery parameters land.- A Train node whose model fails to save now fails the run instead of reporting success with an unusable model reference. Migration: none — the run was already broken downstream; the failure is now visible.
- Run creation rejects an unknown or cross-project
input_dataset_id(404 / 400). Migration: pass a dataset that exists in the flow's project, or omit the field. - "Default" is a reserved project name — creating or renaming a project to it returns 400. Migration: pick another name.
- Plugins are re-verified at load. An installed plugin whose files were modified on disk after installation is refused (migration: reinstall it to re-pin the baseline); an install whose id case-collides with an existing plugin is rejected; a plugin whose metadata id differs from its manifest id is refused; and a node's declared
provideris forced to the owning plugin's id. - Hardened connector mode (
CIAREN_CONNECTOR_BLOCK_PRIVATE_HOSTS=true) now rejects multi-host / URI-style hostnames (e.g. a comma-separated libpq seed list) and refusesverify_tls: false. Migration: use a single plain hostname per connection and keep TLS verification on, or leave the guard off. pythonTransformstrict mode (CIAREN_PYTHON_TRANSFORM_STRICT=true) blocks additional capability modules (io,tempfile,socket,pickle, …). Imports that are allowed now genuinely work at runtime, where they previously failed with an opaque error.
Security
- Connector SSRF guard now fails closed on multi-host / URI-style hostnames (which drivers could expand into a seed list or full URI) and blocks the NAT64 range that maps to the cloud metadata endpoint.
- The REST connector re-validates every HTTP redirect hop against the SSRF guard, rejects non-
http(s)redirect targets, refuses to disable TLS verification in hardened mode, and no longer forwardsAuthorization/ API-key headers when a redirect crosses to a different host. - Marketplace artifact resolution is confined for untrusted (URL-shaped) index sources —
file://, absolute, and..paths are rejected — ahead of any hosted plugin index. - Plugins: code integrity is now verified at load (previously only the manifest was hashed), a planted or altered bytecode cache can no longer execute in place of trusted code, a node's
provideris bound to its owning plugin, and an install whose id case-collides with an existing plugin is rejected. - SQLite foreign-key enforcement is enabled; the
/api/settings/webhookauth exemption is scoped toGET;pythonTransformstrict mode blocks additional capability modules and frame/traceback traversal.
Fixed
- Projects: creating or renaming a project to the reserved name "Default", or deleting a project that holds a dataset whose name also exists in the default project, no longer returns a 500.
- A
PUT /api/flowswith an explicitnullproject id now moves the flow to the default project instead of erroring; an impossible-but-valid cron expression (e.g. Feb 30) now returns a validation error instead of a 500. - Scheduler: daily schedules no longer fire twice during the DST fall-back hour; a concurrent user edit is no longer overwritten by a just-finished run; a manual "Run now" can no longer start a second concurrent run of a flow the
scheduler is already running. - Engine:
groupByAggregateandconcatRowsnow produce identical results on the pandas backend, the polars backend, and the exported code (previously a config valid on one could fail or differ on another). - ML: a Train node whose model fails to save now fails clearly instead of reporting success with an unusable model; prediction aligns feature columns and warns before overwriting an existing column; training hyperparameters are bounded to prevent a runaway fit.
- Frontend: file downloads work when an
API_TOKENis configured; the flow editor warns before discarding unsaved changes; storage-restricted browser contexts no longer crash the editor; the "not authorized" message points to the real?api_token=mechanism. - Database: networked Postgres/MySQL engines use connection health checks (
pool_pre_ping) so a stale pooled connection no longer surfaces as a 500, and startup no longer runs best-effort column patching on Alembic-managed databases. - Listing datasets no longer loads every version of every dataset into memory - the latest version and version count come from an aggregate query, so a workspace with a long run history stays responsive.