catching outdated and insecure packages automatically
A dependency update checker regularly collects the state of outdated packages across multiple repositories and surfaces security advisories before they show up unpleasantly in an audit. This article shows how such a dependency update checker evaluates composer outdated and npm outdated with jq, classifies version jumps, and automatically ships the results as a report.
Table of contents
- 1. Why a dependency update checker pays off
- 2. composer outdated and npm outdated as a data source
- 3. Reading structured JSON output with jq
- 4. Classifying version jumps
- 5. Factoring in known security advisories
- 6. Generating a readable report
- 7. Notification via Slack webhook
- 8. Automation as a cron job and CI job
- 9. Dependency update checker compared to alternatives
- 10. Summary
- 11. FAQ
1. Why a dependency update checker pays off
Dependencies age faster in almost every project than teams actively track. Without a fixed process, the realization that a package has not received a security update for months is often left to chance, for instance when an external pentest finds the gap. A dependency update checker closes this gap by regularly and automatically checking which packages are outdated and how large the backlog really is.
For projects using both PHP with Composer and JavaScript with npm, as is common for Magento or Hyvä based shops, a unified dependency update checker covers both ecosystems at once. Instead of maintaining two separate tools, a Bash script combines the output of composer outdated and npm outdated into a single, consistent report.
The real value of a dependency update checker only emerges through automation: a weekly run that ships the results via Slack or email makes outdated dependencies visible without anyone having to actively remember to trigger the check manually.
2. composer outdated and npm outdated as a data source
Both Composer and npm ship built in commands that list outdated packages: composer outdated --format=json and npm outdated --json. A dependency update checker uses this structured JSON output instead of parsing the human readable table format, which is considerably more robust against format changes between versions.
It is important to handle both commands' return value behavior correctly: npm outdated intentionally returns a non zero exit code as soon as outdated packages are found, which would immediately end the script under set -e. A clean dependency update checker therefore explicitly catches this specific exit code instead of being caught off guard by set -e.
#!/usr/bin/env bash
# collect-outdated.sh — gather raw outdated package data from both ecosystems
set -euo pipefail
collect_composer_outdated() {
local project_dir="$1"
(cd "$project_dir" && composer outdated --direct --format=json 2>/dev/null) || true
}
collect_npm_outdated() {
local project_dir="$1"
# npm outdated exits 1 when packages are outdated — this is expected, not a failure
(cd "$project_dir" && npm outdated --json 2>/dev/null) || true
}
readonly PROJECT_DIR="${1:?Usage: collect-outdated.sh <project-dir>}"
collect_composer_outdated "$PROJECT_DIR" > /tmp/composer-outdated.json
collect_npm_outdated "$PROJECT_DIR" > /tmp/npm-outdated.json
echo "[OK] Raw outdated data collected for ${PROJECT_DIR}" >&2
3. Reading structured JSON output with jq
The raw JSON output of both tools has different structures: Composer returns an object with an installed array, npm returns a flat object with the package name as the key. A dependency update checker normalizes both formats with jq into one common, unified line format containing package name, current version, and available version.
This normalization is the decisive step that later makes every further evaluation, from version classification to report generation, independent of the original ecosystem. From this point on, the dependency update checker no longer needs to know whether a package came from Composer or npm.
#!/usr/bin/env bash
# normalize.sh — unify composer and npm outdated output into one line format
set -euo pipefail
normalize_composer() {
jq -r '.installed[]? | [.name, .version, .latest, "composer"] | @tsv' \
/tmp/composer-outdated.json
}
normalize_npm() {
jq -r 'to_entries[]? | [.key, .value.current, .value.latest, "npm"] | @tsv' \
/tmp/npm-outdated.json
}
{
normalize_composer
normalize_npm
} > /tmp/normalized-outdated.tsv
echo "[OK] Normalized $(wc -l < /tmp/normalized-outdated.tsv) outdated packages" >&2
4. Classifying version jumps
Not every outdated package is equally urgent. A dependency update checker should classify the version jump following semantic versioning: a patch update is usually low risk, a minor update should be checked soon, a major update often requires manual adjustments and its own test run. This classification helps teams focus on the changes that truly matter first, instead of losing track amid twenty patch updates.
The calculation works by having the dependency update checker compare the first digit of the current and available versions. If it differs, it is a major update. If the first digit stays the same but the second differs, it is a minor update. Everything else counts as a patch update.
#!/usr/bin/env bash
# classify.sh — categorize version jumps as patch, minor, or major
set -euo pipefail
classify_bump() {
local current="$1" latest="$2"
local cur_major cur_minor lat_major lat_minor
IFS='.' read -r cur_major cur_minor _ <<< "${current#v}"
IFS='.' read -r lat_major lat_minor _ <<< "${latest#v}"
if [[ "$cur_major" != "$lat_major" ]]; then
echo "major"
elif [[ "$cur_minor" != "$lat_minor" ]]; then
echo "minor"
else
echo "patch"
fi
}
while IFS=$'\t' read -r name current latest source; do
bump="$(classify_bump "$current" "$latest")"
printf '%s\t%s\t%s\t%s\t%s\n' "$name" "$current" "$latest" "$source" "$bump"
done < /tmp/normalized-outdated.tsv > /tmp/classified-outdated.tsv
5. Factoring in known security advisories
Beyond plain version status, a complete dependency update checker also pulls information about known security advisories. composer audit --format=json and npm audit --json each return a list of affected packages with severity. This information should be clearly highlighted in the report, regardless of whether the affected package was otherwise classified as only slightly outdated.
An important difference from plain version checking: a package can be security critical even though the version jump itself is only a patch update. A good dependency update checker therefore treats security advisories as their own, prioritized category, which always appears first in the report regardless of the bump classification.
#!/usr/bin/env bash
# audit.sh — pull known security advisories from both ecosystems
set -euo pipefail
audit_composer() {
local project_dir="$1"
(cd "$project_dir" && composer audit --format=json 2>/dev/null) || true
}
audit_npm() {
local project_dir="$1"
(cd "$project_dir" && npm audit --json 2>/dev/null) || true
}
readonly PROJECT_DIR="${1:?Usage: audit.sh <project-dir>}"
echo "=== Composer security advisories ==="
audit_composer "$PROJECT_DIR" | jq -r '.advisories // {} | to_entries[]? | .key'
echo "=== npm security advisories ==="
audit_npm "$PROJECT_DIR" | jq -r '.vulnerabilities // {} | to_entries[]? | "\(.key): \(.value.severity)"'
6. Generating a readable report
Raw TSV lines are unsuitable for daily use. A dependency update checker should turn the classified data into a readable Markdown document, organized with security advisories first, then major, minor, and finally patch updates. This order reflects actual urgency and spares readers from having to pick out the relevant lines from a long list themselves.
The Markdown output of a dependency update checker can be posted directly as a comment on a GitLab or GitHub issue, which integrates the report immediately into the existing team workflow without needing to run an additional dashboard.
#!/usr/bin/env bash
# generate-report.sh — turn classified data into a readable markdown report
set -euo pipefail
{
echo "# Dependency Update Report — $(date +%F)"
echo
for bump in major minor patch; do
echo "## ${bump^} updates"
echo
echo "| Package | Current | Latest | Source |"
echo "|---|---|---|---|"
awk -F'\t' -v b="$bump" '$5 == b { printf "| %s | %s | %s | %s |\n", $1, $2, $3, $4 }' \
/tmp/classified-outdated.tsv
echo
done
} > dependency-report.md
echo "[OK] Report written to dependency-report.md" >&2
7. Notification via Slack webhook
A report that only sits as a file on disk is rarely looked at actively. A dependency update checker only gains real value through active notification, for example via a Slack incoming webhook that posts a short summary with the number of security advisories and major updates directly into the team channel.
A simple curl call with a JSON payload is enough for the message itself. It is important to keep the message compact and only link to the full report, instead of squeezing the entire table into the chat, which tends to hurt overview rather than help it.
#!/usr/bin/env bash
# notify-slack.sh — post a compact summary to a Slack channel
set -euo pipefail
readonly WEBHOOK_URL="${SLACK_WEBHOOK_URL:?Set SLACK_WEBHOOK_URL}"
security_count="$(grep -c "CVE" dependency-report.md || true)"
major_count="$(awk -F'\t' '$5 == "major"' /tmp/classified-outdated.tsv | wc -l)"
payload=$(jq -n \
--arg sec "$security_count" \
--arg maj "$major_count" \
'{ text: ("Dependency check: \($sec) security advisories, \($maj) major updates pending. See dependency-report.md for details.") }')
curl -sf -X POST -H 'Content-Type: application/json' -d "$payload" "$WEBHOOK_URL" > /dev/null
echo "[OK] Slack notification sent"
8. Automation as a cron job and CI job
For the dependency update checker to actually run regularly, it belongs either in a cron job on the deployment server or as a scheduled job in the CI pipeline. A weekly run, for instance every Monday morning, gives the team enough lead time to react to critical updates before the next release window.
Important for the dependency update checker in a CI context: the job should never fail the build just because outdated packages were found, unless it is a critical security advisory. The task is informational, not blocking, otherwise the team quickly loses patience with constantly red pipelines over uncritical patch updates.
9. Dependency update checker compared to alternatives
There are SaaS solutions like Dependabot or Renovate that automate similar tasks and even solve them with automatic pull requests. A self built dependency update checker in Bash, by contrast, offers full control over format, channel, and criteria, without depending on an external service or its rate limits.
| Approach | Automatic PRs | Control over format | Suited for |
|---|---|---|---|
| Bash dependency update checker | No, reporting only | Full | Internal reports, Slack integration |
| Dependabot | Yes, automatic | Limited configurability | GitHub native projects |
| Renovate | Yes, automatic | Extensively configurable | Large monorepos, many ecosystems |
| Manual review | No | No structure | Very small projects |
For teams who want to decide themselves when and how an update gets rolled out, a dependency update checker without automatic PRs is often the more deliberate choice, especially in sensitive production environments like Magento shops with many custom extensions.
Mironsoft
Shell automation, dependency management, and security tooling
A dependency update checker for your projects?
We build a tailored dependency update checker for Composer and npm, with security auditing, classification, and automatic Slack notification.
Script development
A custom dependency update checker for your stack
Security auditing
Detect known security advisories in Composer and npm packages
Reporting
Weekly reports with Slack or email integration
10. Summary
A dependency update checker in Bash combines the built in commands composer outdated and npm outdated into one unified, prioritized overview of outdated packages. Classification by semantic versioning and factoring in security audits ensure the truly urgent cases do not get lost in a long list.
The decisive lever lies in automation: a dependency update checker that runs weekly and actively ships results via Slack prevents outdated dependencies from going unnoticed for months. For teams with multiple PHP and JavaScript projects, this significantly reduces manual review effort without handing control over the update timing to an external SaaS solution.
Dependency update checker in Bash, the essentials at a glance
Data source
composer outdated --format=json and npm outdated --json as a structured foundation.
Normalization
jq unifies both formats into one shared line structure, independent of the ecosystem.
Prioritization
Security advisories first, then major, minor, and patch, each classified by semantic versioning.
Notification
A compact Slack message linking to the full markdown report, weekly via cron or CI.