GitHub Actions Security Scanning - Walkthrough
Learn Github Actions in action!
Duration: 60-90 minutes Requirements: GitHub account
Step 1: Fork the Repository
Click the Fork button (top-right corner)
Select your GitHub account as destination
Wait for GitHub to create your copy
After forking, you will have your own copy at: https://github.com/YOUR-USERNAME/Github-Actions-Lab
Step 2: Configure Repository Settings
Before we start, you need to check if important settings are configured in your repo.
Make Repository Public
Code scanning (Security tab integration) is only available for public repositories on free GitHub plans. Private repositories would require GitHub Advanced Security (paid enterprise feature).
But forking public repo should already be public. If not check this setting:
Go to Settings (tab at the top of your repository)
Scroll to the bottom → Danger Zone
Click Change visibility → Select Public
Type the repository name to confirm
Step 3: Clone Repository to Your Computer
Open terminal and run:
Replace YOUR-USERNAME with your actual GitHub username.
Create the workflows directory:
Step 4: Exercise 1 - Secret Detection with Gitleaks
What is Gitleaks?
Gitleaks scans your code and Git history for hardcoded secrets like API keys, passwords, and tokens. This is critical because:
Secrets committed to Git remain in history forever (even if deleted later)
Attackers use automated bots to scan public repos for leaked credentials
Leaked AWS keys can be exploited within minutes of being pushed
Create the Workflow File
Create file .github/workflows/gitleaks.yml:
Line-by-Line Explanation
Workflow Name:
This name appears in the GitHub Actions tab. Choose descriptive names so you can identify what each workflow does.
Triggers (when the workflow runs):
push: branches: [main]
Every push to main branch
Scan all new commits for secrets
pull_request: branches: [main]
When PR targeting main is opened/updated
Catch secrets BEFORE they reach main
workflow_dispatch
Manual "Run workflow" button
Testing and on-demand scans
Permissions Block:
This is CRITICAL for Security tab integration:
contents: read
Read repository files
Scanner needs to access code
security-events: write
Upload SARIF to Security tab
Results appear in Code scanning
actions: read
Read workflow information
Required for Gitleaks action
Without security-events: write, the workflow runs but results don't appear in Security tab!
Job Definition:
scan- internal job ID (can be anything)name: Scan for Secrets- display name in Actions UIruns-on: ubuntu-latest- use latest Ubuntu runner (free for public repos)
Checkout Step:
fetch-depth: 0 is CRUCIAL for Gitleaks! Here's why:
1 (default)
Only latest commit
Fast, but misses secrets in history
0
Full Git history
Gitleaks can find secrets in ANY past commit
Why full history? Imagine you committed an AWS key 6 months ago, then deleted it. With fetch-depth: 1, Gitleaks only sees current files - the secret isn't there. With fetch-depth: 0, Gitleaks scans all commits and finds the key in the old commit where it was added.
Important: fetch-depth: 0 is NOT the same as "scan everything". It only means the full history is available on the runner. What Gitleaks actually scans depends on the trigger event:
push
Only the commits in that specific push
If you push 1 commit that adds the workflow file, Gitleaks only scans that 1 commit (not the history)
pull_request
Only the commits in the PR
Same logic: only new changes
workflow_dispatch (manual)
Full repository history
This is when fetch-depth: 0 truly matters
This means: When you first add the Gitleaks workflow and push it, the scan will likely show "No leaks detected" because the push only contains the new .yml file. The secrets in config.js were committed in earlier commits that are NOT part of this push.
To trigger a full history scan, go to Actions > Gitleaks Secret Scan > click Run workflow (manual trigger). This time Gitleaks will scan every commit in the repository and detect the secrets in config.js.
Gitleaks Action:
Environment variables:
GITHUB_TOKEN
${{ secrets.GITHUB_TOKEN }}
Auto-provided token for GitHub API access and SARIF upload
GITLEAKS_ENABLE_UPLOAD_ARTIFACT
false
Don't save results as downloadable artifact (we use SARIF)
GITLEAKS_ENABLE_SUMMARY
true
Show summary in workflow run page
Commit and Push
Where to Find Results
In Actions Tab (detailed logs):
Go to your repository on GitHub
Click Actions tab
Click "Gitleaks Secret Scan" in the left sidebar
Click on the latest run
Click "Scan for Secrets" job
Expand "Run Gitleaks" step

You will see output like:

Expected Result: The workflow will FAIL (red X) because config.js contains hardcoded secrets.
In Security Tab (aggregated view):
Click Security tab (top menu)
Click Code scanning in the left sidebar
You will see alerts for each detected secret
Each alert shows:
Secret type (AWS Access Key, Generic Password, etc.)
File and line number where found
Commit where secret was introduced
Severity level
Note: It may take a minute for results to appear in Security tab after the workflow completes.
Step 5: Exercise 2 - Dependency Scanning with Trivy
What is Trivy?
Trivy is an open-source vulnerability scanner that can scan:
Dependencies (npm, pip, Maven, etc.) - finds CVEs in libraries
Container images - finds OS and app vulnerabilities
Infrastructure as Code - finds misconfigurations in Terraform, CloudFormation
What are CVEs?
CVE (Common Vulnerabilities and Exposures) is a standardized identifier for security vulnerabilities:
CVE-2021-44906 = specific vulnerability in
minimistpackage (Prototype Pollution)Each CVE has a severity: CRITICAL, HIGH, MEDIUM, LOW
Trivy checks your dependencies against CVE databases
Create the Workflow File
Create file .github/workflows/trivy-deps.yml:
Line-by-Line Explanation
Schedule Trigger:
This runs the workflow automatically every day at 6:00 AM UTC.
Why schedule scans? New CVEs are discovered daily. A library that was "safe" yesterday might have a critical vulnerability disclosed today. Daily scans catch new vulnerabilities without manual intervention.
Cron syntax: minute hour day-of-month month day-of-week
0 6 * * *= minute 0, hour 6, every day, every month, every weekday0 0 * * 0= every Sunday at midnight0 */4 * * *= every 4 hours
Why Generate Lock File?
Trivy needs a lock file (package-lock.json) to scan npm dependencies. Here's why:
package.json
Version ranges
"lodash": "^4.17.0" (any 4.17.x)
package-lock.json
Exact versions
"lodash": "4.17.15" (exactly this)
Without the lock file, Trivy can't know which exact version you're using, so it reports Number of language-specific files: 0 (nothing to scan).
The --package-lock-only flag generates the lock file without downloading packages (faster).
First Trivy Step (Console Output):
scan-type: 'fs'
filesystem
Scan local files (not container image)
scan-ref: '.'
current directory
Scan everything in repo
scanners: 'vuln'
vulnerabilities only
Don't scan for secrets or misconfigs
severity: 'CRITICAL,HIGH'
filter
Only show serious issues
format: 'table'
human-readable
Easy to read in logs
exit-code: '0'
don't fail
Workflow continues even if vulns found
Why exit-code: '0'? During initial setup, you want to SEE vulnerabilities without failing the build. Later, change to '1' to enforce security gates.
Second Trivy Step (SARIF for Security Tab):
Why run Trivy twice?
First run with
format: 'table'- human-readable output in Actions logsSecond run with
format: 'sarif'- machine-readable format for Security tab
SARIF (Static Analysis Results Interchange Format) is a standard JSON format that GitHub understands. It enables:
Aggregated view in Security tab
Filtering by severity, tool, file
Tracking when vulnerabilities were introduced/fixed
Upload SARIF Step:
if: always()
Upload even if previous steps failed
sarif_file
Path to the SARIF file generated by Trivy
category
Label in Security tab (helps filter results by tool)
Why if: always()? If Trivy finds vulnerabilities with exit-code: '1', the workflow fails. Without if: always(), the upload step would be skipped and you'd never see results in Security tab.
Commit and Push
Where to Find Results
In Actions Tab:
Actions → Trivy Dependency Scan → click latest run
Click "Scan Dependencies" job
Expand "Run Trivy (Console Output)" step
You will see a table like:
Understanding the output:
Library - which npm package has the vulnerability
Vulnerability - CVE identifier (click to see details on nvd.nist.gov)
Severity - CRITICAL/HIGH/MEDIUM/LOW
Status -
fixedmeans a patched version existsInstalled Version - what you currently have
Fixed Version - minimum version that fixes the issue
In Security Tab:
Security → Code scanning
Filter by Tool: "Trivy" or look for category "trivy-dependencies"
Step 6: Exercise 3 - Container Scanning with Trivy
Why Scan Container Images?
Docker images contain not just your code, but also:
Base OS packages (Alpine, Debian, Ubuntu)
Runtime dependencies
System libraries
Each of these can have vulnerabilities. A "secure" app in a vulnerable container is still vulnerable.
Create the Workflow File
Create file .github/workflows/trivy-container.yml:
Line-by-Line Explanation
Path-Based Triggers:
This workflow ONLY runs when:
Push is to
mainbranch, ANDThe push changes
DockerfileOR any file matchingpackage*.json
Why use paths? Container builds are slow (minutes). No point rebuilding if you only changed README.md. This saves CI minutes and speeds up feedback.
Docker Buildx Setup:
Buildx is Docker's extended build capabilities. It enables:
Multi-platform builds (arm64, amd64)
Better caching
More efficient builds
Build the Image:
context: .
Current directory
Where Dockerfile is located
push: false
Don't push to registry
We only scan, not deploy
load: true
Load into Docker daemon
So Trivy can scan it
tags: app:${{ github.sha }}
Unique tag
Uses commit hash for identification
Container Scan Parameters:
scan-type: 'image'
Docker image
Different from fs (filesystem)
image-ref
The image we just built
Must match the tag from build step
scanners: 'vuln,secret'
Both scans
Check for vulns AND secrets in image
timeout: '10m'
10 minutes
Container scans take longer
Why scanners: 'vuln,secret'? Container images can accidentally include:
Secrets baked into environment variables
.envfiles copied during buildSSH keys in home directories
Commit and Push
Note: Since we use path-based triggers, you may need to manually run the workflow from Actions tab for the first test.
Where to Find the Results
After the workflow completes, your results appear in two places:
1. Console Output (Actions tab)
Go to Actions > click the "Build and Scan Image" run > expand the "Scan image (Console Output)" step. You will see a table listing every package inside the Docker image along with the number of vulnerabilities and secrets found. Pay attention to:
The base image (e.g. Alpine) often has the most vulnerabilities because it includes OS-level packages
The Secrets column shows whether Trivy found any credentials baked into the image

2. Security Tab (Code Scanning alerts)
Go to Security > Code scanning. New alerts from the container scan appear alongside the dependency scan alerts. All of them show "Trivy" as the tool.

How to tell container alerts apart from dependency alerts? Look at the file path under each alert:
/app/..., /usr/..., library/...
Container scan -- these are paths inside the Docker image
package-lock.json, package.json (no container prefix)
Dependency scan -- these are files from your repository
Container scan paths start with the image's internal directory structure (like /app/ from the Dockerfile's WORKDIR). GitHub will show "Preview unavailable" for these because the file does not exist in the repository -- it only exists inside the built Docker image.
Step 7: Exercise 4 - Infrastructure as Code Scanning
What is IaC Scanning?
Infrastructure as Code (IaC) tools like Terraform let you define cloud resources in code. But misconfigurations can create security holes:
Public S3 buckets → data breaches
Open SSH (0.0.0.0/0) → brute force attacks
Unencrypted databases → data exposure
Trivy can scan Terraform, CloudFormation, and Kubernetes manifests to find these issues BEFORE deployment.
Create the Workflow File
Create file .github/workflows/trivy-iac.yml:
Line-by-Line Explanation
IaC Scan Parameters:
scan-type: 'config'
Configuration scan
For IaC files (not dependencies)
scanners: 'misconfig'
Misconfigurations
Look for security issues in config
severity: 'CRITICAL,HIGH,MEDIUM'
Include MEDIUM
IaC issues often MEDIUM but still dangerous
Why include MEDIUM for IaC? Many infrastructure misconfigurations are rated MEDIUM individually but create serious risks when combined:
No S3 access logging
MEDIUM
Can't detect breaches
No VPC flow logs
MEDIUM
Can't trace attacks
Combined:
Attacker exfiltrates data undetected
Commit and Push
Where to Find IaC Results
In Actions Tab:
Expand "Scan IaC (Console Output)" to see:
Step 8: Understanding the Results
Actions Tab Structure
Security Tab Structure
Workflow Status Icons
Yellow dot
Running
Wait for completion
Green check
All steps passed
No blocking issues found
Red X
Failed
Click to see which step failed
Gray circle
Skipped or cancelled
Check workflow conditions
Step 9: Fixing the Vulnerabilities
Now that you have identified all security issues, let's fix them.
Fix 1: Vulnerable Dependencies
Edit package.json to use secure versions:
How to find safe versions:
Check Trivy output - shows "Fixed Version" column
Visit npmjs.com - each package page shows versions
Run
npm auditlocally - gives recommendations
Delete the old lock file so it regenerates:
Fix 2: Hardcoded Secrets
Replace config.js with environment variable references:
Why environment variables?
Secrets stay out of Git history
Different values for dev/staging/production
Can be managed via GitHub Secrets, AWS Secrets Manager, etc.
Fix 3: Terraform Misconfigurations
Replace terraform/main.tf:
Commit and Verify
Go to the Actions tab and wait for all workflows to complete. They should now pass (green checkmarks) because:
No more hardcoded secrets in
config.jsDependencies updated to secure versions
Terraform misconfigurations fixed
Summary
You have completed:
1
Gitleaks
Secrets in code and Git history
fetch-depth: 0
2
Trivy (fs)
npm dependency CVEs
scan-type: 'fs', scanners: 'vuln'
3
Trivy (image)
Container vulnerabilities
scan-type: 'image'
4
Trivy (config)
IaC misconfigurations
scan-type: 'config', scanners: 'misconfig'
Resources
Last updated
