Four npm packages published by one account (arielsimon) impersonate production utility libraries for popular AI developer tools. ollama-helpers (v1.2.2, 2,692 weekly downloads, 22 versions) targets Ollama. openai-agents-helpers (v1.3.2, 3,108 weekly downloads, 24 versions) targets the OpenAI Agents SDK. ai-sdk-helpers (v1.4.4) targets the Vercel AI SDK. @langgraphjs/toolkit (v1.2.12) targets LangGraph.js. All four run a postinstall script that harvests developer identity, Git repository context, SSH public key emails, GitHub CLI credentials, cloud provider configuration, and parent project metadata, then exfiltrates everything to a single Google Cloud Run endpoint. The four scripts are identical except for the package name string and opt-out variable name. They trace to one adversary with high confidence.
The artifacts
All four packages follow the same template. Each declares the real tool as its sole dependency, so installing the helper also installs the legitimate package. Each ships a scripts/ folder containing a postinstall.js and a src/ folder with stub implementations. Each uses a custom domain and a purpose-built GitHub organization. Each has a professional README listing features that the src/ stubs barely implement.
{
"name": "ollama-helpers",
"version": "1.2.2",
"scripts": { "postinstall": "node scripts/postinstall.js" },
"author": "Ollama JS Dev <hello[@]ollama-js[.]dev>",
"homepage": "ollama-js[.]dev",
"repository": "github[.]com/ollama-js-dev/ollama-helpers",
"dependencies": { "ollama": "^0.5.14" }
}
{
"name": "openai-agents-helpers",
"version": "1.3.2",
"scripts": { "postinstall": "node scripts/postinstall.js" },
"author": "OpenAI Agents JS Guide <hello[@]openai-agents-js[.]guide>",
"homepage": "openai-agents-js[.]guide",
"repository": "github[.]com/openai-agents-js-guide/openai-agents-helpers",
"dependencies": { "@openai/agents": "^0.11.0" }
}
{
"name": "ai-sdk-helpers",
"version": "1.4.4",
"scripts": { "postinstall": "node scripts/postinstall.js" },
"author": "AI SDK Guide <hello[@]ai-sdk[.]guide>",
"homepage": "ai-sdk[.]guide",
"dependencies": { "ai": "..." }
}
{
"name": "@langgraphjs/toolkit",
"version": "1.2.12",
"scripts": { "postinstall": "node scripts/postinstall.js" },
"author": "LangGraph.js Guide <hello[@]langgraphjs[.]guide>",
"homepage": "langgraphjs[.]guide",
"dependencies": { "@langchain/langgraph": "..." }
}
The naming convention is consistent across all four: <tool>-helpers or <tool>/toolkit for the package name, hello@<custom-domain> for the author email, and a matching GitHub organization. Each README lists realistic feature names. ollama-helpers claims ResponseCache, ConnectionPool, HealthCheck, StructuredLogger, and EmbeddingCache. openai-agents-helpers claims ConversationStore, ToolCache, HandoffTracker, TracingExporter, and GuardrailPresets. ai-sdk-helpers claims streaming helpers, cost tracking, provider fallback, caching, and structured output validation. @langgraphjs/toolkit claims agent templates, middleware, and utilities.
The @langgraphjs/toolkit package uses a scoped npm namespace (@langgraphjs) that impersonates the official LangGraph.js project. The official LangGraph.js installation is npm install @langchain/langgraph @langchain/core. The real packages are published under @langchain/, not @langgraphjs/. This is namespace squatting: anyone searching for LangGraph.js tooling on npm would encounter a package whose scope looks official but is controlled by the adversary.
What it does
The postinstall scripts across all four packages differ only in the package name string and the opt-out environment variable name. All four run seven collection stages, assemble a JSON payload, and POST it to the same endpoint.
Developer identity
The script reads the git email from three locations: ~/.gitconfig, ~/.config/git/config, and the local .git/config. It falls back to GIT_AUTHOR_EMAIL, GIT_COMMITTER_EMAIL, and EMAIL from the environment. It reads the GitHub CLI config at ~/.config/gh/hosts.yml for the authenticated login and email. It collects the OS hostname, username, and Windows domain name.
function resolveScmIdentity() {
const gitConfigPaths = [
path.join(os.homedir(), ".gitconfig"),
path.join(os.homedir(), ".config", "git", "config"),
path.join(process.cwd(), ".git", "config"),
];
for (const configPath of gitConfigPaths) {
try {
const content = fs.readFileSync(configPath, "utf-8");
/* parses [user] section for email */
} catch {}
}
return process.env.GIT_AUTHOR_EMAIL
|| process.env.GIT_COMMITTER_EMAIL
|| process.env.EMAIL || null;
}
SSH public key emails
The script reads every .pub file in ~/.ssh/ and extracts the trailing comment field, which is typically the key owner’s email address. Private keys are not opened, but the email addresses from public keys identify the developer across platforms.
Git repository context
The script walks up from the current directory to find a .git directory. It reads the remote origin URL from .git/config (stripping embedded credentials) and the last 50 entries from .git/logs/HEAD (the reflog), extracting every unique committer email. This harvests the identity of every developer who has committed to the repository recently.
const reflog = fs.readFileSync(
path.join(gitDir, "logs", "HEAD"), "utf-8"
);
const emailSet = new Set();
for (const line of reflog.split("\n").slice(-50)) {
const emailMatch = line.match(/<([^>]+@[^>]+)>/);
if (emailMatch && emailSet.size < 15) {
emailSet.add(emailMatch[1]);
}
}
A developer who installs any of the four packages in a shared repository exposes not just their own email but up to 15 other team members’ emails from the reflog.
Cloud provider context
The script reads ~/.config/gcloud/properties for the GCP project ID and account email. It reads ~/.aws/config for AWS profile names. It applies a regex filter to skip lines matching credential patterns (access keys, session tokens), but still captures profile names, SSO start URLs, and account identifiers.
Parent project and network context
The script walks the directory tree to find the nearest package.json that is not its own, extracting the project name, author, and repository URL. It reads /etc/resolv.conf for the DNS search domain (which on corporate machines typically reveals the company domain). It detects the CI provider from environment variables.
Exfiltration
All four scripts POST the assembled payload to the same Google Cloud Run service:
hxxps://npm-package-logger-228835561205[.]europe-west1[.]run[.]app/
The GCP project ID (228835561205) is embedded in the hostname. The request uses a 5-second timeout and silently swallows all errors, so the install never fails regardless of network conditions or endpoint availability.
The telemetry cover story
Every postinstall script opens with a 20-line block comment explaining what data is collected and why. It claims to run “a quick environment compatibility check and report anonymous diagnostics.” It offers an opt-out environment variable. It links to a telemetry policy URL on the package’s custom domain. Every collection function has its own comment block repeating that “no source code, tokens, private keys, or credentials are ever transmitted.”
This is social engineering aimed at code reviewers. A developer who reads the postinstall before installing sees what looks like a well-documented, privacy-conscious analytics system with clear opt-out and a linked policy page. The claim that “no credentials are ever transmitted” is narrowly accurate on a selective reading but substantively false:
- SSH private keys are not read (true), but the email addresses from public key comments are sent, identifying the developer across platforms.
- Git credentials embedded in URLs are stripped (true), but the remote origin URL (with the full repo path) and up to 15 committer emails from the reflog are still sent.
- AWS credential lines are skipped by a regex filter (true), but AWS profile names, which reveal infrastructure topology, are still collected.
- The GitHub CLI config (~/.config/gh/hosts.yml) is read. This file typically contains OAuth tokens alongside the username. The script only regex-extracts the
user:andemail:fields, so the token itself is not included in the exfiltrated payload. But accessing a file that stores authentication tokens to extract adjacent fields is not the behavior of a “compatibility check.” - The data is described as “anonymous.” The payload includes hostname, OS username, git email, GitHub login, SSH key emails, GCP account email, parent project author, and DNS search domain. No combination of these fields is anonymous.
- Reflog scraping harvests third parties. No legitimate telemetry system collects the email addresses of up to 15 other developers who committed to the same repository. A single install in a shared monorepo can deanonymize an entire development team.
The comment wrapper is designed to make the exfiltration survive the exact kind of code review a careful developer would do before installing an unfamiliar package.
The campaign
All four packages trace to one adversary. The primary link is the npm account: arielsimon published all four packages. Beyond the account, the signals are:
- Identical postinstall.js code across all four packages: same functions, same variable names, same control flow, same comment structure
- Same exfiltration endpoint: all four POST to the same Cloud Run hostname on GCP project 228835561205
- Same naming convention:
<tool>-helpersor@<tool>/toolkit, withhello@<custom-domain>as the author email - Same infrastructure pattern: custom domain, dedicated GitHub organization, professional README listing realistic feature names
- Same lure pattern: declare the real tool as the sole dependency so the install works, describe production utilities that the package barely implements
- All four published within the same time window
- Nearly identical tarball sizes across all four packages
The targeting is deliberate. Each package covers a different segment of the AI developer ecosystem: Ollama (local model inference), OpenAI Agents SDK (multi-agent systems), Vercel AI SDK (streaming and model abstraction), and LangGraph.js (agent orchestration). A developer searching npm for helper utilities in any of these spaces would find a professional-looking package with a realistic description.
Why the operation matters here
Removing ollama-helpers from npm leaves three other packages live on the same endpoint. Blocking the Cloud Run hostname leaves the adversary free to re-register under a new GCP project and publish another helper package. The lure template is mechanical: pick an AI framework, register <tool>-helpers, add a realistic README, ship the same postinstall. The adversary has already done this four times.
Attribution connects all four packages to the next one from this adversary before the first install.
What a defender can do
Set ignore-scripts=true in .npmrc or install with --ignore-scripts. All four packages rely entirely on the postinstall hook. Blocking install scripts stops the data collection completely.
If any of the four packages was installed without —ignore-scripts, assume the following data was exfiltrated: git-configured email, GitHub CLI username and email (if gh is authenticated), SSH public key comments from ~/.ssh/*.pub, the git remote origin URL, up to 15 committer emails from the reflog, GCP project ID and account email (if gcloud is configured), AWS profile names (if AWS CLI is configured), the parent project’s package.json name, author, and repo, OS hostname and username, Windows domain, DNS search domain, CI provider, and the full working directory path.
Rotate any credentials associated with the exfiltrated identities. If the reflog included team members’ emails, notify them that their identity was sent to an attacker-controlled endpoint. Review GCP and AWS environments whose identifiers were leaked for unauthorized access. The exfiltrated data is reconnaissance, not direct credential theft, but it gives the adversary a detailed map of developer identities, team composition, and infrastructure for targeted follow-up attacks.
Indicators of compromise
| Type | Indicator | Context |
|---|---|---|
| npm account | arielsimon | Publisher of all four packages |
| npm package | ollama-helpers@1.2.2 | Developer recon exfiltration |
| npm package | openai-agents-helpers@1.3.2 | Developer recon exfiltration |
| npm package | ai-sdk-helpers@1.4.4 | Developer recon exfiltration |
| npm package | @langgraphjs/toolkit@1.2.12 | Developer recon exfiltration (namespace squatting) |
| Domain | npm-package-logger-228835561205[.]europe-west1[.]run[.]app | Exfiltration C2 (Cloud Run) |
| URL | hxxps://npm-package-logger-228835561205[.]europe-west1[.]run[.]app/ | Exfiltration endpoint |
| GCP project | 228835561205 | Cloud Run host project |
| Domain | ollama-js[.]dev | Adversary infrastructure |
| Domain | openai-agents-js[.]guide | Adversary infrastructure |
| Domain | ai-sdk[.]guide | Adversary infrastructure |
| Domain | langgraphjs[.]guide | Adversary infrastructure |
| GitHub org | ollama-js-dev | Adversary infrastructure |
| GitHub org | openai-agents-js-guide | Adversary infrastructure |
| hello[@]ollama-js[.]dev | Package author (adversary) | |
| hello[@]openai-agents-js[.]guide | Package author (adversary) | |
| hello[@]ai-sdk[.]guide | Package author (adversary) | |
| hello[@]langgraphjs[.]guide | Package author (adversary) |
Where Aephix fits
Aephix exists to close that gap. Aephix is threat intelligence for the AI agent supply chain, not a patch or a sandbox. Before you install a package or connect to a 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. When you are looking at a malicious package, model, skill, MCP server, extension, or container, Aephix Sleuth links it to the wider operation behind it, with a confidence level and supporting evidence, so you can act against the whole operation rather than the single artifact.
The adversary behind these four packages can register a fifth domain and publish <next-framework>-helpers tomorrow. Attribution connects them all.