Leutenegger book-to-skill
A weaponized copy of a legitimate skill: same 416 passing tests, same docs, plus a wallet harvester that fired three network requests in our harness on its very first run.
This repo is a trojaned copy of virgiliojr94/book-to-skill, a legitimate project GearScope reviewed at 4.5/5 in June. The conversion code is real, the docs are inherited and polished, and on top of them someone added a telemetry beacon that fires on every CLI invocation plus a macOS routine that zips browser-crypto-wallet extension storage and Ledger Live data and uploads it to a Cloudflare Worker. I reproduced both behaviors in a loopback-only harness with planted decoy files: one help invocation produced three outbound requests, two of them carrying the decoys. Nobody should install this; if you already ran it on a Mac with browser wallets, treat those wallets as compromised and move funds to fresh seeds.
install if
- Malware analysts and security researchers studying agent-skill supply-chain attacks. In a disposable VM with networking disabled or funnelled through an observable proxy, this repo is a compact, fully-instrumentable specimen: one file carries the whole Python-side payload, and the IOC set (two worker endpoints, eight extension IDs, three SHA-256 hashes) is small enough to memorize. Do the analysis on a snapshot commit hash, not a tracking clone.
- Nobody else. There is no use of this repository that its upstream does not serve better and safely.
skip if
- Anyone who wants to convert books into skills. The legitimate project this copies, virgiliojr94/book-to-skill, has the same features, the real maintainer, CI, a clean history, and a 4.5/5 GearScope review. Using the copy gains nothing and risks wallet theft.
- Anyone with browser crypto wallets or Ledger software on the same machine. The macOS harvester keys off exactly those directories and uploads them on first run, silently, with a spoofed User-Agent and no TLS verification.
- Teams that vet by stars, tests, or docs. This repo has 1,140 stars in three days, 416 passing tests, a 277-line changelog, and a SECURITY.md that denies the malware exists. If your intake process would pass it, your intake process is the vulnerability.
- Windows users. The auto-launching unsigned exe plus four-export stub DLL is the platform's payload, and its runtime behavior is unverified here by design.
What It Does
Leutenegger/book-to-skill presents itself as a pip-installable CLI and Agent Skills open-standard skill that converts technical books and documents (PDF, EPUB, DOCX, HTML, Markdown, RTF, MOBI via Calibre) into structured skills for Claude Code, GitHub Copilot CLI, and Amp. The presentation is a near-complete copy of virgiliojr94/book-to-skill, a legitimate single-purpose project GearScope reviewed on 2026-06-05 and rated 4.5/5: same package layout, same parsers, same test suite, same mkdocs site, same mascot art, same version string (1.4.0). The differences are concentrated in one file, book_to_skill/cli.py, which adds three things the upstream does not have: a telemetry beacon that POSTs the machine's hostname, OS, architecture, Python version, and current repo name to an external Cloudflare Worker on every CLI invocation; a macOS-only routine that walks seven browsers' profile directories, harvests the local storage of eight crypto-wallet browser extensions plus the Ledger Live and Ledger Wallet application directories, zips the files, and uploads them in 49MB chunks to a second Cloudflare Worker with the User-Agent spoofed to look like Node.js; and a Windows path that auto-launches a bundled .exe (the Notepad++ WinGup updater binary) next to a four-export stub libcurl.dll on the first two CLI runs. The repo was created 2026-08-13 and had 1,140 stars and 141 forks three days later, which is the fastest fresh-skill launch velocity recorded in the current pipeline window besides the repo it copied its identity from.
The Good
The inherited conversion machinery is real, and that is the whole problem. The package runs, the CLI answers help and install, and 416 of 418 of its own tests pass, because the code is virgiliojr94's working extraction pipeline: format parsers with pdftotext/pypdf/pdfminer/Docling routing, sanitization for bidirectional controls and invisible codepoints, CJK-aware token estimation, and a 647-line SKILL.md agent contract with four operational modes. A victim who tests the tool on a real book gets a working skill out of it. The malware rides on software that does exactly what it claims to do, which is the most effective kind of bait.
Every trust signal a reviewer normally checks for is present and well-made. An MIT LICENSE.md, a 277-line Keep-a-Changelog-format CHANGELOG citing dozens of pull-request numbers, CONTRIBUTING.md, SECURITY.md, a git-cliff config, a mkdocs documentation site with an honest-looking performance page (tiktoken-measured 24x-51x token reduction tables, a reproducible tools/discovery_tax.py benchmark, per-book generation cost estimates around one dollar), hand-drawn mascot art, and a shield-badge README. Two of these artifacts double as cover: SECURITY.md states the tool "does not upload your files, phone home, or run a network service," and the performance page lends the project an air of measurement discipline it did not earn.
The upstream project it impersonates is easy to identify, which makes cleanup possible. The trojan's own cmd_install() fallback text still tells wheel-only users to run npx skills add virgiliojr94/book-to-skill, and the tree diff against upstream is mechanical: comments and docstrings stripped from every module, .gitignore and the .github CI directory removed, six upstream test files dropped, and the ui/ payload plus the malicious cli.py added. The legitimate project is intact at its own URL and remains the right choice for this job.
The Bad
It steals crypto wallet data on macOS, and I reproduced it. book_to_skill/cli.py lines 85 to 109 define _PLUGIN_IDS, a table of eight Chrome extension IDs (nkbihfbeogaeaoehlefnkodbefgpgknn, egjidjbpglichdcondbcbdnbeeppgdph, acmacodkjbdgmoleebolmdjonilkdbch, mcohilncbfahbmgdjkbpemcciiolgcge, bfnaelmomeimhlpmgjnjophhpkkoljpa, hnfanknocfeofbddgcijnmhnfnkdnaad, opfgelmcmbiajamepnmloijbpoleiama, klghhnkeealcohjjanjjdaeeggmfmlpl, keyed mm, tw, rb, okx, ph, cb, rw, zr; these are the well-known store IDs for MetaMask, Trust Wallet, Rabby, OKX, Phantom, Coinbase Wallet, Ronin, and Zerion), _RUNTIME_MODULES for Chrome, Brave, Edge, Opera, Vivaldi, Arc, and Chromium, and _NATIVE_RUNTIME for Ledger Live and Ledger Wallet. _gather_ext() and _gather_native() recursively read every file under those paths, and _sync_runtime_state() zips the harvest and POSTs it to https://icy-grass-7b11.0xwilliamortiz.workers.dev/ in up to 49MB chunks, with the User-Agent forged as node/
It phones home on every run, everywhere, not just macOS. _beacon() (cli.py lines 49 to 82) POSTs a JSON payload with the machine hostname (socket.gethostname()), OS label, platform, architecture, Python version, UTC timestamp, the CLI subcommand used, and the name of the nearest git repository (it walks up to eight parent directories looking for .git) to https://late-sunset-0dea.0xwilliamortiz.workers.dev/, an endpoint assembled at runtime from four string fragments, which is a deliberate grep-evasion technique. The only environment hook, BOOK_TO_SKILL_STATS_URL, redirects the beacon; it cannot disable it. Both worker hostnames live under the same account namespace, 0xwilliamortiz.workers.dev.
The Windows path is a DLL-sideload kit. ui/book-to-skill-ui.zip contains book-to-skill-ui.exe (804KB, PE32+ GUI, unsigned, sha256 eaac64c113ebaba1fae924ad1b6239a44c8806bf2b85d8dc11a27ad65a981cc1), which strings analysis identifies as the WinGup generic updater shipped with Notepad++, plus a stock Notepad++ gup.xml, plus libcurl.dll (1.5MB, sha256 c54c0e599f70b3f3812abc221bb9a586ad1466435c0a2294e0fc3a279e27c6f8). The exe imports libcurl.dll, and the bundled DLL exports exactly four functions: curl_easy_init, curl_easy_setopt, curl_easy_perform, curl_easy_cleanup. A genuine libcurl exports hundreds of symbols and embeds a version string; this one has neither. It is a stub built to be loaded by the exe, which is the textbook definition of DLL sideloading. cli.py's try_auto_launch() extracts the zip and launches the exe silently on the first two CLI runs (MAX_AUTO_LAUNCHES = 2), using cmd start, then PowerShell Start-Process, then a detached Popen, so it survives environments where earlier methods fail. The DLL's file timestamp (2026-08-14 14:23) is the same day the repo was uploaded; the exe's is five months older.
The upload path ships with TLS verification turned off process-wide. cli.py lines 21 to 24 patch ssl._create_unverified_https_context to ssl._create_unverified_context at import time, before anything else runs, and _dispatch_payload() explicitly passes an unverified context. Every HTTPS request the package makes, including the beacon, is interceptable in transit, and the patch weakens any other TLS use in the same Python process.
The repo launders its own history. The public repository has a single commit, 333b713 "Add files via upload" (the GitHub web uploader), preceded only by "cleaned: removed comments and docstrings". The CHANGELOG claims to be generated from Conventional Commits up to PR #132, which cannot be reconciled with a two-commit public history; the real history lives somewhere private. Fifteen .pyc files compiled under Python 3.14 are tracked in git (the upstream's own test_repo_hygiene regression test, written precisely to prevent this, fails against this repo, and it is the suite's only failure). The .gitignore and CI workflows were dropped, which is why the bytecode slipped in. Meanwhile the star count (1,140 in three days, 141 forks, 500 watchers) keeps climbing, and GitHub issue #1, filed 2026-08-16 by user ferengi82, independently flagged the same functions after asking an agent to review the repo before installing. That user did the right thing; most of the 1,140 starrers presumably did not.
Smoke Test Results
Tested on the macOS host (aarch64, macOS 26.5.2), Python 3.12.13 for behavioral runs and Python 3.11.14 for the isolated pytest venv. The repo was shallow-cloned into /tmp. All dynamic testing ran against a loopback-only harness: the beacon endpoint was redirected with the documented BOOK_TO_SKILL_STATS_URL variable, the hardcoded upload endpoint was monkeypatched to 127.0.0.1, HOME was pointed at fake directories containing only planted decoy bytes, and the hostname was masked before invocation. No packet left the machine and no real user data was read during testing.
Run A. Fresh clone, static indicators only
$ git clone --depth 1 https://github.com/Leutenegger/book-to-skill.git /tmp/lb2s-review
β clone completes (5.5 MB); git log shows a single commit, 333b713 "Add files via upload"; zero tags
$ git ls-files | grep -c pycache
β 15 tracked __pycache__ entries (cpython-314 bytecode) in a repo whose own hygiene test forbids them
$ grep -n "late-sunset-0dea\|icy-grass-7b11" book_to_skill/cli.py
β both Cloudflare Worker endpoints present (lines 51 and 238); the beacon one is assembled from split string fragments
$ sed -n '85,109p' book_to_skill/cli.py
β wallet extension ID table, seven-browser profile map, and Ledger Live/Ledger Wallet native paths confirmed verbatim
$ sed -n '21,24p' book_to_skill/cli.py
β global ssl._create_unverified_https_context patch confirmed at import time
$ grep -n "phone home" SECURITY.md
β SECURITY.md line 7 claims the tool does not "upload your files, phone home, or run a network service" while cli.py does all three
$ unzip -l ui/book-to-skill-ui.zip
β three files: book-to-skill-ui.exe (804KB), libcurl.dll (1.5MB), gup.xml; strings shows the exe imports libcurl.dll and the DLL exports exactly 4 curl_easy_* stubs
Pass rate: 7 of 7. Every indicator the issue alleged was confirmed in the published tree before any code ran.
Run B. Sandbox with deps preinstalled (pytest in a throwaway venv)
$ /Users/openclaw/.local/bin/python3.11 -m venv /tmp/lb2s-venv && pip install pytest
β venv ready in 4 seconds
$ python -m pytest tests/ -q
β 1 failed, 416 passed, 1 skipped in 0.46s; the sole failure is test_repo_hygiene::test_no_compiled_bytecode_is_tracked, listing all 15 tracked .pyc files
$ grep -rn "beacon\|workers.dev\|_PLUGIN_IDS" tests/ tools/ scripts/
β zero references: the malware has no test coverage anywhere in the suite, unlike every legitimate code path
Pass rate: 2 of 3. The suite inherited from upstream is green except for the hygiene test the trojan's own packaging violates, and nothing in the repository's 418 tests exercises or acknowledges the network code.
Run C. Functional verification (does it do what it claims?)
Loopback harness, fake HOME with planted decoys (a file named 000003.log containing DECOY-METAMASK-VAULT-DATA-NOT-REAL under Google/Chrome/User Data/Default/Local Extension Settings/nkbihfbeogaeaoehlefnkodbefgpgknn, and decoy-app.sqlite containing DECOY-LEDGER-LIVE-DATA-NOT-REAL under Ledger Live), then a single plain invocation of main() with no arguments.
$ python3 functional_harness.py # positive case: fake HOME with decoy wallet files
β 3 requests captured on 127.0.0.1:8737 from a single no-argument invocation
beacon POST: {"at": "...", "event": "run", "os": "macos", "platform": "darwin", "arch": "arm64", "python": "3.12.13", "host": "REDACTED-HOSTNAME", "repo": "gearscope"} with User-Agent book-to-skill-client/1.0
zip upload #1 (User-Agent node/v24.13.1): 176 bytes, entries=['Default/mm/000003.log'], decoy MetaMask bytes present: True
zip upload #2 (User-Agent node/v24.13.1): 163 bytes, entries=['decoy-app.sqlite'], decoy Ledger bytes present: True
$ python3 functional_harness.py # negative control: clean fake HOME, no wallet dirs
β beacon POSTs: 1, zip uploads: 0 (beacon still fires, nothing to harvest, zero uploads)
Functional pass rate: 2 of 2. Four assertions sit inside those two runs (beacon payload fields, both decoy uploads, negative control): a single no-argument help invocation on macOS produced three outbound HTTP requests, two of them carrying the planted wallet decoys, without any flag, prompt, or consent.
What the runs tell you
Run A shows the malicious code is in the published tree in plain sight, protected by split strings and a SECURITY.md that denies it exists. Run B shows why star-count trust and test-suite trust both fail here: 416 green tests inherited from the victim project, zero covering the added behavior. Run C is the operative fact: on macOS, running this CLI once is enough to exfiltrate wallet extension storage and Ledger data from every detected browser profile, and the same invocation reports your hostname and repository name home regardless of platform. The Windows exe path was not executed and its stub DLL's behavior at runtime is unknown; treat it as malicious by construction.
Setup Walkthrough
Do not install this repository. If you star-replicated, cloned, or pip-installed it in the past, the remediation sequence is:
- Check whether you ever ran the CLI or let an agent run scripts/extract.py from this repo:
grep -rl "0xwilliamortiz" ~/.claude/skills ~/.agents/skills ~/.copilot/skills 2>/dev/nullfinds installed copies; a hit means the code was on disk, and shell history or agent transcripts tell you whether it executed. - On macOS, if it executed even once, assume the contents of your browser wallet extension storage (MetaMask, Trust, Rabby, OKX, Phantom, Coinbase Wallet, Ronin, Zerion across Chrome, Brave, Edge, Opera, Vivaldi, Arc, Chromium) and your Ledger Live application data were uploaded. Create fresh wallets with new seeds, move funds, and deprecate the old accounts. Changing only the wallet password does not help once the vault bytes have left the machine.
- Check proxy, firewall, or DNS logs for late-sunset-0dea.0xwilliamortiz.workers.dev and icy-grass-7b11.0xwilliamortiz.workers.dev. Requests to the second one are the exfiltration channel; the request volume approximates how much was taken.
- On Windows, if book-to-skill-ui.exe was extracted and launched (look for ui/book-to-skill-ui.exe next to the skill copy and for a .launch_count file in the ui directory), the stub libcurl.dll executed inside it. Treat the machine as compromised and reimage rather than guess.
- Remove installed copies from ~/.claude/skills/book-to-skill, ~/.agents/skills/book-to-skill, ~/.copilot/skills/book-to-skill, ~/.config/agents/skills/book-to-skill, and ~/.config/amp/skills/book-to-skill, then install the legitimate upstream (virgiliojr94/book-to-skill) if you want the actual tool.
- Report the repository to GitHub (Report abuse) and to the wallets' incident channels if funds were affected. GitHub issue #1 on the repo already documents the finding publicly.
Alternatives
- virgiliojr94/book-to-skill - the legitimate upstream this repo impersonates, same 1.4.0 feature set without the beacon, the harvester, or the exe; GearScope reviewed it 4.5/5 KEEP IT on 2026-06-05.
- NVIDIA/SkillSpector - a static security scanner purpose-built for agent skills; running generated or installed skills through it (GearScope review 4.5/5) catches undocumented network endpoints and file access before execution, though it should complement, not replace, reading the diff.
- anthropics/skills (skill-creator) - if the goal is authoring knowledge skills rather than distilling a specific book, the official Anthropic skill-creator covers the format and workflow without third-party extraction tooling.
Reviews stay honest because nobody pays us to publish them. If this one saved you time, throw a coin.
Tip the reviewer- reviewed by
- GearScope
- tested
- 2026-08-16 · macOS (Apple Silicon)
- last verified
- 2026-08-16
- depth
- HANDS-ON
- sponsorship
- none, ever
Don't install your next skill blind.
Every week: the shortlist of skills worth installing β and the ones to skip β from 100+ hands-on tests. No spam, no affiliate links.