Software Composition Analysis (SCA)
When I joined my previous fintech startup company as a DevSecOps engineer four years ago, we had a severe security incident that I'll never forget. An outdated version of a popular npm package in our e-commerce platform contained a vulnerability that allowed attackers to inject malicious code.
That painful experience taught me the critical importance of Software Composition Analysis (SCA). Today, I want to share my journey implementing SCA into our DevSecOps pipeline using GitLab, and how it transformed our approach to managing third-party dependencies.
Why I Learned to Stop Worrying and Love SCA
Before our security incident, like many developers, I'd casually add packages to projects with little thought beyond "does it solve my problem?" A quick npm install
or pip install
and I was off to the races. I didn't realize I was potentially introducing ticking time bombs into our codebase.
Software Composition Analysis (SCA), as I've come to appreciate, is essentially an X-ray machine for your codebase. It reveals all the open-source components and third-party libraries hiding in your applications, along with any vulnerabilities or license issues they might harbor.
The Day We Discovered What Was Hiding in Our Code
After our security incident, our first comprehensive SCA scan was eye-opening. I remember staring at the results in disbelief:
43% of our dependencies were outdated by at least two major versions
12 components had known critical vulnerabilities
8 libraries violated our company's licensing policies
3 packages were effectively abandoned by their maintainers
Even more shocking was finding components we didn't even know we were using! These were transitive dependencies—packages that our direct dependencies relied on.
Building Our SCA Strategy with GitLab
After evaluating several tools, I decided to implement SCA using GitLab's built-in Dependency Scanning capabilities. Here's the approach I developed and have refined over the past four years:
Step 1: Setting Up Basic Dependency Scanning
I started by adding dependency scanning to our .gitlab-ci.yml
file:
include:
- template: Security/Dependency-Scanning.gitlab-ci.yml
variables:
# Use custom Docker image for Node.js projects
DS_NODEJS_SCAN_IMAGE_SUFFIX: '-custom'
# Enable scanning for all supported package managers we use
DS_PYTHON_VERSION: 3
DS_JAVA_VERSION: 11
# We can customize dependency scanning job
dependency_scanning:
variables:
# Only alert on high and critical vulnerabilities at first
DS_SEVERITY_THRESHOLD: "high"
artifacts:
paths:
- gl-dependency-scanning-report.json
expire_in: 1 week
This basic setup immediately started catching issues, but I soon realized we needed more nuanced control.
Step 2: Creating Custom Policies for Different Project Types
Different projects have different risk profiles. Our public-facing e-commerce platform needed stricter controls than our internal admin tools. I implemented custom policies using GitLab's scanning profiles:
# Custom policy definition
dependency_scanning:
variables:
DS_DEFAULT_ANALYZERS: "gemnasium-maven,gemnasium-python,gemnasium-nodejs"
DS_EXCLUDED_PATHS: "tests/, docs/, examples/"
rules:
# High security requirements for payment-related services
- if: $CI_PROJECT_PATH =~ /payment-services/
variables:
DS_SEVERITY_THRESHOLD: "low"
DS_REMEDIATE: "true"
# Standard requirements for other projects
- if: $CI_COMMIT_BRANCH
variables:
DS_SEVERITY_THRESHOLD: "medium"
Step 3: Adding License Compliance Checks
Security vulnerabilities weren't our only concern. After our legal team raised concerns about open-source license compliance, I added license scanning:
include:
- template: Security/Dependency-Scanning.gitlab-ci.yml
- template: Security/License-Scanning.gitlab-ci.yml
license_scanning:
variables:
LICENSE_FINDER_CLI_OPTS: '--decisions-file=.license_decisions.yml'
artifacts:
reports:
license_scanning: gl-license-scanning-report.json
I created a .license_decisions.yml
file to whitelist approved licenses and explicitly approve/deny certain dependencies:
---
- - :approve
- lodash
- :who: "Jane Doe"
:why: "Reviewed by legal team and approved"
:when: 2024-05-15
- - :deny
- crypto-js
- :who: "John Smith"
:why: "License incompatible with our product"
:when: 2024-06-01
Step 4: Integrating with our Development Workflow
The final piece was making SCA results actionable within our workflow. I configured GitLab to:
Show vulnerabilities directly in merge requests
Block merges for critical issues
Create automatic issues for remediation
dependency_scanning:
artifacts:
reports:
dependency_scanning: gl-dependency-scanning-report.json
after_script:
- |
if [ -f "gl-dependency-scanning-report.json" ]; then
echo "Creating tickets for critical vulnerabilities..."
python3 ./scripts/create_vulnerability_tickets.py gl-dependency-scanning-report.json
fi
My custom Python script parsed the JSON report and created GitLab issues assigned to the appropriate teams.
Real-World Challenges and How We Overcame Them
Implementing SCA wasn't without challenges. Here's how we addressed the major ones:
1. False Positives
Early on, we were flooded with false positives—vulnerabilities that didn't apply to our usage context.
Solution: I created a vulnerability exception process. Team members could request exceptions with proper justification, which I'd review and add to our configuration:
variables:
DS_EXCLUDED_VULNERABILITIES: "CVE-2020-1234, CVE-2020-5678"
2. Legacy Projects
Our older projects had mountains of outdated dependencies that couldn't be updated overnight.
Solution: We implemented a phased approach, focusing first on critical vulnerabilities, then high, and so on. For each project, we created a dependency roadmap with realistic timelines.
3. Developer Resistance
Initially, some developers saw SCA as "yet another hurdle" slowing down development.
Solution: I created dashboards showing vulnerabilities caught and organized "vulnerability of the week" sessions where I'd explain recent findings in practical terms. Once developers saw how SCA prevented issues that would have caused production incidents, resistance faded.
The Business Impact of Our SCA Implementation
After fully implementing our SCA strategy, we saw dramatic improvements:
83% reduction in vulnerable dependencies across our codebase
Zero security incidents related to third-party code in the past two years
40% faster dependency updates due to clear visibility and prioritization
Successful SOC 2 compliance with minimal additional effort due to our comprehensive SBOM
Improved developer awareness about security and licensing risks
Lessons Learned and Best Practices
After four years of working with SCA tools, here are my top recommendations:
Start with high-risk projects: Focus your initial efforts on public-facing and critical applications
Integrate early in the development lifecycle: Catching issues in IDE or during commits is better than in CI
Create clear remediation paths: Don't just report issues—provide guidance on fixing them
Balance security with productivity: Tune your policies to avoid alert fatigue
Build a dependency governance process: Establish procedures for adding new dependencies
One practice that's been particularly effective is our "dependency Tuesday" — we dedicate time each week to update dependencies and address SCA findings.
Beyond Basic SCA: Where We're Heading Next
Our SCA journey continues to evolve. Here's what I'm working on implementing next:
Container scanning integration to analyze base images and installed packages
Runtime SCA to detect vulnerabilities in running applications
Automated remediation using GitLab's auto-fix capabilities
Supply chain integrity verification to ensure dependencies haven't been tampered with
Final Thoughts: SCA as a Foundation for DevSecOps
Looking back, implementing SCA was one of the most impactful security initiatives in our organization. Beyond just finding vulnerabilities, it built a culture of dependency awareness and responsibility.
If you're considering implementing SCA in your organization, I encourage you to start small, focus on critical applications, and integrate with your existing workflows using GitLab's tools. The peace of mind from knowing what's in your software—and that it's secure—is well worth the effort.
Remember, in today's world where applications are more assembled than written from scratch, SCA isn't just a security tool—it's an essential part of responsible software development.
Last updated