Sechno
Devops

Practical Guide: Protect Your Projects from Malicious Open-Source Dependencies

Concrete, implementable techniques for detecting and preventing supply-chain attacks in npm/PyPI projects: integrity checks, SBOMs, CI gating, sigstore, and practical scripts you can add to your toolchain today.

SSechno Team 4 min read 168 views
Practical Guide: Protect Your Projects from Malicious Open-Source Dependencies

Why this matters now

Recent reports highlighted cases where widely used AI libraries contained malicious code or unexpected behavior. See one such write-up: OpenAI Just Killed Sora…. Those incidents are reminders that a project’s security depends not only on its source but on every transitive dependency it installs.

Quick checklist for dependency hardening

  • Pin and lock exact dependency versions and commit lockfiles (package-lock.json, yarn.lock, poetry.lock).
  • Verify distribution digests (compare the downloaded tarball/wheel SHA256 or SHA512 against upstream metadata).
  • Generate and publish SBOMs (use Syft or similar) and keep them in CI artifacts.
  • Use automated scanners like npm audit, pip-audit, OSV, and policy engines in CI.
  • Adopt artifact signing (sigstore/cosign) to verify publisher signatures where available.
  • Enforce least privilege for build and runtime environments so a malicious package can't exfiltrate secrets or escalate privileges.
  • Monitor for typosquatting and changed authorship (package rename/fork alerts)
  • Isolate builds with reproducible build containers and immutable images

Actionable example 1 — Verify a PyPI package file digest in Python

This short script fetches PyPI metadata, picks a source distribution or wheel, downloads it and compares the SHA256 digest from PyPI to an actual computed digest. Run this in an environment where requests is available.

import hashlib
import requests
 
PACKAGE = "requests"  # change to the package you want to verify
PYPI_JSON = f"https://pypi.org/pypi/{PACKAGE}/json"
 
resp = requests.get(PYPI_JSON, timeout=15)
resp.raise_for_status()
meta = resp.json()
 
# Pick the first sdist or wheel as a simple heuristic
file_info = None
for f in meta.get("urls", []):
    if f.get("filename", "").endswith((".whl", ".tar.gz", ".zip")):
        file_info = f
        break
 
if not file_info:
    raise SystemExit("No suitable distribution found in PyPI metadata")
 
url = file_info["url"]
expected_sha256 = file_info.get("digests", {}).get("sha256")
if not expected_sha256:
    raise SystemExit("PyPI metadata missing sha256 digest")
 
print(f"Checking {url}\nexpected sha256: {expected_sha256}")
 
r = requests.get(url, timeout=30)
r.raise_for_status()
actual_sha256 = hashlib.sha256(r.content).hexdigest()
 
if actual_sha256 == expected_sha256:
    print("OK: digests match")
else:
    print("MISMATCH: expected", expected_sha256)
    print("MISMATCH: actual  ", actual_sha256)
    raise SystemExit(2)

Actionable example 2 — Verify an npm tarball integrity in Node.js

package-lock.json contains an integrity field like sha512-BASE64. This Node script downloads the tarball and computes the SHA512 base64 string to compare. Use this to add an explicit verification step in CI.

const https = require('https');
const crypto = require('crypto');
 
const tarballUrl = 'https://registry.npmjs.org/somepkg/-/somepkg-1.2.3.tgz';
const expectedIntegrity = 'sha512-REPLACE_WITH_BASE64_FROM_LOCKFILE'; // from package-lock.json
 
function fetchBuffer(url) {
  return new Promise((resolve, reject) => {
    https.get(url, (res) => {
      const chunks = [];
      res.on('data', c => chunks.push(c));
      res.on('end', () => resolve(Buffer.concat(chunks)));
      res.on('error', reject);
    }).on('error', reject);
  });
}
 
(async () => {
  const buf = await fetchBuffer(tarballUrl);
  const hash = crypto.createHash('sha512').update(buf).digest('base64');
  const candidate = `sha512-${hash}`;
  if (candidate === expectedIntegrity) {
    console.log('Integrity OK');
    process.exit(0);
  }
  console.error('Integrity MISMATCH');
  console.error('expected:', expectedIntegrity);
  console.error('actual:  ', candidate);
  process.exit(2);
})();

CI integration patterns

  1. Fail fast: add integrity/digest verification as an early CI job that runs before build/test.
  2. SBOM generation: produce an SBOM (Syft) and upload it as a CI artifact; compare SBOMs between commits to spot new packages.
  3. Block suspicious changes: run dependency-scanning tools and fail the build on high/critical findings.
  4. Signed artifacts: verify signatures from sigstore/cosign where publishers provide signatures.
  5. Scheduled rescans: run weekly scans of dependency graph (to catch newly discovered vulnerabilities).

Operational tradeoffs

  • Pinning vs updates: strict pinning prevents accidental upgrades to malicious releases but can delay important security fixes. Mitigate with automated dependency update workflows and staged rollout.
  • False positives: aggressive scanners may surface benign issues. Triage rules in CI and allowlist processes are necessary.
  • Performance and cost: scanning and SBOM generation add CI time and storage costs. Prioritize verification for production builds and release pipelines.
  • Supply-chain maturity: some ecosystems offer signatures and verifiable metadata; others do not. Invest where vendor support exists and combine safeguards for weaker ecosystems.

Next steps you can take this afternoon

  • Enable npm audit / pip-audit in your CI and fail on critical findings.
  • Add the Python script above as a pre-release verification step for critical packages.
  • Start producing SBOMs with Syft and store them alongside artifacts.
  • Investigate sigstore for signing your build artifacts and verifying upstream signatures.

Security is layered: dependency verification, CI policies, runtime isolation, and monitoring together reduce risk. No single measure is enough, but the practical steps above make large classes of supply-chain attacks detectable and blockable.

Conclusion

Recent supply-chain incidents show open-source dependencies can be attack vectors. Implementing digest verification, SBOMs, automated scanning, and signature checks are practical, evergreen measures. Start small—add a digest check for the handful of packages you ship in production—then expand coverage as part of CI and release automation.

For more background reading and an incident write-up: Investigative report on a malicious AI library and explore sigstore for artifact signing options.

Was this helpful?

Share this post

Comments (0)

Want to join the conversation?

Log in or sign up to leave a comment and share your thoughts.

Log in to Comment