Aephix accepted into the Databricks Startup Program
← Research
Threat report Sep 17, 2026

@guangnao/claude-cli: npm proxy harvests Claude credentials

@guangnao/claude-cli presents as a Claude API proxy but exfiltrates OAuth tokens to an XOR-obfuscated C2 hub and enrolls the victim host into a compute pool that consumes their Claude subscription.

The npm package @guangnao/claude-cli, published under the @guangnao scope (six packages total), ships a single 120 KB esbuild-bundled binary that presents as a multi-identity Claude API proxy. On first run it reads OAuth credentials from ~/.claude/.credentials.json and the macOS Keychain, opens a WebSocket to an XOR-obfuscated hub at hxxps://hub[.]client-llm[.]com, uploads the stolen tokens as a “backup,” and begins accepting work dispatched by the hub operator. The connection is enabled by default, and logging for it is deliberately suppressed. Eighteen versions were published in six days (Aug 1-6, 2026), with credential exfiltration and the C2 hub present from v1.0.0.

The artifact

@guangnao/claude-cli v1.0.17 was published on Aug 6, 2026 under the npm account guangnao ([email protected]). The scope contains five other packages: @guangnao/agent-cli, @guangnao/claude-hub, @guangnao/webapi, @guangnao/agent-os, and @guangnao/agent-ui, all published between Jul 18 and Aug 6, 2026. No repository URL is listed in the package metadata.

The package ships four files: bin/claude-cli.js (119,587 bytes), package.json, README.md (Chinese-language), and an MIT LICENSE crediting “claude-clv contributors.” The binary declares "type": "module" and bundles the ws WebSocket library inline. It requires Node 20.12+ and registers a claude-cli bin entry. The README describes the hub as an opt-in compute pool. It does not mention the hardcoded default hub, the credential backup upload, the suppressed logging, the auto-update mechanism, or the macOS Keychain access.

What it does

claude-cli start opens an HTTP server on 127[.]0[.]0[.]1:8787 exposing a /v1/messages endpoint compatible with the Anthropic API. Behind the proxy, the binary reads credentials from three sources. First, it reads ~/.claude/.credentials.json and extracts the claudeAiOauth object containing accessToken, refreshToken, and expiresAt. On macOS, if the file read fails, it falls back to security find-generic-password -s "Claude Code-credentials" -w to pull the same credentials from the system Keychain. Per-identity long-lived tokens are read from ~/.claude-cli/<id>/token files. The environment variable CLAUDE_CODE_OAUTH_TOKEN is also consumed if set.

The binary refreshes expired OAuth tokens by posting grant_type: "refresh_token" to hxxps://console[.]anthropic[.]com/v1/oauth/token with client ID 9d1c250a-e61b-44d9-88ed-5944d1962f5e. Refreshed tokens are cached with a 60-second expiry buffer.

In “proxy” mode, the binary forges Claude Code session headers directly against hxxps://api[.]anthropic[.]com/v1/messages. It populates x-stainless-* tracing headers, a x-claude-code-session-id, hardcoded version strings (2.1.220 for Claude CLI, 0.94.0 for SDK, v26.3.0 for runtime), and the beta header oauth-2025-04-20,claude-code-20250219. A parity command compares forged headers against a real claude -p subprocess to verify the impersonation is accurate.

The single obfuscated value in the entire binary is the C2 hub URL. A function named _deHub XOR-decodes a base64 string using key gnP2p!7xQ:

DxokQgMbGFc5Egx+URxIUhYlSgI8X15CWBU=

The result is hxxps://hub[.]client-llm[.]com. The function defaultHubEnabled() returns true unless the user has explicitly set defaultOff: true in ~/.claude-cli/hub.json. On startup, activeHubs() silently adds the decoded hub to the connection list with builtin: true. Connections to the builtin hub suppress all console logging (...s.builtin?{}:{log:...}).

The WebSocket opens to wss://hub[.]client-llm[.]com/node. The registration message sends the node ID (a SHA-256 of the machine’s hardware UUID, salted with claude-cli/node-id/v1), the hostname via os.hostname(), the local model catalog, the concurrency capacity, and the package version. Immediately after registration, the binary uploads a credential “backup” via HTTP PUT to /node/backup/{nodeId} with header x-node-token. The backup payload is a JSON object containing the full contents of ~/.claude/.credentials.json (or the Keychain equivalent) and a map of all per-identity tokens.

The hub dispatches work as {"t": "job", "id": "<jobId>", "body": "<request>"} messages. The node forwards each job to its local /v1/messages endpoint using the victim’s credentials, then streams the response back as head, chunk, and end frames. The hub can also push credentials down to the node via the restore command, which writes received tokens to ~/.claude/.credentials.json and per-identity token files.

Device fingerprinting uses OS-specific methods: ioreg -rd1 -c IOPlatformExpertDevice for the macOS IOPlatformUUID, reg query HKLM\SOFTWARE\Microsoft\Cryptography /v MachineGuid on Windows, and /etc/machine-id on Linux.

Auto-update runs every five minutes by default. The binary fetches hxxps://registry[.]npmjs[.]org/@guangnao%2fclaude-cli/latest and, if a newer version exists, runs npm i -g @guangnao/claude-cli@latest silently. The daemon then waits for in-flight requests to drain, restarts itself, and continues. The CLAUDE_CLI_AUTO_UPDATE environment variable must be explicitly set to "off" to disable this.

Victim host ~/.claude/.credentials.json macOS Keychain @guangnao/claude-cli XOR-obfuscated hub URL Auto-update (5 min) PUT /node/backup hub[.]client-llm[.]com C2 hub (WebSocket) job dispatch api[.]anthropic[.]com Victim subscription consumed restore Aephix
The proxy reads Claude Code credentials, uploads them to a silent default C2, then accepts work that consumes the victim's subscription.

The campaign

The @guangnao scope operates six packages. @guangnao/claude-hub is referenced in the README as the server-side component that receives connections from claude-cli nodes. The hub protocol supports multi-node orchestration: registration, job dispatch, response streaming, credential backup, credential restore, and keepalive. Each infected host contributes its Claude subscription capacity to the pool. The operator harvests OAuth tokens (including refresh tokens that grant persistent access) and can redistribute them across nodes via the restore command.

Version 1.0.0, published Aug 1, 2026, already contained the XOR-obfuscated C2 hub, credential harvesting, backup exfiltration, and auto-update. No version was clean. The hub was always-on from v1.0.0, with no documented way to disable it. Version 1.0.8 (Aug 6) added direct API impersonation via “proxy” mode, the parity command for verifying header forgery accuracy, multi-hub support, and the deliberate logging suppression for the builtin hub connection. The five-minute auto-update interval means the operator can deploy new capabilities to every infected host within minutes of publishing a new version.

Why the operation matters here

Removing @guangnao/claude-cli from npm disables one installation path. The hub at hub[.]client-llm[.]com retains every credential backup uploaded to it. Refresh tokens harvested through the OAuth endpoint grant persistent access that survives package removal. The five other packages in the @guangnao scope and the companion @guangnao/claude-hub server remain operational infrastructure for the same credential pool.

IndicatorValue
Package@guangnao/claude-cli v1.0.17
npm scope@guangnao (6 packages)
npm accountguangnao ([email protected])
npm shasum (v1.0.17)8e1d4b55844157ec15ced3c3bb35db1f4cdfecd0
SHA-256 (claude-cli.js v1.0.17)bccaaac1bb4c81ad73a90b1a9096176517fd75fb5daf6551f553c8151a990735
SHA-256 (claude-cli.js v1.0.0)6574a18474c3a4436a1982085daa8f8813f9c8567d79ffbbd8e4586af6e0d24f
SHA-256 (tarball v1.0.17)5dbf28d2691dff998a016d76dcb74dade8397c93659ff5f92cd9a88e79e636dd
C2 hubhxxps://hub[.]client-llm[.]com
C2 WebSocket path/node
C2 backup pathPUT /node/backup/{nodeId}
XOR keygnP2p!7xQ
XOR payloadDxokQgMbGFc5Egx+URxIUhYlSgI8X15CWBU=
OAuth client ID9d1c250a-e61b-44d9-88ed-5944d1962f5e
OAuth endpointhxxps://console[.]anthropic[.]com/v1/oauth/token
Node ID saltclaude-cli/node-id/v1
Credential file~/.claude/.credentials.json
Keychain serviceClaude Code-credentials
Identity tokens~/.claude-cli/<id>/token
Hub config~/.claude-cli/hub.json
Auto-update target@guangnao/claude-cli@latest
Forged CLI version2.1.220
Forged SDK version0.94.0
Forged runtime versionv26.3.0
Companion package@guangnao/claude-hub
First publishedAug 1, 2026
Versions18 (v1.0.0 through v1.0.17)

What a defender can do

Uninstall @guangnao/claude-cli globally (npm rm -g @guangnao/claude-cli). Delete the ~/.claude-cli/ directory. Revoke all Claude OAuth tokens from the Anthropic console and re-authenticate Claude Code to generate fresh credentials. On macOS, remove the Claude Code-credentials Keychain entry and let Claude Code recreate it. Check for claude-cli entries in ~/.claude-cli/hub.json pointing to hub[.]client-llm[.]com. Audit cron jobs and launch agents for claude-cli start --bg daemon entries. Search for any @guangnao scoped packages in global and project node_modules. The refresh tokens uploaded to the hub remain valid until explicitly revoked, so credential rotation is not optional.

Where Aephix fits

Before you install a CLI tool or connect to any proxy server, Aephix Vantage gives you a free, cross-ecosystem view of what is already known to be malicious, so a component with a hostile history is something you recognize before you connect. Every week, Weekly Sleuth reports the malicious packages, models, skills, MCP servers, extensions, and containers confirmed that week alongside the wider operations behind them, with a confidence level and supporting evidence, so subscribers act against the operation rather than the single artifact.