Releases: DataZooDE/flapi
Release list
v26.06.28
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.28Or download a standalone binary from the assets below.
v26.06.27
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 returningnull). Non-string keys are stringified the same way DuckDB does (e.g.10→"10").UUID— fixed a crash: endpoints returning aUUIDcolumn previously took down the server. UUIDs now serialize as their canonical8-4-4-4-12string.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 its0/1string (e.g."101010") instead of garbage.
NULL values of all these types correctly serialize as null.
Notes
VARINT/BIGNUM,GEOMETRY, andVARIANTcolumns currently serialize asnull; useto_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.27Or download a standalone binary from the assets below.
v26.06.25
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 (forLIST(STRUCT)) or returned every row's elements concatenated together (forLIST(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}, matchingto_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.25Or download a standalone binary from the assets below.
v26.06.14
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
feat: Derive baked-in version from git tag instead of hardcoding (#74)
- Telemetry app_version and
flapi --versionwere 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:
- -DFLAPI_VERSION_OVERRIDE (CI sets this from the git tag)
git describe --tags --dirty(meaningful local dev versions)- "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
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
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
./flapiWhen 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.
- #67 —
feat: 12-factor env vars FLAPI_PORT + FLAPI_HOST (#63) - #68 —
feat(self-packaging #65): ZIP64-sentinel WARN log in bundle_locator - #69 —
fix(self-packaging #62): bundled-mode HTTP serving end-to-end - #70 —
feat(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.yamlflow. - No config-file schema changes (the new
http-hostkey is
optional with a default). - New env vars are opt-in via the
FLAPI_PORT/FLAPI_HOSTnames
— unset environments behave identically to v26.05.23.
v26.05.23
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
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.
executeWritecalls 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. countSqlPlaceholdershelper insrc/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 fordouble,boolean,date,time,uuid,enum,emailso every validator type is exercised end-to-end. Plus a/lookup-int-pagedendpoint 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 aPOST /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)
validateDateandvalidateTimenow demand the entire input string be consumed —2024-03-15' OR 1=1no longer parses to2024-03-15and silently drops the suffix. Same fix asvalidateIntin v26.05.17.
HTTP status correctness
- New
flapi::BadRequestErrorexception class.QueryExecutor::executeWithBindingsthrows it on bind-conversion failure (caller supplied an invalid value for a typed param);RequestHandlercatches it and returns HTTP 400 with a JSON body, instead of the previous500 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
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
LICENSEfor 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: whenmcp.auth.enabled: true, every tool MUST declareallowed-roles— a tool without one refuses every call. Endpoints withmcp.auth.enabledunset keep working role-free forflapii project initdemos. - Dry-run / shadow mode (385f793, #29). Pass
"_dryRun": trueintools/callarguments. 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.responseblock:max-rowscaps the result-set size,redact-columns: [...]replaces listed columns with a redaction sentinel,sample: truereturns 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[*].passwordaccepts the MCF string$pbkdf2-sha256$<iter>$<b64-salt>$<b64-hash>(OpenSSLPKCS5_PBKDF2_HMACwith 600 k iterations, 16-byte salt, 32-byte key — OWASP 2023 minimum). Compatible with Pythonpassliband 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 viacors.allow-origins: [...].flapii project initstill ships["*"]so first-run demos work; the auditor warns when*meetsauth.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 staysipfor backward compatibility;user-or-ipis the recommended setting for share-NAT scenarios where many users share a single egress IP. - TLS in embedded server (e38c715, #35). The
HTTPSConfigstruct is now wired into Crow'sssl_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 viaduckdb_bind_*. The value travels as a primitive, not text — SQL injection becomes structurally impossible for those sites. Triple-brace{{{ params.X }}}is unchanged (forLIKEpatterns and other text-mode use sites). The integer validator was also tightened:1; DROP TABLEno longer slips through as1. - 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 fromdocs/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.hppforward-declaredEndpointConfigasclasswhile the actual type isstruct; 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::validateRequestFieldsrejected 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 inconfig_manager.cppfor cross-TU release linking (debug inlined; release with-Wl,--no-undefinedexposed 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, notcontext.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).