DevTool is an offline-first desktop app. This document states what the app is allowed to do, what it never does, how to report a vulnerability, and — most importantly — how you can verify all of it yourself instead of trusting this page.
Short version: every release binary is built in public by GitHub Actions from a tagged commit, signed, checksummed, and carries a cryptographic build provenance attestation. Nothing is built on a maintainer's laptop.
Please do not open a public issue for a security bug.
- Use GitHub's private reporting: Security → Report a vulnerability on https://github.com/DianaSensei/developer-desktop-utils/security/advisories/new
- Include: version, OS, reproduction steps, and impact.
- Expect an acknowledgement within 7 days and a fix or mitigation plan within 30 days for confirmed issues.
Supported versions: the latest released version only.
What DevTool is: a local toolbox. Data you paste in is processed in your own process, on your own machine.
| Claim | Enforced by | How you check it |
|---|---|---|
| No telemetry, no analytics, no crash reporting | There is no such code and no such dependency | grep -ri "analytics|telemetry|sentry|posthog|mixpanel|amplitude" src/ src-tauri/src/ → the only hit is the UI copy in src/lib/i18n.ts that says so |
| No account, no login, no server owned by us | No auth code; the only fixed endpoint is the GitHub release feed | grep -rn "endpoints" src-tauri/tauri.conf.json |
| Data stays local | Local files + OS keychain-free store plugin, written under the OS app-data dir |
fs:scope-appdata-recursive in src-tauri/capabilities/default.json |
| Network calls only happen when you press a button | The tools that reach the network are marked 🌐 in the README; none run on startup | Watch it: network verification |
| Updates cannot be spoofed | Updater artifacts are minisign-signed; the public key is compiled into the app | plugins.updater.pubkey in src-tauri/tauri.conf.json; the private key exists only as a GitHub Actions secret |
What DevTool is not: a sandbox for hostile input, and not a security boundary between you and content you choose to load. See Known scopes and honest limitations.
- Reproducible origin. Releases are produced only by
.github/workflows/release.yml, triggered by av*tag push, running on GitHub-hosted runners. No maintainer machine touches the artifacts. - Build provenance attestation (SLSA-style, signed via Sigstore) is generated for every published artifact, binding it to the exact repo, commit, workflow and runner that produced it.
- SHA256SUMS are published with each release, per platform.
- SBOM (SPDX) is published with each release, covering both the npm and the Cargo dependency trees.
- Every GitHub Action is pinned to a commit SHA, not a mutable tag. A tag
like
@v4can be repointed by whoever controls the action's repository — as happened totj-actions/changed-filesin March 2025 — which would run their code inside the release job, next to the updater signing key. Pinned SHAs cannot be swapped; Dependabot proposes upgrades as reviewable PRs. - Updater signing uses a minisign keypair; the private half lives only in
secrets.TAURI_SIGNING_PRIVATE_KEY. The app refuses an update whose signature does not verify against the embedded public key.
.github/workflows/security.yml runs on every
push to main, every pull request, and weekly:
| Check | Tool | Covers |
|---|---|---|
| Known-vulnerable npm packages | npm audit |
frontend dependency tree |
| Known-vulnerable Rust crates | cargo audit (RustSec) |
backend dependency tree; unmaintained/unsound notices are reported, vulnerabilities block |
| Code vulnerabilities (taint analysis) | GitHub CodeQL (default setup, configured in repo settings) | TypeScript/JavaScript, Rust, Actions |
| Leaked secrets/keys in history | TruffleHog (verified secrets only) | whole repo, full history on schedule |
| Risky new dependencies in a PR | dependency-review-action |
the PR diff |
Dependency updates arrive as reviewable PRs via
.github/dependabot.yml (npm, Cargo, GitHub Actions).
Both audits are currently clean: npm audit reports zero advisories, and
cargo audit reports zero vulnerabilities. What it does report is 19
warnings — crates flagged unmaintained or unsound that are pulled in
transitively and cannot be upgraded away from here:
| Crates | Advisory kind | Reached through | Why it stays |
|---|---|---|---|
atk, gdk*, gtk*, glib, proc-macro-error |
unmaintained (the gtk-rs GTK3 bindings), plus glib 0.18 unsound iterator (RUSTSEC-2024-0429) |
tauri → wry/webkit2gtk, Linux only |
Tauri 2 targets GTK3; the fixed glib is 0.20+, which the GTK3 bindings do not use. Goes away when Tauri moves to GTK4 |
smartstring |
unmaintained (RUSTSEC-2026-0249) | rhai (Transform tool scripting) |
A non-optional dependency of rhai 1.x |
unic-* |
unmaintained (RUSTSEC-2025-0075/0080/0081/0098/0100) | tauri-plugin-http/tauri-utils → urlpattern 0.3 |
Pinned by Tauri; build-time Unicode tables only |
paste |
unmaintained (RUSTSEC-2024-0436) | netstat2 → netlink-packet-utils |
A proc-macro used at compile time only |
None of these is a known exploitable vulnerability — they are "nobody is shipping fixes for this crate any more" notices, on code we do not call directly. They are reported in every run rather than suppressed, so a real vulnerability landing in one of them is not silently inherited. Reproduce the list yourself:
cd src-tauri && cargo audit # exits 0: 0 vulnerabilities, 19 warnings
npm audit # 0 advisories- Content Security Policy (
app.security.cspinsrc-tauri/tauri.conf.json):default-src 'self',object-src 'none',frame-ancestors 'none',base-uri 'self'. No remote script can be injected into the app shell. - Tauri 2 capability allowlist
(
src-tauri/capabilities/default.json): the webview can only call the permissions listed there. Anything absent — arbitrary shell execution, for instance — is not reachable from the frontend at all. Note there is noshellplugin and noshell:allow-executein this app. - Filesystem scope is limited to the app-data directory
(
fs:scope-appdata-recursive) plus files you explicitly pick through the OS open/save dialog, or drag onto the window. Two commands take a path directly rather than going through thefsplugin —read_file_data_urlandhash_file, which exist because a drop gives the frontend a path and not a readable File. Both check the path against the paths Rust saw in the native drag event (src-tauri/src/dropped.rs), so the frontend cannot name a file you did not drag in. - Memory-safe backend. The Rust side has no
unsafeblocks in app code (grep -rn "unsafe" src-tauri/src/).
Every release artifact carries a Sigstore-backed provenance attestation. With the GitHub CLI:
gh attestation verify ./DevTool_1.0.0_aarch64.dmg \
--repo DianaSensei/developer-desktop-utilsA pass means: this exact file was produced by this repo's release workflow, from a specific commit — not repacked, not rebuilt by a third party, not modified after the fact. A fail means do not run it.
Then check the hash against the published SHA256SUMS-*.txt:
shasum -a 256 -c SHA256SUMS-macos-latest-aarch64-apple-darwin.txt # macOS / Linux
certutil -hashfile DevTool_1.0.0_x64-setup.exe SHA256 # WindowsOr run the bundled helper, which does both:
./scripts/verify-release.sh v1.0.0The privileged surface is exactly the Rust commands and the capability file:
grep -rn "#\[tauri::command\]" src-tauri/src/ # 92 callable backend functions, all listed here
cat src-tauri/capabilities/default.json # every permission the UI holdsThere is no dynamic code loading in the Rust backend and no plugin system that pulls code from the network.
git clone https://github.com/DianaSensei/developer-desktop-utils
cd developer-desktop-utils && git checkout v1.0.0
npm ci && npm run tauri:buildByte-for-byte reproducibility is not claimed (Rust/Tauri bundling embeds timestamps and paths), but a self-built binary is the strongest option if you do not want to trust our CI at all — and it is the same source the attestation points to.
Run the app with a proxy or packet capture and confirm it is idle until you ask for something:
# macOS/Linux
sudo tcpdump -i any -n 'tcp and not port 22'
# or point it at mitmproxy / Proxyman / Fiddler and inspect every requestExpected traffic: nothing at rest, except an update check against
github.com if auto-update is enabled (Settings → Updates → off to stop even
that). Everything else only fires from a tool you invoked.
- OpenSSF Scorecard grades this repo automatically every week — the badge in the README links to the full breakdown at https://scorecard.dev/viewer/?uri=github.com/DianaSensei/developer-desktop-utils. It is scored by the OpenSSF, not by us.
- Snyk's public report for this repository: https://snyk.io/test/github/DianaSensei/developer-desktop-utils
- Actions runs are public: https://github.com/DianaSensei/developer-desktop-utils/actions
- The security workflow's results are visible in the Actions log and in the repository's Security tab (CodeQL alerts).
We would rather write these down than have you find them.
- The HTTP capability is broad.
src-tauri/capabilities/default.jsonallowshttp://**andhttps://**. This is inherent to the API Client tool: an HTTP workbench must be able to call any host you type. It means the webview can issue arbitrary outbound HTTP requests. The mitigation is the CSP (no remote code can get into the webview to abuse it) plus the fact that requests are user-initiated. - API Client scripts still execute — the sandbox bounds them, it does not
vet them. Pre/post-request scripts,
varsexpressions and assertions are real JavaScript, built with theAsyncFunctionconstructor. They run inside a dedicated Web Worker (src/components/tools/apiclient/scriptSandbox.worker.ts, driven byscriptHost.ts), which buys three things: a runaway script can be killed by terminating the worker instead of freezing the app; a script has nowindow, nodocument, nolocalStorage, and no__TAURI_INTERNALS__, so it cannot invoke a Tauri command or read a local file; and if the worker cannot start, phases are refused rather than quietly re-run on the main thread (ScriptSandboxUnavailableError— the UI says so, and it re-probes). What a script does get is thebru/req/res/pmsurface, the curatedrequire()list inmodules.ts, and — in a browser build without the app's CSP — workerfetch. So the trust model is still Postman's and Bruno's: the scripts are your scripts. Do not import a collection containing scripts you have not read — treat a.bru/Postman collection from a stranger like an executable. Importing a collection that carries scripts stops on a consent screen that lists every script and defaults to import without scripts (ImportReviewDialog.tsx); read that screen rather than clicking through it. Verify the worker boundary yourself:grep -n "new Worker" src/components/tools/apiclient/scriptHost.ts. - Most of the Encrypt tab is interop, not protection.
AES-256 GCMis the default and the real one: PBKDF2-HMAC-SHA256 at 600k iterations into AES-256-GCM, through WebCrypto (src/lib/aesGcm.ts), with a fresh salt and IV per message and a tag that catches tampering. Every other mode in that list goes throughcrypto-json purpose — they exist so you can read what acrypto-jscaller wrote — andcrypto-jsstretches a passphrase with OpenSSL's EvpKDF: MD5, one iteration, and authenticates nothing. The UI says so on every one of them. Do not protect anything real with those modes. - macOS builds are ad-hoc signed, not notarized.
signingIdentity: "-"— we do not yet have an Apple Developer certificate, which is why Gatekeeper complains andxattr -cris needed. Verify the attestation (step 1) before you clear the quarantine flag. Windows builds are likewise not Authenticode-signed, so SmartScreen may warn. - Third-party services you point tools at (DNS-over-HTTPS resolvers, IP geolocation, your Kafka/RabbitMQ/Redis/Docker endpoints) see the queries you send them. That is the tool doing its job, not the app phoning home.
- Credentials you enter (broker passwords, API tokens) are stored by the
Tauri
storeplugin as files under the OS app-data directory, not in an OS keychain, and not encrypted at rest. Anyone with your user account can read them. Treat that like a.envfile.
MIT — see LICENSE. The code is fully readable; nothing is minified, obfuscated, or shipped as a prebuilt blob.