Skip to content

feat(kotlin-sdk): implement secure native library downloading with SH…#416

Open
Abhinav-kodes wants to merge 2 commits intoRunanywhereAI:mainfrom
Abhinav-kodes:fix/issue-210-secure-native-downloads
Open

feat(kotlin-sdk): implement secure native library downloading with SH…#416
Abhinav-kodes wants to merge 2 commits intoRunanywhereAI:mainfrom
Abhinav-kodes:fix/issue-210-secure-native-downloads

Conversation

@Abhinav-kodes
Copy link
Copy Markdown

@Abhinav-kodes Abhinav-kodes commented Feb 23, 2026

Description

Fixes #210.

This PR secures the build-time native library download pipeline in the Kotlin SDK against supply-chain vulnerabilities and network hangs.

Key Changes:

  1. Extracted Build Logic: Created a new build-logic convention plugin to centralize the shared download logic via a custom DownloadAndVerifyNativeLibTask.
  2. Removed Unsafe Ant Tasks: Replaced the legacy ant.withGroovyBuilder calls in runanywhere-core-llamacpp and runanywhere-core-onnx.
  3. Security & Timeouts: Implemented HttpURLConnection with strict timeouts (30s connect / 120s read). The task now calculates the SHA256 hash during the stream and validates it against the expected .sha256 release asset before extracting the binaries.
  4. Configuration Cache & Parallelism: Replaced the monolithic, synchronous doLast blocks. By injecting FileSystemOperations and ArchiveOperations into a DefaultTask with @Input and @OutputDirectory annotations, Gradle can now safely cache these tasks, execute the ABI downloads in parallel, and fully support the Configuration Cache.
  5. Filtered Extraction: Safely extracts only the explicitly permitted .so files, ignoring unnecessary META-INF or LICENSE files from the release archives.

Important Note for Maintainers:
The Kotlin SDK is now fully configured to fetch and verify the .sha256 checksums before extraction. However, executing the task currently throws a FileNotFoundException because the GitHub Release assets (e.g., v0.1.5-SNAPSHOT) do not yet contain the .sha256 files. Once the GitHub Actions release workflow is updated to generate and upload these checksums (as mentioned in the issue description), this task will automatically begin verifying them.

Type of Change

  • Bug fix
  • New feature
  • Documentation update
  • Refactoring

Testing

  • Lint passes locally
  • Added/updated tests for changes (Verified via ./gradlew buildEnvironment and local task execution)

Platform-Specific Testing (check all that apply)

(Note: These changes only affect the Gradle build system for the Kotlin SDK, not the runtime code)

Swift SDK / iOS Sample:

  • Tested on iPhone (Simulator or Device)
  • Tested on iPad / Tablet
  • Tested on Mac (macOS target)

Kotlin SDK / Android Sample:

  • Tested on Android Phone (Emulator or Device)
  • Tested on Android Tablet

Flutter SDK / Flutter Sample:

  • Tested on iOS
  • Tested on Android

React Native SDK / React Native Sample:

  • Tested on iOS
  • Tested on Android

Playground:

  • Tested on target platform
  • Verified no regressions in existing Playground projects

Web SDK / Web Sample:

  • Tested in Chrome (Desktop)
  • Tested in Firefox
  • Tested in Safari
  • WASM backends load (LlamaCpp + ONNX)
  • OPFS storage persistence verified (survives page refresh)
  • Settings persistence verified (localStorage)

Labels

Please add the appropriate label(s):

SDKs:

  • Swift SDK - Changes to Swift SDK (sdk/runanywhere-swift)
  • Kotlin SDK - Changes to Kotlin SDK (sdk/runanywhere-kotlin)
  • Flutter SDK - Changes to Flutter SDK (sdk/runanywhere-flutter)
  • React Native SDK - Changes to React Native SDK (sdk/runanywhere-react-native)
  • Web SDK - Changes to Web SDK (sdk/runanywhere-web)
  • Commons - Changes to shared native code (sdk/runanywhere-commons)

Sample Apps:

  • iOS Sample - Changes to iOS example app (examples/ios)
  • Android Sample - Changes to Android example app (examples/android)
  • Flutter Sample - Changes to Flutter example app (examples/flutter)
  • React Native Sample - Changes to React Native example app (examples/react-native)
  • Web Sample - Changes to Web example app (examples/web)

Checklist

  • Code follows project style guidelines
  • Self-review completed
  • Documentation updated (if needed)

Screenshots

N/A - Gradle Build System changes only.


Important

Securely download native libraries in Kotlin SDK using a new plugin with checksum verification, replacing unsafe methods.

  • Build Logic:
    • Introduced build-logic convention plugin with DownloadAndVerifyNativeLibTask for secure native library downloading.
    • Uses HttpURLConnection with timeouts and SHA256 checksum verification.
  • Modules:
    • Updated runanywhere-core-llamacpp and runanywhere-core-onnx to use the new plugin for downloading native libraries.
    • Removed legacy ant.withGroovyBuilder calls.
  • Tasks:
    • Added tasks for downloading JNI libraries for each architecture in runanywhere-core-llamacpp and runanywhere-core-onnx.
    • Aggregated tasks to download all ABIs with checksum verification.

This description was created by Ellipsis for 1c54218. You can customize this summary. It will automatically update as commits are pushed.

Summary by CodeRabbit

  • New Features

    • Automated, per-ABI download and SHA-256 verification of native libraries.
    • Selective extraction of required native binaries into platform-specific folders.
    • Aggregate task to streamline fetching for all supported ABIs.
  • Refactor

    • Replaced ad-hoc download/extract logic with dedicated, declarative build tasks.
  • Chores

    • Added a reusable build plugin and composite build for centralized native library handling.
    • Updated build wiring so JNI-related steps trigger secure downloads when needed.

Greptile Summary

Replaced unsafe ant.withGroovyBuilder download tasks with a centralized Gradle convention plugin that implements SHA256 checksum verification and connection timeouts. The new DownloadAndVerifyNativeLibTask downloads native libraries from GitHub releases, verifies their integrity, and extracts only permitted .so files.

Key improvements:

  • SHA256 checksum verification during download stream
  • HTTP timeouts (30s connect, 120s read) to prevent hangs
  • Parallel ABI downloads with proper Gradle task inputs/outputs
  • Configuration cache support
  • Filtered extraction of whitelisted .so files

Critical issues found:

  • File filtering logic has a bug where exclude() inside eachFile doesn't work, potentially extracting unwanted files
  • Missing error handling for SHA256 file fetch will cause build failures when checksums aren't published
  • SHA256 URL fetch has no timeout, creating potential hang
  • ABI mismatch: downloads armeabi-v7a libraries that aren't included in ndk.abiFilters

Confidence Score: 2/5

  • This PR requires significant fixes before merging due to critical logic bugs in the file filtering and error handling
  • Score reflects three critical issues: the file filtering logic will fail to properly exclude unwanted files due to incorrect exclude() usage inside eachFile, the missing error handling will cause cryptic failures when SHA256 files don't exist (as acknowledged in the PR description), and the SHA256 fetch has no timeout protection. Additionally, there's an ABI configuration mismatch that wastes CI time downloading unused libraries
  • Pay close attention to NativeLibraryDownloadPlugin.kt - the file filtering and error handling logic must be fixed before this can work correctly

Important Files Changed

Filename Overview
sdk/runanywhere-kotlin/build-logic/src/main/kotlin/NativeLibraryDownloadPlugin.kt Introduced secure download task with SHA256 verification, but has critical bugs: missing error handling for SHA256 fetch, incorrect file filtering logic with exclude() inside eachFile, and no timeout on checksum URL fetch
sdk/runanywhere-kotlin/modules/runanywhere-core-llamacpp/build.gradle.kts Replaced unsafe ant tasks with new secure download plugin, but targetAbis includes armeabi-v7a which is not in ndk.abiFilters, causing unnecessary downloads of libraries that won't be packaged
sdk/runanywhere-kotlin/modules/runanywhere-core-onnx/build.gradle.kts Replaced unsafe ant tasks with new secure download plugin, but targetAbis includes armeabi-v7a which is not in ndk.abiFilters, causing unnecessary downloads of libraries that won't be packaged

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    Start[Gradle Build Triggered] --> CheckLocal{testLocal flag?}
    CheckLocal -->|true| Skip[Skip download, use local libs]
    CheckLocal -->|false| ParallelDownload[Parallel ABI Downloads]
    
    ParallelDownload --> Arm64[downloadJniLibs_arm64_v8a]
    ParallelDownload --> ArmV7[downloadJniLibs_armeabi_v7a]
    ParallelDownload --> X64[downloadJniLibs_x86_64]
    
    Arm64 --> FetchSHA1[Fetch SHA256 from GitHub]
    ArmV7 --> FetchSHA2[Fetch SHA256 from GitHub]
    X64 --> FetchSHA3[Fetch SHA256 from GitHub]
    
    FetchSHA1 --> Download1[Download ZIP with timeouts]
    FetchSHA2 --> Download2[Download ZIP with timeouts]
    FetchSHA3 --> Download3[Download ZIP with timeouts]
    
    Download1 --> Verify1[Stream & calculate SHA256]
    Download2 --> Verify2[Stream & calculate SHA256]
    Download3 --> Verify3[Stream & calculate SHA256]
    
    Verify1 --> Check1{Checksum match?}
    Verify2 --> Check2{Checksum match?}
    Verify3 --> Check3{Checksum match?}
    
    Check1 -->|no| Fail1[Delete ZIP, throw error]
    Check2 -->|no| Fail2[Delete ZIP, throw error]
    Check3 -->|no| Fail3[Delete ZIP, throw error]
    
    Check1 -->|yes| Extract1[Extract whitelisted .so files]
    Check2 -->|yes| Extract2[Extract whitelisted .so files]
    Check3 -->|yes| Extract3[Extract whitelisted .so files]
    
    Extract1 --> Flatten1[Flatten to jniLibs/arm64-v8a]
    Extract2 --> Flatten2[Flatten to jniLibs/armeabi-v7a]
    Extract3 --> Flatten3[Flatten to jniLibs/x86_64]
    
    Flatten1 --> Merge[Gradle mergeJniLibFolders]
    Flatten2 --> Merge
    Flatten3 --> Merge
    Skip --> Merge
    
    Merge --> Build[Continue build]
Loading

Last reviewed commit: 1c54218

(2/5) Greptile learns from your feedback when you react with thumbs up/down!

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Feb 23, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 1c54218 and f4f4280.

📒 Files selected for processing (2)
  • sdk/runanywhere-kotlin/build-logic/src/main/kotlin/NativeLibraryDownloadPlugin.kt
  • sdk/runanywhere-kotlin/modules/runanywhere-core-onnx/build.gradle.kts
🚧 Files skipped from review as they are similar to previous changes (2)
  • sdk/runanywhere-kotlin/build-logic/src/main/kotlin/NativeLibraryDownloadPlugin.kt
  • sdk/runanywhere-kotlin/modules/runanywhere-core-onnx/build.gradle.kts

📝 Walkthrough

Walkthrough

Adds a shared Gradle build-logic module providing a native-downloader plugin and a DownloadAndVerifyNativeLibTask (with timeouts and SHA-256 verification). Applies the plugin to llamacpp and onnx modules, replacing inline download/extract logic with per-ABI tasks and an aggregate downloadJniLibs task; includes the composite build.

Changes

Cohort / File(s) Summary
Build Logic Plugin
sdk/runanywhere-kotlin/build-logic/build.gradle.kts, sdk/runanywhere-kotlin/build-logic/settings.gradle.kts, sdk/runanywhere-kotlin/build-logic/src/main/kotlin/NativeLibraryDownloadPlugin.kt
Adds a kotlin-dsl convention plugin project, registers com.runanywhere.native-downloader, and implements DownloadAndVerifyNativeLibTask that performs HTTP download with timeouts, SHA-256 verification, temp-file streaming, selective .so extraction, and lifecycle logging.
Module: LlamaCPP JNI Setup
sdk/runanywhere-kotlin/modules/runanywhere-core-llamacpp/build.gradle.kts
Applies the new plugin; replaces inline download/extract doLast logic with per-ABI DownloadAndVerifyNativeLibTask tasks and an aggregate downloadJniLibs task; wires merge/preBuild tasks to depend on it when not testLocal.
Module: ONNX JNI Setup
sdk/runanywhere-kotlin/modules/runanywhere-core-onnx/build.gradle.kts
Applies the new plugin; introduces per-ABI secure download tasks and an aggregate downloadJniLibs task; removes previous imperative download block and updates ABI list and wiring.
Composite Build Wiring
sdk/runanywhere-kotlin/settings.gradle.kts
Adds includeBuild("build-logic") in pluginManagement to expose the convention plugin to module builds.

Sequence Diagram(s)

sequenceDiagram
    participant Gradle as Gradle Build
    participant Task as DownloadAndVerifyNativeLibTask
    participant HTTP as HTTP Server
    participant ZIP as Zip Extractor
    participant FS as File System

    Gradle->>Task: Execute per-ABI download task
    Task->>HTTP: GET artifact.zip (with connect/read timeouts)
    HTTP-->>Task: Streamed bytes
    Task->>Task: Stream to temp file + compute SHA-256
    Task->>HTTP: GET artifact.zip.sha256
    HTTP-->>Task: Return expected checksum
    Task->>Task: Compare computed vs expected
    alt Match
        Task->>ZIP: Open ZIP and filter allowed .so files
        ZIP->>FS: Write selected .so files to jniLibs/<abi>
        Task-->>Gradle: Task succeeds
    else Mismatch
        Task->>FS: Delete temp file
        Task-->>Gradle: Fail with checksum error
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Suggested labels

kotlin-sdk

Suggested reviewers

  • sanchitmonga22
  • Siddhesh2377

Poem

I nibble bytes beneath the moonlit build,
Checksum in paw, no artifact unfilled.
ZIPs unfurl where ABIs belong,
Timeouts guard my hurried song.
A burrowed task—safe, snug, and skilled. 🐰✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main change: implementing secure native library downloading with SHA256 checksum verification for the Kotlin SDK, which is the primary objective of this PR.
Description check ✅ Passed The PR description is comprehensive, covering key changes, testing performed, labels applied, and a detailed explanation of the security improvements. It follows the template structure with sections for Description, Type of Change, Testing, Labels, and Checklist.
Linked Issues check ✅ Passed The PR successfully addresses all primary objectives from issue #210: implements HttpURLConnection with timeouts, adds SHA256 checksum verification, consolidates download logic into a shared build-logic plugin, and supports Configuration Cache and parallel downloads.
Out of Scope Changes check ✅ Passed All changes are directly related to issue #210 objectives: creating the build-logic plugin, implementing secure downloads with timeouts and checksum verification, updating modules to use the plugin, and removing unsafe ant tasks.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Tip

Issue Planner is now in beta. Read the docs and try it out! Share your feedback on Discord.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@ellipsis-dev ellipsis-dev bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Important

Looks good to me! 👍

Reviewed everything up to 1c54218 in 30 seconds. Click for details.
  • Reviewed 437 lines of code in 6 files
  • Skipped 0 files when reviewing.
  • Skipped posting 0 draft comments. View those below.
  • Modify your settings and rules to customize what types of comments Ellipsis leaves. And don't forget to react with 👍 or 👎 to teach Ellipsis.

Workflow ID: wflow_jGTPZetC2ybYRN1C

You can customize Ellipsis by changing your verbosity settings, reacting with 👍 or 👎, replying to comments, or adding code review rules.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

🧹 Nitpick comments (2)
sdk/runanywhere-kotlin/build-logic/src/main/kotlin/NativeLibraryDownloadPlugin.kt (1)

114-118: Empty NativeLibraryDownloadPlugin.apply() body — detekt EmptyFunctionBlock warning.

The plugin class exists solely for plugin ID registration; the empty apply is intentional but triggers a detekt warning. Add a comment or a @Suppress to signal intent:

✨ Proposed fix
 class NativeLibraryDownloadPlugin : Plugin<Project> {
-    override fun apply(project: Project) {
-        
-    }
+    // Task type (DownloadAndVerifyNativeLibTask) is registered directly by consumers;
+    // no additional project configuration is required here.
+    `@Suppress`("EmptyFunctionBlock")
+    override fun apply(project: Project) = Unit
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@sdk/runanywhere-kotlin/build-logic/src/main/kotlin/NativeLibraryDownloadPlugin.kt`
around lines 114 - 118, The empty apply implementation in
NativeLibraryDownloadPlugin is intentional but triggers detekt's
EmptyFunctionBlock; to fix, either add a single-line comment inside apply like
"// intentionally empty: plugin ID registration only" or annotate the class or
method with `@Suppress`("EmptyFunctionBlock") so detekt knows this is deliberate;
update NativeLibraryDownloadPlugin.apply (or the NativeLibraryDownloadPlugin
class) accordingly to silence the warning while preserving the empty body.
sdk/runanywhere-kotlin/build-logic/build.gradle.kts (1)

20-22: Remove empty dependencies block.

It adds visual noise with no effect.

✨ Proposed cleanup
-dependencies {
-    
-}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@sdk/runanywhere-kotlin/build-logic/build.gradle.kts` around lines 20 - 22,
Remove the empty Gradle DSL block named "dependencies" from build.gradle.kts
since it has no effect; locate the empty dependencies { } declaration in the
file (the dependencies block shown in the diff) and delete it to reduce visual
noise.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In
`@sdk/runanywhere-kotlin/build-logic/src/main/kotlin/NativeLibraryDownloadPlugin.kt`:
- Around line 62-66: The download flow in NativeLibraryDownloadPlugin opens an
HttpURLConnection but doesn't verify the HTTP response code, so non-2xx
responses produce a generic IOException; update the code after
connection.connect() (or immediately after opening the connection) to call
connection.responseCode (and responseMessage), and if the code is not in
200..299 throw a new IOException with a clear message that includes the URL,
responseCode and responseMessage (optionally include the error stream body)
before attempting to read connection.inputStream; this makes failures like 404
explicit and easier to debug.
- Around line 83-109: The checksum check currently embeds tempFile.delete() as a
side effect in the check { } message lambda and tempFile is only deleted after
fsOps.copy, which leaks the temp file if extraction throws; remove the
tempFile.delete() call from the check message and make the code robust by
wrapping the extraction and any subsequent work in a try/finally where
tempFile.delete() is called in the finally block (so tempFile is always
cleaned). Specifically, change the check(...) to only produce the error string
(use calculatedHash and expectedHash) and surround the fsOps.copy { ... } /
logger.lifecycle(...) block with try { ... } finally { tempFile.delete() } so
cleanup always runs; reference symbols: check, calculatedHash, expectedHash,
fsOps.copy, logger.lifecycle, tempFile, destDir, validFiles, RelativePath.
- Line 56: Replace the unprotected URL(shaUrl).readText() call used to compute
expectedHash with an HttpURLConnection-based fetch that sets connectTimeout to
30_000 and readTimeout to 120_000, reads the response body (respecting charset),
trims and splits on whitespace to get the first token; ensure you open the
connection from URL(shaUrl).openConnection() as HttpURLConnection, call
connect(), read the stream into a String, and disconnect/close streams when done
so the SHA256 fetch uses the same timeouts as the main download (reference
expectedHash variable and the same timeout values used in the main download
logic).

In `@sdk/runanywhere-kotlin/modules/runanywhere-core-llamacpp/build.gradle.kts`:
- Line 163: The build config lists targetAbis = listOf("arm64-v8a",
"armeabi-v7a", "x86_64") but ndk { abiFilters } only packages "arm64-v8a" and
"x86_64", causing unnecessary downloads of armeabi-v7a; fix by either adding
"armeabi-v7a" to the abiFilters entry so it matches targetAbis or remove
"armeabi-v7a" from targetAbis in both runanywhere-core-llamacpp and
runanywhere-core-onnx so the download list aligns with the packaged ABIs (update
the targetAbis and/or abiFilters usages accordingly).

In `@sdk/runanywhere-kotlin/modules/runanywhere-core-onnx/build.gradle.kts`:
- Line 171: targetAbis contains "armeabi-v7a" but abiFilters does not, causing
CI to build an extra ABI and waste bandwidth; update the configuration so
targetAbis and abiFilters match by either removing "armeabi-v7a" from the
targetAbis list or adding "armeabi-v7a" to the abiFilters definition so both
variables (targetAbis and abiFilters) contain the same ABI entries for the ONNX
bundle.

---

Nitpick comments:
In `@sdk/runanywhere-kotlin/build-logic/build.gradle.kts`:
- Around line 20-22: Remove the empty Gradle DSL block named "dependencies" from
build.gradle.kts since it has no effect; locate the empty dependencies { }
declaration in the file (the dependencies block shown in the diff) and delete it
to reduce visual noise.

In
`@sdk/runanywhere-kotlin/build-logic/src/main/kotlin/NativeLibraryDownloadPlugin.kt`:
- Around line 114-118: The empty apply implementation in
NativeLibraryDownloadPlugin is intentional but triggers detekt's
EmptyFunctionBlock; to fix, either add a single-line comment inside apply like
"// intentionally empty: plugin ID registration only" or annotate the class or
method with `@Suppress`("EmptyFunctionBlock") so detekt knows this is deliberate;
update NativeLibraryDownloadPlugin.apply (or the NativeLibraryDownloadPlugin
class) accordingly to silence the warning while preserving the empty body.

ℹ️ Review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 4d1ff04 and 1c54218.

📒 Files selected for processing (6)
  • sdk/runanywhere-kotlin/build-logic/build.gradle.kts
  • sdk/runanywhere-kotlin/build-logic/settings.gradle.kts
  • sdk/runanywhere-kotlin/build-logic/src/main/kotlin/NativeLibraryDownloadPlugin.kt
  • sdk/runanywhere-kotlin/modules/runanywhere-core-llamacpp/build.gradle.kts
  • sdk/runanywhere-kotlin/modules/runanywhere-core-onnx/build.gradle.kts
  • sdk/runanywhere-kotlin/settings.gradle.kts

logger.lifecycle("Fetching expected SHA256 from $shaUrl...")

// Grab the first sequence of non-whitespace characters (the hash itself)
val expectedHash = URL(shaUrl).readText().trim().split("\\s+".toRegex()).first()
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

find . -type f -name "NativeLibraryDownloadPlugin.kt" | head -20

Repository: RunanywhereAI/runanywhere-sdks

Length of output: 157


🏁 Script executed:

# Get the file size first to ensure we can read it
wc -l sdk/runanywhere-kotlin/build-logic/src/main/kotlin/NativeLibraryDownloadPlugin.kt

Repository: RunanywhereAI/runanywhere-sdks

Length of output: 159


🏁 Script executed:

# Read the entire file with line numbers
cat -n sdk/runanywhere-kotlin/build-logic/src/main/kotlin/NativeLibraryDownloadPlugin.kt

Repository: RunanywhereAI/runanywhere-sdks

Length of output: 5252


🏁 Script executed:

# Search for PR `#210` or issue references in git history
git log --oneline --all --grep="210" | head -20

Repository: RunanywhereAI/runanywhere-sdks

Length of output: 176


🏁 Script executed:

# Search for any related commits mentioning timeout, URL.readText, or similar
git log --oneline --all --grep="timeout" | head -20

Repository: RunanywhereAI/runanywhere-sdks

Length of output: 867


🏁 Script executed:

# Check for issue tracker references in recent commits
git log --oneline -30 | grep -i "timeout\|hash\|download\|sha"

Repository: RunanywhereAI/runanywhere-sdks

Length of output: 176


🏁 Script executed:

# Show the commit details for PR `#210`
git show 1c542182 --stat | head -50

Repository: RunanywhereAI/runanywhere-sdks

Length of output: 3059


🏁 Script executed:

# Check the diff for the specific file to understand the intent
git show 1c542182:sdk/runanywhere-kotlin/build-logic/src/main/kotlin/NativeLibraryDownloadPlugin.kt | head -100

Repository: RunanywhereAI/runanywhere-sdks

Length of output: 4011


Apply HttpURLConnection with connection and read timeouts to SHA256 fetch (line 56) — currently unprotected.

Line 56 uses URL(shaUrl).readText(), which lacks timeout configuration and contradicts the explicit goal of PR #210 ("implement secure native library downloading with SHA256 and timeouts"). The main download (lines 63–65) correctly enforces 30s connection and 120s read timeouts via HttpURLConnection, but the SHA256 fetch remains vulnerable to indefinite hangs from stalled servers.

Proposed fix — fetch SHA256 via HttpURLConnection with matching timeouts
-        val expectedHash = URL(shaUrl).readText().trim().split("\\s+".toRegex()).first()
+        var shaConnection: HttpURLConnection? = null
+        val expectedHash: String
+        try {
+            shaConnection = URL(shaUrl).openConnection() as HttpURLConnection
+            shaConnection.connectTimeout = 30_000
+            shaConnection.readTimeout = 30_000
+            expectedHash = shaConnection.inputStream.bufferedReader()
+                .use { it.readText() }
+                .trim()
+                .split("\\s+".toRegex())
+                .first()
+        } finally {
+            shaConnection?.disconnect()
+        }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
val expectedHash = URL(shaUrl).readText().trim().split("\\s+".toRegex()).first()
var shaConnection: HttpURLConnection? = null
val expectedHash: String
try {
shaConnection = URL(shaUrl).openConnection() as HttpURLConnection
shaConnection.connectTimeout = 30_000
shaConnection.readTimeout = 30_000
expectedHash = shaConnection.inputStream.bufferedReader()
.use { it.readText() }
.trim()
.split("\\s+".toRegex())
.first()
} finally {
shaConnection?.disconnect()
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@sdk/runanywhere-kotlin/build-logic/src/main/kotlin/NativeLibraryDownloadPlugin.kt`
at line 56, Replace the unprotected URL(shaUrl).readText() call used to compute
expectedHash with an HttpURLConnection-based fetch that sets connectTimeout to
30_000 and readTimeout to 120_000, reads the response body (respecting charset),
trims and splits on whitespace to get the first token; ensure you open the
connection from URL(shaUrl).openConnection() as HttpURLConnection, call
connect(), read the stream into a String, and disconnect/close streams when done
so the SHA256 fetch uses the same timeouts as the main download (reference
expectedHash variable and the same timeout values used in the main download
logic).


// Download LlamaCPP backend libs from GitHub releases (testLocal=false)
val releaseBaseUrl = "https://github.com/RunanywhereAI/runanywhere-sdks/releases/download/v$nativeLibVersion"
val targetAbis = listOf("arm64-v8a", "armeabi-v7a", "x86_64")
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Confirm abiFilters vs targetAbis across both affected modules
rg -n "abiFilters|targetAbis" \
  sdk/runanywhere-kotlin/modules/runanywhere-core-llamacpp/build.gradle.kts \
  sdk/runanywhere-kotlin/modules/runanywhere-core-onnx/build.gradle.kts

Repository: RunanywhereAI/runanywhere-sdks

Length of output: 853


targetAbis includes armeabi-v7a but abiFilters does not — unnecessary download in both modules.

Both runanywhere-core-llamacpp and runanywhere-core-onnx have the same mismatch: targetAbis lists arm64-v8a, armeabi-v7a, and x86_64 (3 ABIs), but ndk { abiFilters } only packages arm64-v8a and x86_64. The armeabi-v7a native library (~34 MB per download) is fetched on every non-testLocal build but never included in the output, wasting CI bandwidth. Either add armeabi-v7a to abiFilters to align with the download list, or remove it from targetAbis if it is not required.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@sdk/runanywhere-kotlin/modules/runanywhere-core-llamacpp/build.gradle.kts` at
line 163, The build config lists targetAbis = listOf("arm64-v8a", "armeabi-v7a",
"x86_64") but ndk { abiFilters } only packages "arm64-v8a" and "x86_64", causing
unnecessary downloads of armeabi-v7a; fix by either adding "armeabi-v7a" to the
abiFilters entry so it matches targetAbis or remove "armeabi-v7a" from
targetAbis in both runanywhere-core-llamacpp and runanywhere-core-onnx so the
download list aligns with the packaged ABIs (update the targetAbis and/or
abiFilters usages accordingly).

Copy link
Copy Markdown
Contributor

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

6 files reviewed, 7 comments

Edit Code Review Agent Settings | Greptile

// Flatten the directory structure
relativePath = RelativePath(true, name)
} else {
exclude()
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

exclude() is not valid inside eachFile. To skip files, set relativePath to null or omit the file

Suggested change
exclude()
exclude()
Prompt To Fix With AI
This is a comment left during a code review.
Path: sdk/runanywhere-kotlin/build-logic/src/main/kotlin/NativeLibraryDownloadPlugin.kt
Line: 103

Comment:
`exclude()` is not valid inside `eachFile`. To skip files, set `relativePath` to `null` or omit the file

```suggestion
                    exclude()
```

How can I resolve this? If you propose a fix, please make it concise.

Comment on lines +55 to +56
// Grab the first sequence of non-whitespace characters (the hash itself)
val expectedHash = URL(shaUrl).readText().trim().split("\\s+".toRegex()).first()
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No error handling for the SHA256 file download. As noted in the PR description, this will throw FileNotFoundException when .sha256 files don't exist in releases. Wrap in try-catch and provide a clear error message about missing checksums

Suggested change
// Grab the first sequence of non-whitespace characters (the hash itself)
val expectedHash = URL(shaUrl).readText().trim().split("\\s+".toRegex()).first()
// Grab the first sequence of non-whitespace characters (the hash itself)
val expectedHash = try {
URL(shaUrl).readText().trim().split("\\s+".toRegex()).first()
} catch (e: Exception) {
throw IllegalStateException("Failed to fetch SHA256 checksum from $shaUrl. Ensure release assets include .sha256 files.", e)
}
Prompt To Fix With AI
This is a comment left during a code review.
Path: sdk/runanywhere-kotlin/build-logic/src/main/kotlin/NativeLibraryDownloadPlugin.kt
Line: 55-56

Comment:
No error handling for the SHA256 file download. As noted in the PR description, this will throw `FileNotFoundException` when `.sha256` files don't exist in releases. Wrap in try-catch and provide a clear error message about missing checksums

```suggestion
        // Grab the first sequence of non-whitespace characters (the hash itself)
        val expectedHash = try {
            URL(shaUrl).readText().trim().split("\\s+".toRegex()).first()
        } catch (e: Exception) {
            throw IllegalStateException("Failed to fetch SHA256 checksum from $shaUrl. Ensure release assets include .sha256 files.", e)
        }
```

How can I resolve this? If you propose a fix, please make it concise.

logger.lifecycle("Fetching expected SHA256 from $shaUrl...")

// Grab the first sequence of non-whitespace characters (the hash itself)
val expectedHash = URL(shaUrl).readText().trim().split("\\s+".toRegex()).first()
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SHA256 URL fetch has no timeout configured. If the checksum file fetch hangs, the entire build will stall indefinitely. Set connection and read timeouts similar to lines 64-65

Prompt To Fix With AI
This is a comment left during a code review.
Path: sdk/runanywhere-kotlin/build-logic/src/main/kotlin/NativeLibraryDownloadPlugin.kt
Line: 56

Comment:
SHA256 URL fetch has no timeout configured. If the checksum file fetch hangs, the entire build will stall indefinitely. Set connection and read timeouts similar to lines 64-65

How can I resolve this? If you propose a fix, please make it concise.

Comment on lines +85 to +86
check(calculatedHash.equals(expectedHash, ignoreCase = true)) {
tempFile.delete() // Nuke the bad file
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Temp file cleanup happens inside the error message construction, which only executes if the check fails. If verification succeeds but extraction fails later, tempFile won't be deleted until line 109. Consider using try-finally or tempFile.deleteOnExit() for guaranteed cleanup

Prompt To Fix With AI
This is a comment left during a code review.
Path: sdk/runanywhere-kotlin/build-logic/src/main/kotlin/NativeLibraryDownloadPlugin.kt
Line: 85-86

Comment:
Temp file cleanup happens inside the error message construction, which only executes if the check fails. If verification succeeds but extraction fails later, `tempFile` won't be deleted until line 109. Consider using try-finally or `tempFile.deleteOnExit()` for guaranteed cleanup

How can I resolve this? If you propose a fix, please make it concise.

Comment on lines +97 to +105
include("**/*.so")
eachFile {
if (validFiles.contains(name)) {
// Flatten the directory structure
relativePath = RelativePath(true, name)
} else {
exclude()
}
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

File filtering logic will include all .so files first (line 97), then try to filter in eachFile. Files not in validFiles list will still be included because exclude() inside eachFile doesn't work as expected. Use include with explicit patterns or filter differently

Suggested change
include("**/*.so")
eachFile {
if (validFiles.contains(name)) {
// Flatten the directory structure
relativePath = RelativePath(true, name)
} else {
exclude()
}
}
include { element ->
element.isDirectory || (element.name.endsWith(".so") && validFiles.contains(element.name))
}
eachFile {
// Flatten the directory structure
relativePath = RelativePath(true, name)
}
Prompt To Fix With AI
This is a comment left during a code review.
Path: sdk/runanywhere-kotlin/build-logic/src/main/kotlin/NativeLibraryDownloadPlugin.kt
Line: 97-105

Comment:
File filtering logic will include all `.so` files first (line 97), then try to filter in `eachFile`. Files not in `validFiles` list will still be included because `exclude()` inside `eachFile` doesn't work as expected. Use `include` with explicit patterns or filter differently

```suggestion
            include { element ->
                element.isDirectory || (element.name.endsWith(".so") && validFiles.contains(element.name))
            }
            eachFile {
                // Flatten the directory structure
                relativePath = RelativePath(true, name)
            }
```

How can I resolve this? If you propose a fix, please make it concise.


// Download LlamaCPP backend libs from GitHub releases (testLocal=false)
val releaseBaseUrl = "https://github.com/RunanywhereAI/runanywhere-sdks/releases/download/v$nativeLibVersion"
val targetAbis = listOf("arm64-v8a", "armeabi-v7a", "x86_64")
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

targetAbis includes "armeabi-v7a" but the ndk.abiFilters on line 127 only includes "arm64-v8a" and "x86_64". This mismatch means the armeabi-v7a libraries will be downloaded but never packaged into the AAR

Prompt To Fix With AI
This is a comment left during a code review.
Path: sdk/runanywhere-kotlin/modules/runanywhere-core-llamacpp/build.gradle.kts
Line: 163

Comment:
`targetAbis` includes `"armeabi-v7a"` but the `ndk.abiFilters` on line 127 only includes `"arm64-v8a"` and `"x86_64"`. This mismatch means the `armeabi-v7a` libraries will be downloaded but never packaged into the AAR

How can I resolve this? If you propose a fix, please make it concise.


// Download ONNX backend libs from GitHub releases (testLocal=false)
val releaseBaseUrl = "https://github.com/RunanywhereAI/runanywhere-sdks/releases/download/v$nativeLibVersion"
val targetAbis = listOf("arm64-v8a", "armeabi-v7a", "x86_64")
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

targetAbis includes "armeabi-v7a" but the ndk.abiFilters on line 134 only includes "arm64-v8a" and "x86_64". This mismatch means the armeabi-v7a libraries will be downloaded but never packaged into the AAR

Prompt To Fix With AI
This is a comment left during a code review.
Path: sdk/runanywhere-kotlin/modules/runanywhere-core-onnx/build.gradle.kts
Line: 171

Comment:
`targetAbis` includes `"armeabi-v7a"` but the `ndk.abiFilters` on line 134 only includes `"arm64-v8a"` and `"x86_64"`. This mismatch means the `armeabi-v7a` libraries will be downloaded but never packaged into the AAR

How can I resolve this? If you propose a fix, please make it concise.

…wnloader

Addresses review feedback from CodeRabbit and Greptile to ensure the native
library download pipeline is fully secure, reliable, and efficient.

Key fixes:
- Added HttpURLConnection timeouts (30s) to the SHA256 checksum fetch to prevent CI hangs.
- Added strict HTTP response code validation (200-299) for both checksum and binary downloads, providing clear error messages on 404s.
- Fixed a potential resource leak by wrapping archive extraction in a try/finally block to guarantee temp zip deletion.
- Corrected archive file filtering by using dynamic `include()` rules instead of the buggy `exclude()` inside Gradle's `eachFile` block.
- Removed unused `armeabi-v7a` from `targetAbis` in both llamacpp and onnx modules to match `ndk.abiFilters`, saving ~60MB of wasted downloads per CI run.
@Siddhesh2377
Copy link
Copy Markdown
Collaborator

@sanchitmonga22 please have a look

What it does: Replaces the old insecure native library downloader with one that verifies SHA256 checksums so nobody can tamper with the downloaded files.

The issue: The .sha256 files don't exist on GitHub Releases yet, so it'll fail on first run. The release pipeline needs to be updated to generate them first.

@Abhinav-kodes
Copy link
Copy Markdown
Author

Hey @sanchitmonga22, just a gentle ping on this! It's been a few days since the last review round. All the critical issues flagged by CodeRabbit and Greptile have been fixed in the latest commit.The only blocker now is on the release pipeline side: the .sha256 files need to be generated and uploaded to the GitHub Release assets before this can run end-to-end. Once that's in place, this PR is ready to merge.Let me know if there's anything else needed from my side!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add timeout and SHA256 checksum verification for native library downloads

2 participants