CI-Embedded Security
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
| Surface | Description |
|---|---|
| Dependencies | Third‑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 code | SQL injection, hard‑coded secrets, unsafe regex… the classics. AI generates these patterns constantly, and so do humans. Nobody’s immune. |
| Secrets | API 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
| Flag | Why |
|---|---|
--severity HIGH,CRITICAL | LOW/MEDIUM create noise – hundreds of alerts, most are theoretical. Focus on what’s actually exploitable. |
--ignore-unfixed | Some vulnerabilities have no patch yet. You can’t fix what isn’t fixed. |
--scanners vuln | Trivy 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
| Flag | Meaning |
|---|---|
-r backend/app | Recursively scan the source directory. |
-ll | Show only medium and high severity findings (low is noisy). |
-ii | Show 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?
| Rule | Reason |
|---|---|
detect-object-injection | Flags every arr[i] and obj[key] – thousands of false positives. |
detect-possible-timing-attacks | Flags 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– catcheseval()abuse.detect-child-process– warns about shell‑injection risks.detect-non-literal-require– prevents dynamicrequirecalls.
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-securityruns with the normal lint job—no extra CI configuration is needed. Just copy, paste, and adapt.
Three Acronyms – Three Different Things
| Acronym | Tool(s) | What It Scans | Typical Question |
|---|---|---|---|
| SCA (Software Composition Analysis) | Trivy | Dependencies | “Is this library vulnerable?” |
| SAST (Static Application Security Testing) | Bandit, eslint‑plugin‑security | Source code | “Did I write something dangerous?” |
| Secrets Detection | Gitleaks | Git 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.