Loading blog posts...
Loading blog posts...
Loading...
Discover proven DevSecOps practices to catch vulnerabilities early in development. Learn SAST tools, custom security rules, and pipeline integration strategies. Start securing your code today.

Your security scan just flagged 847 vulnerabilities across 12 microservices. The dev team is already behind on the sprint. Sound familiar? DevSecOps helps by catching issues while they’re still cheap and fast to fix: minutes, not days. The tools and practices below reflect what teams are actually using right now to weave security into the development lifecycle. Each category maps to a point in the pipeline where vulnerabilities typically slip in.
SAST tools review source code without running it, so you can spot vulnerabilities before the first build even finishes.
yaml#.semgrep.yml - Custom rule for SQL injection rules: - id: sql-injection-format-string patterns: - pattern: | cursor.execute(f".. {$VAR}..") message: "Possible SQL injection via f-string" languages: [python] severity: ERROR
This rule catches something many generic scanners miss: SQL injection via Python f-strings. The $VAR metavariable matches any interpolated value, flagging code where user input could end up in raw SQL. Teams that run this in CI tend to catch these issues within minutes of a commit, instead of discovering them weeks later during a pen test.
Semgrep’s real strength is custom rules. The default rulesets cover the usual suspects, but every codebase has its own “special” patterns. A fintech team might write rules around their authentication flows. A healthcare platform might enforce HIPAA-driven data handling rules.
What it is: Lightweight static analysis engine supporting custom rules across 30+ languages
Pricing: Free (OSS) / Team $40/contributor/mo / Enterprise custom
Best for: Teams wanting fast, customizable SAST in CI
| Strengths | Limitations |
|---|---|
| Sub-second scan times on most repos | Fewer out-of-box rules than commercial tools |
| Write rules in 10 minutes, not days | No dataflow analysis in free tier |
| Runs locally without sending code externally | Pro features require paid plans |
Bottom line: A strong pick if you want tight control over what gets flagged and you care about fast feedback.
SonarQube goes the other direction: deeper analysis plus quality gates that can stop merges.
yaml# sonar-project.properties sonar.projectKey=myapp sonar.sources=src sonar.exclusions=**/test/**,**/vendor/** sonar.qualitygate.wait=true
The sonar.qualitygate.wait=true setting matters. Without it, the scan runs but the pipeline keeps moving no matter what it finds. With it turned on, a failed quality gate blocks the build. Most teams start with this off while they build a baseline, then flip it on once the initial backlog is under control.
SonarQube is especially useful if you need compliance-friendly reporting. Its historical tracking shows security debt trends over time, which tends to play well in SOC 2 or ISO 27001 audits.
Datadog's 2025 State of DevSecOps research found that 86% of commercial codebases contain open source vulnerabilities. SCA tools surface risky dependencies before attackers do.
bash## Scan and auto-fix vulnerable dependencies snyk test --all-projects snyk fix --all-projects
snyk fix tries to automatically remediate by upgrading packages to patched versions where it can. This usually works well for direct dependencies, but transitive dependencies can get messy. If the vulnerable package is three levels deep, you may need to bump an intermediate dependency, and that’s where breaking changes can show up.
Snyk’s container scanning is also worth a look:
bash# Scan container image for vulnerabilities snyk container test myregistry/myapp:latest --file=Dockerfile
Adding --file=Dockerfile gives remediation guidance tied to your base image. For example, switching from node:18 to node:18-slim can wipe out dozens of vulnerabilities simply because the slim image ships fewer packages.
What it is: Developer-first security platform covering code, dependencies, containers, and IaC
Pricing: Free (limited) / Team $52/developer/mo / Enterprise custom
Best for: Teams wanting unified vulnerability management with IDE integration
| Strengths | Limitations |
|---|---|
| Fix PRs generated automatically | Free tier limits scans significantly |
| Strong container and IaC scanning | Can be noisy without tuning |
| Excellent IDE plugins for real-time feedback | Premium features behind enterprise tier |
Bottom line: One of the smoothest developer experiences in SCA, but costs can climb quickly as the team grows.
If dependency updates are the main goal and a commercial tool isn’t in the plan, Dependabot and Renovate cover a lot of ground.
yaml#.github/dependabot.yml version: 2 updates: - package-ecosystem: "npm" directory: "/" schedule: interval: "weekly" groups: production-dependencies: patterns: - "*" exclude-patterns: - "eslint*" - "@types/*"
Grouping related updates cuts PR noise a lot. Without grouping, a typical Node.js project can spit out 20+ PRs a week. With grouping, that often drops to 2-3 manageable updates.
Renovate gives more control for complex monorepos:
json{ "extends": ["config:base"], "packageRules": [ { "matchPackagePatterns": ["^@aws-sdk/"], "groupName": "AWS SDK", "automerge": true } ] }
Auto-merging AWS SDK updates can feel aggressive, but these packages tend to stick to semantic versioning pretty strictly. In most cases, the breakage risk is low, and the payoff of staying current is real.
Leaked credentials are still one of the quickest paths from “minor issue” to “full breach.” The goal is to catch secrets before they ever land in the repo.
yaml#.gitleaks.toml [extend] useDefault = true [[rules]] id = "custom-api-key" description = "Company API Key" regex = '''MYCOMPANY_[A-Z0-9]{32}''' secretGroup = 0 [allowlist] paths = [ '''\.env\.example''', '''docs/''' ]
The allowlist helps keep false positives from docs and example files under control. Without it, teams usually end up with alert fatigue and start ignoring results, which makes the whole setup pointless.
Running Gitleaks as a pre-commit hook catches secrets before they enter git history:
bash#.pre-commit-config.yaml repos: - repo: https://github.com/gitleaks/gitleaks rev: v8.18.0 hooks: - id: gitleaks
Once a secret gets into history, cleaning it up means rewriting history or rotating the credential. Pre-commit checks save you from both.
TruffleHog goes a step further by checking whether discovered credentials actually work:
bash## Scan with verification trufflehog git https://github.com/org/repo --only-verified
--only-verified filters out expired or invalid credentials, which cuts noise a lot. A scan might find 50 “possible” secrets, but only 3 that are still valid. Those 3 should be rotated immediately.
Warning
Verification means TruffleHog attempts to use discovered credentials against their respective services. Run this only on repositories you own, and ensure your security team knows about the activity.
Your pipeline is a production system in its own right. OWASP's CI/CD Security Cheat Sheet lays out how attackers abuse weak pipeline configs to inject malicious code or steal credentials.

yamlname: Secure Build on: [push] permissions: contents: read packages: write jobs: build: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 with: persist-credentials: false - name: Build run: npm ci --ignore-scripts env: NODE_ENV: production
There are a few deliberate security choices here. The permissions block limits what the workflow can touch, following least privilege. persist-credentials: false stops the checkout action from leaving credentials behind for later steps to misuse. And npm ci --ignore-scripts blocks post-install scripts, which malicious packages sometimes use to exfiltrate data or drop backdoors.
Datadog's research found 58% of organizations using AWS and GitHub Actions still relied on long-lived IAM credentials. OIDC federation removes that class of risk:
yamljobs: deploy: runs-on: ubuntu-latest permissions: id-token: write contents: read steps: - uses: aws-actions/configure-aws-credentials@v4 with: role-to-assume: arn:aws:iam:123456789:role/GitHubActionsRole aws-region: us-east-1
With OIDC, there are no static credentials sitting around to leak. The workflow gets short-lived credentials tied to that run. Even if they’re stolen, they typically expire within an hour.
yaml#.gitlab-ci.yml include: - template: Security/SAST.gitlab-ci.yml - template: Security/Dependency-Scanning.gitlab-ci.yml - template: Security/Container-Scanning.gitlab-ci.yml variables: SAST_EXCLUDED_ANALYZERS: "eslint" DS_EXCLUDED_PATHS: "vendor/, node_modules/" stages: - test - security - deploy security-gate: stage: security script: - | if [ "$CI_COMMIT_BRANCH" == "main" ]; then exit $(cat gl-sast-report.json | jq '.vulnerabilities | map(select(.severity == "Critical")) | length') fi allow_failure: false
This security gate counts critical vulnerabilities and exits with that number as the exit code. Zero critical findings means exit code 0 (success). Any critical findings fail the pipeline. The pattern that works well is: make main strict, keep feature branches more flexible.
Container images are deployment artifacts: application code plus the runtime environment. Vulnerabilities in either part create real exposure.
bash## Comprehensive container scan trivy image --severity HIGH,CRITICAL \ --ignore-unfixed \ --format sarif \ --output trivy-results.sarif \ myregistry/myapp:latest
--ignore-unfixed filters out issues with no available patch. That keeps reports focused on what your team can actually fix right now. Otherwise, the output gets noisy fast.
Trivy also scans Infrastructure as Code:
bash# Scan Terraform for misconfigurations trivy config --severity HIGH,CRITICAL./terraform/
Using one tool for containers, filesystems, and IaC reduces context switching. Your team sees results in a consistent format regardless of what’s being scanned.
Datadog's research found container images under 100 MB averaged 3 high and critical vulnerabilities, while images over 500 MB averaged 20. Smaller images usually mean a smaller attack surface.
dockerfile## Multi-stage build for minimal production image FROM node:20-alpine AS builder WORKDIR /app COPY package*.json./ RUN npm ci --only=production FROM gcr.io/distroless/nodejs20-debian12 COPY /app/node_modules /app/node_modules COPY. /app WORKDIR /app CMD ["server.js"]
The distroless base image includes only the Node.js runtime. No shell, no package manager, no extra utilities that make an attacker’s life easier. If someone lands in the container, there’s very little to work with.
Tip
Distroless images can make debugging harder since there’s no shell in the container. Keep a debug variant with a shell for development, but ship the distroless version to production.

IaC scanning catches misconfigurations before they hit real cloud environments. Fixing a public S3 bucket in Terraform is a lot cheaper than fixing it after it’s already in production.
bash# Scan Terraform with custom policy checkov -d./terraform/ \ --framework terraform \ --check CKV_AWS_19,CKV_AWS_20,CKV_AWS_21 \ --soft-fail-on CKV_AWS_144
--check runs only the policies you care about, which is handy if you want a focused scan. --soft-fail-on lets certain findings warn without failing the pipeline, which is useful during migrations where a temporary exception is expected.
Custom policies cover organization-specific requirements:
python## custom_policy.py from checkov.terraform.checks.resource.base_resource_check import BaseResourceCheck from checkov.common.models.enums import CheckResult, CheckCategories class RequireEncryptionTag(BaseResourceCheck): def __init__(self): name = "Ensure all S3 buckets have encryption classification tag" id = "CUSTOM_AWS_1" supported_resources = ['aws_s3_bucket'] categories = [CheckCategories.ENCRYPTION] super.__init__(name=name, id=id, categories=categories, supported_resources=supported_resources) def scan_resource_conf(self, conf): tags = conf.get('tags', [{}]) if 'data_classification' in tags: return CheckResult.PASSED return CheckResult.FAILED check = RequireEncryptionTag
This policy enforces a tagging standard: every S3 bucket must declare its data classification. That classification then feeds encryption decisions and compliance reporting.
Static analysis catches what’s visible in code. Dynamic analysis finds issues that only show up once the app is running.
yaml# ZAP automation framework config env: contexts: - name: "MyApp" urls: - "https://staging.myapp.com" includePaths: - "https://staging.myapp.com/api/.*" excludePaths: - "https://staging.myapp.com/api/health" authentication: method: "script" parameters: script: "auth-script.js" jobs: - type: spider parameters: maxDuration: 5 - type: activeScan parameters: policy: "API-Scan" - type: report parameters: template: "sarif-json" reportFile: "zap-report.sarif"
The automation framework runs ZAP in CI without manual steps. The spider discovers endpoints, then the active scan probes them for vulnerabilities. Pointing this at staging (not production) avoids the risk of scan traffic affecting real users.
What’s often missed: authentication is where ZAP setups usually fall apart. If auth isn’t configured correctly, the scanner only hits public endpoints and never sees the riskiest flows behind login.
Nuclei is great for testing specific, known vulnerabilities:
bash## Scan for specific CVEs nuclei -u https://staging.myapp.com \ -t cves/ \ -t exposures/ \ -severity high,critical \ -rate-limit 50
Because it’s template-based, you can test for newly disclosed CVEs quickly, often within hours of publication. The community maintains thousands of templates covering everything from exposed admin panels to specific product vulnerabilities.
SBOMs list every component in your software, which makes response much faster when something new breaks. When Log4Shell hit, organizations with SBOMs could identify affected systems in hours. Without SBOMs, many teams spent weeks hunting.
bash# Generate SBOM syft myregistry/myapp:latest -o spdx-json > sbom.json # Scan SBOM for vulnerabilities grype sbom:sbom.json --fail-on high
Splitting SBOM generation from vulnerability scanning is practical. Generate the SBOM once during the build, store it as an artifact, then rescan it later as new CVEs are published. You don’t need access to the original image to check whether it’s affected.
yaml## GitHub Action for SBOM workflow - name: Generate SBOM uses: anchore/sbom-action@v0 with: image: ${{ env.IMAGE }} artifact-name: sbom.spdx.json - name: Scan SBOM uses: anchore/scan-action@v3 with: sbom: sbom.spdx.json fail-build: true severity-cutoff: high
Storing SBOMs as build artifacts also creates a clean audit trail. When a vulnerability drops, you can scan older SBOMs to see which deployed versions are impacted, without rebuilding anything.
GitLab's 2025 survey found 97% of organizations are using or planning to use AI in their development workflow. But IBM's research revealed 63% lack AI governance policies, and 97% of organizations experiencing AI-related security incidents had inadequate access controls.
AI assistants often output code that “works” but quietly bakes in security problems:
python# AI-generated code (problematic) def get_user(user_id): query = f"SELECT * FROM users WHERE id = {user_id}" return db.execute(query) # Secure version def get_user(user_id: int): query = "SELECT * FROM users WHERE id =?" return db.execute(query, (user_id,))
The AI-generated version uses string interpolation, which creates SQL injection risk. The safer version uses parameterized queries. AI tools often produce the first pattern because it’s common in training data, including tutorials and posts that favor brevity over security.
Important
Treat AI-generated code the same way you’d treat code from a junior developer: it may function correctly, but it can miss security details that experienced engineers catch automatically.
Organizations need policies covering:
| Policy Area | Recommended Control |
|---|---|
| Approved tools | Whitelist specific AI assistants |
| Code review | Require human review of all AI-generated code |
| Data handling | Block sensitive code from AI prompts |
| Training data | Prevent proprietary code from training models |
Some teams use proxy tools that filter AI prompts to remove sensitive patterns before they reach external services. Others lock AI usage to approved, self-hosted models so data stays inside the organization’s infrastructure.
Raw vulnerability counts bury teams. Datadog’s research showed that once runtime context is applied, only 18% of critical CVSS vulnerabilities still looked critical.

yaml## Example prioritization criteria priority_matrix: critical: - cisa_kev: true - epss_score: ">0.9" - internet_exposed: true - production: true high: - cisa_kev: true - epss_score: ">0.5" - production: true medium: - epss_score: ">0.1" - production: true low: - default: true
CISA’s Known Exploited Vulnerabilities (KEV) catalog tracks vulnerabilities that are actively exploited. If something is in KEV, attackers are already using it, so it’s urgent even if CVSS doesn’t look terrifying.
EPSS (Exploit Prediction Scoring System) estimates the likelihood a vulnerability will be exploited within 30 days. A critical CVSS issue with 0.1% EPSS is usually less urgent than a high CVSS issue with 90% EPSS.
Blend those signals with deployment context (production vs. staging, internet-facing vs. internal), and you get an actionable queue instead of an overwhelming list.
Start here (your first step)
Add Gitleaks as a pre-commit hook to one repository today. It takes five minutes and blocks credential leaks before they become a cleanup project.
Quick wins (immediate impact)
Deep dive (for those who want more)
DevSecOps works best when security feels like invisible infrastructure, not a last-minute checkpoint. The tools above fit into existing workflows, so issues get caught while context is fresh and fixes are still cheap.
Moving from reactive scanning to proactive security engineering takes real investment: tools, process, and culture. Teams that treat CI/CD as production, prioritize vulnerabilities with context (not just raw severity), and keep clear visibility into the software supply chain tend to outperform teams that rely on periodic assessments.
Start with one tool in one pipeline. Track how many late-stage findings disappear. Then expand.
If your organization needs help sequencing the rollout or picking tools that match your environment, Joulyan IT provides DevSecOps assessments that map current state to target architecture with concrete next steps.