Step 01 of 25
DevSecOps fundamentals
Explain shift-left and shared ownership, map security controls onto every SDLC phase, and set triage rules a team will actually follow.
DevOps made shipping fast: small changes, automated pipelines, many deploys a day. Security that still works like a quarterly sign-off from a separate team can't keep up, so it either gets bypassed or becomes the bottleneck. DevSecOps is the fix: build security checks into the same automated flow that builds and ships the code, and make them everyone's job. In this lesson you'll learn the core principles, map concrete controls onto each phase of the software lifecycle, and set the rules for when a finding blocks a merge and who owns fixing it.
From gatekeeper to guardrails
In the traditional model, developers build a feature for weeks, then hand it to a security team for a penetration test just before release. Three things go wrong:
- Late feedback. A design flaw found a week before launch is expensive to fix: the code is written, other code depends on it, and the deadline is fixed. Teams accept the risk because the alternative is missing the date.
- Lost context. The developer who wrote the vulnerable code moved on weeks ago. Fixing it means relearning their own work.
- A bottleneck. One security engineer typically supports dozens or hundreds of developers. Manual review of every release doesn't scale.
DevSecOps replaces the single gate with guardrails: many small, automated checks that run where the work happens, give feedback in minutes, and stop only the problems that clearly matter. A secret scanner in a pre-commit hook tells you about a leaked key before it leaves your laptop. A dependency scanner on a pull request tells you a library has a known critical vulnerability while you still remember why you added it.
Shift left is the slogan for this: move security activities earlier (to the left on a timeline running from plan to production). It does not mean "only test early". Some problems only show up in a running system, so you also shift right: monitor production, collect security logs, and respond to incidents. A mature program does both.
Principles that guide every decision
A handful of principles come up in every later lesson. Learn them by name; you'll use them to argue for or against designs.
| Principle | Meaning | Example |
|---|---|---|
| Least privilege | Every person, service and token gets only the access it needs, for only as long as it needs it. | A CI job that runs tests gets read-only repository access, not admin. |
| Defense in depth | Layer independent controls so one failure isn't a breach. | Parameterised queries and a database user that can't drop tables. |
| Secure by default | The easy path is the safe path. | A service template that ships with TLS, security headers and non-root containers already on. |
| Fail closed | When a check can't decide, deny. | If the authorization service times out, reject the request rather than allow it. |
| Minimise attack surface | Fewer exposed features, ports and dependencies means fewer things to attack. | Remove the unused admin endpoint instead of protecting it. |
| Assume breach | Design as if an attacker is already inside one component. | Short-lived credentials and audit logs, so a stolen token is limited and traceable. |
Behind these sits the CIA triad, the three properties security protects: confidentiality (only authorised parties read data), integrity (data and code aren't changed without authorisation) and availability (the system keeps working). Every vulnerability you meet threatens at least one of them, which is a useful way to describe impact.
The secure SDLC, phase by phase
The software development lifecycle (SDLC) has the same phases whether you run Scrum, Kanban or something else. A secure SDLC attaches specific controls to each one. This table is the map for the rest of this roadmap.
| Phase | Security activity | What it catches | Typical tooling |
|---|---|---|---|
| Plan / design | Threat modeling, security requirements | Design flaws: missing authorization, trust in client input | Whiteboard, data-flow diagrams, STRIDE |
| Code | Secure coding standards, IDE linting, pre-commit hooks | Injection, hard-coded secrets, unsafe APIs | Linters, gitleaks |
| Commit / review | Code review with a security checklist, branch protection | Logic bugs, risky changes to auth or CI config | Pull requests, CODEOWNERS |
| Build | SAST, dependency scanning (SCA), SBOM generation | Known-vulnerable libraries, dangerous code patterns | Semgrep, CodeQL, Trivy, Dependabot |
| Test | DAST, security unit tests | Misconfigured headers, auth bypass in the running app | OWASP ZAP, integration tests |
| Release | Artifact signing, provenance | Tampered builds, unknown artifact origin | Sigstore cosign, SLSA provenance |
| Deploy | IaC scanning, policy as code, least-privilege deploy credentials | Public buckets, open security groups, over-privileged roles | Checkov, OPA, OIDC to the cloud |
| Operate | Logging, alerting, patching, incident response | Active attacks, newly disclosed vulnerabilities | SIEM, runtime detection, on-call runbooks |
The earliest phases catch the most expensive problems: no scanner finds "users can see each other's invoices" as reliably as ten minutes of threat modeling. And each control catches a different class of problem, so they complement each other.
A worked example: one bug, many layers
Follow a single mistake through a pipeline with these controls. A developer adds an endpoint that exports a customer's orders as CSV, and makes three errors: the SQL query is built with string formatting, the endpoint doesn't check that the order belongs to the caller, and a test API key is pasted into a config file.
Developer laptop
pre-commit: gitleaks ............ BLOCKS commit: "generic-api-key in config/dev.py:12"
-> key moved to .env (git-ignored), commit retried
Pull request
CI job "sast" (Semgrep) ......... FAILS: "sqlalchemy raw SQL with string formatting"
-> query rewritten with bound parameters, job passes
CI job "sca" .................... passes (no vulnerable dependencies)
Code review (CODEOWNERS for /api/) reviewer asks: "where is the ownership check?"
-> WHERE order.customer_id = :current_user added, test written
Staging
DAST baseline scan .............. WARN: missing Content-Security-Policy header
-> ticket created, fixed next sprint (medium severity)
Production
Access logs + alerting .......... would flag one user exporting thousands of ordersNo single layer caught everything. The secret scanner can't spot a missing authorization check; the reviewer is unlikely to notice a high-entropy string in a 400-line diff; the header problem only exists in a running app. This is defense in depth applied to the delivery process itself.
Notice also what didn't happen: no meeting, no ticket to a security team, no week of waiting. Each fix happened within minutes of the mistake, by the person who made it.
Shared ownership in practice
"Security is everyone's job" turns into "security is nobody's job" unless you make ownership explicit. Teams that do this well usually have:
- A platform or security team that builds paved roads. They maintain the CI templates, base images and libraries that make the secure path the default. Their customers are developers.
- Development teams that own their findings. A vulnerability in a service is that service team's bug, prioritised like any other bug, with a deadline set by severity.
- Security champions. One developer per team with extra training and a direct line to the security team. They triage scanner output, run threat-modeling sessions and spread knowledge. It's a role, not a job title.
Write the rules down. The most important rule is the remediation SLA: how long a finding of each severity may stay open. A common starting point:
| Severity | Example | Blocks merge? | Fix within |
|---|---|---|---|
| Critical | Exploitable remote code execution, leaked production credential | Yes | 24 hours (rotate secrets immediately) |
| High | SQL injection, authentication bypass | Yes | 7 days |
| Medium | Missing security header, verbose error messages | No | 30 days |
| Low | Informational findings, hardening suggestions | No | Next planned work, or accept |
Exceptions with an expiry date
Sometimes a finding is a false positive, or the risk is genuinely acceptable for now. Record that decision instead of silently ignoring the alert. Most scanners support an ignore file; keep the reason, an owner and an expiry date next to each entry, and review them:
# security-exceptions.yaml (a team convention, reviewed monthly)
- id: CVE-2026-12345
package: libxml-parse 2.4.1
reason: Vulnerable function is only reachable via XML import, which is disabled in our build.
owner: payments-team
approved_by: security-champion@payments
expires: 2026-12-31When the date passes, the exception lapses and the finding blocks again. This turns "we'll deal with it later" into a decision someone made, with a deadline.
Measuring whether it works
What you measure shapes behaviour. Counting raw findings rewards running fewer scanners. Better metrics describe speed and coverage:
- Mean time to remediate (MTTR) per severity. Is it within your SLA?
- Coverage. What share of repositories run secret scanning, SAST and dependency scanning on every pull request?
- Age of the open backlog. Old high-severity findings mean the process is broken somewhere.
- Escaped defects. Vulnerabilities found in production (by pentests, bug bounty or incidents) that an earlier control should have caught. Each one is a prompt to add or tune a control.
Common pitfalls
- Tool sprawl. Five overlapping scanners produce duplicate, conflicting findings. Start with one tool per category and tune it.
- Blocking on noise. A scanner turned on at full strictness across a legacy codebase produces thousands of findings on day one. Block on new findings in changed code first, and burn down the existing backlog separately.
- Security as a checkbox. A green pipeline means "no known patterns were found", not "secure". Threat modeling and review still matter.
- Forgetting the pipeline itself. CI systems hold deploy credentials and can push to production. They are a high-value target and need the same least privilege and review as application code.
Practice
Pick a repository you work on (or any open-source project you know well). Create a one-page security baseline for it:
- For each SDLC phase in the table above, write down which control exists today, or "none".
- Identify the two biggest gaps, and say which kind of problem each gap lets through.
- Propose a remediation SLA table for the team and decide which severities block a merge.
- Write one exception entry for a finding you'd accept, including an expiry date.
Show solution
A worked baseline for a typical small web API (Python, deployed to a cloud provider from CI):
| Phase | Control today |
|---|---|
| Plan / design | None; features go straight to tickets |
| Code | Linter (style only) |
| Commit / review | Pull requests required, one approval, no CODEOWNERS |
| Build | Dependabot alerts enabled but not reviewed; no SAST |
| Test | Unit tests only |
| Release | None |
| Deploy | CI uses a long-lived cloud access key with admin rights |
| Operate | Application logs, no security alerting |
Biggest gaps:
- Deploy credentials. An admin-level, long-lived key in CI means anyone who can change a workflow (or compromise a dependency that runs in CI) can take over the cloud account. Fix: OIDC federation with a role scoped to deployment only.
- No design review. Authorization flaws (IDOR, missing role checks) are invisible to the current controls. Fix: a 30-minute threat model for any feature that touches auth, payments or personal data.
SLA: critical 24 h, high 7 days, medium 30 days, low best effort; critical and high block merges on new findings.
Exception:
- id: python.flask.security.audit.debug-enabled.debug-enabled
path: scripts/local_dev_server.py
reason: Local-only helper, never deployed; excluded from the container image.
owner: api-team
expires: 2027-03-31Key takeaways
- DevSecOps builds automated, fast-feedback security checks into the delivery pipeline and makes developers owners of their findings.
- Shift left to catch problems cheaply, and shift right with monitoring and incident response for what only appears in production.
- Least privilege, defense in depth, secure defaults and fail-closed behaviour are the principles behind almost every control.
- Map controls to every SDLC phase; each catches a different class of problem, so they layer rather than replace each other.
- Block merges only on high-confidence, high-severity findings, set remediation SLAs, and record exceptions with an owner and an expiry date.
- Treat the CI/CD system itself as production infrastructure: it holds the keys.
Check yourself
Quiz
0/4 correct · 4 left
1.What does "shift left" mean in DevSecOps?
2.A team turns on a SAST scanner at maximum strictness and makes every finding fail the build. After a month, developers routinely disable the job. What went wrong?
3.Which control is most likely to catch a missing ownership check (users can read each other's invoices)?
4.Why should a risk-acceptance exception include an expiry date?
Concepts in this lesson
One-page notes on each idea, for review or a quick lookup later.
Finished reading?
Mark it complete to fill in your roadmap.