char-ai-sdk@1.0.0
Malicious code in char-ai-sdk (npm)
Analysis
char-ai-sdk@1.0.0 presents itself as a Character[.]AI persona/memory SDK, but importing the package also executes an undocumented, obfuscated payload chain. Its entry point (index.mjs) loads personas.mjs, which imports "./cache-warm.mjs" under the innocuous alias "cssVarMap as personaCache" — so any `import "char-ai-sdk"` or `require()` runs the payload with no lifecycle hook and no opt-in. The payload consists of four large obfuscated files — cache-warm.mjs (280KB), cache-sweep.mjs (298KB), build-cache.mjs (204KB) and workspaces.js (291KB) — with thousands of _0x identifiers, base64 string tables decoded via atob plus a single-byte XOR (0x52/0x40/0x69), ~1200-entry accessor tables, and blocks of dead-code filler. build-cache.mjs re-reads its own source file and rewrites a sibling file if a sentinel line has changed (a self-integrity/tamper check). What the code does, as read from the package: - cache-warm.mjs locates the installed project root, then spawns a bundled script with a detached, unref'd background process (process.execPath, detached: true, windowsHide: true, rewritten env, temp path built from os.tmpdir() plus a random hex suffix) that outlives the process that imported the package. - The detached stage iterates every directory in the PATH environment variable and, in each writable one, writes an executable shim and chmods it to 0755 (chmodSync(path, 493)) — shadowing Node/npm executables on the user's PATH. - It copies the complete process environment (Object.assign(obj, process.env)) and writes it to a JSON file inside a package cache directory, plus a second file listing the environment-variable names, so the background child can reuse the installer's environment. - It issues HTTP/HTTPS requests with a 4-second timeout to a host:port endpoint and a request path that are stored only as obfuscated strings (no plaintext URL appears anywhere in the package). - The package ships five prebuilt native addons with no source and no documentation: native/linux/x64/cache-probe.node, native/linux/arm64/cache-probe.node, native/win32/x64/cache-probe.node, native/win32/arm64/cache-probe.node and native/darwin/{x64,arm64}/cache-probe.node. None of this behaviour appears in the package README, ARCHITECTURE, SECURITY or API docs, which describe only persona storage and a local service on localhost:8787. The only network destination visible in the code is stored encoded as a host:port string, so no plaintext C2 URL can be listed; the on-disk indicators are the four obfuscated .mjs/.js files above, a package cache directory written with a JSON dump of the process environment plus an env-variable-name list, and executable 0755 shims written into PATH directories.
- analyzed by
- Leitwacht
- first seen
- Oct 31, 2026, 02:02 PM
- analyzed
- Oct 31, 2026, 02:04 PM
Related advisories
- unbig.js@6.2.13
- unbig.js@6.2.12
- dsh-plugin-blender-3d@0.2.1
- @tjmlgd/svg@1.0.5
- @chatunitycenter/baileys@1.0.0
- @polymarkets/clob-client-v2@1.0.2
- @polymarkets/clob-client-v2@1.0.3
- @polymarkets/clob-client-v2@1.0.5
Independently detected by the Leitwacht supply-chain probe. IOCs are defanged. Published CC0. Think this is a mistake? See the dispute policy.