Skip to content

Dependency Management

Application dependencies likely represent our largest attack vector for security vulnerabilities. As a result, it is critical that we keep our project’s dependencies up to date to prevent exploitation. To achieve this, we use a hybrid approach of automated dependency checks via GitHub bots and manual audits.

Manual audits should be performed on a quarterly basis. They allow us to verify that our automated dependency checks are working as expected. This is important because automated checks are not perfect and may miss critical vulnerabilities.

  1. Local Setup Install the latest production version of the project locally and ensure it runs as expected. This allows us to verify it is working correctly before attempting to update the dependencies.

  2. Branching Create a new branch from production that will be used to update the dependencies.

  3. Audit Run a composer audit to identify any vulnerabilities.

    Terminal window
    composer audit
  4. Update Go through the list of vulnerabilities and update each dependency.

    Terminal window
    composer update <package> --with-dependencies
  5. Verify Fixes Once all vulnerabilities have been fixed, re-run composer audit to verify they have been resolved.

  6. Regression Testing Verify the project is still working as expected locally.

  7. Deployment Commit the changes and push to the branch. Create a Pull Request (PR) into production for review.

Updated package requires a higher PHP version
Section titled “Updated package requires a higher PHP version”

If running artisan commands fails and complains about the PHP version, you can identify the problematic package by running:

Terminal window
composer prohibits <package> <version>

This will show you all the packages that are incompatible with that version.

One solution is to specify the PHP version in the composer.json file and then run composer update <package> to install a compatible version.

composer.json
"config": {
"platform": {
"php": "8.3.17" // or whatever version you need
}
}
  1. Local Setup Install the latest production version of the project locally and ensure it runs as expected. This allows us to verify it is working correctly before attempting to update the dependencies.

  2. Branching Create a new branch from production that will be used to update the dependencies.

  3. Audit Run an audit to identify any vulnerabilities.

    Terminal window
    npm audit
  4. Update Go through the list of vulnerabilities and update each dependency.

    Terminal window
    npm audit fix
  5. Verify Fixes Once all vulnerabilities have been fixed, re-run npm audit or yarn audit to verify they have been resolved.

  6. Regression Testing Finally, try and build the project to confirm it is working as expected. Check out troubleshooting for common issues.

If you are gettting type errors in node_modules, this is likely because some of your packages are expecting different versions of a dependency. This is likely only an outdated type definition however, so you can set tsc to ignore these errors by adding the following to your tsconfig.json:

tsconfig.json
{
"compilerOptions": {
"skipLibCheck": true
}
}

Dependabot is a feature of GitHub used to generate automated pull requests updating dependencies for projects.

  • Checks for the latest version of a dependency that’s resolvable given a project’s other dependencies
  • Generate updated manifest and lockfiles for a new dependency version
  • Generate PR descriptions that include the updated dependency’s changelogs, release notes, and commits

To get the most out of Dependabot, we must review, fix and merge the changes it has made on a weekly basis. As our configuration examples always run at 4pm on Sundays, we should provision time on Mondays to implement dependency updates across our deployments.

We should have different Dependabot configurations depending on the tech stack, risk profile, dependency structure, workflow, and team expectations within a project.

In this dependabot.yml example, we:

  1. Split updates between composer and npm ecosystems.
  2. Use a weekly update schedule to run at 4pm on Sunday.
  3. Ignore breaking changes, like major framework upgrades (these should be planned).
  4. Group dependencies updates by usage, to improve the isolation of code changes.
dependabot.yml
version: 2
updates:
#
# =====================================
# Composer – backend (highest risk)
# =====================================
#
- package-ecosystem: "composer"
directory: "/"
schedule:
interval: "weekly"
day: "sunday"
time: "16:00"
timezone: "UTC"
open-pull-requests-limit: 5
rebase-strategy: "auto"
versioning-strategy: "increase-if-necessary"
labels:
- "dependencies"
- "composer"
- "backend"
ignore:
# Platform upgrades are always planned
- dependency-name: "php"
update-types: ["version-update:semver-major"]
# Framework upgrades are explicit roadmap items
- dependency-name: "laravel/framework"
update-types: ["version-update:semver-major"]
# Guaranteed blast-radius packages
- dependency-name: "stancl/tenancy"
update-types: ["version-update:semver-major"]
groups:
#
# Core framework – absolutely isolated
#
laravel-core:
patterns:
- "laravel/framework"
- "illuminate/*"
#
# Multitenancy & SaaS-critical infrastructure
#
tenancy-and-realtime:
patterns:
- "stancl/*"
- "laravel/reverb"
- "phrity/*"
- "ratchet/*"
#
# Observability, error reporting, monitoring
#
observability:
patterns:
- "sentry/*"
- "laravel/nightwatch"
#
# External services & integrations
#
integrations:
patterns:
- "laravel/forge-sdk"
- "league/*"
- "barryvdh/*"
- "coconutcraig/*"
- "intervention/*"
#
# Dev & test tooling – safest to batch
#
dev-dependencies:
dependency-type: "development"
update-types:
- "patch"
- "minor"
#
# =====================================
# npm – frontend (high churn, controlled)
# =====================================
#
- package-ecosystem: "npm"
directory: "/"
schedule:
interval: "weekly"
day: "sunday"
time: "16:00"
timezone: "UTC"
open-pull-requests-limit: 5
rebase-strategy: "auto"
versioning-strategy: "increase-if-necessary"
labels:
- "dependencies"
- "npm"
- "frontend"
ignore:
# React majors = explicit migration projects
- dependency-name: "react"
update-types: ["version-update:semver-major"]
- dependency-name: "react-dom"
update-types: ["version-update:semver-major"]
# Vite majors can break build + SSR
- dependency-name: "vite"
update-types: ["version-update:semver-major"]
# Tailwind majors affect entire UI
- dependency-name: "tailwindcss"
update-types: ["version-update:semver-major"]
groups:
#
# React & core ecosystem
#
react-core:
patterns:
- "react"
- "react-dom"
- "@types/react*"
#
# Inertia & Laravel bridge
#
inertia:
patterns:
- "@inertiajs/*"
- "laravel-vite-plugin"
- "@laravel/*"
#
# UI primitives (Radix, Headless UI, Tailwind)
#
ui-primitives:
patterns:
- "@radix-ui/*"
- "@headlessui/*"
- "tailwind*"
- "class-variance-authority"
- "clsx"
#
# Charts, motion & UX
#
ux-and-data-viz:
patterns:
- "recharts"
- "motion"
- "react-*"
- "sonner"
#
# Tooling & build chain
#
tooling:
patterns:
- "vite"
- "@vitejs/*"
- "typescript"
- "@biomejs/*"
- "dotenv"