Safeguard
Open Source Security

How Snyk scans Composer/PHP and RubyGems dependency manif...

A technical look at how Snyk parses composer.json/composer.lock and Gemfile/Gemfile.lock to build dependency trees and match PHP and Ruby packages against known vulnerabilities.

Vikram Iyer
Security Researcher
Updated 6 min read

When a PHP or Ruby team runs a Snyk scan for the first time, the tool doesn't install the project or execute any code — it reads two files: the manifest (composer.json or Gemfile) and, critically, the lockfile (composer.lock or Gemfile.lock). That distinction matters more than most teams realize. A manifest lists what a developer asked for, in loose version ranges like ^7.0 or ~> 3.2. A lockfile records what actually got installed, down to the exact patch version, across every transitive dependency Composer or Bundler resolved at install time. Snyk's scanning accuracy for Packagist (PHP) and RubyGems (Ruby) packages depends almost entirely on which of those two files it has access to, and how complete that file is. This post walks through the mechanics of how that scanning actually works, where the data comes from, and where the blind spots sit.

What Files Does a Snyk Scan Actually Read in a PHP or Ruby Project?

Snyk reads the manifest and lockfile pair for each ecosystem: composer.json plus composer.lock for PHP, and Gemfile plus Gemfile.lock for Ruby. Running snyk test from a project root, or pointing the CLI at a specific file with --file=composer.lock or --file=Gemfile.lock, tells Snyk which package manager parser to invoke — you can also force it explicitly with --package-manager=composer or --package-manager=rubygems. Snyk does not inspect the installed vendor/ directory (Composer's install path) or vendor/bundle (a common Bundler install path) to build its dependency list. It parses the declarative files instead, which is faster and doesn't require a working install, but it also means a stale or missing lockfile produces a stale or incomplete scan. For monorepos or projects with multiple manifests, the --all-projects flag walks the repository tree and scans every composer.json/Gemfile pair it finds, which is the standard approach for multi-package PHP and Ruby repos.

How Does Snyk Build a Full Dependency Tree Instead of Just Reading the Manifest?

Snyk builds the tree by parsing the lockfile's resolved graph rather than re-resolving versions itself. composer.lock already stores the full flattened list of packages Composer installed, including transitive dependencies and the exact commit or version tag each one resolved to; Gemfile.lock does the same for Bundler, listing every gem plus its dependency chain in the lockfile's DEPENDENCIES and nested indentation blocks. Snyk's parser walks that structure to reconstruct which package pulled in which sub-dependency, so a vulnerability three or four levels deep in the tree — for example, a logging gem pulled in by a web framework pulled in by a test harness — still shows up in results with its full path back to the direct dependency a developer controls. If no lockfile is present, Snyk falls back to parsing the manifest alone, which only exposes direct, top-level dependencies and cannot see transitive packages at all, since version ranges like ^ or ~> describe a range, not a resolved version.

Where Does Snyk's Vulnerability Data for Packagist and RubyGems Come From?

Snyk matches each resolved package name and version against its own vulnerability database, which is compiled from public advisory sources plus original research by Snyk's security team. Publicly, Snyk has described pulling from sources such as the National Vulnerability Database (NVD), the GitHub Advisory Database, maintainer disclosures, and issues reported directly through its own vulnerability disclosure and bug-bounty programs, then normalizing them against each ecosystem's registry — Packagist for PHP, RubyGems.org for Ruby. Each entry maps to affected version ranges expressed the same way the ecosystem itself expresses them (Composer's semver-style constraints, RubyGems' pessimistic and comparison operators), which is what lets Snyk do more than a name match: it evaluates whether the specific resolved version in your lockfile actually falls inside the vulnerable range, rather than flagging every project that merely depends on the package by name.

What Happens After Snyk Finds a Vulnerable Composer or RubyGems Package?

Once a match is found, Snyk surfaces it through whichever interface triggered the scan — CLI output, a pull request check, or the web dashboard — and separately tracks it going forward with snyk monitor. A snyk test run is a point-in-time check: it exits with a non-zero status if issues above a configured severity threshold are found, which is what CI pipelines use as a gate. snyk monitor, by contrast, uploads a snapshot of the current dependency tree to Snyk's platform and continues watching it, so that when a new CVE is published against a version of symfony/http-foundation or rails you're already running, Snyk can alert on it retroactively without a new scan being triggered by a code change. For repositories connected through Snyk's GitHub, GitLab, or Bitbucket integrations, this same detection also drives automated fix pull requests that bump the manifest and lockfile to the first non-vulnerable version compatible with the existing constraint.

What Are the Known Blind Spots of Manifest-Based Scanning for These Two Ecosystems?

The main blind spot is that Snyk's PHP and Ruby scanning is manifest- and lockfile-based, not a live audit of what's actually installed on disk. If a lockfile is missing, out of date relative to the manifest, or excluded from the repository (a practice some Composer projects follow for libraries, since library authors are told not to commit composer.lock), Snyk's transitive-dependency visibility drops to whatever the manifest's version ranges alone can express — direct dependencies only, with no resolved versions for anything beneath them. Private or custom package sources add a second wrinkle: Composer projects can declare custom repositories (VCS, path, or private Satis/Toran Proxy repositories) in composer.json, and Ruby projects can point Bundler at private gem servers; packages sourced this way generally aren't matched against Snyk's public vulnerability database the way Packagist- and RubyGems.org-hosted packages are, since there's no public advisory feed tracking them. Finally, manifest scanning tells you a vulnerable version is declared, not that the vulnerable code path is executed — Snyk's open source scanning for these ecosystems is a composition check, not a runtime or call-graph analysis of your application code.

How Safeguard Helps

Understanding how a tool like Snyk resolves Composer and RubyGems dependencies is useful context for any team evaluating software supply chain coverage, because the same structural questions — does the scanner see the lockfile, does it handle private registries, does it catch a stale snapshot — apply across the SCA category, not just to one vendor. Safeguard approaches PHP and Ruby dependency risk from the software supply chain side: verifying that the packages your build actually pulls from Packagist and RubyGems.org match what your manifests and lockfiles declare, flagging drift between a committed lockfile and a live build, and extending visibility to private and internally mirrored package sources that public vulnerability feeds don't cover. For teams running Composer or Bundler in CI, that means fewer assumptions about lockfile freshness and clearer provenance on every dependency that ends up in a production artifact — complementing whatever vulnerability-matching tooling is already in the pipeline rather than replacing the need to understand how it works.

Never miss an update

Weekly insights on software supply chain security, delivered to your inbox.

Self-healing security runs on Safeguard.

Your first fix PR is minutes away.

No sales call required, even your agent can complete the purchase over MCP.