CI-Embedded Security

Published: (February 4, 2026 at 03:01 PM EST)
5 min read
Source: Dev.to

Source: Dev.to

Check before you wreck

Part of The Coercion Saga – making AI write quality code.

Linters catch your mistakes. Type checkers catch your assumptions.
But what about security? That’s a different beast entirely.

Three Attack Surfaces – Three Different Problems

SurfaceDescription
DependenciesThird‑party code. You install a package, it works. Six months later a critical vulnerability is discovered – it’s been in your production code the whole time and you had no idea.
Your codeSQL injection, hard‑coded secrets, unsafe regex… the classics. AI generates these patterns constantly, and so do humans. Nobody’s immune.
SecretsAPI keys, passwords, tokens. One accidental commit and they’re in your Git history forever. Scrubbing them is a nightmare.

You can’t manually track thousands of CVEs, review every line for security anti‑patterns, or grep every commit for leaked credentials. So I let robots do all three.

1️⃣ Scanning dependencies with Trivy

Every merge request is scanned and every dependency is checked.
HIGH and CRITICAL vulnerabilities block the merge – no exceptions.

# .gitlab-ci.yml – Dependency scanning
trivy:backend:
  stage: security
  image: aquasec/trivy:latest
  script:
    - trivy fs --severity HIGH,CRITICAL --ignore-unfixed --scanners vuln backend/
  allow_failure: false

trivy:frontend:
  stage: security
  image: aquasec/trivy:latest
  script:
    - trivy fs --severity HIGH,CRITICAL --ignore-unfixed --scanners vuln frontend/
  allow_failure: false

The flags that matter

FlagWhy
--severity HIGH,CRITICALLOW/MEDIUM create noise – hundreds of alerts, most are theoretical. Focus on what’s actually exploitable.
--ignore-unfixedSome vulnerabilities have no patch yet. You can’t fix what isn’t fixed.
--scanners vulnTrivy can also scan misconfigurations, secrets, licenses. Too much at once is overwhelming – start with vulnerabilities.

Example output

backend/requirements.txt
========================
Total: 1 (HIGH: 1, CRITICAL: 0)

┌─────────────┬─────────────────┬──────────┬─────────────────┐
│ Library     │ Vulnerability   │ Severity │ Fixed Version   │
├─────────────┼─────────────────┼──────────┼─────────────────┤
│ cryptography│ CVE-2023-XXXXX  │ HIGH     │ 41.0.0          │
└─────────────┴─────────────────┴──────────┴─────────────────┘

The CI job fails, showing exactly which package, which CVE, and the version that fixes it. Update the package → CI passes. Done.

2️⃣ Scanning Your Python Code with Bandit

Static analysis for security issues—SQL injection, hard‑coded passwords, dangerous function calls, shell injection—the stuff that ends careers.

# .gitlab-ci.yml – Python security linting
bandit:backend:
  stage: security
  image: python:3.11-slim
  before_script:
    - pip install bandit
  script:
    - bandit -r backend/app -ll -ii
  allow_failure: false

The Flags That Matter

FlagMeaning
-r backend/appRecursively scan the source directory.
-llShow only medium and high severity findings (low is noisy).
-iiShow only medium and high confidence (filters out guesses).

Run locally first:

bandit -r backend/app -ll -ii

What It Catches

>> Issue: [B608:hardcoded_sql_expressions] Possible SQL injection
   Severity: Medium   Confidence: Medium
   Location: backend/app/api/routes/users.py:45
   More Info: https://bandit.readthedocs.io/en/latest/...

44      query = f"SELECT * FROM users WHERE id = {user_id}"
45      cursor.execute(query)

Classic string interpolation in SQL – use parameterised queries instead.

3️⃣ Security‑focused ESLint for TypeScript/JavaScript

Install the plugin:

npm install -D eslint-plugin-security

Add it to your ESLint configuration (eslint.config.js or .eslintrc.js):

import security from 'eslint-plugin-security';

export default [
  // …other configs
  security.configs.recommended,
  {
    rules: {
      // Too noisy for frontend code
      'security/detect-object-injection': 'off',
      'security/detect-possible-timing-attacks': 'off',
    },
  },
];

Why disable those rules?

RuleReason
detect-object-injectionFlags every arr[i] and obj[key] – thousands of false positives.
detect-possible-timing-attacksFlags password comparisons. Front‑end validation isn’t a timing‑attack vector; it’s a server‑side concern.

Rules that remain enabled

  • detect-unsafe-regex – protects against ReDoS attacks.
  • detect-eval-with-expression – catches eval() abuse.
  • detect-child-process – warns about shell‑injection risks.
  • detect-non-literal-require – prevents dynamic require calls.

Now npm run lint will surface security issues, and your CI pipeline already runs the lint job—no extra step required.

4️⃣ Scanning for leaked secrets with Gitleaks

# .gitlab-ci.yml – Secret scanning
gitleaks:
  stage: security
  image:
    name: zricethezav/gitleaks:latest
    entrypoint: [""]
  script:
    - gitleaks detect --source . --verbose
  allow_failure: false

It scans the entire Git history – every commit, every branch, every file that ever existed.

Example finding

Finding:     POSTGRES_PASSWORD: SuperSecretPassword123
Secret:      SuperSecretPassword123
RuleID:      generic-api-key
File:        docker-compose.yml
Line:        9
Commit:      abc123...

Game over – you committed a secret.

The noise problem

Gitleaks is paranoid. It will flag example passwords in docs, test JWT tokens, and template files with placeholder values.

Solution – an allow‑list (.gitleaks.toml)

[extend]
useDefault = true

[allowlist]
description = "Allowlisted files and patterns"
paths = [
  '''infra/\.env\..*\.template''',
  '''docs/.*\.md''',
  '''docker-compose\.yml''',
]

Template files, documentation, and local‑dev config are ignored – they’re not production secrets.

5️⃣ Full CI Configuration (Single Security Stage)

stages:
  - security

# ── Dependency scanning ────────────────────────────────────────
trivy:backend:
  stage: security
  image: aquasec/trivy:latest
  script:
    - trivy fs --severity HIGH,CRITICAL --ignore-unfixed --scanners vuln backend/
  allow_failure: false

trivy:frontend:
  stage: security
  image: aquasec/trivy:latest
  script:
    - trivy fs --severity HIGH,CRITICAL --ignore-unfixed --scanners vuln frontend/
  allow_failure: false

# ── Python static analysis ───────────────────────────────────────
bandit:backend:
  stage: security
  image: python:3.11-slim
  before_script:
    - pip install bandit
  script:
    - bandit -r backend/app -ll -ii
  allow_failure: false

# ── Secret scanning ───────────────────────────────────────────────
gitleaks:
  stage: security
  image:
    name: zricethezav/gitleaks:latest
    entrypoint: [""]
  script:
    - gitleaks detect --source . --verbose
  allow_failure: false
  • All five jobs run in the security stage.
  • Each job must pass before a merge is allowed.

Bottom line: Let the robots do the heavy lifting. Scan dependencies, source code, and Git history automatically, block merges on real‑world HIGH/CRITICAL findings, and keep the noise low with sensible allow‑lists and flag choices. Your CI pipeline becomes a security gatekeeper, not a manual checklist.

CI Security Jobs

bandit:backend:
  stage: security
  image: python:3.11-slim
  before_script:
    - pip install bandit
  script:
    - bandit -r backend/app -ll -ii
  allow_failure: false

gitleaks:
  stage: security
  image:
    name: zricethezav/gitleaks:latest
    entrypoint: [""]
  script:
    - gitleaks detect --source . --verbose
  allow_failure: false

Note: eslint-plugin-security runs with the normal lint job—no extra CI configuration is needed. Just copy, paste, and adapt.


Three Acronyms – Three Different Things

AcronymTool(s)What It ScansTypical Question
SCA (Software Composition Analysis)TrivyDependencies“Is this library vulnerable?”
SAST (Static Application Security Testing)Bandit, eslint‑plugin‑securitySource code“Did I write something dangerous?”
Secrets DetectionGitleaksGit history“Did I commit something I shouldn’t have?”

You need all three:

  • A secure library won’t protect you from SQL injection.
  • Clean code won’t protect you from a leaked API key.
  • Secret scanning won’t protect you from a vulnerable dependency.

Think of it as belt, suspenders, and a backup belt.


Why This Matters

  • I don’t track CVEs manually.
  • I don’t read security newsletters.
  • I don’t grep commits for passwords.

The scanners run on every merge request, ensuring that:

  • Vulnerable dependencies never reach main.
  • Dangerous code patterns never reach main.
  • Leaked secrets never reach main.

Result: Security you don’t have to think about.


What’s Next?

[Backend Tests] – coming soon
Dependencies are secure. Code is clean. Now prove the backend actually works.

Back to Blog

Related posts

Read more »

Jenkins Runs Commands For Me

Before Jenkins - Engineer runs commands manually - Forgets steps - Makes mistakes - Cannot repeat reliably With Jenkins - Commands are written once - Jenkins r...