Build a DevSecOps Pipeline
Automate security scanning for secrets, CVEs, and code vulnerabilities.
Introduction
30 Second Summary
Security breaches caused by leaked API keys and vulnerable dependencies cost companies millions every year. The best engineering teams catch these issues automatically, before code ever reaches production.
In this project, you will build an automated security pipeline using GitHub Actions that scans your code for leaked secrets and vulnerable dependencies every time you push.
What You'll Build
A DevSecOps pipeline with two parallel security scanning jobs that run automatically on every push to your GitHub repository.
By the end of this project, you'll have:
- A GitHub Actions workflow that triggers parallel security scans on every push and pull request.
- A secrets detection scan using Gitleaks that catches hardcoded credentials in your code.
- A dependency vulnerability scan using Trivy that finds known CVEs and reports them to GitHub's Security tab via SARIF.
- Secret Mission: Add a SAST job with Semgrep to catch code-level vulnerabilities like SQL injection patterns.
Are there any prerequisites?
Nope. This project includes full setup instructions for every tool you need. You just need a free GitHub account and a computer with a terminal.
Set Up Your GitHub Repository
Every DevSecOps pipeline needs a home. Your pipeline will live inside a GitHub repository alongside the code it protects.
In this step, you'll create a public repository, clone it to your machine, and set up the folder structure for both your application code and your security pipeline.
In this step, get ready to:
- Create a public GitHub repository and clone it locally.
- Initialize a Node.js project with intentionally vulnerable dependencies.
- Create the directory structure for your application code and pipeline.
Create a new public GitHub repository
Your security pipeline will run inside GitHub Actions. For this to work, you need a public repository on GitHub.
- Open github.com in your browser.
- Click the + icon in the top-right corner, then select New repository.
- Enter devsecops-demo in the Repository name field.
- Select Public for the visibility.
- Leave all other checkboxes unchecked (no README, no .gitignore, no license).
- Click Create repository.
Why a public repository?
On GitHub Free, uploading SARIF scan results to the Security tab only works on public repositories. Keeping the repo public ensures you can view all your vulnerability findings in one centralized dashboard later.
GitHub shows you a quick setup page after creating the repository. You'll see a URL ending in .git at the top of the page.
- Copy the HTTPS clone URL shown on the quick setup page.
Clone the repository to your machine
Cloning downloads a copy of the repository to your local machine so you can add files and push them back to GitHub.
- Open your terminal. Press Cmd+Space (macOS) or the Windows key (Windows) to open your search bar.
- Type Terminal (macOS) or PowerShell (Windows) and press Enter to open it.
- Move to your Desktop by running this command:
cd ~/Desktop
- Clone your repository by running this command:
git clone [[REPO_URL="https://github.com/YOUR-USERNAME/devsecops-demo.git"]]
- Move into the cloned repository by running:
cd devsecops-demo
- Confirm you're inside the devsecops-demo folder by running:
ls
The folder should be empty (or show only a hidden .git directory). That's expected for a fresh repository.
Seeing 'repository not found'?
- Double-check that you copied the HTTPS URL from the quick setup page, not the SSH URL.
- Make sure the repository name in the URL matches devsecops-demo exactly (no typos or extra characters).
- Confirm you're signed into the correct GitHub account in your browser.
Help me troubleshoot why my git clone command isn't working.
Initialize the Node.js project
Your pipeline will scan a Node.js application for vulnerabilities. The first step is initializing the project with a package.json file that declares your dependencies.
- Initialize a Node.js project by running this command:
npm init -y
What does this command do?
The npm init -y command creates a package.json file with default values. The -y flag skips the interactive prompts and accepts all defaults.
The default package.json doesn't have the structure or dependencies you need. You'll replace its contents with a version that includes an intentionally outdated dependency for testing.
- Open package.json in your code editor and replace its entire contents with this code:
{
"name": "devsecops-demo",
"version": "1.0.0",
"description": "A deliberately vulnerable app for DevSecOps pipeline testing",
"main": "src/app.js",
"scripts": {
"start": "node src/app.js"
},
"dependencies": {
"express": "4.17.1"
}
}
What does this code do?
- The main field points to src/app.js, which is where your application code will live.
- The start script defines how to run the application.
- The express dependency is pinned to version 4.17.1, a 2021 release with known security vulnerabilities. This is intentional. Your pipeline will detect these vulnerabilities later.
- Save package.json.
JSON syntax errors?
- Make sure every string is wrapped in double quotes, not single quotes.
- Check that there are no trailing commas after the last item in any object or array.
- Confirm you replaced the entire file contents, not just pasted below the existing content.
Help me fix my package.json file.
Create the directory structure
Your project needs two directories. The src/ folder holds your application code. The .github/workflows/ folder is where GitHub Actions looks for pipeline definitions.
macOS
- Create both directories by running these commands:
mkdir src
mkdir -p .github/workflows
- Confirm the directories were created by running:
ls -a
You should see src, .github, and package.json in the output.
Windows
- Create both directories by running these commands:
mkdir src
New-Item -ItemType Directory -Path .github/workflows -Force
- Confirm the directories were created by running:
Get-ChildItem -Force
You should see src, .github, and package.json in the output.
Don't see the .github folder?
- Folders starting with a dot are hidden by default. On macOS/Linux, the -a flag in ls -a shows them. On Windows, Get-ChildItem -Force shows hidden items.
- If you're in the wrong directory, run pwd to check. You should be inside devsecops-demo.
Help me figure out why my directory structure isn't showing up correctly.
✔️ Awesome, I've got everything!
Great. Double check you've saved package.json and confirmed both directories exist.
ⓧ I'd like to double check the full code
Here is the complete package.json you should have at this point:
{
"name": "devsecops-demo",
"version": "1.0.0",
"description": "A deliberately vulnerable app for DevSecOps pipeline testing",
"main": "src/app.js",
"scripts": {
"start": "node src/app.js"
},
"dependencies": {
"express": "4.17.1"
}
}
Your repository is ready with the right structure in place. Next up, you'll create a deliberately vulnerable application that gives your security pipeline something to catch.
Create a Deliberately Vulnerable Application
Your repository is set up with the project structure in place. Now you need something for the security pipeline to actually scan.
In this step, you'll create a Node.js application that contains deliberate security mistakes. These are real-world anti-patterns that developers accidentally ship to production every day. Your pipeline will catch them automatically in later steps.
In this step, get ready to:
- Create src/app.js with intentional security anti-patterns.
- Install the project dependencies.
- Verify the project structure before building the pipeline.
Create the vulnerable application file
Security scanners need something to find. You'll plant realistic vulnerabilities that mirror common mistakes developers make in real codebases: hardcoded credentials that should be in environment variables.
- Open src/app.js in your code editor (create it inside the src/ folder if it doesn't exist yet).
- Paste the following code into the file:
const express = require('express');
const app = express();
// WARNING: This is intentionally vulnerable for demo purposes
const API_KEY = "AKIAIOSFODNN7EXAMPLE";
const DB_PASSWORD = "SuperSecret123!";
app.get('/', (req, res) => {
res.json({ status: 'running', version: '1.0.0' });
});
app.get('/data', (req, res) => {
// Simulating a database connection with hardcoded credentials
const connectionString = `postgres://admin:${DB_PASSWORD}@localhost:5432/mydb`;
res.json({ message: 'Data endpoint', db: 'connected' });
});
const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
console.log(`Server running on port ${PORT}`);
});
What does this code do?
This is a simple Express server with two deliberate security vulnerabilities planted inside it:
- API_KEY is a hardcoded AWS-style access key. Gitleaks will flag this because it matches known credential patterns.
- DB_PASSWORD is a hardcoded database password used in a connection string. This is exactly the kind of secret that should live in an environment variable, never in source code.
- The two routes (/ and /data) give the app just enough functionality to look realistic.
- Save src/app.js.
File not saving?
- Make sure you're saving inside the src/ directory, not the repository root. The file path should be src/app.js.
- Check that the src/ folder exists. If not, create it first.
Help me create src/app.js in the right location
Install the project dependencies
Your package.json already lists express@4.17.1 as a dependency. This version was released in 2021 and has multiple known CVEs that Trivy will detect later.
- Install the dependencies by running this command:
npm install
- Confirm the installation created the expected files by running:
ls
You should see node_modules and package-lock.json listed alongside your existing files.
Why use an outdated dependency?
Express 4.17.1 has multiple documented vulnerabilities in its dependency chain. When Trivy scans your project later, it cross-references every installed package against a database of known CVEs. Using an outdated version guarantees the scanner has something to find.
npm install failing?
- Make sure you're running the command from the devsecops-demo folder (the repository root where package.json lives).
- If you see a JSON parse error, double-check that your package.json has no trailing commas or missing brackets.
Help me fix my npm install error
Verify the project structure
Before building the pipeline, confirm that all the pieces are in place. The security scanners need specific files to exist.
- Check your full project structure by running:
ls -R src/ .github/
You should see src/app.js and the empty .github/workflows/ directory listed in the output.
✔️ Awesome, I've got everything!
Great. Double check you've saved your file.
ⓧ I'd like to double check the full code
Here is the complete src/app.js file:
const express = require('express');
const app = express();
// WARNING: This is intentionally vulnerable for demo purposes
const API_KEY = "AKIAIOSFODNN7EXAMPLE";
const DB_PASSWORD = "SuperSecret123!";
app.get('/', (req, res) => {
res.json({ status: 'running', version: '1.0.0' });
});
app.get('/data', (req, res) => {
// Simulating a database connection with hardcoded credentials
const connectionString = `postgres://admin:${DB_PASSWORD}@localhost:5432/mydb`;
res.json({ message: 'Data endpoint', db: 'connected' });
});
const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
console.log(`Server running on port ${PORT}`);
});
And here is the complete package.json (unchanged from the previous step):
{
"name": "devsecops-demo",
"version": "1.0.0",
"description": "A deliberately vulnerable app for DevSecOps pipeline testing",
"main": "src/app.js",
"scripts": {
"start": "node src/app.js"
},
"dependencies": {
"express": "4.17.1"
}
}
Your vulnerable app is ready and waiting to be scanned. Next up, you'll build the first layer of your security pipeline: a secrets detection job that catches those hardcoded credentials automatically.
Build the Secrets Detection Job
Your vulnerable application is ready with hardcoded credentials sitting in plain sight. Now you need an automated guard that catches these secrets before they ever reach production.
GitHub Actions lets you define workflows that run automatically on every push or pull request. In this step, you'll create your first security job that scans every commit for leaked credentials using Gitleaks, a popular open-source secrets detection tool.
In this step, get ready to:
- Create the workflow file with trigger configuration.
- Add a secrets detection job using Gitleaks.
- Push your code and verify the scan catches hardcoded credentials.
Create the workflow trigger configuration
A GitHub Actions workflow starts with a trigger definition. Triggers tell GitHub when to run your security scans.
- Open .github/workflows/security.yml in your code editor. This file is empty right now.
- Add the following workflow trigger configuration:
name: DevSecOps Pipeline
on:
push:
branches: [main]
pull_request:
branches: [main]
permissions:
contents: read
security-events: write
What does this configuration do?
The on: block defines when the workflow runs. Here it triggers on every push to main and on every pull request targeting main.
The permissions: block grants the workflow read access to your code and write access to the Security tab. You'll use security-events: write in the next step when uploading scan results.
- Save .github/workflows/security.yml.
Add the secrets detection job
Gitleaks scans your entire git history for patterns that look like credentials. It catches API keys, passwords, and tokens that were accidentally committed at any point in your repo's lifetime.
- In .github/workflows/security.yml, add the following job definition below the permissions block:
jobs:
secrets-scan:
name: Secrets Detection
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Run Gitleaks
uses: gitleaks/gitleaks-action@v2
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
What does this job do?
The secrets-scan job runs on a fresh Ubuntu machine provided by GitHub. It has two steps.
- fetch-depth: 0 tells the checkout action to clone the full git history, not just the latest commit. Gitleaks needs this to scan all historical commits for leaked secrets.
- gitleaks/gitleaks-action@v2 runs the Gitleaks scanner. The GITHUB_TOKEN is a built-in secret that GitHub automatically provides to every workflow run.
Why Gitleaks over other tools?
Gitleaks has a simpler GitHub Action setup compared to alternatives like TruffleHog. It works free for personal account repositories without requiring a license key. For organization-owned repos, a GITLEAKS_LICENSE is required, but for this personal learning project you're all set.
- Save .github/workflows/security.yml.
✔️ Awesome, I've got everything!
Great. Double check you've saved your file before moving on.
ⓧ I'd like to double check the full code
name: DevSecOps Pipeline
on:
push:
branches: [main]
pull_request:
branches: [main]
permissions:
contents: read
security-events: write
jobs:
secrets-scan:
name: Secrets Detection
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Run Gitleaks
uses: gitleaks/gitleaks-action@v2
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
Push the code and verify the secrets scan
Your workflow is ready. Now push everything to GitHub so the pipeline triggers automatically.
- Stage, commit, and push your code by running these commands in your terminal:
git add .
git commit -m "Add secrets detection workflow"
git push origin main
What does this do?
These commands stage all your files, create a commit, and push to the main branch on GitHub. Because your workflow triggers on pushes to main, the security scan starts running immediately.
- Open your repository on GitHub in your browser.
- Click the Actions tab at the top of the repository page.
- Click on the workflow run named Add secrets detection workflow (this matches your commit message).
- Click on the Secrets Detection job to expand its logs.
You should see the workflow run has failed with a red X. This is expected. Gitleaks detected the hardcoded credentials in src/app.js and exited with code 1 to block the build.
What should I see in the logs?
In the Run Gitleaks step output, you'll see a summary table showing which secrets were detected. It includes details like the rule ID, the commit where the secret appeared, and the file and line number.
The AKIAIOSFODNN7EXAMPLE key pattern matches Gitleaks' built-in rules for AWS access keys. Your pipeline just proved it can catch leaked credentials automatically.
Don't see the workflow running?
- Make sure you pushed to the main branch. If your default branch is named master, update the branch name in your workflow file to match.
- Check that .github/workflows/security.yml is in the correct directory path. The .github folder starts with a dot.
- Verify your YAML indentation. YAML is whitespace-sensitive. Every nested level uses exactly 2 spaces.
Why isn't my GitHub Actions workflow showing up?
Your first security layer is live. Every future push will now be scanned for hardcoded secrets. Next up, you'll add a second scan that checks your dependencies for known vulnerabilities.
Add Dependency Vulnerability Scanning
Your secrets detection job is running and catching hardcoded credentials. Now you will add a second layer of security that scans your project's dependencies for known vulnerabilities.
Outdated packages often contain publicly disclosed security flaws called CVEs. Trivy is an open-source vulnerability scanner that checks your dependency tree against a constantly updated database of known CVEs. You will configure it to run as a parallel job in your pipeline.
In this step, get ready to:
- Add a dependency scanning job using Trivy to your workflow.
- Configure SARIF output and upload results to the GitHub Security tab.
- Push and verify Trivy detects vulnerabilities in your outdated Express dependency.
Add the Trivy vulnerability scanner job
The secrets-scan job catches credentials, but it does not check whether your packages have known security flaws. A second job running in parallel covers that gap.
- Open .github/workflows/security.yml in your editor.
- Find the end of the secrets-scan job (after the GITHUB_TOKEN line).
- Add the following dependency-scan job below the secrets-scan job by copying this code:
dependency-scan:
name: Dependency Vulnerability Scan
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Run Trivy vulnerability scanner
uses: aquasecurity/trivy-action@v0.36.0
with:
scan-type: 'fs'
scan-ref: '.'
format: 'sarif'
output: 'trivy-results.sarif'
severity: 'CRITICAL,HIGH'
What does this code do?
This defines a new job called dependency-scan that runs on its own runner, in parallel with secrets-scan. Jobs in GitHub Actions run in parallel by default unless you add dependency constraints.
- scan-type: 'fs' tells Trivy to scan the filesystem (your project files and node_modules) instead of a container image.
- severity: 'CRITICAL,HIGH' filters results to only the most serious vulnerabilities, keeping the output actionable.
- format: 'sarif' and output: 'trivy-results.sarif' write the results in SARIF format, which GitHub can display in its Security tab.
- Save security.yml but do not push yet. You will add the upload step next.
What is SARIF?
SARIF (Static Analysis Results Interchange Format) is an open standard for representing the output of static analysis tools. GitHub parses SARIF files and displays the findings as code scanning alerts in your repository's Security tab.
Using SARIF gives you a centralized dashboard for all security findings, regardless of which scanner produced them.
Upload scan results to the GitHub Security tab
Trivy now writes a SARIF file, but GitHub will not display it until you explicitly upload it. The github/codeql-action/upload-sarif@v4 action handles that upload.
- In the same security.yml file, add the following step directly below the Trivy step (inside the dependency-scan job's steps: list):
- name: Upload Trivy scan results to GitHub Security tab
uses: github/codeql-action/upload-sarif@v4
if: always()
with:
sarif_file: 'trivy-results.sarif'
category: 'trivy-dependency-scan'
What does this step do?
- if: always() ensures the upload runs even if Trivy reports vulnerabilities (which would normally set a non-zero exit code and skip subsequent steps).
- sarif_file: 'trivy-results.sarif' points to the file Trivy wrote in the previous step.
- category: 'trivy-dependency-scan' labels this set of results so GitHub can distinguish them from other scanners (like the Semgrep scan you may add later).
- Save security.yml.
Why does the workflow need security-events: write?
The permissions block you added in the previous step already includes security-events: write. This permission is required for the upload-sarif action to write findings to the GitHub Security tab. Without it, the upload step would fail with a permissions error.
Seeing YAML indentation errors?
YAML is sensitive to spacing. Make sure dependency-scan is indented at the same level as secrets-scan (two spaces under jobs:). Each - name: step should be indented six spaces from the left edge.
help me fix YAML indentation in my GitHub Actions workflow
✔️ Awesome, I've got everything!
Great. Double check you have saved security.yml before continuing.
ⓧ I'd like to double check the full code
Here is the complete .github/workflows/security.yml after adding the dependency-scan job:
name: DevSecOps Pipeline
on:
push:
branches: [main]
pull_request:
branches: [main]
permissions:
contents: read
security-events: write
jobs:
secrets-scan:
name: Secrets Detection
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Run Gitleaks
uses: gitleaks/gitleaks-action@v2
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
dependency-scan:
name: Dependency Vulnerability Scan
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Run Trivy vulnerability scanner
uses: aquasecurity/trivy-action@v0.36.0
with:
scan-type: 'fs'
scan-ref: '.'
format: 'sarif'
output: 'trivy-results.sarif'
severity: 'CRITICAL,HIGH'
- name: Upload Trivy scan results to GitHub Security tab
uses: github/codeql-action/upload-sarif@v4
if: always()
with:
sarif_file: 'trivy-results.sarif'
category: 'trivy-dependency-scan'
Push and verify the dependency scan
With both jobs defined, push your updated workflow to trigger the full pipeline. Both jobs will run in parallel.
- Stage, commit, and push your changes by running these commands:
git add .
git commit -m "Add dependency vulnerability scanning with Trivy"
git push
- Open your repository on GitHub.
- Click the Actions tab.
- Click the latest workflow run (it will match your commit message).
- You should see two jobs running in parallel: Secrets Detection and Dependency Vulnerability Scan.
What should I see in the Actions output?
The Dependency Vulnerability Scan job should complete successfully (green check). Trivy writes its findings to the SARIF file without failing the job, so the upload step runs and pushes the results to the Security tab.
The Secrets Detection job will likely fail (red X) because Gitleaks detects the hardcoded credentials in your code. That is expected behaviour from the previous step.
- Navigate to your repository's Security tab (click Security in the top navigation bar of your repository).
- In the left sidebar, click Code scanning.
- You should see vulnerability alerts from Trivy listing issues found in the express 4.17.1 dependency.
Filter by tool
If you have many alerts, use the Tool dropdown filter on the code scanning page to show only Trivy results.
Not seeing alerts in the Security tab?
- Make sure your repository is public. SARIF uploads to the Security tab require a public repository on GitHub Free.
- Check that the Upload Trivy scan results step passed in the Actions log. If it failed, verify your permissions block includes security-events: write.
- Alerts may take a minute or two to appear after the workflow completes. Refresh the page.
help me troubleshoot why SARIF results are not appearing in my GitHub Security tab
Your pipeline now has two parallel security layers. Secrets detection catches leaked credentials and dependency scanning catches known CVEs. Next up, you will trigger the full pipeline and review the combined results.
Test the Pipeline and Review Results
Your pipeline is fully built with two security layers. You have secrets detection with Gitleaks and dependency scanning with Trivy, both configured to run automatically on every push.
Now it's time to see the full pipeline in action. You'll trigger a fresh run, watch both jobs execute in parallel, and explore where GitHub surfaces the security findings.
In this step, get ready to:
- Trigger the full pipeline by pushing a commit.
- Review scan results in the GitHub Actions tab.
- Explore vulnerability findings in the GitHub Security tab.
Trigger the full pipeline
Your workflow triggers on every push to the main branch. To see both jobs run fresh, you'll push an empty commit that triggers the pipeline without changing any files.
- Push an empty commit to trigger the pipeline by running these commands:
git commit --allow-empty -m "Trigger security pipeline"
git push
What does this command do?
The --allow-empty flag lets you create a commit with no file changes. This is useful for re-triggering GitHub Actions workflows without modifying code.
- Open your repository on GitHub in your browser.
- Click the Actions tab under the repository name.
- Click the workflow run named Trigger security pipeline to open the run summary.
You should see a visualization graph showing both jobs: Secrets Detection and Dependency Vulnerability Scan. They run in parallel because neither depends on the other.
Why do the jobs run in parallel?
In GitHub Actions, jobs run in parallel by default unless you specify a needs dependency between them. Your two security jobs are independent checks, so running them simultaneously saves time.
Don't see the workflow run?
Make sure you pushed to the main branch. Your workflow only triggers on pushes to main.
If you see no runs at all, confirm the .github/workflows/security.yml file exists in your repository by checking the Code tab.
Help me debug why my GitHub Actions workflow isn't triggering.
Review the Security tab findings
Trivy uploads its scan results in SARIF format to GitHub's Security tab. This gives you a centralized dashboard to view all vulnerability findings.
- Wait for the workflow run to finish. The Secrets Detection job will show a red X (failed) and the Dependency Vulnerability Scan job will show a green checkmark (passed).
- Click the Security tab in your repository navigation. If you cannot see the Security tab, click the overflow menu and then click Security.
- In the left sidebar, click Code scanning.
You should see vulnerability alerts from Trivy related to the outdated express 4.17.1 dependency. Each alert shows the severity level and the CVE identifier.
What is SARIF?
SARIF stands for Static Analysis Results Interchange Format. It is a standard JSON format that security tools use to report findings. GitHub's Security tab understands SARIF natively, so any tool that outputs SARIF can feed results into the same centralized view.
No alerts in the Security tab?
SARIF upload requires the security-events: write permission in your workflow. Confirm this is set under the permissions block in security.yml.
SARIF uploads to the Security tab only work on public repositories with GitHub Free. If your repository is private, you need GitHub Code Security enabled.
Help me figure out why my Trivy SARIF results are not showing in the GitHub Security tab.
Verify the secrets scan failed the build
A key behaviour of the secrets detection job is that it fails the workflow when it finds leaked credentials. This is intentional. In a real team workflow, this failure would block a pull request from merging.
- Click the Actions tab to return to the workflow runs.
- Click the failed workflow run (marked with a red X).
- Click the Secrets Detection job in the left sidebar to view its logs.
- Expand the Run Gitleaks step to see the scan output.
You should see Gitleaks reporting the hardcoded credential it found in src/app.js. The job exits with a non-zero code, which marks the entire workflow run as failed.
Why does a failed secrets scan block the build?
Gitleaks exits with a non-zero status code when it detects secrets. GitHub Actions treats any non-zero exit as a job failure. In a real project with branch protection rules, this failure would prevent the pull request from being merged until the leaked secret is removed.
This is the "shift-left" principle in action. Security issues get caught during development, not after deployment.
Both jobs report independently
Even though the secrets scan failed, the dependency scan still ran and completed successfully. Because the jobs are parallel and independent, one failure does not cancel the other. You get full visibility from both scans on every push.
Secret mission
Add SAST with Semgrep
Your pipeline catches leaked secrets and vulnerable dependencies. But what about vulnerabilities hiding in the code itself? Add a third security layer that catches dangerous coding patterns before they reach production.
Clean Up Your Resources
Clean Up Your Resources
This project runs entirely on GitHub's free tier and your local machine, so there are no ongoing costs. Decide whether to keep your resources running, pause them to come back later, or delete them entirely.
Resources you used:
- GitHub repository with your DevSecOps pipeline and vulnerable sample app.
- Local project folder on your Desktop.
- GitHub Actions workflow runs (stored in your repository).
Keep everything running
No action needed. Choose this if you're still actively building or want to keep the pipeline as a portfolio piece.
- Your GitHub repository stays public and accessible.
- The pipeline continues to run on every push at no cost.
- Your local project folder remains on your Desktop.
Pause - I'll come back to this later
Archive the repository to make it read-only while preserving all your work.
- Open your repository on GitHub.
- Click Settings in the repository navigation bar.
- Scroll down to the Danger Zone section.
- Click Archive this repository and confirm.
- You can unarchive it later from the same Settings page when you're ready to resume.
Delete - I don't want to use this again
Remove all project resources and start fresh. This permanently deletes your repository and workflow history.
- Open your repository on GitHub.
- Click Settings in the repository navigation bar.
- Scroll down to the Danger Zone section and click Delete this repository.
- Click I want to delete this repository, then click I have read and understand these effects.
- Type the repository name to confirm, then click Delete this repository.
- Delete your local project folder by running the following commands in your terminal:
macOS
cd ~/Desktop
rm -rf devsecops-demo
Windows
cd ~/Desktop
rmdir /s /q devsecops-demo
That's a Wrap!
That's a Wrap!
Nice work! You just built a multi-layer DevSecOps pipeline that automatically scans your code for security vulnerabilities on every push.
You've learned how to:
- Built a GitHub Actions workflow with parallel security scanning jobs that run automatically on every push.
- Implemented secrets detection with Gitleaks to catch hardcoded credentials before they reach production.
- Configured dependency vulnerability scanning with Trivy and surfaced findings in the GitHub Security tab using SARIF reporting.
- Secret Mission: Added SAST with Semgrep to catch code-level vulnerabilities like injection attacks.
Ready to quiz yourself?