Skip to content

Releases: DataZooDE/flapi

v26.06.28

Choose a tag to compare

@github-actions github-actions released this 27 Jun 07:35
60d8d56

Bug fixes

REST API: serialize the last remaining column types (#89)

Completes the type-coverage work from v26.06.27. These DuckDB column types previously serialized as null in REST responses and now return real values:

  • VARINT / BIGNUM — arbitrary-precision integers now serialize as their exact decimal string (lossless).
  • GEOMETRY — now serializes as its text form, e.g. "POINT (1 2)" (with the spatial extension loaded).
  • VARIANT — now serializes as nested JSON when its value is JSON-representable, otherwise as its string form.
  • Any other or future column type without a dedicated handler now falls back to a best-effort string instead of null.

NULL values of these types still serialize as null.

With this release, every DuckDB type flAPI can return is serialized correctly — completing the fixes for nested, binary, and exotic types tracked in #89.

Install

pip install --upgrade flapi-io==26.6.28

Or download a standalone binary from the assets below.

v26.06.27

Choose a tag to compare

@github-actions github-actions released this 27 Jun 05:38
187f4bb

Bug fixes

REST API: complete fix for nested and binary column types (#89)

This release finishes the nested-type serialization work started in v26.06.25 and fixes several more column types that returned wrong data — or, in one case, crashed the server.

  • MAP — now serializes as a JSON object {key: value} (was returning null). Non-string keys are stringified the same way DuckDB does (e.g. 10"10").
  • UUID — fixed a crash: endpoints returning a UUID column previously took down the server. UUIDs now serialize as their canonical 8-4-4-4-12 string.
  • HUGEINT / UHUGEINT — fixed wrong values (large numbers were truncated or corrupted across rows). These 128-bit integers now serialize as exact decimal strings so no precision is lost.
  • BLOB — now serializes as DuckDB's blob string form instead of raw, possibly-invalid bytes.
  • BIT — now serializes as its 0/1 string (e.g. "101010") instead of garbage.

NULL values of all these types correctly serialize as null.

Notes

  • VARINT/BIGNUM, GEOMETRY, and VARIANT columns currently serialize as null; use to_json(col) as a workaround for these.
  • If you previously worked around any of the above with to_json(col), that's no longer necessary for the fixed types — native columns now round-trip correctly.

Install

pip install --upgrade flapi-io==26.6.27

Or download a standalone binary from the assets below.

v26.06.25

Choose a tag to compare

@github-actions github-actions released this 25 Jun 14:38
ab902db

Bug fixes

REST API: nested-type columns now serialize correctly (#89)

Endpoints returning DuckDB nested types previously emitted corrupted JSON. This release fixes all of them:

  • LIST(...) — each row now returns its own list. Previously a list column repeated the first element (for LIST(STRUCT)) or returned every row's elements concatenated together (for LIST(VARCHAR) and other element types).
  • STRUCT(...) — multi-row results now return the correct struct per row instead of repeating the first row's values.
  • ARRAY(...) — fixed-size array columns now serialize per row.
  • UNION(...) — only the active member is emitted (as {member: value}, matching to_json), instead of leaking every candidate member.

NULL values inside these types now correctly serialize as null.

If you were working around this with SELECT to_json(col), that's no longer necessary — native columns round-trip correctly.

Install

pip install --upgrade flapi-io==26.6.25

Or download a standalone binary from the assets below.

v26.06.14

Choose a tag to compare

@github-actions github-actions released this 14 Jun 14:40
961b7fb

flAPI v26.06.14

A CLI- and tooling-focused release: new flapii commands plus fixes to the CLI and the VS Code extension. The server is unchanged from v26.06.13.

New flapii commands

  • flapii health — show server health at a glance: database connectivity, number of endpoints, and storage/credential status.
  • flapii config env — list the environment variables your configuration can use and whether each is currently set.
  • flapii config filesystem — view your project's file and endpoint structure.
  • flapii endpoints parameters <path> — list an endpoint's request parameters, including where each comes from and whether it's required.
  • flapii cache gc <path> — run garbage collection on an endpoint's cached snapshots.
  • flapii cache audit [path] — view cache refresh history for one endpoint, or across all endpoints.

Bug fixes

  • CLI: endpoint/template testing now calls the correct server route — previously it could fail with a 404.
  • VS Code extension: fixed a startup error that prevented the extension from loading.
  • VS Code extension: when a SQL template is shared by multiple endpoints, you're now prompted to choose which one to test instead of one being picked silently.
  • VS Code extension: endpoint configuration files that reuse shared snippets via {{include: ... }} are now read correctly when testing and showing inline actions.

Install / upgrade

pip install -U flapi-io   # server
pip install -U flapii     # CLI

v26.06.13

Choose a tag to compare

@github-actions github-actions released this 13 Jun 09:57
5f62437

feat: Derive baked-in version from git tag instead of hardcoding (#74)

  • Telemetry app_version and flapi --version were always "0.3.0" (the
    hardcoded project() version), while real releases use CalVer tags
    (v26.06.11). The tag only flowed into the wheel name, never the binary.
  • CMakeLists.txt now resolves FLAPI_VERSION in priority order:
    1. -DFLAPI_VERSION_OVERRIDE (CI sets this from the git tag)
    2. git describe --tags --dirty (meaningful local dev versions)
    3. "latest" (fallback: source tarball / no git)
      A leading "v" is stripped so the binary reports clean semver.
  • Makefile: forward $(CMAKE_EXTRA_FLAGS) on the macOS release-x86_64 /
    release-arm64 targets (release already forwarded it).
  • build.yaml: on tag pushes, inject -DFLAPI_VERSION_OVERRIDE from
    github.ref_name across Windows, Linux (docker -e), and macOS builds.

v26.06.11

Choose a tag to compare

@github-actions github-actions released this 11 Jun 17:23
291cb50

chore: bump bundled DuckDB to v1.5.3 (#73)

  • chore: bump bundled DuckDB to v1.5.3

Updates the DuckDB submodule from v1.5.2 to the latest stable bugfix
release v1.5.3 (released 2026-05-20), and the matching version references.

  • duckdb submodule: v1.5.2 -> v1.5.3
  • CMakeLists.txt: DUCKDB_EXPLICIT_VERSION 1.5.2 -> 1.5.3 (+ comments)
  • docs: AGENTS.md (CLAUDE.md symlink), Readme.md, CONFIG_REFERENCE.md

Unit tests pass (642/642) and runtime SELECT version() reports v1.5.3.

  • ci: pin windows-build to windows-2022

The windows-latest runner image was migrated and no longer exposes
Visual Studio 2022, so cmake -G "Visual Studio 17 2022" fails at
configure time with "could not find any instance of Visual Studio"
(before DuckDB is even added). Pin the build job to windows-2022,
which ships VS 2022, restoring the last-known-good environment.

The smoke-test-windows / pack-smoke-windows jobs only run the prebuilt
binary (no VS needed) and stay on windows-latest.

v26.05.25

Choose a tag to compare

@github-actions github-actions released this 25 May 14:01
eae15c1

Headline: the self-packaging story is now complete end-to-end —
bundled flapi binaries actually serve HTTP from a clean cwd, the
12-factor envs cover port + host, the bundle locator names ZIP64
inputs explicitly in operator logs, and flapi pack now handles
universal macOS binaries.

Four PRs land in this release; nothing here changes the unbundled
behaviour. Operators running thin binaries on the existing
-c flapi.yaml flow see no change.

Before this release, flapi pack produced a binary that could
info / unpack / repack itself but crashed on startup when
asked to serve:

[INFO] Bundle detected and registered (386 entries)
[INFO] Loading configuration file: "flapi.yaml"
[ERROR] Could not open file: flapi.yaml
Aborted (core dumped)

The EmbeddedArchiveFileProvider was registered globally, but
ExtendedYamlParser, loadEndpointConfigsRecursively and the SQL
template processor all bypassed it via std::ifstream /
std::filesystem::recursive_directory_iterator. Four call sites
now route through FileProviderFactory, the template-path
normalisation skips std::filesystem::absolute() in bundled mode,
and the endpoint-vs-template path join no longer double-prefixes.

The new test/integration/test_self_packaging_http.py suite packs a
fixture, deletes the source tree, runs the bundled binary from an
empty cwd, and verifies both /hello and a SQL template that calls
read_csv('embed://data/cities.csv') over HTTP — covering spike
behaviours #1, #3, and #9 that were explicitly deferred when #56
landed.

New env-var fallbacks for the bind address with the standard
CLI > env > config > built-in default precedence. Invalid
FLAPI_PORT (non-integer, <1, >65535) exits 1 with a single-line
stderr message — typos surface immediately instead of silently
falling through to the config file. Paired with a new --host CLI
flag and an http-host YAML key (default 0.0.0.0).

export FLAPI_PORT=9000
export FLAPI_HOST=127.0.0.1
./flapi

When a hostile or accidentally-grown bundle exceeds 4 GiB or 65535
entries, the EOCD's cd_size / cd_offset / entry-count fields
are pinned to 0xffffffff / 0xffff and the real values live in a
ZIP64 EOCD record we don't parse. The defensive range-check
rejection was already in place; this release adds a single-line
WARNING log naming the mismatch so the failure mode is no longer
indistinguishable from "no bundle present" in operator output.

LocateFlapiSectionInBuffer now accepts FAT_MAGIC /
FAT_MAGIC_64 universal binaries (lipo -create output, the
format Homebrew formulas often ship). The parser walks
fat_arch[], picks the slice whose cputype matches the host arch
(compile-time, first-slice fallback), and recurses into the inner
thin Mach-O. MachOSection::file_offset carries the absolute
offset in the fat file, so OverwriteFlapiSection lands in the
correct slice with no caller changes.

  • #67feat: 12-factor env vars FLAPI_PORT + FLAPI_HOST (#63)
  • #68feat(self-packaging #65): ZIP64-sentinel WARN log in bundle_locator
  • #69fix(self-packaging #62): bundled-mode HTTP serving end-to-end
  • #70feat(self-packaging #66): fat / universal macOS binary support

Plus a documentation-only triage close on #64 (the 12-factor credential
env-var inventory was already in docs/CONFIG_REFERENCE.md).

  • No behaviour change for thin-binary deployments on the existing
    -c flapi.yaml flow.
  • No config-file schema changes (the new http-host key is
    optional with a default).
  • New env vars are opt-in via the FLAPI_PORT / FLAPI_HOST names
    — unset environments behave identically to v26.05.23.

v26.05.23

Choose a tag to compare

@github-actions github-actions released this 23 May 11:25

docs: Readme — self-packaging feature bullet + dedicated section

Adds a self-packaging entry to the Features list (with anchor to the
new section) and a dedicated '📦 Self-packaging (single-binary deploy)'
section after the YAML/env-vars section.

The section covers:

  • pack/info/unpack invocation
  • runtime mechanism (bundle locator + EmbeddedArchiveFileProvider +
    embed:// DuckDB FS) and the macOS reserved-segment variant
  • secret deny list at pack time (*.env, secrets/*, *.pem,
    *.key)
  • reproducible builds via SOURCE_DATE_EPOCH
  • 12-factor env-var precedence (FLAPI_CONFIG / FLAPI_LOG_LEVEL)
  • macOS notes (FLAPI_RESERVED_BUNDLE_MIB, --macos-append legacy)

Cross-links to docs/CLI_REFERENCE.md §3 and
docs/spec/DESIGN_DECISIONS.md §9 for the upstream reference and
rationale.

v26.05.18 — Prepared-statement coverage sweep

Choose a tag to compare

@github-actions github-actions released this 17 May 10:55

v26.05.18 — Prepared-statement coverage swept across every code path

Follow-up to v26.05.17. After v26.05.17 shipped, an internal audit found that the prepared-statement path was only wired into the GET endpoint executor — POST/PUT/PATCH writes and the Arrow-streaming endpoint still rendered Mustache templates as strings. This release closes that gap.

Coverage extension

  • POST/PUT/PATCH writes now take the prepared path. executeWrite calls the rewriter first, splits the rewritten SQL into statements (quote-aware), distributes the binding plan across statements by counting ? placeholders per statement, and prepares + binds + executes each one. Multi-statement INSERT…;SELECT…RETURNING templates keep working — each statement is its own prepared statement with the right slice of the binding plan.
  • Arrow streaming (executeQueryRaw) now routes through the prepared path with the same fall-back-to-string behaviour when the binding plan is empty.
  • countSqlPlaceholders helper in src/sql_utils.cpp — quote-aware ? counter (skips placeholders inside '…', "…", $tag$…$tag$) used by the write-path distributor. Covered by 6 new Catch2 cases.

Coverage extension — tests

  • Read-path corpus extended from 37 → 99 parameterised payloads (test/integration/test_sql_injection_corpus.py). Adds endpoints for double, boolean, date, time, uuid, enum, email so every validator type is exercised end-to-end. Plus a /lookup-int-paged endpoint that proves pagination + prepared bindings work together (count + paginated wraps both bind correctly).
  • New write-path corpus (test/integration/test_sql_injection_write_corpus.py, 19 cases). Fires the classic injection payloads at a POST /widgets/ endpoint and asserts the payload lands as a literal string column value, never as a side-effect that drops the table or smuggles extra rows. Includes a multi-statement INSERT-then-SELECT-RETURNING endpoint to exercise binding-plan slicing.

Validator hardening (defense in depth)

  • validateDate and validateTime now demand the entire input string be consumed — 2024-03-15' OR 1=1 no longer parses to 2024-03-15 and silently drops the suffix. Same fix as validateInt in v26.05.17.

HTTP status correctness

  • New flapi::BadRequestError exception class. QueryExecutor::executeWithBindings throws it on bind-conversion failure (caller supplied an invalid value for a typed param); RequestHandler catches it and returns HTTP 400 with a JSON body, instead of the previous 500 Internal Server Error. Server-side prepare/execute failures still return 500 (they're not client errors).

Tests

  • 586 C++ unit assertions (Catch2; +6 for countSqlPlaceholders).
  • 483 integration tests passing (+81 from the corpus extensions). 21 skip in environments without the relevant fixtures.

v26.05.17

Choose a tag to compare

@github-actions github-actions released this 17 May 08:18

v26.05.17 — Security roadmap (Waves 0–3) + BSL relicense

Headline: in-product MCP + general security hardening — per-tool RBAC, shadow/dry-run, response shaping, description hygiene, prepared-statement SQL-injection defense, PBKDF2 password hashing, audit log, per-user rate limit, CORS allowlist, TLS wire-up, startup auditor. Simple things stay simple — every new control is opt-in via single-line YAML.

License

  • Apache 2.0 → BSL v1.1 (e1b465e). The Business Source License is source-available; non-production use is permitted without a commercial agreement. The Change License (MPL 2.0) takes effect five years after first publication of each version. See LICENSE for the full text.

MCP hardening — Wave 2 (#24)

  • Per-tool RBAC (8886cd2, #27). mcp-tool.allowed-roles: [admin, analyst] in the endpoint YAML restricts a tool to JWT/OIDC principals carrying one of those roles. Deny-by-default: when mcp.auth.enabled: true, every tool MUST declare allowed-roles — a tool without one refuses every call. Endpoints with mcp.auth.enabled unset keep working role-free for flapii project init demos.
  • Dry-run / shadow mode (385f793, #29). Pass "_dryRun": true in tools/call arguments. flAPI runs validators + template expansion + EXPLAIN and returns the rendered SQL + plan as JSON, but never executes the query. The same controls that gate a real call (RBAC, rate limit) gate a dry-run too.
  • Tool-description hygiene scanner (63a1af7, #28). At config-load time, descriptions are scanned for control characters, JSON-breakout patterns, and known role-override phrases ("ignore previous instructions"). Strict-mode opt-in via mcp.strict-descriptions: true — refuses to start when any tool fails the scan.
  • Per-tool response shaping (9c9cd55, #30). New mcp-tool.response block: max-rows caps the result-set size, redact-columns: [...] replaces listed columns with a redaction sentinel, sample: true returns only summary metadata (row_count, columns, sampled: true).
  • Per-tool rate limit (0a7d69c, #34). New mcp-tool.rate-limit: { enabled, max, interval } keyed on the authenticated principal (with an anonymous fallback bucket per tool).

General security wins — Wave 1 (#23)

  • PBKDF2-SHA256 password hashing (db87b8e, #36). auth.users[*].password accepts the MCF string $pbkdf2-sha256$<iter>$<b64-salt>$<b64-hash> (OpenSSL PKCS5_PBKDF2_HMAC with 600 k iterations, 16-byte salt, 32-byte key — OWASP 2023 minimum). Compatible with Python passlib and any other PBKDF2-SHA256 generator. Plaintext and 32-char-hex MD5 hashes still verify, but the startup auditor emits a deprecation warning.
  • Config-driven CORS allowlist (f1a6751, #32). The legacy wildcard Access-Control-Allow-Origin: * is gone — default is same-origin only. Opt into specific origins via cors.allow-origins: [...]. flapii project init still ships ["*"] so first-run demos work; the auditor warns when * meets auth.enabled: true.
  • JSONL request audit log (1c762d4, #31). audit: { enabled, sink: stdout|file, path, redact: [...] } emits one JSON line per request (REST and MCP) with principal, method/target, params (redacted per config), status, row count, latency. Off by default, one-line to enable.
  • Per-user rate limit (b44c92d, #33). New rate-limit.key: ip | user | user-or-ip. The default stays ip for backward compatibility; user-or-ip is the recommended setting for share-NAT scenarios where many users share a single egress IP.
  • TLS in embedded server (e38c715, #35). The HTTPSConfig struct is now wired into Crow's ssl_file() chain. Reverse-proxy termination is still recommended for production, but direct TLS is supported for self-contained deployments.

SQL-injection defense — Wave 3 (#25)

  • Prepared-statement path for typed scalar params (8bf073d + ca16217, #37). {{ params.X }} (double-brace) references on fields with typed validators (int, double, boolean, date, time, uuid, enum, email, string) are now rewritten to ? and bound via duckdb_bind_*. The value travels as a primitive, not text — SQL injection becomes structurally impossible for those sites. Triple-brace {{{ params.X }}} is unchanged (for LIKE patterns and other text-mode use sites). The integer validator was also tightened: 1; DROP TABLE no longer slips through as 1.
  • W3.3: SQL-keyword regex demotion (ca16217). For numeric/temporal bindable fields, the historic keyword regex is demoted to a debug-level log line — the prepared bind is the hard defense, and the regex's false positives (latitude=1.111) are gone. Varchar-classified fields keep the regex because flAPI templates routinely embed them via triple-brace.
  • 37-payload integration corpus at test/integration/test_sql_injection_corpus.py — every classic injection pattern (UNION, OR 1=1, comment-evasion, xkcd 327) returns zero rows; legitimate values still match.

Honest defaults & honest docs — Wave 0 (#22)

  • Startup security auditor (655d61f, #26). At boot, flAPI scans the loaded config and emits structured warnings for: plaintext passwords, MD5 passwords, MCP exposed without auth on a non-loopback bind, and CORS wildcard combined with auth.enabled.
  • Documentation correctness. The misleading claim that {{{ }}} "prevents SQL injection" is gone from docs/CONFIG_REFERENCE.md. The actual layered defense (validators → prepared bind → regex fallback for triple-brace and untyped fields) is documented in § 9 SQL Templates.

Fixes

  • Windows release link (4619687). mcp_authorization_policy.hpp forward-declared EndpointConfig as class while the actual type is struct; MSVC encodes that keyword into mangled symbols, so the call site and definition emitted different names. Fixed by aligning the forward decl.
  • Auth-context param leak (4619687). RequestValidator::validateRequestFields rejected every authenticated write request as containing five phantom unknown fields (__auth_username / _email / _roles / _type / _authenticated). The reserved __auth_* prefix is now silently skipped.
  • Release linker fix (1116f25). Explicit safeGet<int> template instantiation in config_manager.cpp for cross-TU release linking (debug inlined; release with -Wl,--no-undefined exposed the missing definition).
  • Cross-platform smoke tests in CI (2f25366, 822ea3e, b3c9744). Each platform binary (linux-amd64, linux-arm64, macos-arm64, windows-amd64) is now booted in CI before release; the four smoke jobs gate create-release.
  • Auth template variable names (8b2b8d8). Doc fix: it's auth.username, not context.auth.username.

Tests

  • 580 C++ unit assertions (Catch2).
  • 402 integration tests passing (37 of them parameterised SQL-injection payloads). 21 skip in environments without the relevant fixtures (AWS Secrets Manager, OIDC issuers).