

Dev (Development Team)
Primary Goal: Deliver new features, fix bugs, and meet product deadlines (speed & change).
Responsibilities:
Ops (Operations Team)
Primary Goal: Keep the production environment stable, secure, and available (stability & control).
Responsibilities:
DevOps is a set of practices and culture that combines Development (the people who write code) and Operations (the people who run and maintain systems). The goal is simple but powerful: deliver software faster, more reliably, and continuously.
Let's look at the core principles:
DevSecOps is the natural evolution of DevOps.
The key concept here is Shift Left Security.
What is the basic problem CI/CD solves?
When people write software, they make many small changes over time. Each change can break the software. Without automation, people have to manually test and move each change to the live system. This is slow and error-prone. CI/CD automates these steps.
Step 1: Understand the pieces of software work
Before CI/CD, you need to know these terms:
Step 2: What is Continuous Integration (CI)?
Continuous Integration means: every time a developer saves a new change to the shared repository, an automated system immediately does three things:
If the build fails or any test fails, the system sends an alert to the developers. The developers must fix the problem before moving forward.
Why this matters: Without CI, developers might work for weeks before combining their changes. When they finally combine them, many conflicts and failures appear at once, and it takes a long time to find the cause. With CI, problems are found within minutes of being added.
Step 3: What is Continuous Delivery (CD)?
Continuous Delivery means: after the CI steps pass successfully, the system automatically prepares the software for release to production. This includes:
At this point, the package is ready. A human must manually click a button to send it to production. That manual step is the difference between Continuous Delivery and Continuous Deployment.
Step 4: What is Continuous Deployment (also CD)?
Continuous Deployment removes the manual button. If all CI and staging tests pass, the system automatically deploys the package to production immediately.
This requires more trust in the tests and better monitoring, because there is no human to stop a bad change before it reaches users.
1. Unit Tests
What they check: Individual small pieces of code, such as a single function or a single method in a class. Unit tests verify that each piece works correctly in isolation, separate from the rest of the system.
When they run: Very early in the CI pipeline, usually within seconds of the build completing.
How they work: The test calls a function with specific inputs and checks that the output matches the expected value.
Example: A function that adds two numbers. A unit test calls it with inputs 2 and 3 and checks that the result is 5.
Who writes them: Developers, at the same time they write the code.
Failure meaning: A specific small part of the code is logically wrong.
2. Integration Tests
What they check: Whether multiple pieces of code work correctly when combined. For example, does the database access code correctly talk to the actual database? Does the payment module correctly call the shipping module?
When they run: After unit tests pass. They run in the CI pipeline but require external systems (like a database or a message queue) to be available.
How they work: The test sets up real dependencies (or lightweight versions of them), runs a sequence of operations across multiple components, and verifies the final state.
Example: A test that creates a user account in the database using the user registration function, then logs in with that account, and checks that the login succeeds.
Failure meaning: Components are not communicating correctly. The interfaces between them have mismatches.
3. Static Analysis Tests
What they check: The source code itself, without running the program. These tests look for style violations, potential bugs, security issues, or code that does not follow team rules.
When they run: Before or during the build step, because they do not require the program to run.
How they work: A tool reads the source code files and checks them against a set of rules.
Examples of rules:
Failure meaning: The code is poorly formatted or contains a potential risk, even if it would run correctly.
4. Security Tests (SAST and DAST)
These come in two forms:
SAST (Static Application Security Testing):
DAST (Dynamic Application Security Testing):
When they run: SAST runs early in CI (similar to static analysis). DAST runs later, after the application is deployed to a staging environment.
Failure meaning: The software contains a security vulnerability that could be exploited by an attacker.
Now let's look at the specific security risks that can appear at each stage of this pipeline. First, at the Code stage.
The biggest risks here are things developers do accidentally or without thinking about security:
A developer needs to connect to a database, so they write the username and password directly into the code.
Now that secret is in the codebase, accessible to anyone who can see the code.
If the code is in a public repository, it's instantly compromised.
So what's the right way to handle secrets?
You never hardcode them. Instead, you load them from a secret manager or environment variables.
Next, risks appear at the Build stage. This is when we pull in dependencies external libraries that our code relies on.
"The risks here are:
"The code example shows outdated library versions. requests==2.19.0 and pyaml==5.1 are old versions with known vulnerabilities (CVEs). If you don't regularly update your dependencies, you're building known vulnerabilities into your application."
Risk 1: No security test coverage
Risk 2: Bypassing quality/security gates
Security-relevant observations for the slide’s example:
Countermeasure: Make security scans mandatory (not skippable) and enforce that tests must pass before merge.
The Deploy stage is where configuration mistakes happen.
This is particularly common with containerized environments like Kubernetes and Docker.
Look at this security context.
privileged: true means the container has full, unrestricted access to the host machine.
runAsUser: 0 means it's running as root. If an attacker compromises this container, they own the entire host machine.
Also note the hostPath volume. This mounts a real folder from the host machine into the container
So the container can:
Another deployment risk is exposed cloud storage.
This is a Terraform configuration for AWS S3.
acl = "public-read" makes this bucket readable by anyone on the internet. This is a classic misconfiguration. Someone intended to store logs, but accidentally made them public. Sensitive data gets exposed.
You see headlines about this all the time: 'Company X exposed millions of customer records in an unsecured S3 bucket.' This is how it happens.
Finally, at the Monitor stage, the risk is that we're logging sensitive information.
logger.info("Login for user", {
email: req.body.email,
password: req.body.password, // sensitive!
token: user.sessionToken // secret!
});Look at this logging line. It's logging the user's email, password, and session token. The password is sensitive. The session token is a secret that can be used to impersonate the user. Logging this is a massive security violation. Anyone with access to the logs—which might include many developers and operators—now has these secrets.
Good practice: Never log passwords, tokens, or personally identifiable information (PII) like credit card numbers or social security numbers. If you need to log something for debugging, redact it or use a unique identifier instead.
Look at this flow in the slide. A developer writes code and pushes it to the code hosting platform (like GitHub). That push triggers the CI/CD pipeline. As part of that pipeline, a SAST tool runs automatically. Within minutes, the developer gets a report of any vulnerabilities found.
This is powerful for two reasons:
One of the simplest ways to start automating security checks is using regular expressions, or regex.
"This is a simple grep command. grep -ir 'eval(' searches recursively through all files for the string 'eval('.
The example finds the eval() call in the Python file. This is a quick way to find a dangerous pattern.
But this is a very basic approach. It's like using a flashlight to search a dark room. It works, but it's limited.
Let's see the limitations of regex. Here are different uses of eval() in Python.
[Walk through each example]
So, if regex isn't enough, what's the solution? What do SAST tools use to understand code at a deeper level? The answer is the Abstract Syntax Tree, or AST. Let's explore that.
An Abstract Syntax Tree is a data structure that represents the structure of a program. Think of it as a map of your code that shows how all the pieces relate to each other.
Instead of seeing code as a flat sequence of characters, the AST represents it as a tree. Each node in the tree is a piece of the program: a function definition, a variable, a function call, an operator.
The advantages of working with ASTs are:
Let's look at a concrete example. This is a simple Python function that uses eval().
The AST for this code is a tree structure. At the top is the function definition node. Inside it, there are nodes for the assignment to data, the call to eval(), and the return statement.
With an AST, a SAST tool can see that eval() is being called with data['expressions'] as its argument.
It can then ask: where does data['expressions'] come from? It comes from user input via request.get_json(). Now the tool knows that user-controlled data is flowing into a dangerous eval() function. That's a real vulnerability not a false positive.
Here's a simple rule in a SAST tool like Semgrep. The rule is: find any call to eval().
The pattern: eval(...) tells the tool to look for function calls where the function name is eval. The tool uses the AST to identify this structure precisely.
This is still a simple rule, but because it's based on the AST, it won't match strings, comments, or different function names. It's more accurate than regex.
Building a SAST tool that uses AST analysis is not trivial. There are significant challenges:
This is why most organizations don't build their own SAST tools. They use existing solutions.
The key takeaway here is that building a SAST tool from scratch is complex and resource-intensive. It's not something most organizations should attempt.
Instead, we leverage existing solutions. There are many SAST tools available, both open-source and commercial. They differ in:
The analysis asks: where does this data come from? Is it from a trusted source (like a configuration file) or an untrusted source (like user input or an environment variable)? And where does it go? If untrusted data reaches a dangerous function like eval(), that's a real vulnerability.
Taint analysis is what makes SAST tools truly powerful. It finds issues that simple pattern matching misses.
Code example:
import os
def get_hostname():
expression_from_env = os.environ.get('EXPRESSION')
return eval(expression_from_env)Simple analysis:
Taint analysis:
The Bandit output on the slide shows taint analysis in action:
Issue: [B307:blacklist] Use of possibly insecure function - eval
Severity: Medium
Confidence: High
CWE: CWE-78 (OS Command Injection)
Location: test.py:5Bandit (a Python SAST tool) uses taint analysis. It knows that os.environ.get() is a source.
Why taint analysis is more powerful:
Why taint analysis is harder:
(Silent slide - allows students to absorb the previous one.)
Semgrep is a fast, open-source static analysis tool.
Semgrep’s killer feature: custom rules are very easy to write using a YAML syntax that looks like the code you’re searching for.
The Semgrep Registry is a public collection of ready-made security rules for the static analysis tool Semgrep.
You can browse the registry, find rules relevant to your stack, and start using them immediately. For example, there are rules for finding hardcoded secrets, SQL injection vulnerabilities, dangerous function usage, and many other issues.
It’s basically a library of detection rules that help you find:
Instead of writing rules from scratch, you can:
And of course, you can also write your own custom rules for organization-specific needs.
Example (exploring the website)
1. Go to the Semgrep Registry (website)
Search for:
You’ll find rules like:
2. Open the eval rule
When you click it, you’ll see:
Rule details
Pattern (important part)
You’ll see something like:
pattern: eval(...)Sometimes more advanced patterns too (taint tracking, etc.)
3. Try it directly in the browser
The website usually has a “Try this rule” or playground editor.
You can paste code like:
user_input=input()
result=eval(user_input)It will highlight the vulnerable line immediately.
4. Copy the CLI command (Run Locally)
Each rule page gives you a ready command:
semgrep --config=p/python.lang.security.audit.evalSo you don’t need to memorize anything.
Example (Using from the registry in your code)
1. Vulnerable Python example
user_input=input("Enter something: ")
result=eval(user_input)
print(result)If a user enters:
__import__('os').system('rm -rf /')You just gave them code execution
2. Run Semgrep using a registry rule
With Semgrep, you don’t need to write anything.
Run this command:
semgrep --config r/python.lang.security.audit.eval-detected .3. Example output
You’ll see something like:
eval-use:
Dangerous use of eval detected
--> app.py:2
result = eval(user_input)4. What rule is actually doing
Behind the scenes, the rule looks for patterns like:
eval(...)But smarter than simple grep—it understands structure (AST), not just text.
5. Pro tip (real-world usage)
You can scan for multiple Python issues at once:
semgrep --config=p/python .This includes:
semgrep scan --config auto
- Runs Semgrep with automatic configuration selection
- Semgrep decides which rules to run based on your codebase (language, frameworks, etc.)
- Good for general-purpose scanning without specific rule knowledge
semgrep --config=p/python
- Runs Semgrep using the "p/python" rule pack (community rules for Python)
- Targets Python-specific security and correctness issues
- More focused and deterministic than auto
Running Semgrep locally is great, but in DevSecOps, we want automation. We want security tests to run on every code change. That's where CI/CD systems like GitHub Actions come in.
GitHub Actions is a continuous integration and continuous delivery platform built into GitHub. It allows you to automate your build, test, and deployment pipeline.
In GitHub Actions, workflows are defined by YAML files stored in your repository.
Specifically, they go in the .github/workflows directory. A repository can have multiple workflows for different purposes.
Each workflow has basic components:
Events are the triggers that cause a workflow to run. Here are some common events:
You can also trigger workflows on a schedule (like daily scans) or manually. The complete list of events is available in the GitHub documentation.
A runner is a server that executes your workflow jobs.
GitHub provides hosted runners:
You can also host your own self-hosted runners if you need special hardware or security isolation.
Each runner can run one job at a time. For a small team, GitHub’s free runners are sufficient.
An action is a custom application for the GitHub Actions platform that performs a complex but frequently repeated task.
For example, there's an action to set up Python, an action to check out your code, and importantly an action to run Semgrep. Instead of writing complex scripts to install and run Semgrep, you can use a pre-built action from the GitHub Marketplace.
The marketplace has thousands of actions. You can find actions for security scanning, code quality, deployment, and much more.
Inside your project folder, create this path:
your-project/
├── .github/
│ └── workflows/
│ └── semgrep.yml ← HERE
├── app.py
├── requirements.txtThe key rule:
name: Semgrep Security Scan
# -----------------------------
# EVENTS (Triggers)
# -----------------------------
on:
push:
branches: ["main","dev" ]
pull_request:
branches: ["main"]
schedule:
- cron:"0 2 * * *"# Runs daily at 2 AM UTC
workflow_dispatch:# Manual trigger
# -----------------------------
# JOBS
# -----------------------------
jobs:
semgrep-scan:
# -----------------------------
# RUNNER
# -----------------------------
runs-on: ubuntu-latest
# -----------------------------
# STEPS
# -----------------------------
steps:
# Step 1: Checkout repository code
- name: Checkout code
uses: actions/checkout@v4
# Step 2: Set up Python (required for Semgrep)
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version:"3.11"
# Step 3: Install Semgrep
- name: Install Semgrep
run: pip install semgrep
# Step 4: Run Semgrep scan
- name: Run Semgrep scan
run: semgrep scan --config=auto
# Step 5: Upload results (optional)
- name: Upload scan results
if: always()
uses: actions/upload-artifact@v4
with:
name: semgrep-results
path: semgrep-report.jsonBreakdown (mapped to concepts)
1. Events (Triggers)
on:
push:
pull_request:
schedule:
workflow_dispatch:This means the workflow runs when:
2. Runner
runs-on: ubuntu-latestOther options:
3. Jobs
jobs:
semgrep-scan:4. Steps
Each job is made of steps:
Action example
uses: actions/checkout@v4Script example
run: pip install semgrep5. Actions (Marketplace)
Examples used:
Instead of writing everything manually, you reuse these.
As soon as the file is pushed:
GitHub automatically detects it
Then:
Real-world mental model
Your repo becomes like this:
Code → Push → GitHub Actions → Security Scan → ResultStep 1: Make sure your structure is correct
Inside your project folder, you should have:
semgrep-demo/
├── demo.py
├── eval_rule.yaml
└── .github/
└── workflows/
└── semgrep.ymlIf this structure is wrong, the workflow will not run.
Step 2: Initialize Git (if you didn’t already)
In VS Code terminal:
git init
git add .
git commit -m "Initial commit with Semgrep demo"Step 3: Create a github account and create a repo
Set a name for you repo (e.g. semgrep-demo)
Step 4: Connect to your GitHub repo
If you already created a repo on GitHub, link it:
git remote add origin https://github.com/<your-username>/semgrep-demo.git
git branch -M main
git push -u origin mainStep 4: Trigger the workflow
As soon as you push:
The workflow runs automatically
Go to:
You should see:
Semgrep Security ScanStep 5: Check results
Click the workflow run → open the job → look at logs
You should see something like:
Avoid using eval() - it's unsafe!Step 6:
If you wanna make any changes to your code locally then upload it to GitHub:
git add .
git commit -m "Your message"
git push origin mainThen it will automate the process and scan your code automatically.