Skip to main content
Version: v1

Dependency audit

A project carries 2 dependency trees, each audited by its own package manager: composer audit for the PHP packages and npm audit for the JavaScript packages. Both run locally and in the CI security audit workflow.

PHP dependencies

composer audit checks the installed packages against published security advisories and reports abandoned packages.

Composer 2.10.0 introduced a unified config.policy setting that controls both security auditing (reporting known security vulnerabilities and abandoned packages) and version blocking (refusing to install or update affected packages). Vortex uses it to keep vulnerabilities visible without blocking day-to-day dependency work.

Usage

Check your dependencies for security issues manually:

# Audit all installed packages
ahoy composer audit
# Audit only production dependencies (exclude dev)
ahoy composer audit --no-dev
# Audit packages from lock file (faster)
ahoy composer audit --locked
# Control abandoned package behavior
ahoy composer audit --abandoned=ignore # Skip abandoned packages
ahoy composer audit --abandoned=report # Show but don't fail

Configuration

Configure the policy in your composer.json under the config section:

{
"config": {
"policy": {
"advisories": {
"block": false,
"audit": "fail"
},
"abandoned": {
"audit": "report"
}
}
}
}

Vortex sets the following values:

  • advisories.block to false: security advisories don't block composer update, composer require, or composer remove, so dependency updates stay reproducible even when a new advisory is published upstream against an existing dependency. composer install resolves nothing from the lock file, so this setting never affects it. (Composer's own default is true.)
  • advisories.audit to fail: the composer audit command still fails when an advisory is found, keeping vulnerabilities visible locally and in CI.
  • abandoned.audit to report: abandoned packages are reported as warnings but don't fail the audit. (Composer's own default is fail.)

Each section accepts block (refuse affected versions during composer update/require/remove), audit (ignore, report, or fail for the composer audit command), and ignore/ignore-id/ignore-severity for assessed exceptions.

Composer 2.10+

config.policy requires Composer 2.10 or newer, which ships in the Vortex container images. On older Composer versions, use the legacy config.audit keys (block-insecure, abandoned) instead - they continue to work as a fallback.

Why advisories don't block dependency updates

Coupling dependency resolution to advisory publication makes updates non-deterministic. A newly published advisory against an already-installed dependency can fail every composer update or composer require - including work unrelated to security - until the advisory is assessed and ignored. Vortex decouples resolution from advisory publication by setting advisories.block to false while keeping advisories.audit at fail. This keeps updates reproducible while composer audit and the CI security audit workflow still surface vulnerabilities.

If your project requires dependency resolution to hard-stop on advisories - for example, a production site with strict supply-chain controls - set advisories.block to true.

Ignoring

When you've assessed an advisory and determined it doesn't affect your project, add it to the ignore-id list with a reason:

{
"config": {
"policy": {
"advisories": {
"ignore-id": {
"CVE-2024-1234": "Component is not used in our implementation.",
"GHSA-xxxx-yyyy-zzzz": "Patched via custom security fix."
}
}
}
}
}
Document your decisions

Record the reason for each ignored advisory in your Git commit message. This helps your team understand why a vulnerability was deemed acceptable and review the decision later.

Continuous integration

Vortex runs composer audit --locked in the security audit workflow, so the audited set is exactly what composer.lock pins. Because advisories.audit is fail, the audit reports vulnerabilities and the workflow fails when any are found - even though installs and updates aren't blocked.

Ignoring failures

Set the repository variable VORTEX_CI_COMPOSER_AUDIT_IGNORE_FAILURE to 1 to make the audit step run and report without failing that workflow - useful as a one-off bypass while a known advisory is being addressed.

➡️ See Ignore tool failures.

Where findings appear

In GitHub Actions, the audit findings are also uploaded to the repository's Security → Code scanning tab under the composer-audit category. Each advisory becomes an alert carrying its severity, its affected version range and a link to the advisory, located at the package's entry in composer.lock. Abandoned packages are reported there as well, at note severity, and so are dependency policy matches - including Composer's built-in malware policy, which is active by default. The audit fails on all 3.

Advisories already listed under ignore-id aren't uploaded, so the code scanning inventory holds the same set of findings the audit fails on.

➡️ See Code scanning.

Dismissing a finding

Dismissing an alert in the Security → Code scanning tab records a reason against that finding and removes it from the open list. That's the right tool for tracking an assessment.

It doesn't change what the audit does. composer audit still reports the advisory and still fails the workflow until the advisory is added to ignore-id as described in Ignoring. Dismiss the alert to record the assessment, and add the ignore-id entry to stop the build failing.

JavaScript dependencies

npm audit checks the packages resolved from package-lock.json against the advisories published in the npm registry.

A project has 2 JavaScript dependency trees, each with its own package.json and lock file. The root one is for the custom modules' tooling and the test runner, and the custom theme's one is for the front-end build. They're audited separately.

Usage

# Audit the root packages
ahoy cli "npm audit"
# Audit the theme packages
ahoy cli "npm audit --prefix=\${WEBROOT}/themes/custom/\${DRUPAL_THEME}"
# Audit from the lock file, without an installed tree
ahoy cli "npm audit --package-lock-only"

Severity threshold

npm fails the audit on any advisory by default. A development dependency tree of this size carries low-severity advisories in transitive packages at most times, which would block every build, so Vortex raises the floor to high in .npmrc:

audit-level=high

npm reads the project configuration from the directory of the package.json it audits, so both .npmrc files carry the setting - one in the project root and one in the custom theme. The accepted values are info, low, moderate, high, critical, and none; lower the value to widen what fails the build.

The threshold is deliberately not a Vortex variable: it belongs to npm's own configuration, where a project changes it without a template variable existing for it.

Ignoring

npm audit has no per-advisory ignore list, so an advisory is excluded by removing the affected version from the tree. When the advisory is in a transitive dependency and a patched version exists, pin it with an overrides entry in package.json:

{
"overrides": {
"vulnerable-package": "^2.1.4"
}
}

npm then resolves every dependent to the pinned version. Run npm install to update the lock file, and commit both files.

Document your decisions

Record the advisory that each overrides entry addresses in your Git commit message, the same as for the Composer exceptions described earlier.

Continuous integration

Vortex runs npm audit --package-lock-only in the security audit workflow for both dependency trees, so the audited set is exactly what the lock files pin and no installed tree is needed.

Ignoring failures

Set the repository variable VORTEX_CI_NPM_AUDIT_IGNORE_FAILURE to 1 to make both audit steps run and report without failing that workflow - useful as a one-off bypass while a known advisory is being addressed. The finding is still printed, so a tolerated advisory stays visible in the job log.

➡️ See Ignore tool failures.

Where npm findings appear

In GitHub Actions, both trees are uploaded to Security → Code scanning under separate categories: npm-audit for the root and npm-audit-theme for the custom theme. A finding can be traced back to the tree it came from. Each entry in a package's via list becomes its own alert, identified by its GHSA advisory, carrying the advisory's own CVSS score and located at the package's entry in that tree's package-lock.json.

Dismissing an alert records the assessment but doesn't stop the check failing; that still needs the affected version removed from the tree, as described in Ignoring.

➡️ See Code scanning.