Challenge 15: Dependency management and vulnerability scanning
This challenge covers dependency security workflows on both GitHub and Azure DevOps.
Exam skills
- Recommend package management tools including GitHub Packages and Azure Artifacts
- Design and implement package feeds and views for local and upstream packages
Scenario
Contoso's security team runs a quarterly audit and discovers that 3 of their 15 production microservices depend on a library with a critical CVE (CVE-2024-29041, an Express.js path traversal vulnerability). The issues are:
- No automated process detects vulnerable dependencies
- Developers are unaware which transitive dependencies carry risk
- There is no policy for license compliance (some teams accidentally included GPL-licensed code in proprietary services)
- Remediation takes weeks because nobody knows which services are affected
You must implement a comprehensive dependency management and vulnerability scanning strategy.
Tasks
Task 1: Configure Dependabot for automated dependency updates
Dependabot automatically opens pull requests to update dependencies on a schedule.
Step 1: Create the Dependabot configuration file
In your repository, create .github/dependabot.yml:
version: 2
updates:
# npm dependencies
- package-ecosystem: "npm"
directory: "/"
schedule:
interval: "weekly"
day: "monday"
time: "09:00"
timezone: "America/New_York"
open-pull-requests-limit: 10
reviewers:
- "contoso/backend-team"
labels:
- "dependencies"
- "automated"
commit-message:
prefix: "deps"
include: "scope"
groups:
dev-dependencies:
dependency-type: "development"
update-types:
- "minor"
- "patch"
production-minor:
dependency-type: "production"
update-types:
- "minor"
- "patch"
# NuGet dependencies
- package-ecosystem: "nuget"
directory: "/src/Contoso.Api"
schedule:
interval: "weekly"
open-pull-requests-limit: 5
ignore:
- dependency-name: "Microsoft.Extensions.*"
update-types: ["version-update:semver-major"]
# Docker base images
- package-ecosystem: "docker"
directory: "/"
schedule:
interval: "weekly"
labels:
- "docker"
- "dependencies"
# GitHub Actions
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "weekly"
labels:
- "ci"
- "dependencies"
Step 2: Verify Dependabot is active
After pushing the configuration:
gh api /repos/contoso/auth-service/vulnerability-alerts --method PUT
Check the status of Dependabot:
gh api /repos/contoso/auth-service/dependabot/alerts \
--jq '.[0:5] | .[] | {package: .security_advisory.summary, severity: .security_advisory.severity, state: .state}'
Task 2: Set up Dependabot security alerts
Security alerts differ from version updates. They trigger immediately when a new CVE is published that affects your dependency tree.
Step 1: Enable security alerts for the organization
gh api --method PUT /orgs/contoso/dependabot/alerts
Enable for all repositories:
gh api --method PUT /orgs/contoso \
--field dependabot_security_updates_enabled_for_new_repositories=true \
--field dependency_graph_enabled_for_new_repositories=true \
--field dependabot_alerts_enabled_for_new_repositories=true
Step 2: List current security alerts across repositories
gh api /orgs/contoso/dependabot/alerts \
--jq '.[] | select(.state == "open") | {repo: .repository.name, package: .dependency.package.name, severity: .security_advisory.severity, cve: .security_advisory.cve_id}'
Filter for critical and high severity alerts only:
gh api "/orgs/contoso/dependabot/alerts?severity=critical,high&state=open" \
--jq '.[] | "\(.repository.name): \(.dependency.package.name) - \(.security_advisory.severity) - \(.security_advisory.cve_id)"'
Step 3: Dismiss an alert (false positive)
gh api --method PATCH /repos/contoso/auth-service/dependabot/alerts/42 \
--field state=dismissed \
--field dismissed_reason=tolerable_risk \
--field dismissed_comment="This code path is not reachable in our configuration"
Task 3: Azure Artifacts and Defender for DevOps integration
Step 1: Enable Microsoft Defender for DevOps
Connect your Azure DevOps organization to Defender for Cloud:
az security devops azuredevopsorg create \
--name contoso-ado \
--resource-group rg-security \
--org-name contoso
Step 2: Configure the Microsoft Security DevOps Azure DevOps extension
Add the security scanning task to your Azure Pipeline:
trigger:
- main
pool:
vmImage: ubuntu-latest
steps:
- task: MicrosoftSecurityDevOps@1
displayName: 'Run security analysis'
inputs:
categories: 'dependencies'
tools: 'eslint,trivy'
- task: PublishBuildArtifacts@1
inputs:
pathToPublish: $(System.DefaultWorkingDirectory)/.gdn
artifactName: security-results
Step 3: Configure Azure Artifacts package vulnerability alerts
Enable package scanning on your Azure Artifacts feed:
az rest --method patch \
--uri "https://feeds.dev.azure.com/contoso/ContosoServices/_apis/packaging/feeds/contoso-packages?api-version=7.1-preview.1" \
--body '{
"badgesEnabled": true,
"hideDeletedPackageVersions": true
}'
Task 4: License compliance scanning
Step 1: Create a license policy with GitHub Actions
Create .github/workflows/license-check.yml:
name: License compliance check
on:
pull_request:
paths:
- 'package.json'
- 'package-lock.json'
- '**/*.csproj'
jobs:
license-check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install license checker
run: npm install -g license-checker
- name: Check npm licenses
run: |
license-checker --production --failOn \
"GPL-2.0-only;GPL-3.0-only;AGPL-3.0-only;SSPL-1.0" \
--summary
- name: Check for unknown licenses
run: |
UNKNOWN=$(license-checker --production --unknown | wc -l)
if [ "$UNKNOWN" -gt 1 ]; then
echo "ERROR: Found packages with unknown licenses"
license-checker --production --unknown
exit 1
fi
Step 2: NuGet license validation
For .NET projects, use dotnet-project-licenses:
dotnet tool install --global dotnet-project-licenses
dotnet-project-licenses \
--input ./src/Contoso.Api/Contoso.Api.csproj \
--output-directory ./license-report \
--banned-license-types ./banned-licenses.json
Create banned-licenses.json:
{
"banned": [
"GPL-2.0-only",
"GPL-3.0-only",
"AGPL-3.0-only",
"SSPL-1.0"
]
}
Task 5: Create a dependency review workflow in GitHub Actions
The dependency review action runs on pull requests and blocks merging if new dependencies introduce vulnerabilities or disallowed licenses.
Create .github/workflows/dependency-review.yml:
name: Dependency review
on:
pull_request:
branches: [main]
permissions:
contents: read
pull-requests: write
jobs:
dependency-review:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Dependency review
uses: actions/dependency-review-action@v4
with:
fail-on-severity: high
deny-licenses: GPL-2.0-only, GPL-3.0-only, AGPL-3.0-only
allow-ghsas: GHSA-xxxx-yyyy-zzzz
comment-summary-in-pr: always
warn-only: false
base-ref: ${{ github.event.pull_request.base.sha }}
head-ref: ${{ github.event.pull_request.head.sha }}
Task 6: Implement dependency allow and deny lists
Step 1: npm allow list with .npmrc
Restrict registries to only approved sources:
@contoso:registry=https://npm.pkg.github.com
registry=https://pkgs.dev.azure.com/contoso/ContosoServices/_packaging/contoso-packages/npm/registry/
This prevents developers from accidentally pulling packages from public npm directly.
Step 2: NuGet package source mapping
In nuget.config, map specific package patterns to specific sources:
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<packageSources>
<clear />
<add key="contoso-packages" value="https://pkgs.dev.azure.com/contoso/ContosoServices/_packaging/contoso-packages/nuget/v3/index.json" />
<add key="nuget.org" value="https://api.nuget.org/v3/index.json" />
</packageSources>
<packageSourceMapping>
<packageSource key="contoso-packages">
<package pattern="Contoso.*" />
</packageSource>
<packageSource key="nuget.org">
<package pattern="Microsoft.*" />
<package pattern="System.*" />
<package pattern="Newtonsoft.*" />
</packageSource>
</packageSourceMapping>
</configuration>
Step 3: GitHub Actions dependency deny list
Block specific known-bad packages from being introduced:
- name: Check for banned packages
run: |
BANNED_PACKAGES=("event-stream" "flatmap-stream" "ua-parser-js@0.7.29")
LOCKFILE="package-lock.json"
for pkg in "${BANNED_PACKAGES[@]}"; do
if grep -q "\"$pkg\"" "$LOCKFILE"; then
echo "ERROR: Banned package found: $pkg"
exit 1
fi
done
echo "No banned packages detected"
Break and fix
Scenario: Dependabot PR breaks build due to breaking change
Dependabot opens a PR to update @contoso/auth-sdk from 1.2.0 to 2.0.0. The CI pipeline fails with:
TypeError: AuthClient.validateToken is not a function
at Object.<anonymous> (src/middleware/auth.js:15:32)
The Dependabot PR is one of 12 open dependency updates. The team needs a strategy to handle this.
Show solution
Root cause: Dependabot updated a package across a major version boundary, introducing a breaking API change. The validateToken method was renamed to verifyToken in version 2.0.0.
Immediate fix: Close the Dependabot PR and pin to the safe version range:
gh pr close 847 --comment "Breaking change in v2.0.0. Pinning to 1.x until migration is complete."
Step 1: Prevent major version bumps via Dependabot config
Update .github/dependabot.yml to ignore major version updates for critical packages:
updates:
- package-ecosystem: "npm"
directory: "/"
schedule:
interval: "weekly"
ignore:
- dependency-name: "@contoso/auth-sdk"
update-types: ["version-update:semver-major"]
Step 2: Pin the version range in package.json
Use a tilde range to allow only patch updates:
{
"dependencies": {
"@contoso/auth-sdk": "~1.2.0"
}
}
Or use a caret range to allow minor and patch but not major:
{
"dependencies": {
"@contoso/auth-sdk": "^1.2.0"
}
}
Step 3: Create a migration plan for the major update
Track the breaking change as a separate work item rather than relying on Dependabot:
gh issue create \
--title "Migrate to @contoso/auth-sdk v2.0.0" \
--body "auth-sdk 2.0.0 renames validateToken to verifyToken. All services using auth middleware need updating." \
--label "breaking-change,dependencies" \
--assignee "@contoso/backend-team"
Step 4: Add CI checks that catch breaking changes early
Ensure your test suite covers the integration points. Add a smoke test:
const { AuthClient } = require('@contoso/auth-sdk');
const client = new AuthClient();
// Verify expected API surface exists
assert(typeof client.validateToken === 'function',
'auth-sdk API contract violated: validateToken must exist');
Step 5: Group related dependency updates
Configure Dependabot to group minor/patch updates so breaking changes are isolated:
groups:
contoso-internal:
patterns:
- "@contoso/*"
update-types:
- "minor"
- "patch"
This ensures major version bumps appear as individual PRs that are easy to identify and defer.
Knowledge check
1. A Dependabot configuration has 'schedule.interval: "weekly"' and 'open-pull-requests-limit: 5'. What happens when there are 8 outdated dependencies?
2. Which GitHub Actions action blocks PR merging when a new dependency introduces a known vulnerability?
3. In NuGet package source mapping, what happens if a package matches no configured pattern?
4. A team wants to automatically remediate critical CVEs but manually review all other dependency updates. Which Dependabot configuration achieves this?
Cleanup
Remove Dependabot configuration:
rm .github/dependabot.yml
git add -A && git commit -m "Remove Dependabot configuration"
git push origin main
Remove workflow files:
rm .github/workflows/license-check.yml
rm .github/workflows/dependency-review.yml
git add -A && git commit -m "Remove dependency scanning workflows"
git push origin main
Disable Dependabot alerts for the repository:
gh api --method DELETE /repos/contoso/auth-service/vulnerability-alerts
Remove the license checker tool:
npm uninstall -g license-checker
dotnet tool uninstall --global dotnet-project-licenses
Remove NuGet source mapping (restore original nuget.config):
git checkout HEAD -- nuget.config