Conversation
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughAdds comprehensive multi-language FFI bindings (C, JS for Bun/Deno, Zig) via a new Changes
Sequence Diagram(s)sequenceDiagram
participant Client
participant JSWrapper as JS Wrapper (mdk.js)
participant FFIBackend as FFI Backend (BunFfi/DenoFfi)
participant NativeLib as Native Library (libmdk)
participant Memory as Memory Manager (C strings/bytes)
Client->>JSWrapper: Mdk.create(dbPath, ...)
JSWrapper->>FFIBackend: callWithStringOut(mdk_new, ...)
FFIBackend->>NativeLib: mdk_new (C ABI)
NativeLib->>NativeLib: Initialize handle + storage
NativeLib-->>FFIBackend: error code + out ptr
FFIBackend->>Memory: readAndFreeString(ptr)
Memory-->>FFIBackend: parsed handle
FFIBackend-->>JSWrapper: handle returned
JSWrapper-->>Client: Mdk object
Client->>JSWrapper: getGroups()
JSWrapper->>FFIBackend: callWithJsonOut(mdk_get_groups, handle)
FFIBackend->>NativeLib: mdk_get_groups
NativeLib->>NativeLib: Lock + query groups
NativeLib-->>FFIBackend: error code + json ptr
FFIBackend->>Memory: readAndFreeString(ptr)
Memory-->>FFIBackend: parsed JSON
FFIBackend-->>JSWrapper: groups
JSWrapper-->>Client: JS objects
sequenceDiagram
participant GH as GitHub Actions
participant Build as build-ffi-native
participant PublishC as publish-cbindings
participant PublishZ as publish-zig
participant PublishJS as publish-js
participant RepoC as CBINDINGS_REPO
participant RepoZ as ZIG_REPO
participant RepoJS as JS_REPO
GH->>Build: triggered (release)
Build->>Build: multi-OS native builds
Build->>Build: prepare artifacts & upload
Build-->>PublishC: artifacts available
PublishC->>PublishC: assemble C package
PublishC->>RepoC: checkout + commit + conditional tag push
Build-->>PublishZ: artifacts available
PublishZ->>PublishZ: assemble Zig package
PublishZ->>RepoZ: checkout + commit + conditional tag push
Build-->>PublishJS: artifacts available
PublishJS->>PublishJS: assemble JS package
PublishJS->>RepoJS: checkout + commit + conditional tag push
Estimated code review effort🎯 5 (Critical) | ⏱️ ~120 minutes Possibly related PRs
Suggested labels
Suggested reviewers
🚥 Pre-merge checks | ✅ 4✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
❌ Coverage: 91.07% → 87.53% (-3.54%) |
❌ Coverage: 91.07% → 87.53% (-3.54%) |
❌ Coverage: 91.07% → 87.53% (-3.54%) |
There was a problem hiding this comment.
Actionable comments posted: 5
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
.github/workflows/package-mdk-bindings.yml (1)
611-611:⚠️ Potential issue | 🟡 MinorInconsistent
actions/checkoutversion — standardize with other steps.Line 611 uses
actions/checkout@v6, but all other 16 checkout steps in this workflow use@v4. Whilev6does exist, this inconsistency should be resolved for maintainability.🔧 Proposed fix
- name: Checkout Kotlin package repo - uses: actions/checkout@v6 + uses: actions/checkout@v4 with: repository: ${{ env.KOTLIN_PACKAGE_REPO }}🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In @.github/workflows/package-mdk-bindings.yml at line 611, The workflow contains an inconsistent checkout action — the step currently using actions/checkout@v6 should be changed to actions/checkout@v4 to match the other checkout steps; locate the step that references actions/checkout@v6 and update the version tag to `@v4` so all checkout uses are standardized (ensure no other steps reference `@v6`).
🧹 Nitpick comments (8)
crates/mdk-js/README.md (1)
15-21: Add language specifier to fenced code block.Per coding guidelines, fenced code blocks should have a language specified. Use
textorplaintextfor directory listings.📝 Suggested fix
-``` +```text src/ # JS source native/ # prebuilt libmdk per platform linux-x86_64/libmdk.so macos-aarch64/libmdk.dylib windows-x86_64/mdk.dll</details> <details> <summary>🤖 Prompt for AI Agents</summary>Verify each finding against the current code and only fix it if needed.
In
@crates/mdk-js/README.mdaround lines 15 - 21, Update the fenced code block
in the README.md directory listing to include a language specifier (use "text"
or "plaintext") so the block starts withtext instead of; locate the
block shown under the directory listing (the triple-backtick fenced block
containing "src/ # JS source" and the native/ lines) and replace the opening
fence accordingly to comply with the coding guideline.</details> </blockquote></details> <details> <summary>crates/mdk-zig/README.md (1)</summary><blockquote> `66-82`: **Consider documenting single-threaded usage requirement.** Based on learnings from the mdk-uniffi crate, MDK instances should be used in a single-threaded context only. This limitation should be mentioned in the documentation to prevent misuse in concurrent Zig applications. <details> <summary>📝 Suggested addition after line 82</summary> ```diff // Always call deinit when done defer m.deinit(); ``` + +> ⚠️ **Thread Safety**: The `Mdk` instance is not thread-safe and should only be used from a single thread. ``` </details> <details> <summary>🤖 Prompt for AI Agents</summary> ``` Verify each finding against the current code and only fix it if needed. In `@crates/mdk-zig/README.md` around lines 66 - 82, Add a short note in the README near the Mdk usage examples that the Mdk instance is not thread-safe and must be used from a single thread; update the section containing the example calls (Mdk.init, Mdk.initWithKey, Mdk.initUnencrypted) and the reminder to call m.deinit() to include a warning like "The Mdk instance is not thread-safe and should only be used from a single thread" so consumers know to avoid sharing Mdk across threads. ``` </details> </blockquote></details> <details> <summary>crates/mdk-js/src/ffi_bun.js (1)</summary><blockquote> `119-129`: **Potential precision loss when converting pointer to `Number`.** `Number(buf[0])` converts a `BigUint64` to a JavaScript `Number`. On 64-bit systems with addresses above `Number.MAX_SAFE_INTEGER` (2^53), this could lose precision and produce incorrect pointer values. While most user-space addresses are below this threshold, it's a latent risk. Consider keeping the value as `BigInt` if Bun's FFI layer supports it, or document the limitation. <details> <summary>🤖 Prompt for AI Agents</summary> ``` Verify each finding against the current code and only fix it if needed. In `@crates/mdk-js/src/ffi_bun.js` around lines 119 - 129, The read() function in allocOutPtr (using BigUint64Array buf) converts buf[0] to a JavaScript Number which can lose precision for 64-bit pointers; change read() to return the raw BigInt (e.g., return buf[0]) instead of Number(buf[0]) and update any callers of allocOutPtr().read() to accept a BigInt pointer (or, if the surrounding Bun FFI cannot handle BigInt, add a clear comment documenting the precision limitation and assert/guard for addresses <= Number.MAX_SAFE_INTEGER). Reference: allocOutPtr, buf, read, BigUint64Array, ptr. ``` </details> </blockquote></details> <details> <summary>crates/mdk-cbindings/src/media.rs (1)</summary><blockquote> `79-90`: **Consider simplifying `.as_ref().clone()` to `.to_vec()` or similar.** Line 80 uses `prepared.encrypted_data.as_ref().clone()`, which is an unusual pattern. If `encrypted_data` is already a `Vec<u8>` or implements `Clone`, a simpler pattern would be more idiomatic. However, if `encrypted_data` is a wrapper type where `as_ref()` returns `&Vec<u8>`, this pattern is correct. <details> <summary>🤖 Prompt for AI Agents</summary> ``` Verify each finding against the current code and only fix it if needed. In `@crates/mdk-cbindings/src/media.rs` around lines 79 - 90, The field assignment for PreparedImageJson.encrypted_data uses the odd pattern prepared.encrypted_data.as_ref().clone(); replace it with a clearer conversion depending on the type: if prepared.encrypted_data implements AsRef<[u8]> use prepared.encrypted_data.as_ref().to_vec() or prepared.encrypted_data.to_vec(), otherwise use prepared.encrypted_data.clone(); update the assignment to use one of these idiomatic forms (refer to PreparedImageJson and the prepared.encrypted_data expression) so the conversion is explicit and readable. ``` </details> </blockquote></details> <details> <summary>crates/mdk-cbindings/src/lib.rs (1)</summary><blockquote> `11-14`: **Reconsider `#![deny(unsafe_code)]` immediately followed by `#![allow(unsafe_code)]`.** The current pattern effectively disables the lint crate-wide. Per coding guidelines, crates should use `#![forbid(unsafe_code)]` or `#![deny(unsafe_code)]`. For FFI crates where unsafe is necessary, the idiomatic approach is: 1. Keep `#![deny(unsafe_code)]` at crate root 2. Use `#[allow(unsafe_code)]` on individual `extern "C"` functions that require it This ensures any accidental unsafe code in non-FFI helpers is still flagged. <details> <summary>♻️ Suggested approach</summary> ```diff -#![deny(unsafe_code)] -// We allow `unsafe` only in explicitly marked blocks — the deny is overridden -// per-function with `#[allow(unsafe_code)]` where FFI requires it. -#![allow(unsafe_code)] +#![deny(unsafe_code)] #![warn(missing_docs)] ``` Then on each FFI function: ```rust #[allow(unsafe_code)] #[unsafe(no_mangle)] pub unsafe extern "C" fn mdk_new(...) -> MdkError { ``` </details> Based on learnings: "All crates use `#![forbid(unsafe_code)]` or `#![deny(unsafe_code)]` - flag any `unsafe` blocks." <details> <summary>🤖 Prompt for AI Agents</summary> ``` Verify each finding against the current code and only fix it if needed. In `@crates/mdk-cbindings/src/lib.rs` around lines 11 - 14, Remove the crate-level override that disables the deny (delete `#![allow(unsafe_code)]`) so `#![deny(unsafe_code)]` is effective, and instead add `#[allow(unsafe_code)]` attributes directly on each FFI function (the `pub unsafe extern "C" fn`/`#[no_mangle]` functions such as mdk_new and other extern "C" entry points) that legitimately require unsafe; this keeps the crate-wide lint while scoping allowed unsafe to only the specific FFI functions. ``` </details> </blockquote></details> <details> <summary>crates/mdk-js/src/ffi_deno.ts (2)</summary><blockquote> `27-38`: **TYPE_MAP is missing entries for some NativeType values.** The `NativeType` union includes `isize`, `f32`, `f64`, `pointer`, and `buffer`, but `TYPE_MAP` only maps a subset. If `symbols.js` uses any unmapped types, `toDenoSymbols` will produce `undefined` entries, causing runtime failures. Compare with `ffi_bun.js` which includes `i32` but also lacks some types. Ensure all types used in `symbols.js` are covered. <details> <summary>♻️ Proposed fix to add missing type mappings</summary> ```diff const TYPE_MAP: Record<string, NativeType> = { ptr: "pointer", cstr: "buffer", u8: "u8", u16: "u16", u32: "u32", u64: "u64", usize: "usize", + isize: "isize", bool: "bool", i32: "i32", + f32: "f32", + f64: "f64", void: "void", }; ``` </details> <details> <summary>🤖 Prompt for AI Agents</summary> ``` Verify each finding against the current code and only fix it if needed. In `@crates/mdk-js/src/ffi_deno.ts` around lines 27 - 38, TYPE_MAP is incomplete and can return undefined for NativeType entries used by toDenoSymbols; update TYPE_MAP to include mappings for all NativeType variants (isize, f32, f64, pointer, buffer and any other types present in the NativeType union and symbols.js) so toDenoSymbols always produces valid Deno FFI types. Locate the TYPE_MAP constant and add the missing keys with their corresponding Deno-native values (e.g., map pointer/isize/buffer/f32/f64 appropriately), then run tests or inspect toDenoSymbols consumers to confirm no undefined entries remain. ``` </details> --- `66-71`: **Platform detection hardcodes CPU architecture.** Darwin is assumed to be `aarch64` and Linux/Windows are assumed to be `x86_64`. This will fail on Intel Macs or ARM Linux/Windows systems. <details> <summary>♻️ Proposed fix to detect architecture dynamically</summary> ```diff - const platform = - Deno.build.os === "darwin" - ? "macos-aarch64" - : Deno.build.os === "windows" - ? "windows-x86_64" - : "linux-x86_64"; + const arch = Deno.build.arch === "aarch64" ? "aarch64" : "x86_64"; + const osName = + Deno.build.os === "darwin" + ? "macos" + : Deno.build.os === "windows" + ? "windows" + : "linux"; + const platform = `${osName}-${arch}`; ``` </details> <details> <summary>🤖 Prompt for AI Agents</summary> ``` Verify each finding against the current code and only fix it if needed. In `@crates/mdk-js/src/ffi_deno.ts` around lines 66 - 71, The platform detection currently hardcodes CPU architecture in the `platform` ternary (using `Deno.build.os` only); update it to use `Deno.build.arch` as well and construct the platform string dynamically (e.g., combine `Deno.build.os` and a mapped `Deno.build.arch` value) so macOS can resolve to `macos-x86_64` or `macos-aarch64` and Linux/Windows get the correct architecture too; modify the `platform` expression to consult `Deno.build.arch` (or map its values like "x86_64" → "x86_64", "aarch64" → "aarch64") and then produce the final strings used elsewhere in this file (`platform`). ``` </details> </blockquote></details> <details> <summary>crates/mdk-js/src/mdk.js (1)</summary><blockquote> `297-313`: **Consider caching the ffi() result to avoid repeated calls.** These methods call `ffi()` twice each. While the overhead is minimal (it's just checking `_ffi`), caching the result would be more consistent with other methods. <details> <summary>♻️ Optional: Cache ffi() result</summary> ```diff /** Merge pending commit. `@param` {string} mlsGroupId */ mergePendingCommit(mlsGroupId) { - const cGid = ffi().toCString(mlsGroupId); - check(ffi().sym.mdk_merge_pending_commit(this.#handle, cGid.ptr)); + const f = ffi(); + const cGid = f.toCString(mlsGroupId); + check(f.sym.mdk_merge_pending_commit(this.#handle, cGid.ptr)); } /** Clear pending commit. `@param` {string} mlsGroupId */ clearPendingCommit(mlsGroupId) { - const cGid = ffi().toCString(mlsGroupId); - check(ffi().sym.mdk_clear_pending_commit(this.#handle, cGid.ptr)); + const f = ffi(); + const cGid = f.toCString(mlsGroupId); + check(f.sym.mdk_clear_pending_commit(this.#handle, cGid.ptr)); } /** Sync group metadata from MLS. `@param` {string} mlsGroupId */ syncGroupMetadata(mlsGroupId) { - const cGid = ffi().toCString(mlsGroupId); - check(ffi().sym.mdk_sync_group_metadata(this.#handle, cGid.ptr)); + const f = ffi(); + const cGid = f.toCString(mlsGroupId); + check(f.sym.mdk_sync_group_metadata(this.#handle, cGid.ptr)); } ``` </details> <details> <summary>🤖 Prompt for AI Agents</summary> ``` Verify each finding against the current code and only fix it if needed. In `@crates/mdk-js/src/mdk.js` around lines 297 - 313, The three methods mergePendingCommit, clearPendingCommit, and syncGroupMetadata call ffi() twice each; cache the ffi() result at the start of each method (e.g., const f = ffi()) and use f.sym.* for the check calls so you don't call ffi() again, keeping the existing cGid handling and check(…) usage unchanged. ``` </details> </blockquote></details> </blockquote></details> <details> <summary>🤖 Prompt for all review comments with AI agents</summary>Verify each finding against the current code and only fix it if needed.
Inline comments:
In@crates/mdk-cbindings/CHANGELOG.md:
- Around line 7-19: The Unreleased changelog only contains an "Added" list
without the required Keep a Changelog sections and missing PR link suffixes;
update crates/mdk-cbindings/CHANGELOG.md by adding the standard Keep a Changelog
sections (e.g., Added, Changed, Fixed, Removed as applicable) under "Unreleased"
and append PR reference links to each bullet in the format
(#123); ensure each bullet from the
current Added list (e.g., "Initial C ABI bindings for mdk-core with SQLite
storage", "33-function API surface mirroring mdk-uniffi exactly", "Opaque
MdkHandle...", "JSON serialization...", "Thread-local error message
storage...", "Panic safety viacatch_unwind...", "Null pointer guards...",
"mdk_string_free...", "cbindgen-based header generation (include/mdk.h)")
has a corresponding PR number/link and move any non-Added items into their
appropriate sections per the project changelog guidelines.In
@crates/mdk-cbindings/README.md:
- Around line 17-22: Update the fenced code block in the README.md that lists
the generated artifacts to include a language specifier (e.g., change the
opening "" to "text"); locate the block containing the lines
"include/mdk.h", "lib/linux-x86_64/libmdk.so", "lib/macos-aarch64/libmdk.dylib",
and "lib/windows-x86_64/mdk.dll" and add the specifier so the block starts with
"```text" to satisfy markdown linting rules.In
@crates/mdk-cbindings/src/types.rs:
- Around line 67-84: The ffi_catch wrapper currently extracts and forwards panic
payload content to set_last_error, risking sensitive data leakage; change
ffi_catch (the function using std::panic::catch_unwind) so that on Err(payload)
it does NOT include the payload text in set_last_error but records a generic
message (e.g., "Internal panic occurred") and optionally logs the payload
locally (not via set_last_error) for debugging; ensure the function still
returns MdkError::Mdk and keep the generic message in mdk_last_error_message()
instead of any panic payload.In
@crates/mdk-cbindings/src/welcomes.rs:
- Around line 60-66: The Welcome objects returned by to_json(&welcomes) expose
sensitive identifiers; update the Welcome struct (in
crates/mdk-storage-traits/src/welcomes/types.rs) to prevent serialization of the
mls_group_id and nostr_group_id fields by annotating both fields with
#[serde(skip_serializing)], then rebuild and ensure callers (e.g., the
get_pending_welcomes path used by lock_handle(...).get_pending_welcomes and the
to_json call) still compile and return JSON without those fields.In
@crates/mdk-js/src/ffi_bun.js:
- Around line 41-46: The platform mapping in the const platform block
incorrectly assumes darwin is always macos-aarch64; update the detection to also
check process.arch (e.g., process.arch === "arm64" vs "x64") and map darwin to
"macos-aarch64" for arm64 and "macos-x86_64" (or "macos-x86-64" matching your
build names) for x86_64 so the platform constant correctly reflects Intel vs
Apple Silicon Macs; adjust the ternary/branch around process.platform ===
"darwin" in the platform variable to use process.arch for the darwin case.
Outside diff comments:
In @.github/workflows/package-mdk-bindings.yml:
- Line 611: The workflow contains an inconsistent checkout action — the step
currently using actions/checkout@v6 should be changed to actions/checkout@v4 to
match the other checkout steps; locate the step that references
actions/checkout@v6 and update the version tag to@v4so all checkout uses are
standardized (ensure no other steps reference@v6).
Nitpick comments:
In@crates/mdk-cbindings/src/lib.rs:
- Around line 11-14: Remove the crate-level override that disables the deny
(delete#![allow(unsafe_code)]) so#![deny(unsafe_code)]is effective, and
instead add#[allow(unsafe_code)]attributes directly on each FFI function
(thepub unsafe extern "C" fn/#[no_mangle]functions such as mdk_new and
other extern "C" entry points) that legitimately require unsafe; this keeps the
crate-wide lint while scoping allowed unsafe to only the specific FFI functions.In
@crates/mdk-cbindings/src/media.rs:
- Around line 79-90: The field assignment for PreparedImageJson.encrypted_data
uses the odd pattern prepared.encrypted_data.as_ref().clone(); replace it with a
clearer conversion depending on the type: if prepared.encrypted_data implements
AsRef<[u8]> use prepared.encrypted_data.as_ref().to_vec() or
prepared.encrypted_data.to_vec(), otherwise use prepared.encrypted_data.clone();
update the assignment to use one of these idiomatic forms (refer to
PreparedImageJson and the prepared.encrypted_data expression) so the conversion
is explicit and readable.In
@crates/mdk-js/README.md:
- Around line 15-21: Update the fenced code block in the README.md directory
listing to include a language specifier (use "text" or "plaintext") so the block
starts withtext instead of; locate the block shown under the directory
listing (the triple-backtick fenced block containing "src/ # JS source" and the
native/ lines) and replace the opening fence accordingly to comply with the
coding guideline.In
@crates/mdk-js/src/ffi_bun.js:
- Around line 119-129: The read() function in allocOutPtr (using BigUint64Array
buf) converts buf[0] to a JavaScript Number which can lose precision for 64-bit
pointers; change read() to return the raw BigInt (e.g., return buf[0]) instead
of Number(buf[0]) and update any callers of allocOutPtr().read() to accept a
BigInt pointer (or, if the surrounding Bun FFI cannot handle BigInt, add a clear
comment documenting the precision limitation and assert/guard for addresses <=
Number.MAX_SAFE_INTEGER). Reference: allocOutPtr, buf, read, BigUint64Array,
ptr.In
@crates/mdk-js/src/ffi_deno.ts:
- Around line 27-38: TYPE_MAP is incomplete and can return undefined for
NativeType entries used by toDenoSymbols; update TYPE_MAP to include mappings
for all NativeType variants (isize, f32, f64, pointer, buffer and any other
types present in the NativeType union and symbols.js) so toDenoSymbols always
produces valid Deno FFI types. Locate the TYPE_MAP constant and add the missing
keys with their corresponding Deno-native values (e.g., map
pointer/isize/buffer/f32/f64 appropriately), then run tests or inspect
toDenoSymbols consumers to confirm no undefined entries remain.- Around line 66-71: The platform detection currently hardcodes CPU architecture
in theplatformternary (usingDeno.build.osonly); update it to use
Deno.build.archas well and construct the platform string dynamically (e.g.,
combineDeno.build.osand a mappedDeno.build.archvalue) so macOS can
resolve tomacos-x86_64ormacos-aarch64and Linux/Windows get the correct
architecture too; modify theplatformexpression to consultDeno.build.arch
(or map its values like "x86_64" → "x86_64", "aarch64" → "aarch64") and then
produce the final strings used elsewhere in this file (platform).In
@crates/mdk-js/src/mdk.js:
- Around line 297-313: The three methods mergePendingCommit, clearPendingCommit,
and syncGroupMetadata call ffi() twice each; cache the ffi() result at the start
of each method (e.g., const f = ffi()) and use f.sym.* for the check calls so
you don't call ffi() again, keeping the existing cGid handling and check(…)
usage unchanged.In
@crates/mdk-zig/README.md:
- Around line 66-82: Add a short note in the README near the Mdk usage examples
that the Mdk instance is not thread-safe and must be used from a single thread;
update the section containing the example calls (Mdk.init, Mdk.initWithKey,
Mdk.initUnencrypted) and the reminder to call m.deinit() to include a warning
like "The Mdk instance is not thread-safe and should only be used from a single
thread" so consumers know to avoid sharing Mdk across threads.</details> --- <details> <summary>ℹ️ Review info</summary> **Configuration used**: Path: .coderabbit.yaml **Review profile**: CHILL **Plan**: Pro <details> <summary>📥 Commits</summary> Reviewing files that changed from the base of the PR and between 0fbb6b4d6364becc7b8a8bf230c0cf88b2ebe7c0 and 57a1c9cc0942b9ee9afa6821120a3dacf48219c4. </details> <details> <summary>⛔ Files ignored due to path filters (1)</summary> * `Cargo.lock` is excluded by `!**/*.lock` </details> <details> <summary>📒 Files selected for processing (30)</summary> * `.github/workflows/package-mdk-bindings.yml` * `.gitignore` * `Cargo.toml` * `crates/mdk-cbindings/CHANGELOG.md` * `crates/mdk-cbindings/Cargo.toml` * `crates/mdk-cbindings/README.md` * `crates/mdk-cbindings/build.rs` * `crates/mdk-cbindings/cbindgen.toml` * `crates/mdk-cbindings/include/mdk.h` * `crates/mdk-cbindings/src/error.rs` * `crates/mdk-cbindings/src/free.rs` * `crates/mdk-cbindings/src/groups.rs` * `crates/mdk-cbindings/src/key_packages.rs` * `crates/mdk-cbindings/src/lib.rs` * `crates/mdk-cbindings/src/media.rs` * `crates/mdk-cbindings/src/messages.rs` * `crates/mdk-cbindings/src/types.rs` * `crates/mdk-cbindings/src/welcomes.rs` * `crates/mdk-js/README.md` * `crates/mdk-js/src/ffi_bun.js` * `crates/mdk-js/src/ffi_deno.ts` * `crates/mdk-js/src/index.js` * `crates/mdk-js/src/mdk.js` * `crates/mdk-js/src/mod.ts` * `crates/mdk-js/src/symbols.js` * `crates/mdk-zig/README.md` * `crates/mdk-zig/build.zig` * `crates/mdk-zig/build.zig.zon` * `crates/mdk-zig/src/c.zig` * `crates/mdk-zig/src/mdk.zig` </details> </details> <!-- This is an auto-generated comment by CodeRabbit for review status -->
Rust (mdk-cbindings): - #![deny(unsafe_code)] now actually denies — per-function #[allow] instead of crate-level allow that made the deny theatrical - Deduplicate UpdateGroupResultJson and serializer into types.rs - Collapse three-layer ffi_catch/ffi_try/ffi_try_unwind_safe into one function - Extract null-check boilerplate into require_non_null!/deref_handle! macros (60 replacements, ~180 lines of ceremony eliminated) - Eliminate ConfigOverrides: add #[serde(default)] + Deserialize to MdkConfig in mdk-core, deserialize directly (fixes u32→usize truncation too) - Fix mdk_bytes_free leaking zero-length allocations (remove len>0 guard) - Feature-gate media.rs behind #[cfg(feature = "mip04")] - Document thread-local error semantics and secret key lifetime limitations - Add 36 tests for FFI boundary code (was: zero) JavaScript (mdk-js): - Platform detection now checks process.arch/Deno.build.arch (ARM Linux, Intel Mac) - Guard BigInt→Number precision loss in Bun allocOutPtr with MAX_SAFE_INTEGER check - Add use-after-close guard (#assertOpen on all 25 methods) + FinalizationRegistry - Remove JSON double-encoding for key package events (single parse pass) Zig (mdk-zig): - Replace hand-rolled JSON serializer with std.json.Stringify.valueAlloc - Initialize all out pointers to null/0 instead of undefined - Add 7 new tests (sliceToC, configToJson, optSliceToC round-trips) CI: - Add validation to version extraction (fail on empty instead of silent) - Remove 2>/dev/null || true from artifact copies (surface missing files) - Add mdk.h to .gitignore (generated by cbindgen, shouldn't be tracked) - Document auto-populated repo links in binding READMEs
❌ Coverage: 91.07% → 88.54% (-2.53%) |
There was a problem hiding this comment.
Actionable comments posted: 11
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In @.github/workflows/package-mdk-bindings.yml:
- Around line 846-847: Replace all occurrences of the force-push invocation git
push origin "v${VERSION}" --force with a non-destructive push (git push origin
"v${VERSION}") so tags are not rewritten; locate the three places where the
workflow runs git push for the v${VERSION} tag (the lines showing git push
origin "v${VERSION}" --force) and remove the --force flag (or implement an
explicit check that the tag does not already exist before creating/pushing it)
to avoid invalidating existing release tags and provenance.
- Around line 705-714: The matrix currently hardcodes OS-only entries under
strategy.matrix.include (the tuples with os and lib) which causes macOS to be
published as macos-aarch64 regardless of runner architecture; update the matrix
to include an explicit arch dimension (e.g., matrix: os: [ubuntu-latest,
macos-latest, windows-latest]; arch: [x86_64, aarch64]) or expand include to
enumerate both macos x86_64 and macos aarch64 entries, and then reference
matrix.arch when naming artifacts and choosing the library filename; locate the
entries referencing strategy.matrix.include and the artifact naming steps and
replace the hardcoded lib values with expressions that depend on matrix.os and
matrix.arch (or a small mapping) so macOS builds are produced and labeled for
both Intel and Apple Silicon.
In `@crates/mdk-cbindings/src/error.rs`:
- Around line 85-93: The current from_storage_error and from_mdk_error call
set_last_error with the full error formatted ("Storage error: {e}" / "MDK error:
{e}"), which can leak sensitive internals; change these to record a generic or
sanitized message instead (e.g., "Storage error: internal" / "MDK error:
internal" or include only a non-sensitive error code), and ensure StorageError
and CoreMdkError are not directly interpolated into set_last_error; update
from_storage_error and from_mdk_error to call set_last_error with the new
generic text while still returning MdkError::Storage and MdkError::Mdk
respectively.
In `@crates/mdk-cbindings/src/media.rs`:
- Around line 24-31: Remove the sensitive upload_secret_key from the JSON
returned by mdk_prepare_group_image: stop serialising the upload_secret_key
field (and any equivalent field used at lines ~87-98) into the response
struct/string, rely on callers using mdk_derive_upload_keypair for derivation,
and ensure the struct used by the JSON serializer no longer contains or emits
upload_secret_key (update the response type/serde attributes and any
Debug/Display implementations accordingly); also update related tests, docs, and
FFI contract/comments to reflect that the secret key is not returned over JSON.
- Around line 157-170: The temporary plaintext buffers key_arr and nonce_arr
must be explicitly wiped after they are wrapped in mdk_storage_traits::Secret
and after core_decrypt usage; update the code around where key_arr and nonce_arr
are created/copied and passed into Secret::new (and the call to core_decrypt) to
call a zeroization method (e.g., zeroize::Zeroize::zeroize or similar) on those
arrays immediately after constructing the Secret instances and/or after
core_decrypt returns; ensure both the occurrences around the first block (lines
showing key_arr/nonce_arr and core_decrypt) and the second occurrence mentioned
(around 216-220) are updated so no plaintext remains on the stack.
In `@crates/mdk-js/README.md`:
- Around line 15-21: The fenced code block showing the project paths (the block
containing the lines "src/", "native/", "linux-x86_64/libmdk.so",
"macos-aarch64/libmdk.dylib", "windows-x86_64/mdk.dll") is missing a language
specifier; update the opening fence for that block in README.md from ``` to
```text (or another appropriate language) so the markdown linter recognizes it
as a code/text block without altering the block content or directory entries.
In `@crates/mdk-js/src/ffi_deno.ts`:
- Around line 57-64: The Windows library basename is wrong: the code builds base
as `libmdk.dll` but the packaged artifact is `mdk.dll`; update the logic that
computes `ext`/`base` in ffi_deno.ts (the constants ext, base, and related path
construction using pkgRoot) so that on Windows the basename is `mdk.dll` (and on
macOS/Linux keep the existing `libmdk.dylib`/`libmdk.so` names), ensuring the
binary lookup uses the actual packaged name.
- Around line 182-190: The early return when len === 0 can leak the native
allocation because mdk_bytes_free is skipped; update the out-bytes handling in
the block that reads ptrBuf/lenBuf (variables pVal and len) so that when pVal
!== 0n you always call ffi.sym.mdk_bytes_free(p, BigInt(len)) before returning
null for zero length (create the UnsafePointer p only when pVal !== 0n, free it
even if len === 0, and only then return null); reference the existing symbols
pVal, lenBuf, Deno.UnsafePointer.create, Deno.UnsafePointerView, and
ffi.sym.mdk_bytes_free to locate and apply the change.
- Around line 27-30: TYPE_MAP currently maps cstr to "buffer" but code passes
toCString(...).ptr (a Deno.UnsafePointer); change the mapping for "cstr" in
TYPE_MAP from "buffer" to "pointer" and update any other duplicate mappings (the
ones around the 123–125 region) so functions that receive toCString(...).ptr are
declared as "pointer" types; keep the existing logic that keeps the original
Uint8Array alive while passing the UnsafePointer (e.g., callers of toCString) so
the pointer remains valid at call time.
In `@crates/mdk-zig/src/mdk.zig`:
- Around line 161-163: Change the Mdk struct's handle field from a raw pointer
to an optional pointer (e.g. ?*raw.MdkHandle), then make Mdk.deinit check for a
non-null handle before freeing and set the handle to null after free; update any
other deinit-like code paths (the other deinit block around the second
occurrence) to follow the same pattern so calling deinit() multiple times is a
no-op rather than UB, and adjust constructors/initializers to store a non-null
optional when creating the Mdk instance.
- Around line 187-190: The wrapper should validate the out pointer even when
raw.mdk_new returns success: after calling try check(raw.mdk_new(..., &out))
verify out is not null and return an appropriate error (or map to
MDK_ERROR_NULL_OUTPUT) instead of unwrapping out.?; update the block around
variable out and the return {.handle = out.?, .allocator = allocator} to
explicitly handle out == null and propagate a failure, and apply the same
null-check pattern to the similar block at the other occurrence (lines around
the second raw.mdk_new call).
ℹ️ Review info
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (21)
.github/workflows/package-mdk-bindings.yml.gitignorePR-REVIEW.mdcrates/mdk-cbindings/README.mdcrates/mdk-cbindings/include/mdk.hcrates/mdk-cbindings/src/error.rscrates/mdk-cbindings/src/free.rscrates/mdk-cbindings/src/groups.rscrates/mdk-cbindings/src/key_packages.rscrates/mdk-cbindings/src/lib.rscrates/mdk-cbindings/src/media.rscrates/mdk-cbindings/src/messages.rscrates/mdk-cbindings/src/types.rscrates/mdk-cbindings/src/welcomes.rscrates/mdk-core/src/lib.rscrates/mdk-js/README.mdcrates/mdk-js/src/ffi_bun.jscrates/mdk-js/src/ffi_deno.tscrates/mdk-js/src/mdk.jscrates/mdk-zig/README.mdcrates/mdk-zig/src/mdk.zig
✅ Files skipped from review due to trivial changes (1)
- crates/mdk-zig/README.md
🚧 Files skipped from review as they are similar to previous changes (4)
- .gitignore
- crates/mdk-cbindings/src/types.rs
- crates/mdk-js/src/ffi_bun.js
- crates/mdk-cbindings/src/lib.rs
❌ Coverage: 91.07% → 88.54% (-2.53%) |
There was a problem hiding this comment.
Actionable comments posted: 3
🧹 Nitpick comments (2)
crates/mdk-js/src/ffi_deno.ts (1)
64-64: Windows pathname from URL includes leading slash, but Deno APIs handle this transparently.
new URL(".", import.meta.url).pathnamereturns/C:/Users/...on Windows per the URL standard. While Deno's file APIs likeDeno.dlopenhandle this format correctly in modern versions, the path is technically not in standard Windows format.[optional_refactor]
💡 Optional: Use standard Windows path format for clarity
- const pkgRoot = new URL(".", import.meta.url).pathname; + let pkgRoot = new URL(".", import.meta.url).pathname; + // Remove leading slash on Windows paths for standard format + if (Deno.build.os === "windows" && pkgRoot.match(/^\/[A-Za-z]:\//)) { + pkgRoot = pkgRoot.slice(1); + }This improves path readability without being functionally necessary, as Deno already normalizes these paths internally before system calls.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@crates/mdk-js/src/ffi_deno.ts` at line 64, The pkgRoot assignment uses new URL(".", import.meta.url).pathname which yields a POSIX-style leading-slash Windows path; change this to produce a platform-native path by using the std/path helper (fromFileUrl) or otherwise converting the file URL to a native Windows path when Deno.build.os === "windows"; update the pkgRoot initialization in ffi_deno.ts (the const pkgRoot binding) to call fromFileUrl(new URL(".", import.meta.url)) so pkgRoot is in standard Windows format on Windows and unchanged on POSIX.crates/mdk-zig/src/mdk.zig (1)
269-273: Missing null-check on output pointers for operations returningCString.While constructors validate output pointers, operations like
createKeyPackage,createKeyPackageWithOptions, andparseKeyPackagedirectly returnCString{ .ptr = out }without checking ifoutis null after the C call succeeds. This is inconsistent with the constructor pattern and could lead to crashes if the C layer has bugs.Consider adding validation for consistency:
♻️ Example fix for createKeyPackage
var out: [*c]u8 = null; const h = try self.liveHandle(); try check(raw.mdk_create_key_package(h, c_pk, c_relays, &out)); - return CString{ .ptr = out }; + if (out == null) return Error.NullPointer; + return CString{ .ptr = out };Also applies to: 287-291, 301-305
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@crates/mdk-zig/src/mdk.zig` around lines 269 - 273, The functions createKeyPackage, createKeyPackageWithOptions, and parseKeyPackage return CString{ .ptr = out } without verifying that the C-out pointer is non-null; replicate the constructor pattern by checking that out != null after the successful raw call (e.g., after raw.mdk_create_key_package/have corresponding raw calls) and return an appropriate error (or use try) if out is null before constructing and returning CString; reference the functions createKeyPackage/createKeyPackageWithOptions/parseKeyPackage, the local variable out, and the raw.mdk_* calls (and liveHandle()) when adding the null-checks.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@crates/mdk-cbindings/src/media.rs`:
- Around line 47-49: Update the doc comment for mdk_prepare_group_image to
reflect current behavior: it encrypts the image and derives the upload keypair
but intentionally omits upload secret key material from the returned JSON;
ensure the description explicitly states that only public key/upload metadata
(not the private/secret key) is included in the returned JSON so callers
understand secrets are not returned.
- Around line 75-78: Replace FFI-visible error messages that interpolate
internal errors (e.g., in the core_prepare error mapping where
error::set_last_error(&format!("Prepare group image failed: {e}")) and the
similar occurrences around the other reported spots) with sanitized static
messages or by converting the error via error::from_mdk_error so no internal
details are exposed; keep detailed error logging internal (e.g., use debug/error
logs that are not set via mdk_last_error) but ensure error::set_last_error
receives only a fixed, non-leaking string (e.g., "Prepare group image failed")
or a sanitized mapping function.
In `@crates/mdk-zig/src/mdk.zig`:
- Around line 133-137: In configToJson replace the invalid call to
std.json.Stringify.valueAlloc with the correct std.json.stringifyAlloc API: call
std.json.stringifyAlloc(allocator, conf, .{ .emit_null_optional_fields = false
}) (or adjust the argument order to match the current Zig stdlib signature) and
update error/return handling to match stringifyAlloc's return type; keep the
early return for null config and return the allocated JSON slice from
configToJson.
---
Nitpick comments:
In `@crates/mdk-js/src/ffi_deno.ts`:
- Line 64: The pkgRoot assignment uses new URL(".", import.meta.url).pathname
which yields a POSIX-style leading-slash Windows path; change this to produce a
platform-native path by using the std/path helper (fromFileUrl) or otherwise
converting the file URL to a native Windows path when Deno.build.os ===
"windows"; update the pkgRoot initialization in ffi_deno.ts (the const pkgRoot
binding) to call fromFileUrl(new URL(".", import.meta.url)) so pkgRoot is in
standard Windows format on Windows and unchanged on POSIX.
In `@crates/mdk-zig/src/mdk.zig`:
- Around line 269-273: The functions createKeyPackage,
createKeyPackageWithOptions, and parseKeyPackage return CString{ .ptr = out }
without verifying that the C-out pointer is non-null; replicate the constructor
pattern by checking that out != null after the successful raw call (e.g., after
raw.mdk_create_key_package/have corresponding raw calls) and return an
appropriate error (or use try) if out is null before constructing and returning
CString; reference the functions
createKeyPackage/createKeyPackageWithOptions/parseKeyPackage, the local variable
out, and the raw.mdk_* calls (and liveHandle()) when adding the null-checks.
ℹ️ Review info
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (1)
Cargo.lockis excluded by!**/*.lock
📒 Files selected for processing (7)
.github/workflows/package-mdk-bindings.ymlcrates/mdk-cbindings/Cargo.tomlcrates/mdk-cbindings/src/error.rscrates/mdk-cbindings/src/media.rscrates/mdk-js/README.mdcrates/mdk-js/src/ffi_deno.tscrates/mdk-zig/src/mdk.zig
❌ Coverage: 91.07% → 88.53% (-2.54%) |
There was a problem hiding this comment.
Actionable comments posted: 1
♻️ Duplicate comments (3)
crates/mdk-zig/src/mdk.zig (2)
140-143:⚠️ Potential issue | 🔴 CriticalUse
std.json.stringifyAlloc;std.json.Stringify.valueAllocis not a valid Zig stdlib call.Line 142 is a compile blocker with current Zig stdlib API naming.
🛠️ Suggested fix
- const result = try std.json.Stringify.valueAlloc(allocator, conf, .{ .emit_null_optional_fields = false }); + const result = try std.json.stringifyAlloc(allocator, conf, .{ .emit_null_optional_fields = false });🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@crates/mdk-zig/src/mdk.zig` around lines 140 - 143, The call to std.json.Stringify.valueAlloc in configToJson is invalid with the current Zig stdlib; replace that call with std.json.stringifyAlloc (using the same allocator and conf, and preserve the options {.emit_null_optional_fields = false}) so configToJson compiles; update the invocation inside fn configToJson(allocator: std.mem.Allocator, cfg: ?Config) to call std.json.stringifyAlloc and return its result.
712-731:⚠️ Potential issue | 🟠 MajorValidate
out_ptrbefore constructingCBytes.After Line 718 succeeds, the wrapper still assumes
out_ptris non-null. A defensive null-check prevents creating an invalid owned buffer if the C contract drifts.🛡️ Suggested hardening
try check(raw.mdk_decrypt_group_image( data.ptr, data.len, hash_ptr, hash_len, key, 32, nonce, 12, &out_ptr, &out_len, )); + if (out_ptr == null) return Error.NullPointer; return CBytes{ .ptr = out_ptr, .len = out_len };🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@crates/mdk-zig/src/mdk.zig` around lines 712 - 731, After calling raw.mdk_decrypt_group_image (in the wrapper that returns CBytes), validate that out_ptr is not null before constructing and returning CBytes; if out_ptr is null or out_len is zero treat it as an error (return a Zig error or call the existing error path) so you don't create an invalid owned buffer—add the nil-check for out_ptr immediately after the try check and handle it consistently with the function's error-reporting convention.crates/mdk-cbindings/src/media.rs (1)
78-82:⚠️ Potential issue | 🔴 CriticalRemove raw
{e}interpolation from warning logs in these FFI crypto paths.At Line 79, Line 168, and Line 218,
tracing::warn!(... {e})can leak internal details from core errors. Keep the warning text static/sanitized in these paths.🔐 Patch pattern (apply to all three `map_err` blocks)
- let prepared = core_prepare(image_bytes, mime_str).map_err(|e| { - tracing::warn!("Prepare group image failed: {e}"); + let prepared = core_prepare(image_bytes, mime_str).map_err(|_| { + tracing::warn!("Prepare group image failed"); error::set_last_error("Prepare group image failed"); MdkError::Mdk })?;As per coding guidelines: "Never log sensitive identifiers ... in tracing::* macros."
Also applies to: 167-171, 217-221
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@crates/mdk-cbindings/src/media.rs` around lines 78 - 82, Remove the raw {e} interpolation from the three map_err closures and replace it with a static/sanitized warning message: locate the map_err around core_prepare (the closure currently calling tracing::warn!("Prepare group image failed: {e}")) and the two other similar map_err blocks referenced in the comment, and change the tracing::warn! call to a fixed message like tracing::warn!("Prepare group image failed") (or an equivalent static text for the other two contexts); keep error::set_last_error("Prepare group image failed") and returning MdkError::Mdk unchanged so only the logged text is sanitized while preserving existing error flow.
🧹 Nitpick comments (3)
crates/mdk-js/src/ffi_deno.ts (2)
47-51: Consider validating unknown types in symbol conversion.If
defscontains a type not present inTYPE_MAP, the mapping silently producesundefined, which may cause confusing runtime errors fromDeno.dlopen. A defensive check could improve debuggability.💡 Suggested defensive check
for (const [name, def] of Object.entries(defs)) { + const parameters = def.params.map((t) => { + const mapped = TYPE_MAP[t]; + if (!mapped) throw new Error(`Unknown FFI type "${t}" in symbol "${name}"`); + return mapped; + }); + const result = TYPE_MAP[def.result]; + if (!result) throw new Error(`Unknown FFI result type "${def.result}" in symbol "${name}"`); out[name] = { - parameters: def.params.map((t) => TYPE_MAP[t]), - result: TYPE_MAP[def.result], + parameters, + result, }; }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@crates/mdk-js/src/ffi_deno.ts` around lines 47 - 51, Loop over defs and validate that every type referenced in def.params and def.result exists in TYPE_MAP before assigning to out[name]; if any mapping yields undefined, throw a clear Error that includes the symbol name (name) and the unknown type(s). Update the conversion logic around the loop that builds out[name] (the code iterating "for (const [name, def] of Object.entries(defs))") to collect unknown types from def.params and def.result and fail-fast with a descriptive message (so the Deno.dlopen consumer sees the exact missing type).
79-85: Consider adding monorepo sibling paths for dev parity with Bun backend.The Bun backend includes additional candidate paths for monorepo development (
mdk-cbindings/target/...). Adding these would provide consistent developer experience across runtimes.💡 Suggested additional paths
const candidates = [ // Packaged layout `${pkgRoot}../native/${platform}/${base}`, // Development: cargo build output `${pkgRoot}../../../target/release/${base}`, `${pkgRoot}../../../target/debug/${base}`, + // Monorepo sibling + `${pkgRoot}../../mdk-cbindings/target/release/${base}`, + `${pkgRoot}../../mdk-cbindings/target/debug/${base}`, ];🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@crates/mdk-js/src/ffi_deno.ts` around lines 79 - 85, The candidates array for native lookup (variable candidates using pkgRoot, platform, base) is missing monorepo sibling paths used by the Bun backend; add sibling dev paths that point to the mdk-cbindings crate build outputs (e.g. sibling mdk-cbindings/target/release/${base} and mdk-cbindings/target/debug/${base}) alongside the existing cargo target entries so the lookup in the same candidates array (where candidates is declared) will find monorepo-built artifacts during development.crates/mdk-cbindings/include/mdk.h (1)
126-127: Prefer plain C symbol references in header comments over Rust intra-doc links.Line 126 and Line 439 include Rust-style links (e.g.,
crate::...) that are not meaningful to C consumers. Consider emitting plain function names in generated comments for portability/readability.Also applies to: 439-440
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@crates/mdk-cbindings/include/mdk.h` around lines 126 - 127, Replace Rust intra-doc links in the C header comments with plain C symbol names: change occurrences like `crate::free::mdk_string_free` to just `mdk_string_free` (and likewise for any other `crate::...` links found around the other comment blocks such as the ones at lines 439-440) so that the generated comments reference the C ABI symbols directly and are readable by C consumers.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@crates/mdk-zig/src/mdk.zig`:
- Around line 769-772: The test "check returns error on non-OK" is asserting an
error union incorrectly using std.testing.expectEqual; replace that assertion to
use std.testing.expectError so the call to check(raw.MDK_ERROR_STORAGE) is
asserted to return Error.Storage; locate the test block named "check returns
error on non-OK" and change the expectEqual(Error.Storage, result) usage to
std.testing.expectError(Error.Storage, check(raw.MDK_ERROR_STORAGE)) (or
otherwise call expectError with the expression), keeping references to the check
function, raw.MDK_ERROR_STORAGE, and Error.Storage.
---
Duplicate comments:
In `@crates/mdk-cbindings/src/media.rs`:
- Around line 78-82: Remove the raw {e} interpolation from the three map_err
closures and replace it with a static/sanitized warning message: locate the
map_err around core_prepare (the closure currently calling
tracing::warn!("Prepare group image failed: {e}")) and the two other similar
map_err blocks referenced in the comment, and change the tracing::warn! call to
a fixed message like tracing::warn!("Prepare group image failed") (or an
equivalent static text for the other two contexts); keep
error::set_last_error("Prepare group image failed") and returning MdkError::Mdk
unchanged so only the logged text is sanitized while preserving existing error
flow.
In `@crates/mdk-zig/src/mdk.zig`:
- Around line 140-143: The call to std.json.Stringify.valueAlloc in configToJson
is invalid with the current Zig stdlib; replace that call with
std.json.stringifyAlloc (using the same allocator and conf, and preserve the
options {.emit_null_optional_fields = false}) so configToJson compiles; update
the invocation inside fn configToJson(allocator: std.mem.Allocator, cfg:
?Config) to call std.json.stringifyAlloc and return its result.
- Around line 712-731: After calling raw.mdk_decrypt_group_image (in the wrapper
that returns CBytes), validate that out_ptr is not null before constructing and
returning CBytes; if out_ptr is null or out_len is zero treat it as an error
(return a Zig error or call the existing error path) so you don't create an
invalid owned buffer—add the nil-check for out_ptr immediately after the try
check and handle it consistently with the function's error-reporting convention.
---
Nitpick comments:
In `@crates/mdk-cbindings/include/mdk.h`:
- Around line 126-127: Replace Rust intra-doc links in the C header comments
with plain C symbol names: change occurrences like
`crate::free::mdk_string_free` to just `mdk_string_free` (and likewise for any
other `crate::...` links found around the other comment blocks such as the ones
at lines 439-440) so that the generated comments reference the C ABI symbols
directly and are readable by C consumers.
In `@crates/mdk-js/src/ffi_deno.ts`:
- Around line 47-51: Loop over defs and validate that every type referenced in
def.params and def.result exists in TYPE_MAP before assigning to out[name]; if
any mapping yields undefined, throw a clear Error that includes the symbol name
(name) and the unknown type(s). Update the conversion logic around the loop that
builds out[name] (the code iterating "for (const [name, def] of
Object.entries(defs))") to collect unknown types from def.params and def.result
and fail-fast with a descriptive message (so the Deno.dlopen consumer sees the
exact missing type).
- Around line 79-85: The candidates array for native lookup (variable candidates
using pkgRoot, platform, base) is missing monorepo sibling paths used by the Bun
backend; add sibling dev paths that point to the mdk-cbindings crate build
outputs (e.g. sibling mdk-cbindings/target/release/${base} and
mdk-cbindings/target/debug/${base}) alongside the existing cargo target entries
so the lookup in the same candidates array (where candidates is declared) will
find monorepo-built artifacts during development.
ℹ️ Review info
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (4)
crates/mdk-cbindings/include/mdk.hcrates/mdk-cbindings/src/media.rscrates/mdk-js/src/ffi_deno.tscrates/mdk-zig/src/mdk.zig
❌ Coverage: 91.07% → 88.52% (-2.55%) |
There was a problem hiding this comment.
Actionable comments posted: 2
♻️ Duplicate comments (1)
crates/mdk-zig/src/mdk.zig (1)
140-143:⚠️ Potential issue | 🔴 CriticalUse
std.json.stringifyAlloc; current API call is invalid.
std.json.Stringify.valueAllocis not a valid stdlib JSON allocation API and will fail to compile on current Zig stdlib.✅ Proposed fix
fn configToJson(allocator: std.mem.Allocator, cfg: ?Config) std.mem.Allocator.Error!?[]const u8 { const conf = cfg orelse return null; - const result = try std.json.Stringify.valueAlloc(allocator, conf, .{ .emit_null_optional_fields = false }); + const result = try std.json.stringifyAlloc(allocator, conf, .{ .emit_null_optional_fields = false }); return result; }#!/bin/bash set -euo pipefail # 1) Show current callsite(s) in this wrapper. rg -n 'std\.json\.Stringify\.valueAlloc|std\.json\.stringifyAlloc' crates/mdk-zig/src/mdk.zig # 2) Verify stdlib API names from Zig's std/json/stringify.zig. curl -fsSL https://raw.githubusercontent.com/ziglang/zig/main/lib/std/json/stringify.zig \ | rg -n 'pub fn stringifyAlloc|Stringify\.valueAlloc'🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@crates/mdk-zig/src/mdk.zig` around lines 140 - 143, The call to the non-existent std.json.Stringify.valueAlloc in function configToJson is invalid; replace it with the correct API std.json.stringifyAlloc by calling std.json.stringifyAlloc(allocator, conf, .{ .emit_null_optional_fields = false }) inside configToJson and return its result (keeping the existing return type Error!?[]const u8). Update the function body in configToJson to use std.json.stringifyAlloc and ensure any error propagation (try) and the null-early-return (cfg orelse return null) remain unchanged.
🧹 Nitpick comments (1)
crates/mdk-cbindings/src/error.rs (1)
157-168: Test name and behavior are mismatched.
from_storage_error_sets_messagecurrently testsinvalid_input, notfrom_storage_error, so it won’t catch regressions in storage-error mapping.🧪 Suggested cleanup
- fn from_storage_error_sets_message() { - // We can't easily construct a StorageError, but we can test - // the simpler helpers + fn invalid_input_sets_message() { + // Tests invalid_input helper behavior. let code = invalid_input("bad input"); assert_eq!(code, MdkError::InvalidInput);🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@crates/mdk-cbindings/src/error.rs` around lines 157 - 168, The test named from_storage_error_sets_message is asserting the behavior of invalid_input instead of exercising from_storage_error; either rename the test to reflect it checks invalid_input (e.g., invalid_input_sets_message) or change the test body to actually call from_storage_error with a constructed StorageError and assert the returned MdkError and message via from_storage_error and mdk_last_error_message; update references inside the test (from_storage_error_sets_message, invalid_input, from_storage_error, mdk_last_error_message) accordingly so the name and assertions match the behavior being tested.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@crates/mdk-cbindings/src/media.rs`:
- Around line 78-83: The tracing::debug call inside the core_prepare error map
closure is interpolating the raw error (`{e}`) which may leak sensitive
identifiers; change the closure used with `core_prepare(image_bytes,
mime_str).map_err(|e| { ... })` to log a generic, non-sensitive message (e.g.,
"prepare_group_image failed") and avoid printing `e` directly—if you need an
identifier for debugging, log a sanitized/structured error code or use a
non-sensitive derived property (e.g., an error enum variant or `.kind()`), then
set the last error and return `MdkError::Mdk`; apply the same change pattern to
the similar tracing usages around the other map_err sites referenced (the blocks
around the groups at the other noted locations).
In `@crates/mdk-zig/src/mdk.zig`:
- Around line 1-5: The crate-level docs currently state that "memory management
is handled automatically via RAII-style owned types" which is misleading for
Zig; update the top comments (and the similar text around lines 41-44) to
explicitly state that callers must call deinit() (typically via defer) on owned
types to free resources rather than relying on automatic scope-based
destruction; reference the term "owned types" in the doc text so readers of
mdk.zig know which types require explicit deinit().
---
Duplicate comments:
In `@crates/mdk-zig/src/mdk.zig`:
- Around line 140-143: The call to the non-existent
std.json.Stringify.valueAlloc in function configToJson is invalid; replace it
with the correct API std.json.stringifyAlloc by calling
std.json.stringifyAlloc(allocator, conf, .{ .emit_null_optional_fields = false
}) inside configToJson and return its result (keeping the existing return type
Error!?[]const u8). Update the function body in configToJson to use
std.json.stringifyAlloc and ensure any error propagation (try) and the
null-early-return (cfg orelse return null) remain unchanged.
---
Nitpick comments:
In `@crates/mdk-cbindings/src/error.rs`:
- Around line 157-168: The test named from_storage_error_sets_message is
asserting the behavior of invalid_input instead of exercising
from_storage_error; either rename the test to reflect it checks invalid_input
(e.g., invalid_input_sets_message) or change the test body to actually call
from_storage_error with a constructed StorageError and assert the returned
MdkError and message via from_storage_error and mdk_last_error_message; update
references inside the test (from_storage_error_sets_message, invalid_input,
from_storage_error, mdk_last_error_message) accordingly so the name and
assertions match the behavior being tested.
ℹ️ Review info
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (5)
crates/mdk-cbindings/include/mdk.hcrates/mdk-cbindings/src/error.rscrates/mdk-cbindings/src/media.rscrates/mdk-js/src/ffi_deno.tscrates/mdk-zig/src/mdk.zig
🚧 Files skipped from review as they are similar to previous changes (1)
- crates/mdk-js/src/ffi_deno.ts
❌ Coverage: 91.09% → 88.56% (-2.53%) |
❌ Coverage: 91.09% → 88.7% (-2.39%) |
There was a problem hiding this comment.
Actionable comments posted: 8
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
.github/workflows/package-mdk-bindings.yml (1)
611-611:⚠️ Potential issue | 🔴 Critical
actions/checkout@v6does not exist.This checkout step uses
actions/checkout@v6, but the latest stable version isv4. All other checkout steps in this workflow correctly usev4. This will cause the workflow to fail.🐛 Proposed fix
- name: Checkout Kotlin package repo - uses: actions/checkout@v6 + uses: actions/checkout@v4 with: repository: ${{ env.KOTLIN_PACKAGE_REPO }}🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In @.github/workflows/package-mdk-bindings.yml at line 611, The workflow step uses an invalid action version "actions/checkout@v6"; update the checkout step's uses value to the correct stable release "actions/checkout@v4" (match the other checkout steps) so the job will run successfully.
♻️ Duplicate comments (2)
crates/mdk-zig/src/mdk.zig (2)
42-44:⚠️ Potential issue | 🟡 MinorClarify
lastErrorMessagecleanup docs to require explicitdeinit().Line 44 still implies scope-based cleanup; this type must be explicitly deinitialized.
📝 Proposed doc fix
/// Returns `null` if no error has been recorded on this thread (or if the /// message has already been consumed). The returned `CString` must be -/// allowed to go out of scope (its `deinit` frees the C memory). +/// explicitly deinitialized (e.g. `defer msg.deinit();`).🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@crates/mdk-zig/src/mdk.zig` around lines 42 - 44, The docs for lastErrorMessage incorrectly imply scope-based cleanup; update the comment for the function/variable named lastErrorMessage to explicitly state that the returned CString must be explicitly deinit()'d by the caller (no automatic scope-based free), e.g., replace wording that says it "must be allowed to go out of scope" with a sentence that callers are required to call CString.deinit() to free the C memory after use and that null indicates no error or already-consumed message.
141-144:⚠️ Potential issue | 🔴 CriticalReplace invalid JSON API call with
std.json.stringifyAlloc.Line 143 uses a non-existent stdlib API and will fail compilation.
🔧 Proposed fix
fn configToJson(allocator: std.mem.Allocator, cfg: ?Config) std.mem.Allocator.Error!?[]const u8 { const conf = cfg orelse return null; - const result = try std.json.Stringify.valueAlloc(allocator, conf, .{ .emit_null_optional_fields = false }); + const result = try std.json.stringifyAlloc(allocator, conf, .{ .emit_null_optional_fields = false }); return result; }#!/bin/bash set -euo pipefail # Verify current API usage in Zig files. rg -n --type zig 'std\.json\.Stringify\.valueAlloc|std\.json\.stringifyAlloc' # Inspect the exact affected block. sed -n '141,145p' crates/mdk-zig/src/mdk.zig🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@crates/mdk-zig/src/mdk.zig` around lines 141 - 144, The call to a non-existent std.json.Stringify.valueAlloc in function configToJson causes compilation failure; replace it with the correct Zig stdlib API std.json.stringifyAlloc and pass the same allocator, value, and options (e.g., .{ .emit_null_optional_fields = false }) so the function returns the allocated JSON slice as before. Update the invocation inside configToJson to use std.json.stringifyAlloc(allocator, conf, .{ .emit_null_optional_fields = false }) and keep the existing error handling and return path.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@crates/mdk-cbindings/src/groups.rs`:
- Around line 74-77: After validating the out_json pointer with
require_non_null!(out_json, "out_json"), immediately clear the caller-visible
output by writing *out_json = std::ptr::null_mut(); (inside an unsafe block) so
any early return/error path cannot leave a stale pointer; apply this change in
each function that accepts out_json (the ffi_try_unwind_safe closures that call
deref_handle! and then require_non_null!(out_json, "out_json") — update all
occurrences listed in the review so the output is set to null at entry.
In `@crates/mdk-cbindings/src/lib.rs`:
- Around line 84-89: Before doing any constructor work inside the
ffi_try_unwind_safe closure, explicitly set the output pointer to null to avoid
returning stale handles: after the require_non_null!(out, "out"); call in each
constructor (the blocks using types::ffi_try_unwind_safe where you convert
db_path/service_id/db_key_id and parse_config), perform an unsafe write like
*out = std::ptr::null_mut(); so that if later conversion/constructor fails the C
caller gets a null handle; apply the same change to the other two constructor
blocks that follow the same pattern.
In `@crates/mdk-cbindings/src/messages.rs`:
- Around line 66-69: Before performing any fallible operations in the
ffi_try_unwind_safe closure, explicitly initialize *out_json to null to avoid
leaving callers with stale pointers; e.g. inside the closure containing
deref_handle!(h) and before parse_group_id or any ?-returning call, add an
unsafe guard that if !out_json.is_null() then *out_json = std::ptr::null_mut();
apply the same change to every function that accepts out_json (the closures
around the symbols handling mls_group_id and the other locations noted) so
out_json is always null on early-return.
In `@crates/mdk-js/src/mdk.js`:
- Around line 16-22: The setBackend function currently allows replacing the
global _ffi which is unsafe; change setBackend(backend) so that if _ffi !== null
it refuses to replace it (throw a clear Error like "MDK: backend already set" or
return early) instead of logging and assigning; ensure any calls to ffi().sym.*
rely on _ffi being immutable after first initialization by preventing
reassignment in setBackend and leaving _ffi assignment only when it was
previously null.
- Around line 134-142: Ensure callers validate fixed-size crypto buffers before
crossing FFI: in createWithKey (which calls mdk_new_with_key) check that key is
exactly 32 bytes and throw a clear JS error if not; likewise add explicit length
checks for any other functions around lines 532–559 that pass buffers to FFI
(the sites that call f.bufferPtr for nonce and hash) to enforce a 12-byte nonce
and an optional 32-byte hash (error if present but wrong size). Perform these
checks in JS prior to calling f.bufferPtr/f.buffer or mdk_* FFI functions and
return/throw immediately to avoid sending incorrectly sized buffers to native
code. Ensure error messages mention the function name (e.g., createWithKey) and
expected sizes to aid debugging.
- Around line 66-72: callWithStringOut currently allocates native memory via
ffi().allocOutString() but calls check() before freeing, which can leak when
check() throws; wrap the FFI allocation in a try/finally so out.readAndFree()
always runs (use finally or catch+rethrow) after calling fn(..., out.ptr) and
check(); apply the same pattern to any similar helpers that use
allocOutBytes/allocOutString (e.g., decryptGroupImage and other allocation
sites) so mdk_string_free/mdk_bytes_free are always invoked on both success and
error paths.
In `@crates/mdk-zig/src/mdk.zig`:
- Line 79: The documentation for CBytes is incorrect: it references slice() but
the public accessor is bytes(); update the doc comment on CBytes (the line
mentioning `deinit()` and the accessor) to reference `bytes()` instead of
`slice()` so it matches the actual public method name (`CBytes.bytes()`),
leaving the rest of the comment intact.
---
Outside diff comments:
In @.github/workflows/package-mdk-bindings.yml:
- Line 611: The workflow step uses an invalid action version
"actions/checkout@v6"; update the checkout step's uses value to the correct
stable release "actions/checkout@v4" (match the other checkout steps) so the job
will run successfully.
---
Duplicate comments:
In `@crates/mdk-zig/src/mdk.zig`:
- Around line 42-44: The docs for lastErrorMessage incorrectly imply scope-based
cleanup; update the comment for the function/variable named lastErrorMessage to
explicitly state that the returned CString must be explicitly deinit()'d by the
caller (no automatic scope-based free), e.g., replace wording that says it "must
be allowed to go out of scope" with a sentence that callers are required to call
CString.deinit() to free the C memory after use and that null indicates no error
or already-consumed message.
- Around line 141-144: The call to a non-existent std.json.Stringify.valueAlloc
in function configToJson causes compilation failure; replace it with the correct
Zig stdlib API std.json.stringifyAlloc and pass the same allocator, value, and
options (e.g., .{ .emit_null_optional_fields = false }) so the function returns
the allocated JSON slice as before. Update the invocation inside configToJson to
use std.json.stringifyAlloc(allocator, conf, .{ .emit_null_optional_fields =
false }) and keep the existing error handling and return path.
ℹ️ Review info
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (12)
.github/workflows/package-mdk-bindings.ymlcrates/mdk-cbindings/Cargo.tomlcrates/mdk-cbindings/src/error.rscrates/mdk-cbindings/src/groups.rscrates/mdk-cbindings/src/lib.rscrates/mdk-cbindings/src/media.rscrates/mdk-cbindings/src/messages.rscrates/mdk-cbindings/src/welcomes.rscrates/mdk-js/src/ffi_bun.jscrates/mdk-js/src/ffi_deno.tscrates/mdk-js/src/mdk.jscrates/mdk-zig/src/mdk.zig
🚧 Files skipped from review as they are similar to previous changes (5)
- crates/mdk-cbindings/src/welcomes.rs
- crates/mdk-cbindings/src/media.rs
- crates/mdk-cbindings/Cargo.toml
- crates/mdk-js/src/ffi_bun.js
- crates/mdk-js/src/ffi_deno.ts
❌ Coverage: 91.09% → 88.7% (-2.39%) |
There was a problem hiding this comment.
Actionable comments posted: 1
♻️ Duplicate comments (1)
crates/mdk-zig/src/mdk.zig (1)
141-145:⚠️ Potential issue | 🔴 Critical
std.json.Stringify.valueAllocis not a valid Zig standard library API.The Zig standard library provides
std.json.stringifyAllocfor JSON serialization, notstd.json.Stringify.valueAlloc. This will cause a compilation error.🐛 Proposed fix
fn configToJson(allocator: std.mem.Allocator, cfg: ?Config) std.mem.Allocator.Error!?[]const u8 { const conf = cfg orelse return null; - const result = try std.json.Stringify.valueAlloc(allocator, conf, .{ .emit_null_optional_fields = false }); + const result = try std.json.stringifyAlloc(allocator, conf, .{ .emit_null_optional_fields = false }); return result; }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@crates/mdk-zig/src/mdk.zig` around lines 141 - 145, The call in function configToJson uses a non-existent API std.json.Stringify.valueAlloc; replace it with the correct Zig stdlib function std.json.stringifyAlloc and adapt the call signature to match stringifyAlloc (pass allocator and the value, and any options via std.json.StringifyOptions if needed) so configToJson compiles; update any option names to the types expected by std.json.stringifyAlloc and ensure the return/errset stays consistent with configToJson's signature.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In @.github/workflows/package-mdk-bindings.yml:
- Around line 704-714: The matrix entry uses an unavailable runner value
"macos-13"; update the matrix include entry so that the os value is
"macos-15-intel" (i.e., replace the existing "macos-13" string in the matrix
include block) to target Intel macOS builds; keep the surrounding keys (matrix,
include, os, lib) unchanged and only change that os value.
---
Duplicate comments:
In `@crates/mdk-zig/src/mdk.zig`:
- Around line 141-145: The call in function configToJson uses a non-existent API
std.json.Stringify.valueAlloc; replace it with the correct Zig stdlib function
std.json.stringifyAlloc and adapt the call signature to match stringifyAlloc
(pass allocator and the value, and any options via std.json.StringifyOptions if
needed) so configToJson compiles; update any option names to the types expected
by std.json.stringifyAlloc and ensure the return/errset stays consistent with
configToJson's signature.
ℹ️ Review info
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (1)
Cargo.lockis excluded by!**/*.lock
📒 Files selected for processing (7)
.github/workflows/package-mdk-bindings.ymlcrates/mdk-cbindings/include/mdk.hcrates/mdk-cbindings/src/groups.rscrates/mdk-cbindings/src/lib.rscrates/mdk-cbindings/src/messages.rscrates/mdk-js/src/mdk.jscrates/mdk-zig/src/mdk.zig
❌ Coverage: 91.09% → 88.66% (-2.43%) |
There was a problem hiding this comment.
Actionable comments posted: 3
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In @.github/workflows/package-mdk-bindings.yml:
- Around line 993-997: The cp line that copies macOS Intel artifact uses the
wrong artifact name `ffi-native-macos-13`; update that command to reference the
correct artifact `ffi-native-macos-15-intel` (i.e., change
`ffi-native-macos-13/libmdk.dylib` to `ffi-native-macos-15-intel/libmdk.dylib`)
so the file copied into `js-package/native/macos-x86_64/` matches the produced
artifact name used in other publish jobs.
- Around line 895-899: The workflow copies a non-existent artifact
"ffi-native-macos-13" — update the third macOS copy line so it references the
correct artifact name used elsewhere ("ffi-native-macos-15-intel") instead of
"ffi-native-macos-13"; modify the cp command that currently targets
ffi-native-macos-13/libmdk.dylib so it points to
ffi-native-macos-15-intel/libmdk.dylib (the other cp lines and artifact names
like ffi-native-macos-latest and ffi-native-macos-13 should not be changed).
- Around line 793-797: The copy step references a nonexistent artifact folder
"ffi-native-macos-13"; update the artifact name to match the build matrix (e.g.,
replace "ffi-native-macos-13" with "ffi-native-macos-15-intel") so the cp
command in the workflow copies from the actual produced artifact (change the
entry that uses ffi-native-macos-13/libmdk.dylib to the matching artifact name
used by the matrix such as ffi-native-macos-15-intel/libmdk.dylib and keep the
destination cbindings-package/lib/macos-x86_64/).
❌ Coverage: 91.1% → 88.86% (-2.24%) |
❌ Coverage: 91.1% → 90.11% (-0.99%) |
There was a problem hiding this comment.
♻️ Duplicate comments (1)
crates/mdk-js/src/mdk.js (1)
559-573:⚠️ Potential issue | 🔴 CriticalAvoid unconditional out-buffer cleanup in
decryptGroupImagecatch path.At Line 571,
out.readAndFree()runs even if argument evaluation throws before the native call executes (for example,f.bufferPtr(data)), which can free unread/uninitialized out pointers.🔧 Proposed fix
export function decryptGroupImage(data, expectedHash, key, nonce) { @@ const f = ffi(); const out = f.allocOutBytes(); + let called = false; try { - check(f.sym.mdk_decrypt_group_image( + const code = f.sym.mdk_decrypt_group_image( f.bufferPtr(data), data.length, expectedHash ? f.bufferPtr(expectedHash) : f.nullptr, expectedHash ? expectedHash.length : 0, f.bufferPtr(key), key.length, f.bufferPtr(nonce), nonce.length, out.ptrPtr, out.lenPtr, - )); + ); + called = true; + check(code); return out.readAndFree(); } catch (e) { - out.readAndFree(); + if (called) out.readAndFree(); throw e; } }#!/bin/bash set -euo pipefail echo "=== Inspect allocOutBytes/readAndFree contracts in backends ===" rg -n -C4 'allocOutBytes|readAndFree|mdk_bytes_free|ptrPtr|lenPtr' crates/mdk-js/src/ffi_bun.js crates/mdk-js/src/ffi_deno.ts echo echo "=== Inspect decryptGroupImage error-path cleanup ===" sed -n '548,575p' crates/mdk-js/src/mdk.jsExpected verification result: if backend
readAndFree()assumes pointers are written by native code, unconditional cleanup on pre-call exceptions is unsafe and must stay guarded.Based on learnings: MDK handles cryptographic operations so be careful with key material. Review security implications of any changes to cryptographic code.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@crates/mdk-js/src/mdk.js` around lines 559 - 573, The catch path unconditionally calls out.readAndFree() even if argument evaluation (e.g., f.bufferPtr(data)) threw before the native call wrote the out pointers; change decryptGroupImage so the out buffer is only cleaned up when the native call actually ran and populated the pointers: allocate out as now, wrap only the native call+readAndFree sequence so that after check(f.sym.mdk_decrypt_group_image(...)) returns successfully you call out.readAndFree(), and in the catch block only call out.readAndFree() if a boolean flag (e.g., nativeCalled or outPopulated) was set immediately after the native call succeeded; reference symbols: decryptGroupImage, allocOutBytes, out.readAndFree, f.sym.mdk_decrypt_group_image, f.bufferPtr.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Duplicate comments:
In `@crates/mdk-js/src/mdk.js`:
- Around line 559-573: The catch path unconditionally calls out.readAndFree()
even if argument evaluation (e.g., f.bufferPtr(data)) threw before the native
call wrote the out pointers; change decryptGroupImage so the out buffer is only
cleaned up when the native call actually ran and populated the pointers:
allocate out as now, wrap only the native call+readAndFree sequence so that
after check(f.sym.mdk_decrypt_group_image(...)) returns successfully you call
out.readAndFree(), and in the catch block only call out.readAndFree() if a
boolean flag (e.g., nativeCalled or outPopulated) was set immediately after the
native call succeeded; reference symbols: decryptGroupImage, allocOutBytes,
out.readAndFree, f.sym.mdk_decrypt_group_image, f.bufferPtr.
ℹ️ Review info
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (8)
crates/mdk-cbindings/src/error.rscrates/mdk-cbindings/src/groups.rscrates/mdk-cbindings/src/lib.rscrates/mdk-cbindings/src/media.rscrates/mdk-cbindings/src/types.rscrates/mdk-cbindings/src/welcomes.rscrates/mdk-js/src/mdk.jscrates/mdk-zig/src/mdk.zig
🚧 Files skipped from review as they are similar to previous changes (3)
- crates/mdk-cbindings/src/error.rs
- crates/mdk-cbindings/src/welcomes.rs
- crates/mdk-zig/src/mdk.zig
❌ Coverage: 91.1% → 90.11% (-0.99%) |
There was a problem hiding this comment.
🧹 Nitpick comments (2)
.github/workflows/package-mdk-bindings.yml (2)
701-714: Consider documenting the OS-to-architecture mapping assumption.The matrix implicitly assumes
macos-latestproduces aarch64 binaries. While this is currently correct, adding a brief comment documenting this assumption would help future maintainers. Additionally, Linux ARM64 (aarch64) builds are not included—this may be intentional for initial release but worth tracking if ARM Linux support is desired.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In @.github/workflows/package-mdk-bindings.yml around lines 701 - 714, Add a short inline comment above the build-ffi-native matrix explaining the OS-to-architecture assumptions (e.g., that macos-latest targets aarch64 while macos-15-intel targets x86_64) so future maintainers understand the mapping, and optionally note that Linux ARM64 (aarch64) is not currently included; update the matrix declaration near the build-ffi-native job and the matrix include entries (matrix.include, macos-latest, macos-15-intel, ubuntu-latest, windows-latest) to reflect this comment and/or add a TODO entry for adding an ubuntu-arm64 (aarch64) row if ARM Linux support should be tracked.
773-782: Consider extracting version extraction into a reusable workflow or composite action.The version extraction logic is duplicated across
publish-cbindings,publish-zig, andpublish-jsjobs. While the current approach is clear and self-contained, extracting this into a composite action would reduce duplication and ensure consistency if the extraction logic needs to change.Also applies to: 871-880, 972-981
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In @.github/workflows/package-mdk-bindings.yml around lines 773 - 782, The "Extract version" step (id: cargo_version) duplicates the same shell extraction logic across the publish-cbindings, publish-zig, and publish-js jobs; refactor this by moving the grep/sed extraction into a reusable composite action or a reusable workflow that exposes the extracted VERSION as an output, then replace each inlined "Extract version" step with a call to that composite (or uses workflow_call) so all jobs consume the single source-of-truth output and avoid duplication.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Nitpick comments:
In @.github/workflows/package-mdk-bindings.yml:
- Around line 701-714: Add a short inline comment above the build-ffi-native
matrix explaining the OS-to-architecture assumptions (e.g., that macos-latest
targets aarch64 while macos-15-intel targets x86_64) so future maintainers
understand the mapping, and optionally note that Linux ARM64 (aarch64) is not
currently included; update the matrix declaration near the build-ffi-native job
and the matrix include entries (matrix.include, macos-latest, macos-15-intel,
ubuntu-latest, windows-latest) to reflect this comment and/or add a TODO entry
for adding an ubuntu-arm64 (aarch64) row if ARM Linux support should be tracked.
- Around line 773-782: The "Extract version" step (id: cargo_version) duplicates
the same shell extraction logic across the publish-cbindings, publish-zig, and
publish-js jobs; refactor this by moving the grep/sed extraction into a reusable
composite action or a reusable workflow that exposes the extracted VERSION as an
output, then replace each inlined "Extract version" step with a call to that
composite (or uses workflow_call) so all jobs consume the single source-of-truth
output and avoid duplication.
❌ Coverage: 91.25% → 90.24% (-1.01%) |
UniFFI Binding Coverage7 public method(s) not bound in UniFFI Coverage: 30/37 bindable methods (81.1%)
|
| Method | Source |
|---|---|
delete_key_package_from_storage |
crates/mdk-core/src/key_packages.rs:598 |
delete_key_package_from_storage_by_hash_ref |
crates/mdk-core/src/key_packages.rs:613 |
get_ratchet_tree_info |
crates/mdk-core/src/groups.rs:554 |
pending_added_members_pubkeys |
crates/mdk-core/src/groups.rs:610 |
pending_member_changes |
crates/mdk-core/src/groups.rs:678 |
pending_removed_members_pubkeys |
crates/mdk-core/src/groups.rs:643 |
prepare_group_image_for_upload_with_options |
crates/mdk-core/src/extension/group_image.rs:459 |
✅ Bound methods (30)
| Method | Source |
|---|---|
accept_welcome |
crates/mdk-core/src/welcomes.rs:315 |
add_members |
crates/mdk-core/src/groups.rs:724 |
clear_pending_commit |
crates/mdk-core/src/groups.rs:1441 |
create_group |
crates/mdk-core/src/groups.rs:1116 |
create_key_package_for_event |
crates/mdk-core/src/key_packages.rs:53 |
create_key_package_for_event_with_options |
crates/mdk-core/src/key_packages.rs:91 |
create_message |
crates/mdk-core/src/messages/create.rs:79 |
decline_welcome |
crates/mdk-core/src/welcomes.rs:350 |
decrypt_group_image |
crates/mdk-core/src/extension/group_image.rs:227 |
derive_upload_keypair |
crates/mdk-core/src/extension/group_image.rs:332 |
get_group |
crates/mdk-core/src/groups.rs:474 |
get_groups |
crates/mdk-core/src/groups.rs:486 |
get_last_message |
crates/mdk-core/src/messages/mod.rs:249 |
get_members |
crates/mdk-core/src/groups.rs:520 |
get_message |
crates/mdk-core/src/messages/mod.rs:182 |
get_messages |
crates/mdk-core/src/messages/mod.rs:221 |
get_pending_welcomes |
crates/mdk-core/src/welcomes.rs:72 |
get_relays |
crates/mdk-core/src/groups.rs:1062 |
get_welcome |
crates/mdk-core/src/welcomes.rs:40 |
groups_needing_self_update |
crates/mdk-core/src/groups.rs:504 |
leave_group |
crates/mdk-core/src/groups.rs:1388 |
merge_pending_commit |
crates/mdk-core/src/groups.rs:1492 |
parse_key_package |
crates/mdk-core/src/key_packages.rs:263 |
prepare_group_image_for_upload |
crates/mdk-core/src/extension/group_image.rs:413 |
process_message |
crates/mdk-core/src/messages/process.rs:301 |
process_welcome |
crates/mdk-core/src/welcomes.rs:178 |
remove_members |
crates/mdk-core/src/groups.rs:818 |
self_update |
crates/mdk-core/src/groups.rs:1287 |
sync_group_metadata_from_mls |
crates/mdk-core/src/groups.rs:1563 |
update_group_data |
crates/mdk-core/src/groups.rs:995 |
➖ Intentionally excluded (5)
| Method | Reason |
|---|---|
builder |
Wrapped by new_mdk(), new_mdk_with_key(), new_mdk_unencrypted() free functions |
load_mls_group |
Only available behind debug-examples feature flag, not for production use |
media_manager |
Returns borrowed reference with lifetime — cannot cross FFI boundary |
migrate_group_image_v1_to_v2 |
Migration utility for internal use only, not needed in client bindings |
new |
Wrapped by new_mdk(), new_mdk_with_key(), new_mdk_unencrypted() free functions |
❌ Coverage: 91.21% → 90.49% (-0.72%) |
❌ Coverage: 91.21% → 90.36% (-0.85%) |
Someone at the marmot house casually whispered something about C and Zig bindings into the void... soo... naturally I built it because why not
then hzrd said why not do JS too, so I added those... so obviously I threw those in too
and technically the C bindings now make it super easy to do bindings in other languages too... like go and php...
because marmots are legally and morally obligated to inject MLS into absolutely everything, or something, I don't know
This PR adds first-class FFI bindings for C, Zig, and JavaScript (Bun/Deno) and the CI packaging pipeline to build and publish those bindings, enabling MDK consumers in non‑Rust ecosystems. It exposes a C ABI for core MDK functionality, provides high‑level JS and Zig wrappers that mirror the Rust API, and wires CI jobs to build native artifacts and publish per-language packages.
What changed:
Security impact:
Protocol changes:
API surface:
Testing: