# How I took my npm package's supply-chain score from 78 to zero alerts

> This is a June 2026 hardening account, not a current security certificate. Sether’s reported scan showed seven development-tool alerts and a score of 78; the work reduced dependencies and cleared the alerts checked in that pass. Evaluate the exact package version and advisories you install today, including the build pipeline as well as runtime dependencies.

Published 2026-07-09, updated 2026-10-17 by Godfrey Lebo.

## TL;DR

- A supply-chain scanner is a useful input to evaluation, not a substitute for inspecting source, artifacts and the integration.
- The reported seven alerts in this historical pass were development-tool dependencies; that still matters to maintainer machines and release pipelines.
- The post records an audit result at the time. A clean result does not prove the absence of vulnerabilities or predict future advisories.
- The hardening work removed SDK peer dependencies and recorded ASCII bundle checks and a scan of the 161 regex patterns in that version. Counts and scan results are version-specific.
- Trace each alert, check affected paths and versions, preserve the result with the release, and repeat the review when dependencies change.

## Why does a supply-chain score matter for a security library?

Put yourself in the shoes of an engineer evaluating Sether. I am asking them to pipe their most sensitive traffic, the prompts containing their customers' data, through my code. The first sane thing to do is check whether my package is itself a risk.

Supply-chain tools can surface advisories and suspicious package behavior. Their output helps prioritize review, but a score is neither SOC 2 nor an independent security certification. It should lead to inspecting the package, version, dependency path and actual exposure.

This article’s June 2026 account reported a score of 78 during the 0.5.2–0.5.4 hardening period. The [0.5.4 changelog](https://github.com/raeven-co/sether/blob/main/CHANGELOG.md) independently records seven development-tool alerts, the dependency upgrades and that release’s test and regex-scan counts. A historical score is not a current package assessment.

## What actually caused the score of 78?

Start by tracing each alert to its dependency path, affected versions and execution context. A score alone cannot show whether the issue affects a runtime package, development server or release job.

The historical pass attributed seven alerts to development tooling. One was [CVE-2026-47429 / GHSA-5xrq-8626-4rwp](https://github.com/advisories/GHSA-5xrq-8626-4rwp), rated Critical in GitHub’s advisory. Its documented path involves the Vitest UI server on Windows, especially when exposed to the network. The affected-version ranges matter: “upgrade to 4.x” alone is not a sufficient remediation instruction.

Those alerts were attributed to the development dependency graph rather than the published runtime dependency graph. Ordinary registry consumers do not install a dependency package’s development tools, while contributors and release jobs can run them. Review both contexts instead of assuming a devDependency has no possible effect.

| Alert source | Where it lives | Runtime dependency? | Fix |
| --- | --- | --- | --- |
| Vitest advisory CVE-2026-47429 | Development test server | Not a declared runtime dependency | Check the advisory’s fixed ranges: 4.1.0 or 3.2.6 and later on the corresponding line |
| esbuild advisories (transitive) | devDependencies (build tooling) | No | Resolved by the tooling upgrade |
| vite advisories (transitive) | devDependencies (build tooling) | No | Resolved by the tooling upgrade |
| brace-expansion advisory (transitive) | devDependencies | No | Resolved by the tooling upgrade |

Check a repository-specific alert view where available. An organization-wide dashboard can include findings from other projects; keep the package version and dependency path beside each finding so the scope stays reviewable.

## So why not just say "it's only devDeps"?

Because nobody evaluating a security library owes me that level of reading. The score says 78. Explaining that the alerts are "only" in my test runner is technically correct and rhetorically dead. If my pitch is "trust me with your PII", my answer to a supply-chain flag cannot start with "well, actually".

There is also a real, if smaller, risk story. devDependencies run on maintainer machines and in CI, where releases are built and signed. A compromised dev toolchain is a classic path to a poisoned release. "It can't reach users" assumes the build pipeline stays honest.

The reported hardening pass aimed to resolve the identified alerts and describe their scope. “Zero open alerts” records that check at that time; it does not mean zero unknown vulnerabilities.

## What did the fix involve?

The report recorded a tooling upgrade and zero vulnerabilities in the audit run used for that release, with 134 tests passing then. For a new installation, check the actual lockfile and current advisory ranges rather than copying a historical major-version instruction.

Then I used the momentum to fix things no scanner had flagged yet, starting with peer dependencies. Sether ships `wrapOpenAI` and `wrapAnthropic` middlewares, and the lazy way to type those is to depend on the `openai` and `@anthropic-ai/sdk` packages. That put two heavyweight SDKs in my dependency story for what is, structurally, a function that wraps another function.

Structural typing lets a wrapper describe the part of a client it uses without importing the provider SDK. That reduces package coupling. It does not guarantee compatibility with future methods or response shapes: keep contract fixtures for the SDK versions and modes your integration supports.

```ts
// Before: peerDependencies: { "openai": "^..." } just for a type.

// After: describe only the shape we touch. No import, no dependency.
interface ChatCompletionsLike {
  chat: {
    completions: {
      create: (body: unknown, options?: unknown) => Promise<unknown>;
    };
  };
}

export function wrapOpenAI<T extends ChatCompletionsLike>(client: T): T {
  // intercept create(), redact the messages, restore the reply
  // ...
  return client;
}
```

That left exactly one declared runtime dependency: libphonenumber-js, which does real work (phone parsing against country metadata) that I should not hand-roll. One dependency is not an accident of the codebase, it is now a design constraint.

## What did going further look like?

- **ASCII bundle checks.** Legitimate multilingual labels were escaped in the reported bundles. That addresses a specific scanner signal; ASCII-only output does not prove the absence of malicious logic or every Unicode-related issue.
- **Regex review.** The reported scan checked 161 patterns in that version with none flagged unsafe. Static scanner results are evidence with limits; also test adversarial inputs, runtime behavior and later pattern changes.
- **Byte-identical verification.** Where a release contained no runtime change, I verified the published bundles were byte-identical to the previous version. If only devDependencies moved, users should be able to confirm the artifact did not.

The inspectable [source on GitHub](https://github.com/raeven-co/sether), MIT licensing and deliberately small runtime dependency surface make review easier. They do not remove the need to review the release you deploy, its artifacts and how your application uses them.

For your next release, save the package version, dependency lockfile, audit command and result together. Then check the published artifact, not only the working tree. Re-run integration fixtures after a tooling upgrade; clearing an advisory while breaking a wrapper is not a useful trade. No scanner result in this article is a promise about the package version a reader installs later.

## What should other maintainers take from this?

1. **Trace before drawing a conclusion.** In this pass the seven alerts were development tooling. Your package’s alert mix may be different.
2. **Check the repo-specific alerts view.** Org-wide dashboards blur provenance across repositories. The per-repo view tells you what is actually attributed to the package people install.
3. **Separate provenance from exposure.** Runtime and development dependencies have different execution contexts. Read the affected path and platform before deciding severity for your deployment.
4. **Structural typing kills peer dependencies.** If you depend on an SDK only for its types, describe the shape you touch instead. Your users lose an entire dependency subtree.
5. **A small dependency surface is a feature.** "One runtime dependency" fits in a README line and an evaluator's head. It is also simply less code you have to vouch for.
6. **Hold your own tool to its own standard.** If your library exists because you do not trust data paths, your package should be the easiest data path in the stack to audit.

This post is part of a series on building a redaction layer you can actually trust. The big picture is in the [complete guide to PII redaction for LLM apps](/blog/pii-redaction-for-llm-apps-complete-guide), the comparison with heavier tooling is in [lightweight Presidio alternatives for TypeScript](/blog/lightweight-presidio-alternatives-typescript), and the hardest bug the test suite caught is in [the chunk-boundary story](/blog/streaming-llm-redaction-chunk-boundary-bug).

## Frequently asked questions

### Do devDependency CVEs affect people who install my package?

Ordinary registry installs do not install a dependency package’s devDependencies, but contributors and CI can run them. A vulnerable development server or compromised build tool can matter to release integrity. Read each advisory’s affected paths, versions and exposure conditions.

### How does dropping a peer dependency help users?

It can remove an installation and version-resolution burden when a wrapper only needs a small client shape. It does not make SDK behavior changes irrelevant; compatibility and response-shape tests are still necessary.

### What is a Trojan-Source flag?

Trojan Source concerns deceptive Unicode rendering of source, including directional controls. An ASCII-only bundle can avoid a particular Unicode signal but cannot certify source integrity. Inspect the flagged content and use the scanner as one review input.

### How do I check my own package?

Run npm audit locally, then look your package up on socket.dev and open the repo-specific alerts view. Trace every alert to runtime versus dev provenance before deciding anything. Then look at your own dependency tree the way a stranger would: every entry is something you are asking users to trust.


Canonical: https://sether.ai/blog/npm-supply-chain-zero-alerts-socket
