Supply Chain Security: The Attack Through Dependencies
AI generated
OWASP
0x00
Security · Supply Chain · Composer · npm
Supply Chain Security: The Attack Through Dependencies
When trust becomes the vulnerability

Every Composer and npm dependency is a silent trust contract with someone else's code. Typosquatting packages, hijacked maintainer accounts, and malicious install scripts inject attacks straight into build pipelines. This article covers real attack patterns and concrete countermeasures for PHP and Magento projects.

16 min read Composer · npm · Lockfiles · CI/CD Magento 2.4.8 · PHP 8.4

1. What supply chain security means for PHP and Magento projects

An average Magento project pulls in several hundred to a thousand transitive dependencies through Composer and npm. Every single one of them executes at build and runtime with the same privileges as the project's own code. Supply chain security deals precisely with this trust chain: the risk is not the project's own source code, but the sum of every foreign package it includes, plus the infrastructure through which those packages are distributed.

The reason this topic has gained urgency in recent years is simple: an attack on a single popular package automatically scales to every project that includes it. For attackers, that is economically more attractive than a direct attack on one specific target. For Magento operators, it means the organization's own security posture depends not only on its own code, but on the security discipline of thousands of unknown, uncontrolled third-party maintainers.

2. Typosquatting and compromised maintainer accounts

Typosquatting exploits typing mistakes during package installation: an attacker publishes a package named symfony/proces instead of symfony/process, or monolog/monolog-bundle instead of symfony/monolog-bundle, filled with malicious code that activates on install or execution. In npm this pattern has been especially widespread due to the sheer number of packages, with thousands of discovered typosquatting packages. Composer is somewhat more resilient thanks to its smaller, vendor-scoped naming structure, but it is not immune.

Far more dangerous are compromised maintainer accounts, because no typo is required. The real-world event-stream case in the npm ecosystem showed in 2018 how an attacker used social engineering to take over maintainer rights of a popular package and inject targeted malicious code that only activated for certain cryptocurrency wallets. The 2024 xz-utils attack showed the same pattern at the operating system level: a trust relationship built over years was exploited to inject a backdoor into a fundamental compression tool. Both cases demonstrate that reputation alone is not a reliable security signal.


# Composer: verify package names against typos before installing
composer show --all --name-only symfony/process 2>/dev/null || \
  echo "WARNING: package name does not exist as expected - check for typos"

# Audit currently installed dependencies for known security vulnerabilities
composer audit --format=json > audit-report.json

# npm equivalent for frontend build dependencies (e.g. Hyva Tailwind build)
npm audit --audit-level=high --json > npm-audit-report.json

3. Malicious postinstall scripts in npm and Composer

npm packages can define arbitrary JavaScript code in the scripts.postinstall field, which runs automatically on every installation without a developer ever reading it. Exactly this mechanism has been used in several documented attacks to grab environment variables, SSH keys, or cloud credentials from CI/CD environments and send them to external servers. Composer has a comparable, though less frequently abused, concept with scripts.post-install-cmd and scripts.post-package-install.

The fundamental problem: an install script runs with the same privileges as the calling process, often a CI runner with access to deployment secrets. Setting npm install --ignore-scripts as the default in CI pipelines and allowing install scripts only selectively for trusted, vetted packages drastically reduces this attack surface. Composer offers a similar tool with the allow-plugins configuration in composer.json: plugins that want to execute code at install time must be explicitly approved instead of running automatically.


{
  "name": "mironsoft/magento-project",
  "require": {
    "php": "~8.4.0",
    "magento/product-community-edition": "2.4.8"
  },
  "config": {
    "allow-plugins": {
      "magento/composer-root-update-plugin": true,
      "magento/composer-dependency-version-audit-plugin": true,
      "dealerdirect/phpcodesniffer-composer-installer": false
    },
    "audit": {
      "abandoned": "report"
    }
  },
  "scripts": {
    "post-install-cmd": [
      "@php -r \"echo 'Install scripts only enabled after manual review';\""
    ]
  }
}

4. Lockfile integrity: composer.lock and package-lock.json

A lockfile pins not only the exact version of every dependency, but also its cryptographic hash. composer.lock and package-lock.json must without exception be committed to version control, never end up in .gitignore. Without a lockfile, every build potentially installs a different version, which makes reproducible deployments impossible and opens a window in which a package version that has since been compromised gets installed unnoticed.

Equally important is how pull requests handle lockfile changes: a change to composer.lock that is not justified by a corresponding change in composer.json should always trigger a question in code review. composer install in CI pipelines should also be combined with the --no-scripts option in early validation steps, so install scripts only run after an explicit integrity check. npm ci instead of npm install is mandatory in CI environments because it installs strictly against the lockfile and aborts immediately on any deviation, instead of silently updating the lockfile.


#!/usr/bin/env bash
# ci-verify-lockfiles.sh - enforce lockfile integrity before the build
set -euo pipefail

echo "[CHECK] Validating composer.lock against composer.json"
composer validate --strict --no-check-publish

echo "[CHECK] Installing strictly against the lockfile, no scripts"
composer install --no-scripts --no-interaction --prefer-dist

echo "[CHECK] npm: reproducible install against package-lock.json"
npm ci --ignore-scripts

echo "[CHECK] Security audit after installation"
composer audit
npm audit --audit-level=high

echo "[OK] Lockfile integrity confirmed, scripts now enabled selectively"

5. Verifying package signatures and provenance

Provenance answers the question of whether a published package actually originates from the claimed source repository and build process. npm has supported signed provenance statements via Sigstore since 2023, which cryptographically prove that a package was built from a specific GitHub Actions workflow in a specific repository. npm audit signatures checks whether installed packages carry valid registry signatures.

In the PHP ecosystem, signature verification is less standardized, but Packagist displays links to the source repository, download counts, and maintenance status for every package, which serve as rough trust signals. More important here is the SLSA framework (Supply-chain Levels for Software Artifacts), which categorizes build processes into maturity levels and is increasingly being adopted by PHP projects through reproducible builds and signed release artifacts. Anyone with doubts about the origin of a critical package should diff the source code on GitHub directly against the archive published on Packagist instead of trusting it blindly.

6. Minimizing dependency surface area as a defense

The most effective defense against supply chain attacks is not perfectly vetting every single dependency, but simply having fewer dependencies. Every additional package additively increases the attack surface, often disproportionately due to its own transitive dependencies. A single npm package for a trivial function like date formatting can easily pull in fifty more packages, any one of which can potentially be compromised.

In practice, that means asking before every new dependency whether the functionality could not be covered with a few lines of custom code or an already existing dependency. composer show --tree and npm ls --all reveal the actual scope of transitive dependencies, which is easily underestimated when just looking at composer.json or package.json. Tools like composer-unused additionally identify packages that are installed but never used in the code, representing pure risk without benefit.

7. Magento Marketplace extensions and third-party modules

Magento projects have an additional supply chain dimension that pure PHP projects do not: third-party extensions from the Magento Marketplace or private Composer repositories. These modules run with full application privileges inside Magento, including database access, and are rarely reviewed with the same rigor as core dependencies. A compromised payment module or tracking plugin can directly capture credit card data or customer data without the compromise being noticed during normal operation.

Before installing a new extension, it is worth reviewing the source code for suspicious patterns: outbound HTTP requests to unknown domains, eval() calls, obfuscated code, or unusually broad ACL permission requests. Magento-specific static analysis tools like magento/magento-coding-standard and the Marketplace's own technical review process cover part of this, but they do not replace manual review for security-critical modules such as payment or auth integrations.

8. Hardening CI/CD and responding to compromised packages

CI/CD pipelines are themselves part of the supply chain and are targeted deliberately because they often have broad access to deployment secrets and production environments. GitHub Actions workflows that reference third-party actions via a tag like @v3 instead of the full commit SHA are vulnerable to that tag later being repointed to a malicious commit. Pinning to the full SHA hash prevents exactly that.

Automated update tools like Dependabot or Renovate close known vulnerabilities promptly, but should never deploy to production automatically without the CI pipeline running the updated dependencies through the same audit, test, and lockfile validation process as any manual change. A separate, isolated CI environment without access to production secrets for the pure build and test step reduces the damage should a compromised dependency ever slip through automated checks.


# .github/workflows/security-audit.yml
name: Supply Chain Audit

on: [pull_request]

jobs:
  audit:
    runs-on: ubuntu-latest
    permissions:
      contents: read
    steps:
      # Third-party actions pinned to full commit SHA, not a mutable tag
      - uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 # v4.1.1

      - uses: shivammathur/setup-php@2cb9b829437ee246e9b3cac53555a39208b805be # v2.30.0
        with:
          php-version: '8.4'

      - name: Verify lockfile integrity
        run: composer validate --strict --no-check-publish

      - name: Install without executing scripts
        run: composer install --no-scripts --no-interaction

      - name: Run dependency audit
        run: composer audit

      - name: Frontend dependency audit (Hyva Tailwind build)
        run: npm ci --ignore-scripts && npm audit --audit-level=high

If an installed dependency is reported as compromised, reaction speed matters more than a perfect analysis. The first step is always to determine whether the affected version is actually referenced in the project's own lockfile, using composer why-not vendor/package version or npm ls vendor-package. That is followed immediately by switching to a cleaned version or, if none is available, temporarily replacing it with a fork or a functional alternative.

In parallel, every secret that the affected build or runtime process had access to must be rotated, regardless of whether actual misuse has already been proven. Logs from CI runs that executed with the compromised version should be checked for unusual outbound network connections. A documented incident response plan that clearly defines who is notified in a supply chain incident and which systems get isolated significantly shortens reaction time compared to an improvised response.


<?php

declare(strict_types=1);

namespace Mironsoft\SupplyChainAudit\Console;

use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;

/**
 * CLI command to compare locked package hashes against a known-good baseline
 * and flag any package that changed outside of an intentional composer update.
 */
class VerifyLockfileIntegrityCommand extends Command
{
    private const BASELINE_FILE = 'var/security/composer-lock-baseline.json';

    /**
     * Compares the current composer.lock content hash to the last approved baseline.
     *
     * @param InputInterface $input Console input, unused in this command.
     * @param OutputInterface $output Console output for reporting results.
     * @return int Exit code, 0 on match, 1 on unexpected drift.
     */
    protected function execute(InputInterface $input, OutputInterface $output): int
    {
        $lockData = json_decode((string) file_get_contents('composer.lock'), true, 512, JSON_THROW_ON_ERROR);
        $currentHash = hash('sha256', (string) json_encode($lockData['packages'] ?? []));

        if (!file_exists(self::BASELINE_FILE)) {
            $output->writeln('<comment>No baseline present, storing current hashes.</comment>');
            file_put_contents(self::BASELINE_FILE, json_encode(['hash' => $currentHash]));
            return Command::SUCCESS;
        }

        $baseline = json_decode((string) file_get_contents(self::BASELINE_FILE), true, 512, JSON_THROW_ON_ERROR);

        if ($baseline['hash'] !== $currentHash) {
            $output->writeln('<error>Lockfile drift detected, manual review required.</error>');
            return Command::FAILURE;
        }

        $output->writeln('<info>Lockfile matches the approved baseline.</info>');
        return Command::SUCCESS;
    }
}

9. Supply chain risks compared directly

The following overview organizes the most common supply chain risks by attack vector, typical symptom, and the most effective countermeasure for PHP and Magento projects.

Attack vector Insecure behavior Secure countermeasure Effect
Typosquatting Copying package names from search results Manually verify vendor names against Packagist Prevents installing the wrong package
Postinstall scripts npm install with no restrictions Set --ignore-scripts as the CI default Blocks automatic code execution
Lockfile drift composer.lock not committed Lockfile mandatory in version control Reproducible, vetted builds
Compromised action Reference via @v3 tag Pin to the full commit SHA Tag hijacking becomes ineffective
Bloated dependency trees Pulling a package for a trivial function Actively minimize the dependency surface Less attack surface overall

Mironsoft

Security audits, dependency hardening, and CI/CD hardening for Magento projects

Want to uncover supply chain risks in your project?

We review your Composer and npm dependencies, harden your CI/CD pipeline against supply chain attacks, and set up automated audit processes that run on every deployment.

Dependency audit

Full analysis of dependency trees for known and structural risks

CI/CD hardening

Action pinning, lockfile validation, and script restrictions in your pipeline

Incident response

Documented response plan for compromised packages and secret rotation

10. Summary

Supply chain security shifts the security focus from a project's own code to the entire chain of third-party packages a Magento project depends on. Typosquatting and compromised maintainer accounts show that reputation alone is not a reliable trust signal. Lockfiles, consistently committed and strictly validated in CI, prevent a package version that has since been compromised from being installed unnoticed. Postinstall scripts should be disabled by default and enabled only selectively for vetted packages.

The single most effective measure remains minimizing the dependency surface: fewer packages mean fewer potential attack vectors, regardless of how well each individual one is vetted. CI/CD pipelines with pinned actions, isolated secrets, and automated audit steps close the gap between a known vulnerability and its resolution in production.

Supply Chain Security: The key takeaways

Know the attack patterns

Typosquatting and hijacked maintainer accounts are real, documented attack vectors, not theory.

Commit lockfiles

composer.lock and package-lock.json always in version control, validated in CI with npm ci.

Restrict scripts

--ignore-scripts by default, exceptions only after manually reviewing the package.

Minimize the surface

Question every dependency, visualize the transitive tree with composer show --tree.

11. FAQ: Supply Chain Security for Magento Projects

1What is supply chain security in software?
All measures that secure the trust chain between a project and the third-party packages it includes via Composer, npm, or other registries.
2What is typosquatting in package names?
Packages with names deliberately similar to popular packages, designed to profit from typing mistakes and inject malicious code.
3Why are compromised maintainer accounts more dangerous?
Malicious code lands directly in an already trusted package, no typo needed. Existing users get it through a regular update.
4Why must composer.lock and package-lock.json be in the repository?
They pin exact versions and hashes. Without them, every build potentially installs a different, unvetted version.
5What makes a postinstall script dangerous?
Runs automatically with the privileges of the calling process, often a CI runner with access to secrets, without the code ever being read.
6How do I verify the origin of an npm package?
npm audit signatures checks registry signatures. Since 2023, signed provenance statements via Sigstore are also available.
7Why is minimizing the dependency surface so effective?
Every additional dependency additively increases the attack surface, often disproportionately due to its own transitive dependencies.
8Are Magento Marketplace extensions a particular risk?
Yes, they run with full application privileges including database access and are reviewed less rigorously than core dependencies.
9What does pinning to a commit SHA in CI pipelines mean?
Referencing via the full, immutable commit hash instead of a mutable tag like @v3, preventing tag hijacking.
10What is the first step after a compromised package is reported?
Check whether the version is referenced in the project's own lockfile, then switch immediately and rotate all affected secrets.