Native desktop client for the public JLab static JAR scanner. Drop a .jar (or a .zip, .mcpack, or .mrpack that contains one), the app uploads the file to the JLab API and shows the matched signatures grouped by severity. The HTTP upload runs in Rust, so no file bytes leak through the JavaScript layer.
- Drag and drop a
.jar,.zip,.mcpack, or.mrpack. For container archives, the largest inner.jaris extracted and scanned. - Native multipart upload from Rust (
reqwest+rustls). No browser fetch on the hot path. - Strict CSP. The webview can only talk to the Rust side. No outbound network from JavaScript.
- Severity-grouped signature view (
critical,high,medium,low,info) with file metadata, family tags, and a copy-friendly card layout. - Inline error banner with a live
Retry-Aftercountdown for rate limits (HTTP 429). - Local size validation (50 MB) and zip-magic check before any network call.
- Cancellable scans, phase-aware progress UI with a live event log.
- Local scan history (last 100 scans, summary only). Stored on your device, never uploaded. File bytes and signature payloads are not persisted. Each row also shows the folder the file was scanned from.
- Optional folder watcher (opt-in): auto-scans new
.jarfiles in folders you choose, with quarantine, system-tray, autostart, and configurable alert thresholds. See Folder watcher below. - In-app notification history. A bell icon in the top bar lists every native toast the watcher fires, so events the OS dropped or suppressed (Do Not Disturb, tray-minimized, AFK) still surface inside the app. Stored locally (last 100 entries, summary only), with mark-read and clear actions.
- Small (well under 10 MB), starts fast, no analytics, no auth. The header status indicator pings
jlab.threat.rip/api/public/heartbeatonce on launch and then once a minute while the window is visible. Each scan also fetchesjlab.threat.rip/api/public/threat-intel/<sha256>to enrich the report. See SECURITY.md for the full list of outbound endpoints.
Pre-built installers are published on the GitHub Releases page on every push to main.
- macOS (universal, Apple Silicon and Intel):
JLab.Desktop_x.y.z_universal.dmg - Windows (setup, recommended for personal use):
JLab.Desktop_x.y.z_x64-setup.exe - Windows (MSI, for IT-managed deployments):
JLab.Desktop_x.y.z_x64_en-US.msi - Linux (Debian / Ubuntu):
JLab.Desktop_x.y.z_amd64.deb - Linux (Fedora / RHEL / openSUSE):
JLab.Desktop-x.y.z-1.x86_64.rpm - Linux (universal):
JLab.Desktop_x.y.z_amd64.AppImage
On Windows the .exe setup is the easy path: per-user install, no admin prompt, normal wizard. Pick the .msi only if your IT department deploys it via Group Policy, Intune, or SCCM.
The current builds are not yet signed with an Apple Developer ID. macOS Gatekeeper will warn that the app is from an unidentified developer or that the app is "damaged". This is expected for an unsigned binary downloaded from the internet.
Use one of these to allow the app:
-
Right-click the app in
Applications, chooseOpen, then confirm the dialog. macOS remembers the choice. -
Or remove the quarantine attribute from a terminal:
xattr -dr com.apple.quarantine "/Applications/JLab Desktop.app"
Signed builds will land in a later release.
The current builds are not signed with a code-signing certificate. Windows SmartScreen will show "Windows protected your PC". Click More info, then Run anyway. Windows remembers the choice for that file.
Signed builds will land in a later release.
The Linux bundles are built on Ubuntu 24.04 against webkit2gtk-4.1 and target x86_64. Pick the format that matches your distro:
-
Debian / Ubuntu (and derivatives like Mint, Pop!_OS):
sudo apt install ./JLab.Desktop_x.y.z_amd64.deb
-
Fedora / RHEL / openSUSE:
sudo dnf install ./JLab.Desktop-x.y.z-1.x86_64.rpm # or, on openSUSE: sudo zypper install ./JLab.Desktop-x.y.z-1.x86_64.rpm -
Anything else (Arch, NixOS, immutable distros, etc.) via AppImage:
chmod +x JLab.Desktop_x.y.z_amd64.AppImage ./JLab.Desktop_x.y.z_amd64.AppImage
AppImage needs FUSE on the host. On Ubuntu 24.04 install
libfuse2t64; on older distros installlibfuse2. On systems without FUSE you can extract and run instead:./JLab.Desktop_x.y.z_amd64.AppImage --appimage-extract-and-run
The Linux builds are not signed. There is no Linux-equivalent of Gatekeeper or SmartScreen, so installs proceed normally, but verify the SHA-256 against the GitHub Release page if you want a signature-style check.
Updates are manual. The app checks the GitHub releases API once on startup and, if a newer version is available, shows a small "Update to vX" button in the header that opens the release page in your browser. Nothing is downloaded or installed automatically. To update, grab the new installer from the Releases page and run it. You can dismiss the update notice; it stays hidden until the next release.
- You pick a file (or drop one on the window).
- The Rust side validates the extension and reads the first bytes to confirm the file is really a zip archive.
- For container archives (
.zip,.mcpack,.mrpack), the largest inner.jarby uncompressed size is extracted in memory. Inner jars over 50 MB are rejected up front, which guards against zip bombs. - Rust uploads the jar to
https://jlab.threat.rip/api/public/static-scanviamultipart/form-data. No file bytes cross the IPC boundary into the webview. - The frontend renders the response, grouped by severity.
The desktop client keeps the JavaScript side from making network calls. The Content Security Policy is connect-src ipc: http://ipc.localhost. Both sources are Tauri 2's IPC handler, neither is a public network egress, so any future fetch() to an external URL would fail at runtime.
The folder watcher is an opt-in subsystem that auto-scans new .jar files (or supported containers) that appear in folders you choose. It is off by default and explicitly is not an antivirus: no driver, no kernel hooks, no on-access blocking. It is a plain user-space filesystem subscriber that reuses the same scan_jar HTTP pipeline as the manual scan.
- Subscribes to OS filesystem events (
FSEventson macOS,ReadDirectoryChangesWon Windows,inotifyon Linux) via thenotifycrate, with a 500 ms debounce. - Snapshots a baseline on watch start. Files that already exist when watching starts are ignored unless you click "scan all now" on a folder.
- Queues qualifying files through a token-bucket rate limiter capped at 12 uploads per minute (the public API allows 15 / min / IP).
- Coalesces native notifications inside a 4 s window so multiple hits produce one toast, not a flood.
- Persists settings atomically as
watcher-settings.jsonnext tohistory.json.
Open the panel from the "folder watcher" card on the idle dashboard.
- Notifications (on by default): native OS toast when a scan crosses the alert threshold.
- Alert threshold:
1 critical,multiple criticals, orfamilies. The multi-critical count is configurable from 2 to 4. - Auto-action (default
quarantineatmultiple criticals): when the threshold is met, the file is either moved to the in-app quarantine folder or sent to the OS trash. Both are recoverable; quarantine keeps the file out of the user's recycle bin. - Hold until scanned: rename
foo.jartofoo.jar.jlab-pendingwhile the scan is in flight so Java launchers cannot load it. Restored when the scan clears. - Rescan after: re-upload files in watched folders on a schedule (off / 7 / 14 / 30 days).
- Minimize to tray, start minimized, launch at login: optional desktop integration.
- Reset: wipes all watcher settings (including watched folders) and disables autostart.
| Platform | Settings + history | Quarantine |
|---|---|---|
| macOS | ~/Library/Application Support/JLab/ |
~/Library/Application Support/JLab/quarantine/ |
| Windows | %APPDATA%\JLab\ |
%APPDATA%\JLab\quarantine\ |
| Linux | $XDG_DATA_HOME/JLab/ or ~/.local/share/JLab/ |
same dir + /quarantine/ |
Quarantined files are renamed <unix-timestamp>-<original-name>.quarantined. The .quarantined suffix prevents Java launchers from loading them and gives any local AV a clear hint that the file is held intentionally. To restore, rename back to the original extension or move the file out of the folder.
You need:
- Node.js 20 or newer
- Rust 1.85 or newer (the project pins
rust-version = "1.85") - The Tauri 2 platform prerequisites for your OS: https://tauri.app/start/prerequisites/
Install and run:
npm install
npm run tauri devUseful commands:
npm run check # TypeScript type-check
cargo check --manifest-path src-tauri/Cargo.toml # Rust type-check
cargo fmt --manifest-path src-tauri/Cargo.toml # format Rust
cargo clippy --manifest-path src-tauri/Cargo.toml -- -D warnings
npm run tauri build # release bundleThe release bundle lands in src-tauri/target/release/bundle/.
The repo ships with the icons referenced in src-tauri/tauri.conf.json. To regenerate them from a 1024 x 1024 PNG:
npm run tauri icon path/to/source.pngThis populates src-tauri/icons/ with all required sizes and formats (.png, .icns, .ico).
The app talks to a single endpoint:
POST https://jlab.threat.rip/api/public/static-scan
Content-Type: multipart/form-data
Field: file (max 50 MB, .jar archive)
Rate limit: 15 requests / minute / IP
For .zip, .mcpack, and .mrpack drops, the desktop client opens the archive locally, picks the largest inner .jar, and uploads only that file. The endpoint itself only accepts .jar.
No authentication is required. See https://jlab.threat.rip/api-docs.html for the full schema.
Pull requests welcome. See CONTRIBUTING.md for the branch model, commit style, and how releases are cut. The short version: feature work lands on dev, releases ship from main.
If you find a security issue, please report it privately. See SECURITY.md for the disclosure process. Do not file public issues for vulnerabilities.
Licensed under either of
- Apache License, Version 2.0 (LICENSE-APACHE)
- MIT license (LICENSE-MIT)
at your option.
Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in this work, as defined in the Apache-2.0 license, shall be dual-licensed as above, without any additional terms or conditions.