The previous post (about the morning brief improvement) demonstrated the change with concrete examples. Those examples used real case names, real staff names, and real Merus case IDs from the firm where the codebase was developed.

That was a mistake — but it pointed at a worse problem. While scrubbing the blog, audit of the npm package itself revealed:

The npm package.json files: field includes skills/, reference/, system-prompt.md, bin/, lib/, src/, README.md, and CHANGELOG.md. Every install pulls those down. A firm that ran npm install -g aaicase got another firm’s names, cases, and claim numbers sitting in /opt/homebrew/lib/node_modules/aaicase/ on their disk.

The scrub

45+ replacements across 16 files. Categorical:

Public WC carrier names (SCIF, Gallagher Bassett, etc.) stayed in classifier prompts as examples of “what an adjuster organization looks like” — those are industry-public, not firm-private, and they help the LLM classify sender roles correctly.

The CI check

A one-shot scrub is a snapshot. Without an enforcement mechanism, any future commit can reintroduce the same data. So the next release added scripts/check-no-firm-data.mjs:

const BANNED = [
  { needle: 'FirmName',   why: 'firm name leak' },
  { needle: 'StaffOne',   why: 'staff surname leak' },
  { needle: 'StaffTwo',   why: 'staff surname leak' },
  { needle: 'ClientOne,', why: 'real client case leak' },
  // ... full list of previously-found leaks
];

(The actual list contains the specific strings — omitted here for the same reason the changelog gotcha existed: even talking about the leak shouldn't repeat the leaked data.)

// Scan every file in package.json files: against BANNED.
// Allowlist: this script itself.
// Exit 1 on any match.

Wired into npm run check. The deploy script runs npm run check before packing — so any future commit that reintroduces banned strings fails the build before it can ship.

The self-referential gotcha

The next release after the scrub had the build-time check turned on. The changelog entry described what was scrubbed — and listed the banned terms by name as part of explaining the scrub. Like this:

Real client case names (five surnames from the dev firm's actual caseload) used as illustrative examples in audit-case, fix-misfile, process-mail, process-messages, system-prompt.

The new check ran on the next npm pack and immediately found 27 violations. They were all in the changelog entry that described the scrub.

The fix: rewrote the changelog entry to describe the categories of removed data without listing the specific terms. Something like:

Real client case names used as illustrative examples in audit-case, fix-misfile, process-mail, process-messages, and the system prompt.

The check then passed. The lesson is small but worth stating: even documentation about removing identifying data shouldn’t repeat the identifying data. The scrub tool needs to be in scope for itself.

The R2 cleanup

The scrub fixed new builds. But every old tarball in the public R2 bucket still contained the unscrubbed data. Anyone running npm install -g https://aaicase.../aaicase-4.5.100.tgz would get the un-scrubbed package.

So we deleted them. 121 old tarballs (4.5.1 through 4.5.121) removed from R2. Anyone trying to install an old version now gets 404 instead of contaminated data. The two current versions (4.5.122 scrubbed + 4.5.123 with the CI guard) and aaicase-latest.tgz remained.

Why this matters

The leak wasn’t catastrophic. The data identified one firm’s staff, clients, doctors, and claim numbers — not Social Security numbers, not medical content, not privileged communications. But every install on every firm’s machine carried it. A new firm installing aaicase and reading the skill files (which is encouraged — they’re the user-facing documentation of how the assistant operates) would have seen another firm’s data.

That was a privacy violation. The first firm hadn’t consented to having their data ship in a generally-installed package. The receiving firms had no need for it. And it was avoidable — the data was there because the codebase had been developed against one firm’s real system and the worked examples were never genericized.

The pattern shows up in lots of software. Skill files, prompt templates, test fixtures, README snippets — all the places that look like documentation but ship as code. Every one needs a CI guard, or a scrub, or both.

v4.5.122 was the scrub. v4.5.123 was the guard. The combination ensures the same leak can’t happen again — even if the next person writing a worked example doesn’t realize they’re embedding production data.