What the AsyncAPI incident changed
Over two weeks in July 2026, five AsyncAPI package versions circulated with signed OIDC provenance. The attackers had compromised the publishing pipeline, not the signing key. The result: npm audit signatures returned a clean result, and dependency managers raised no alerts. A signature proves the integrity of the package at publication time, not the integrity of the process that produced it.
Concrete measures to put in place
- --frozen-lockfile policy — prevent any silent modification of package-lock.json in CI (
npm cienforces this natively;--frozen-lockfilefor yarn/pnpm). - Hash verification in CI — add a job that compares SHA-512 hashes from the lockfile against the npm registry.
- Automated SCA — integrate a software composition analysis scanner (OWASP Dependency-Check, Trivy, Snyk) as a blocking CI step.
- SLSA provenance level 2 minimum — generate and attach SLSA attestations when publishing; verify critical packages carry them when consuming.
- CI image digest pinning — pin each Docker image to its sha256 digest rather than a floating tag.
- Alerts on unplanned new versions — configure Dependabot or Renovate to notify on any version published outside your usual update window.
What the lockfile guarantees — and what it does not
The package-lock.json records the exact version and hash of each resolved dependency. It prevents a silent version bump on a subsequent npm install. What it does not do: verify that the matching package content has not changed on the registry side, or that the pipeline which produced the package was itself sound.
Hardening your CI pipeline step by step
Switch to `npm ci` and ban `npm install` in CI
Replace every npm install with npm ci. The command refuses to run if package-lock.json is absent or diverges from package.json. Add --ignore-scripts if your dependency tree contains unaudited third-party postinstall hooks.
Enable `npm audit` in blocking mode
Add npm audit --audit-level=high after npm ci and before the build. In GitHub Actions, the step fails if a high or critical severity result is detected.
Verify hashes with `npm audit signatures`
Since npm 8.8, npm audit signatures verifies that each installed package has a valid signature registered in the registry. Run it after npm ci.
Integrate an SCA scanner (Trivy or OWASP Dependency-Check)
Add a dedicated SCA job to your CI workflow. Trivy analyses package-lock.json directly (trivy fs --scanners vuln .). Configure it to fail on CVEs with CVSS score above 7.
Consume and emit SLSA attestations
For your own published packages, enable SLSA provenance in your publish workflow (GitHub Actions slsa-framework/slsa-github-generator).
Pin CI actions and images to their sha256 digest
Replace floating tags with sha256 digests in your workflow files. A floating tag may point to a different image after a registry compromise.
Never patch a dependency directly in production by editing node_modules by hand: the lockfile would diverge, and the next CI deployment would reintroduce the original package. Every fix goes through the versioned lockfile, replayed by CI.
Integration into your hosted Git workflow
These controls are pipeline steps, not manual habits: they belong in your ci.yml or .gitlab-ci.yml, triggered on every push and pull request. On a self-hosted repository (Gitea, GitLab CE), the same recipes apply. Protect your main branch with a conditional merge rule gating on a green CI.