DevSecOps: Securing Code, Dependencies and Containers
Exit

Lesson 1 of 11 · Fundamentals

Shift-left and the OWASP Top 10: security starts in the editor

45 min Node.js 22 (Debian, git, npm, python3)

Objective

Understand why security must enter developers' work from the very first line of code (the "shift-left"), and learn to recognize, in a real source file, the major families of risks described by the OWASP Top 10:2025.

Key points

  • The later a flaw is found, the more it costs: a line fixed in the editor costs a few minutes; the same flaw exploited in production costs a data leak, a notification to the CNDP and your customers' trust.
  • DevSecOps adds security to every step of the DevOps cycle (plan, code, build, test, release, operate), automated as much as possible and shared by the whole team.
  • Shift-left = moving controls to the left of the cycle: threat modeling, analysis of the code, dependencies and secrets before the merge, not after going live.
  • The OWASP Top 10 is the reference list of the ten most serious risks for web applications. The 2025 version starts with broken access control (A01) and adds the software supply chain (A03).
  • A tool does not replace reasoning: you must be able to read a flaw, classify it and fix it at the root.

1. Why "shift-left"?

Imagine the life cycle of an application drawn from left to right: you design, code, build, test, deploy, operate. For a long time, security lived on the far right: once the application was finished, an auditor ran a penetration test a few days before going live, delivered a 60-page report… and the team discovered that the whole authentication had to be redone.

Take a Casablanca fintech developing a mobile wallet. Here is the same flaw — a password stored in MD5 — discovered at four different moments:

When it is discovered What you must do Approximate cost
In the editor (extension or code review) Change two lines A few minutes
In CI, before the merge Fix it, rerun the pipeline One hour
During the penetration test before launch Fix it, migrate the test accounts, retest, postpone the date Several days
After a leak in production Force a reset of all passwords, investigate, inform the CNDP and the customers, communicate Weeks, money, reputation

Shift-left means pushing controls to the left: you do not remove the final audit, but you make sure it finds almost nothing.

What this changes in practice

# Before: security comes at the end, once a year
#   code → build → deployment → (6 months later) audit → panic

# After: each step carries its own small automatic check
#   editor       : Semgrep extension, alert on a secret pasted into the code
#   pre-commit   : gitleaks rejects a commit containing a key
#   pull request : SAST (Semgrep), dependency audit (npm audit, pip-audit)
#   build        : image scan (Trivy), SBOM (Syft), signature (cosign)
#   deployment   : rejection of an unsigned image or one carrying a critical flaw
#   production   : logs, alerts, quick fixes

You will see each of these tools later in the course.

2. DevSecOps: a culture before being tools

"DevSecOps" is neither a job title nor a piece of software you buy. It is a way of working based on three ideas.

  1. Security is everyone's business. The developer who writes the SQL query is the best placed to protect it. The security team (when there is one) becomes a support: it provides rules, templates and training, instead of playing the police officer at the end of the project.
  2. Automate what can be automated. A human forgets; a pipeline does not. Every repetitive check (secrets, dependencies, Docker image) must run by itself on every change.
  3. Fast and useful feedback. An alert must arrive while the developer still has the code in mind, with an explanation and a hint for the fix. A thousand untriaged alerts, on the other hand, get ignored: a few reliable rules are better than a lot of noise.

The Moroccan legal framework

In Morocco, law 09-08 protects personal data. The controller of a processing operation (a bank, an online shop, an administration offering an online service) must take appropriate technical and organizational measures to protect this data against destruction, loss, alteration or unauthorized access. The CNDP (Commission nationale de contrôle de la protection des données à caractère personnel, the Moroccan data protection authority) enforces these obligations.

In other words: an SQL injection that exposes your customers' CIN (national ID card) numbers and phone numbers is not just a "bug". It is a breach of a legal obligation. DevSecOps gives you concrete evidence that you took these measures: analysis rules, scan reports, history of fixes.

3. OWASP and its Top 10

OWASP (Open Worldwide Application Security Project) is a non-profit foundation that publishes free guides, tools and standards for application security. Its best-known document, the Top 10, ranks the most serious risks observed in web applications, based on real data provided by companies and on the community's opinion.

The 2025 version is as follows:

Code Risk Moroccan example
A01 Broken Access Control On an e-commerce portal, changing /commandes/1041 into /commandes/1042 shows another customer's order.
A02 Security Misconfiguration An administration dashboard left public with the admin/admin account, a storage bucket open to everyone.
A03 Software Supply Chain Failures An abandoned npm library, or a latest Docker image that changes without warning and ships a booby-trapped component.
A04 Cryptographic Failures Passwords hashed with MD5, card numbers stored in clear text, a site without HTTPS.
A05 Injection The "CIN number" field of an administrative form is pasted as is into an SQL query.
A06 Insecure Design A 4-digit verification code sent by SMS, with no limit on attempts: it can be guessed in a few minutes.
A07 Authentication Failures No lockout after failed attempts, sessions that never expire.
A08 Software or Data Integrity Failures An update downloaded and installed without checking its signature.
A09 Security Logging and Alerting Failures A brute-force attack lasts three days without a single alert being sent.
A10 Mishandling of Exceptional Conditions An error returns the full stack trace to the client, or an exception leaves a transaction half committed.

Remember the two new entries of 2025: the supply chain (A03) — everything you do not write yourself but still ship — and the handling of exceptional conditions (A10). These are precisely two topics that DevSecOps handles with automatic tools (dependency audit, SBOM, signatures, error tests).

The Top 10 is an awareness tool, not an exhaustive list. To check an application point by point, OWASP offers a more complete standard: the ASVS (Application Security Verification Standard).

4. Reading code with an attacker's eyes

Let's look at a typical extract, very close to the one in the lab. It is a fintech API: a logged-in customer views a transaction.

async function voirTransaction(req, res, db) {
  const lignes = await db.query(`SELECT * FROM transactions WHERE id = ${req.params.id}`);
  res.json(lignes[0]);
}

Two flaws hide in these three lines.

Flaw 1 — A05 Injection. The value req.params.id comes from the URL, so from the attacker. It is pasted into the SQL text. If you request /transactions/0 OR 1=1, the query becomes ... WHERE id = 0 OR 1=1 and returns the first transaction in the database, whatever it is. With a little more imagination (UNION SELECT ...), you can read other tables.

The fix: a parameterized query. The SQL text contains ? placeholders and the values travel separately; the database driver never interprets them as SQL.

const lignes = await db.query(
  "SELECT * FROM transactions WHERE id = ? AND client_id = ?",
  [req.params.id, req.user.id]          // valeurs envoyées séparément
);

Flaw 2 — A01 Broken Access Control. Even without injection, the first version returns the requested transaction without checking that it belongs to the logged-in customer. This is the most frequent flaw of the Top 10 (also called IDOR, Insecure Direct Object Reference). The fix above handles it too: the condition AND client_id = ? limits the search to the transactions of the authenticated user.

Storing passwords

function hacherMotDePasse(mdp) {
  return crypto.createHash("md5").update(mdp).digest("hex");
}

A04 Cryptographic Failures. MD5 is a fast and broken hash function. Fast is a flaw here: a graphics card computes billions of MD5 hashes per second, and without a salt, two customers who have the same password (Casa2026!) have the same hash, which can be found in precomputed tables.

You need a slow and salted function, designed for passwords: scrypt, Argon2id or bcrypt. Node.js provides scrypt with no dependency:

const crypto = require("node:crypto");

function hacherMotDePasse(mdp) {
  const sel = crypto.randomBytes(16);                     // sel aléatoire, unique par compte
  const empreinte = crypto.scryptSync(mdp, sel, 64);      // dérivation volontairement coûteuse
  return sel.toString("hex") + ":" + empreinte.toString("hex");   // on stocke les deux
}

(sel means salt and empreinte means hash: the salt is random and unique per account, the derivation is deliberately expensive, and both values are stored.)

To check a password at login, you read the stored salt again, recompute the hash and compare with crypto.timingSafeEqual (constant-time comparison).

Errors that talk too much

function gererErreur(err, req, res, next) {
  res.status(500).json({ erreur: err.message, pile: err.stack });
}

A10 Mishandling of Exceptional Conditions (it used to be classified as misconfiguration, A02). The stack trace reveals file paths, library versions, sometimes a full SQL query: a gift for the attacker. You log the details on the server side and return a neutral message:

function gererErreur(err, req, res, next) {
  console.error(err);                                     // le détail reste dans les journaux
  res.status(500).json({ erreur: "Erreur interne, réessayez plus tard" });
}

5. Which tool for which risk?

No tool covers the whole Top 10. Here is the map of what you are going to learn and what each tool catches:

Practice (lesson) What it mainly detects Targeted categories
STRIDE threat modeling (1.2) Design flaws, missing access controls A01, A06
Secret detection — Gitleaks (1.3) Committed API keys and passwords A04, A07
Static analysis — Semgrep (2.1) Injections, weak cryptography, errors A05, A04, A10
Dependency audit — npm audit, pip-audit (2.2) Vulnerable libraries A03
SBOM — Syft (2.3) Inventory of everything you ship A03
Image and IaC scanning — Trivy (3.1) Vulnerable packages, dangerous configurations A02, A03
Hardened Dockerfile (3.2) Root containers, oversized images A02
Signatures — cosign (3.3) Tampered artifacts A08
Gates in CI (3.4) Everything above, on every change all

What tools find poorly: business logic flaws (a refund that can be requested twice, an access control that is "almost" correct). For those, you still have threat modeling, code review and tests.

6. Where to start in a small team?

A three-developer start-up in Rabat has neither a budget nor a security team. A realistic order:

# Week 1: stop the bleeding
#   - gitleaks as pre-commit and in CI (secrets leak first)
#   - npm audit / pip-audit in CI, "critical" threshold
# Week 2: the code
#   - Semgrep with a standard rule set, in "warning" mode first
# Week 3: the containers
#   - non-root Dockerfile, precise base image, Trivy scan of the image
# Then: threat modeling of every new sensitive feature,
#           SBOM and signing of releases, then stricter thresholds

What matters is to start small and never go back: once a gate blocks critical flaws, you never remove it.

Common pitfalls

  • Believing a tool is enough. A green scanner does not prove the application is safe; it only proves it found nothing among what it knows how to look for.
  • Enabling every rule on day one. Hundreds of untriaged alerts discourage the team, which ends up disabling the tool. Start with high severities.
  • Fixing the symptom, not the cause. Filtering the word OR in a field does not fix an injection; only a parameterized query does.
  • Forgetting access control. A parameterized query that returns another customer's transaction is still a flaw (A01).

Lab

You do this lab on your own working virtual machine: run sudo ml demarrer devsecops-1-1, do the tasks in your terminal, then check with sudo ml verifier.

You are in charge of the security review of an extract of the API of AtlasPay, a Casablanca fintech: /root/atlaspay/src/paiements.js. Four flaws are marked [1] to [4] in it.

  1. Classify each flaw in the OWASP Top 10:2025 (file constats.txt).
  2. Fix the injection and the access control with a parameterized query filtered on the logged-in customer.
  3. Replace MD5 with scrypt and a random salt.
  4. Make sure errors no longer reveal anything to the client.

After each change, check the syntax with node --check /root/atlaspay/src/paiements.js and reread the file with cat -n.

Quiz

  1. What does "shift-left" mean?
  2. In which OWASP Top 10:2025 category do you place an API that returns another customer's order when you change the identifier in the URL?
  3. Why does a parameterized query protect against SQL injection, while filtering apostrophes is not enough?
  4. Why is MD5 a bad choice for storing passwords, and what should you use instead?
  5. Which category appeared in the Top 10:2025 to cover the libraries, images and tools that you ship without having written them?
Answers
  1. Moving security controls to the beginning of the development cycle (design, code, pull request) instead of concentrating them at the end, where fixes cost the most.
  2. A01, broken access control (a typical IDOR case).
  3. Because the values are sent separately from the SQL text: the engine never interprets them as code. Filtering always misses a case (another encoding, another character, a numeric value without an apostrophe).
  4. MD5 is fast (billions of attempts per second) and, without a salt, two identical passwords give the same hash. You need a slow and salted function designed for this: scrypt, Argon2id or bcrypt.
  5. A03, Software Supply Chain Failures.

Reference sources: OWASP (CC BY-SA 4.0), Node.js documentation, CNDP. Original MasterLinux text.

Practical exercises

To do on Node.js 22 (Debian, git, npm, python3)

Do this lab on your own machine

The terminal is yours: a Linux VM (VirtualBox, VMware, Hyper-V, WSL…) or Docker/Podman. The ml tool prepares the exercise and checks your work; the lesson is validated here automatically.

Create a free account to get your personal command and have your labs checked automatically.

Create my account

Tasks

  1. Read /root/atlaspay/src/paiements.js. In /root/atlaspay/constats.txt, write the OWASP Top 10:2025 category of each of the four flaws marked [1] to [4], one line per flaw in the format numero=Axx (for example 1=A07).
    Show a hint[1]: you read something that does not belong to you. [2]: user data goes into an SQL query. [3]: a broken hashing algorithm. [4]: a badly handled error that talks too much. For example, write: printf '%s\n' 1=A.. 2=A.. 3=A.. 4=A.. > /root/atlaspay/constats.txt
    Show the solution
    printf '%s\n' 1=A01 2=A05 3=A04 4=A10 > /root/atlaspay/constats.txt

    Check : f=/root/atlaspay/constats.txt && grep -qx "1=A01" "$f" && grep -qx "2=A05" "$f" && grep -qx "3=A04" "$f" && grep -Eqx "4=A(10|02)" "$f"

  2. Fix voirTransaction: the query must be parameterized (WHERE id = ? AND client_id = ?) with the values passed separately, in an array [req.params.id, req.user.id]. No more ${req…} interpolation in the SQL.
    Show a hintReplace the whole line const lignes = … with: const lignes = await db.query("SELECT * FROM transactions WHERE id = ? AND client_id = ?", [req.params.id, req.user.id]); (sed -i 's|^ const lignes = .*|…|' works well).
    Show the solution
    sed -i 's|^  const lignes = .*|  const lignes = await db.query("SELECT * FROM transactions WHERE id = ? AND client_id = ?", [req.params.id, req.user.id]);|' /root/atlaspay/src/paiements.js

    Check : f=/root/atlaspay/src/paiements.js && ! grep -qF '${req' "$f" && grep -qF 'WHERE id = ? AND client_id = ?' "$f" && grep -qF 'req.user.id' "$f" && node --check "$f"

  3. Replace the MD5 hash with crypto.scryptSync and a random 16-byte salt (crypto.randomBytes): the function must return the salt and the hash in hexadecimal, separated by ":". Two calls with the same password must therefore give two different results.
    Show a hintIn hacherMotDePasse: const sel = crypto.randomBytes(16); return sel.toString("hex") + ":" + crypto.scryptSync(mdp, sel, 64).toString("hex");
    Show the solution
    sed -i 's|return crypto.createHash("md5").update(mdp).digest("hex");|const sel = crypto.randomBytes(16); return sel.toString("hex") + ":" + crypto.scryptSync(mdp, sel, 64).toString("hex");|' /root/atlaspay/src/paiements.js

    Check : f=/root/atlaspay/src/paiements.js && ! grep -qi 'createHash(.md5' "$f" && grep -q scryptSync "$f" && node -e 'const m=require("/root/atlaspay/src/paiements.js");const a=m.hacherMotDePasse("Rabat2026"),b=m.hacherMotDePasse("Rabat2026");process.exit(a!==b&&a.includes(":")&&a.length>100?0:1)'

  4. The error handler sends the message and the stack trace to the client. Log the error on the server side (console.error(err)) and return only a generic message with code 500: no property of err in the json(...) any more.
    Show a hintA single line is enough: console.error(err); res.status(500).json({ erreur: "Erreur interne, réessayez plus tard" }); (French for "Internal error, try again later")
    Show the solution
    sed -i 's|res.status(500).json({ erreur: err.message, pile: err.stack });|console.error(err); res.status(500).json({ erreur: "Erreur interne, reessayez plus tard" });|' /root/atlaspay/src/paiements.js

    Check : f=/root/atlaspay/src/paiements.js && ! grep -Eq 'json\(.*err\.' "$f" && grep -q 'status(500)' "$f" && grep -q 'console.error(err)' "$f" && node --check "$f"

Official references consulted: OWASP Top 10:2025 — https://owasp.org/Top10/2025/ ; OWASP DevSecOps Guideline — https://owasp.org/www-project-devsecops-guideline/ ; OWASP Cheat Sheet Series — Password Storage, SQL Injection Prevention, Error Handling — https://cheatsheetseries.owasp.org/ ; OWASP Application Security Verification Standard (ASVS) — https://owasp.org/www-project-application-security-verification-standard/ ; Node.js — documentation du module crypto (scrypt) — https://nodejs.org/api/crypto.html ; CNDP — Loi 09-08 relative à la protection des personnes physiques à l'égard du traitement des données à caractère personnel — https://www.cndp.ma/. Original MasterLinux text.