Changelog
Release notes for Dynoxide. Pulled from the source CHANGELOG.
Security
-
Close a DNS rebinding vulnerability in the MCP HTTP transport (GHSA-89vp-x53w-74fx / CVE-2026-42559) by upgrading
rmcpfrom 1.1.1 to 1.6.0 in both lockfiles. A malicious page could make the user's browser send requests to a loopback MCP server with a non-loopbackHostheader, which the server would then process. Affects 0.9.3 to 0.9.12. Users runningdynoxide mcp --httpordynoxide serve --mcpshould upgrade; stdio transport is unaffected. -
Close a related cross-origin CSRF gap: a page could
fetchthe loopback endpoint withmode: 'no-cors', and the Host check would pass while the Origin header went unchecked. Affected write tools:put_item,update_item,delete_item,create_table, andbatch_write_item. Fixed by setting an explicit Host and Origin allowlist onStreamableHttpServerConfig. Native MCP clients (Claude Code, Cursor, the dynoxide CLI) don't send an Origin header and are unaffected.
Fixed
-
Unix: port releases immediately after
dynoxide serveshuts down. The listener used to skipSO_REUSEADDR, leaving leftoverTIME_WAITsockets from connected clients to block restart for ~60s. Live-listener conflict detection is unaffected:SO_REUSEADDRonly bypassesTIME_WAIT, not active sockets.Windows: unchanged.
SO_REUSEADDRlets another process hijack an active bind there, so we leave it off.
Fixed
dynoxide serve --mcpnow exits cleanly on Ctrl+C when an MCP client (Claude Code, Cursor) is holding a connection open. The MCP server's graceful-shutdown drain used to wait for those connections forever, hanging the process until something SIGKILLed it (#22)
Security
- Refresh
Cargo.lockfor the dependabot patches reachable within MSRV:aws-lc-sys0.37.1 to 0.40.0 (5 high-severity AWS-LC issues),openssl0.10.75 to 0.10.79 (5 buffer-overflow advisories),rand0.8.5 to 0.8.6. Remainingrustls-webpki/time/aws-sdk-dynamodbalerts are dev-dependency only (test-suite AWS SDK chain, not the production binary) and stay pinned by MSRV 1.85 until v0.10.0
Fixed
- 16 places where dynoxide's error strings drifted from real AWS DynamoDB. Mostly small things you only notice when you assert the message:
tableNamelength validation is now per-operation (1 char on read/write, 3 stays onCreateTable),Selectenum order matches AWS rather than alphabetical,QueryvsScanLimit=0messages are different on purpose now, batch/transact empty and oversize requests use the standard validation envelope, andUpdateExpression/ProjectionExpressionsyntax errors include the AWSnear: "..."window (#11, #12, #13, #15, #16, #17, #18) TransactGetItemswith a bad action key now comes back as aTransactionCanceledExceptionwithValidationErrorrather than HTTP 500. The 500 was a real leak: the dedup loop called the server-fault helper before key validation (#19)
Fixed
KeyConditionExpressionnow accepts parenthesised sub-expressions, matching DynamoDB. Forms like(#pk = :pk) AND (#sk = :sk)previously returnedValidationException: Expected attribute name, got (. Both outer-wrap and per-condition parens are now handled (#4, #7)UpdateItemandTransactWriteItems.Updatenow evaluateConditionExpressionagainst the existing item before populating key attributes for upsert. Previouslyattribute_exists(pk)on a non-existent key succeeded and created a ghost item (#5)- Paginated
Scanon a GSI now returns all items when multiple items share the same GSI partition key. Previously the second page returned 0 items because the pagination cursor used only(gsi_pk, gsi_sk)instead of the full 4-tuple primary key (#6) <>on missing attributes now returns true, matching DynamoDB. All other comparison operators continue to return false on missing operands. Previously<>also returned false, breakingPutItemconditional idioms likestatus <> "working"against fresh keys (#8)
Fixed
- Dynoxide no longer orphans when backgrounded in npm scripts (
dynoxide & sleep 1 && npm run seed && react-router dev) -- the port is released when the parent process exits (nubo-db/dynoxide#2) - The Rust server now handles SIGTERM for graceful shutdown, not just SIGINT (Ctrl+C) --
kill <pid>now works as expected - The npm wrapper switches from
spawnSyncto asyncspawnwith explicit signal forwarding (SIGINT, SIGTERM, SIGHUP) and double-signal SIGKILL escalation - Parent-death detection via PPID polling catches the backgrounded case where no signal is delivered to the wrapper
Fixed
- Benchmark sanity checks were blocking README updates during release - 10 stale values from the v0.9.6 pipeline now corrected
- Binary download size in README was wrong (~5 MB, actually ~3 MB compressed / ~6 MB on disk)
- Docker image sizes now show both download and on-disk measurements - the old "225 MB" was the compressed download, the actual on-disk size is 471 MB
- MCP tool count in README was 33, should be 34 -
execute_transaction_partiqlwas missing from the list - npm README had incorrect
--inputand--db-pathflags for the import command (should be--source,--schema,--output) - Dropped the
servesubcommand from npm examples (baredynoxide --port 8000is the preferred form)
Changed
- Restructured release pipeline for token efficiency and reliability - dispatch verification, idempotent crate/npm publishing, template-based Homebrew formula updates
- npm publishing uses OIDC provenance via a dedicated
npm.ymlworkflow - Cross-compilation switched to cargo-zigbuild for aarch64-musl targets
- Commit Cargo.lock for reproducible CI builds (was previously gitignored)
- Updated npm package README to reflect current CLI usage and features
Security
- Updated
aws-lc-sys0.37.1 to 0.39.1 (10 high-severity advisories - PKCS7 verification bypass, timing side-channel in AES-CCM, CRL/name constraint issues) - Updated
rustls-webpki0.103.9 to 0.103.10 (2 medium-severity CRL Distribution Point matching issues)
Fixed
- Statically link the MSVC C runtime on Windows so the release binary no longer requires VCRUNTIME140.dll
- Switch Linux aarch64 target to musl for fully static binaries (matching x86_64)
Changed
- Drop the separate x86_64-unknown-linux-gnu release target (the musl build is already fully portable)
Added
- DynamoDB conformance suite — 526 independently written tests across 3 tiers, validated against real DynamoDB ground truth. Dynoxide: 100%. DynamoDB Local: 92%. See dynamodb-conformance.
- Dynalite external conformance — 817/1039 passing (87.1% DynamoDB parity) against Dynalite's test suite, where real DynamoDB itself only passes 51%
- DynamoDB compatibility audit — code-verified compatibility matrix with file/line references, public-facing summary with DynamoDB Local comparison column, prioritised gap tracking
- Correctness audit — 41 issues identified and resolved across core operations and PartiQL
- Reserved word validation — 573 DynamoDB reserved keywords rejected in ConditionExpression, UpdateExpression, FilterExpression, and ProjectionExpression with correct error messages
- README benchmark automation — CI benchmark numbers auto-updated via template markers and Python script; PR-based review with sanity checking
- IdempotentParameterMismatchException — TransactWriteItems detects same token with different payload
- AccessDeniedException — returned for tag operations on non-existent ARNs (matches DynamoDB behaviour)
Changed
- BigDecimal replaces f64 for all number comparisons and arithmetic — eliminates silent precision loss beyond 15 significant digits; f64 fast-path for ≤15 significant digits preserves performance
- PartiQL INSERT now fails with
DuplicateItemExceptionif item already exists (previously silently overwrote) - PartiQL tokeniser — correct handling of negative numbers, escaped single quotes, unknown characters (error instead of silent skip)
- Query/Scan COUNT now returns filtered count, not scanned count, when
FilterExpressionis present - begins_with sort key — SQL LIKE wildcards (
%,_) properly escaped - Condition + write operations wrapped in SQLite transactions to prevent TOCTOU races
- 1MB response limit now counts all scanned items, not just filtered results
- GSI query/scan LastEvaluatedKey now includes base table key attributes
- BatchWriteItem rejects duplicate keys within the same request
- TransactWriteItems — 4MB size check uses accurate item size calculation; CancellationReasons returned as structured top-level JSON field;
ReturnValuesOnConditionCheckFailurereturns ALL_OLD item on condition failure - UpdateItem rejects empty update expressions; protects key attributes from REMOVE/ADD/DELETE
- ReturnValues validated against allowed values per operation
- UnprocessedKeys in BatchGetItem preserves per-table settings
- SET on list index beyond bounds extends the list with NULL padding (previously returned error)
- SET on empty list at index 0 now succeeds
- Projection with list index correctly reconstructs list structure (previously created Map where List was needed)
- Select validation — invalid Select values and SPECIFIC_ATTRIBUTES without ProjectionExpression rejected
- ConsistentRead on GSI rejected with correct error message
- Limit of 0 rejected with constraint error
- Query/Scan validation ordering matches DynamoDB (input validation before table existence check)
- Expression attribute usage validated syntactically (at parse time) not semantically (at runtime) — fixes false positives with
if_not_existsshort-circuiting - SerializationException pre-checks for non-list field types with DynamoDB-compatible error format
- Error type prefix —
ValidationExceptionusescom.amazon.coral.validate#prefix matching real DynamoDB - BatchExecuteStatement uses short error codes (
ResourceNotFoundnot fully qualified type) and rejects empty Statements array - UpdateTable GSI delete returns
ResourceNotFoundExceptionfor non-existent GSI (previouslyValidationException) - StreamSpecification included in DescribeTable response
- Stack overflow protection: 32-level nesting depth limit on item validation (matches DynamoDB)
- AND/OR short-circuit evaluation in condition expressions
Fixed
size()function no longer evaluates on invalid attribute types- Idempotency tokens correctly compared in TransactWriteItems
- PutItem no longer double-reads item for conditional checks
- GSI sort key replacement handles all edge cases
- Nested projection preserves document structure (no longer flattens)
- Double-quote identifier escaping in PartiQL
- PartiQL DELETE with missing sort key returns proper error
- PartiQL nested SET paths create correct nested structure (no longer creates literal dot-notation keys)
- PartiQL SELECT with nested map paths resolves correctly
- TTL expiry cleans up LSI entries (previously left orphans)
- GSI/LSI name collision detected and rejected at CreateTable time
- LSI pagination uses composite cursor to handle duplicate sort key values
- ExecuteTransaction breaks on first failure (previously continued executing then rolled back)
- Partition size calculation for ItemCollectionMetrics sums across base table and all LSI tables
- Error message fidelity improvements across empty string, deletion protection, scan segment, and query validation messages
Added
- Local Secondary Indexes (LSI) — full lifecycle: creation, query/scan routing, projection types (ALL, KEYS_ONLY, INCLUDE), sparse index behaviour, write path maintenance across all operations including TTL expiry
- ExecuteTransaction — PartiQL transactional execution with all-or-nothing semantics, condition checks, per-statement cancellation reasons, ConsumedCapacity support
- Parallel Scan — SQLite-level segment filtering via registered FNV-1a scalar function; validated segment/total parameters
- CreateTable extensions —
SSESpecification,TableClass(validated),Tags(inline),DeletionProtectionEnabledwith enforcement on DeleteTable and toggle via UpdateTable - PartiQL WHERE clause extensions —
BETWEEN,IN,CONTAINS,IS MISSING,IS NOT MISSING,OR,NOT, parenthesised grouping - PartiQL nested path projections —
SELECT address.city, tags[0] FROM ...with correct nested structure preservation - PartiQL REMOVE clause —
UPDATE ... REMOVE attribute - PartiQL SET expressions — arithmetic (
count + 1),list_append,if_not_existsin SET clauses - PartiQL IF NOT EXISTS —
INSERT ... VALUE {...} IF NOT EXISTS - PartiQL set literals —
<< 'a', 'b', 'c' >>syntax for SS/NS/BS - PartiQL COUNT(*) and LIMIT support
- Item validation — empty string/set rejection, number precision validation (38 significant digits, ±9.99E+125 range), set deduplication (NS by numeric equivalence)
- Unused expression attribute rejection — unreferenced
ExpressionAttributeNames/ExpressionAttributeValuesentries returnValidationException - ReturnItemCollectionMetrics — partition collection size across base table and all LSI tables
- Per-GSI ConsumedCapacity —
INDEXESmode returns per-GSI breakdown inGlobalSecondaryIndexesmap
Changed
- TrackedExpressionAttributes — unified expression resolution with usage tracking; removed duplicate untracked code paths (~400 LOC reduction)
- ScanParams / QueryParams structs replace parameter sprawl in storage layer
- CreateTableMetadata consolidates previously triple-duplicated row mapping
- GSI/LSI secondary indexes on
(base_pk, base_sk)/(table_pk, table_sk)columns — eliminates full table scans during index maintenance - Schema v5 migration with automatic secondary index creation on existing tables
Added
- MCP Server — 33 tools exposing DynamoDB operations for coding agents (Claude Code, Cursor, etc.)
- stdio and Streamable HTTP transports
--read-only,--max-items,--max-size-bytessafety flagsbulk_put_itemstool for batch loading- OneTable
--data-modelintegration with entity-aware agent context and--data-model-summary-limit --mcpflag ondynoxide serveto run MCP alongside HTTP server- Snapshots:
create_snapshot,restore_snapshot,list_snapshots,delete_snapshotwith auto-snapshot beforedelete_table get_database_infotool with data model context
- Import CLI —
dynoxide importfor DynamoDB Export data (JSON Lines format)- Anonymisation rules: fake, mask, hash, redact, null actions
- Cross-table consistency for specified fields
- zstd compression (
--compress) --continue-on-error,--tablesfiltering, atomic--forceoverwrite- Stream-aware import (reproduces source table's StreamSpecification)
- CLI restructuring —
dynoxide serve,dynoxide mcp,dynoxide importsubcommands - Database introspection and port conflict detection on startup
RUST_LOGdebug tracing throughout HTTP and MCP servers
Added
- SQLCipher encryption —
encryptionfeature (vendored OpenSSL via SQLCipher) andencryption-ccfeature (Apple CommonCrypto backend) for encryption at rest - Secure key handling via
--encryption-key-fileorDYNOXIDE_ENCRYPTION_KEYenvironment variable - UpdateTable —
StreamSpecificationsupport, GSI create/delete with backfill - Tag operations —
TagResource,UntagResource,ListTagsOfResource - ReturnValuesOnConditionCheckFailure for TransactWriteItems
- GitHub Action —
nubo-db/dynoxide@v1with optionalsnapshot-urlpreloading - Homebrew formula —
brew install nubo-db/tap/dynoxide - Release CI workflow with cross-platform binary builds (Linux x86_64/aarch64/musl, macOS Intel/Apple Silicon, Windows)
- Private-to-public repo publishing pipeline
- DynamoDBStreams target prefix — server accepts
DynamoDB_20120810.ListStreamsand Streams-prefixed actions From/TryFromconversions for request/response typesitem!macro for ergonomic item construction in tests- Table metadata cache for reduced SQLite round-trips
- Stripped release binaries
Changed
nubo-app→nubo-dbGitHub organisation rename
Added
ServerandX-Dynoxide-Versionheaders on all HTTP responsesTableArn,LatestStreamArn, and related ARN fields in API responses- Comprehensive benchmarking suite comparing Dynoxide against DynamoDB Local and LocalStack
- Criterion, iai-callgrind, and custom benchmark binaries
- CI workflows for regression detection and historical tracking
- Standard 13-step workload with JVM warmup protocol
Changed
http-serverfeature is now enabled by default- Package renamed to
dynoxide-rsfor crates.io publishing
Fixed
- Rustdoc warnings
- README version reference
Added
- Core DynamoDB emulator backed by SQLite via
rusqlite - In-memory and persistent database modes
- Table operations: CreateTable, DeleteTable, DescribeTable, ListTables
- Item operations: PutItem, GetItem, DeleteItem, UpdateItem
- Query and Scan with full expression support and pagination
- Batch operations: BatchGetItem, BatchWriteItem
- Transactions: TransactWriteItems, TransactGetItems
- Global Secondary Indexes (GSI)
- DynamoDB Streams (all four view types)
- TTL with background sweep
- Full expression language: KeyCondition, Filter, Condition, Projection, Update
- PartiQL: ExecuteStatement, BatchExecuteStatement
- ReturnConsumedCapacity (TOTAL and INDEXES modes)
- HTTP server (axum-based, DynamoDB JSON wire protocol)
- 300+ tests