As a security automation engineer, I spend most of my days working with developers to scale and enhance our Application Security Posture through automation. Like most companies today, JupiterOne writes and utilizes open source software. And with that, we have to be mindful of the vulnerabilities in third party code dependencies. So today we will be answering 3 questions:
- What dependencies do we have with vulnerabilities
- What is the best approach to patching these dependencies
- Who is responsible for fixing these issues?
To answer these questions and provide solutions, we need 2 things - visibility and automation. JupiterOne provides the visibility and an engineer, such as myself, provides the automation.
Gaining Visibility Across Source Control Systems and Tools
Most people talk about using automation to keep a situation manageable. For example, companies will use automation to shift their security left, also known as DevSecOps. This process encourages continuous improvement by automating security processes in the initial part of the Software Development Lifecycle. Instead of waiting until software is ready for production to test the security of software, test it early, and test it often by scanning the code, dependencies, and for secrets when it’s committed to the code repository.
All source control systems support DevSecOps practices in some form or fashion. JupiterOne’s Data Model enables our queries to cover multiple source control systems and tools, as long as the integration or ingestion adheres to the same class model.
For example, if I search for a class called “Findings” “THAT RELATES TO” “CodeRepo”, the “Finding” will be a code or dependency vulnerability and the “CodeRepo” type will be github_repo. If this was Azure DevOps, the class would still be CodeRepo, but with a type of azure_devops_repo. By using classes, we are able to use a single query to return data from multiple source control systems.
Efficiently Tracking Vulnerabilities Across Systems and Repos
JupiterOne, from its inception to present day, values open source. All JupiterOne integrations are open source, so anyone can submit features and enhancements. When I started at JupiterOne, I was writing automation to secure our applications and GitHub organization. I noticed that branch protection policies were missing from our GitHub ingestion. As a security engineer, it was important to track branch protection rules.
So, I created a feature branch for the JupiterOne GitHub integration project to ingest branch protection rules as a “Rule” class. Now I can search for “Rules” “THAT RELATE TO” “CodeRepos”. I ran into this same situation with our static scanning. Because CodeQL directly integrates into GitHub and the GitHub APIs, I deployed a CodeQL GitHub workflow to all of our code repos for static scans. Then, I wrote a new feature for the GitHub integration to pull in CodeQL static scan data as “Finding(s)”. The integration team is currently reviewing my feature and it should be released soon.
Deploying CodeQL workflows to our (hundreds and hundreds of) repositories is a good segue into the second use case for automation in security: what happens when a massive overhaul needs to be performed to get us to a place where we can start implementing DevSecOps? This brings us to JupiterOne and the importance of establishing complete visibility as a prologue to your DevSecOps processes.
Ask yourself: are we even ready to implement DevSecOps or do we need to perform some clean up first? You can’t implement security and start blocking builds headed to production without understanding what you have and how you should get to where you need to be. Everything would grind to a halt.
How to Identify All Dependency Vulnerabilities
Before we start patching vulnerabilities, we need to find out what vulnerabilities we have. To do this we will check both our code as well as the code dependencies - just because we don’t have critical vulnerabilities showing up in our static scans for the code we write, doesn’t mean they don’t exist somewhere else. For better results we combine static security scanning with dependency scanning. This ensures that all dependencies - including build and development dependencies are covered as well. Build and development dependencies might not show up in production, but a compromised developer’s laptop or CI/CD environment can be just as devastating as an exploit in production. So, let’s run a check against all findings that are related to our CodeRepo.
That’s a huge number. But, that can include “Finding(s)” from critical all the way down to informational. So let’s narrow it down to just criticals and make sure we aren’t including archived repositories.
You’ll likely see a lot of issues. Again, we are searching for all findings - even build and development dependencies. Also note, these dependencies might not be direct dependencies, but dependencies of dependencies, of dependencies. So, we need to figure out how to tackle these.
Finding the Most Common Dependency Vulnerabilities
First, let’s see if the majority of these results are the same package that can be updated across the organization and put a big dent in that number.
Looks like there are a handful of packages that are the biggest contributor to the large vulnerability number. Almost 40% are caused by the top 2 packages.
Let’s take a look at which repos have json-schema.
How to Identify Code Owners to Triage Vulnerabilities
Now let’s adjust that query to find out who has access to the repos, so we can start contacting teams to find out what this package is used for.
Now, we can download a CSV of the output and start contacting teams.
If we aren’t sure who to contact, we can just check for the users:
How to Validate Code Owners Have Closed Dependency Vulnerabilities
At this point, we’ve contacted all the teams, and no one has an issue with trying to patch it. I write automation to login to all the repos, create a new branch, patch the code, and create a pull request. As part of my automation, I use a standard naming convention for the pull request (PR) title so I can query on it in JupiterOne. I let the developers know that the code is there and should be merged into the main branch by next week.
Next week comes around and it’s time to check to see who’s merged their code.
How many have merged:
Now, we adjust that query to see if there are any pull requests that have not been merged.
Again, which engineering groups do we contact to ensure the merge happens?
Validating Dependencies are Checked Before Merging
Finally, we get the numbers down to a manageable count, so I start rolling out workflows to all the repos to validate that critical dependencies are addressed before merging. There is a field in GitHub called “requiredStatusChecks” in the branch protection rules. So I’m going to query against that.
Here’s my list of repositories where the status check isn’t configured:
And, that is a day in the life of a JupiterOne Security Automation Engineer!
To learn more about how JupiterOne security uses JupiterOne, we recommend Erich Smith’s blog on How We Generate a Software Bill of Materials (SBOM) with CycloneDX.