There’s a moment in most engineering projects where someone says “we’ll handle security at the end.” We’ve seen it plenty of times. And we’ve also seen what happens next — rushed reviews, missed vulnerabilities, and release delays that didn’t have to happen.
This project was different. The client wanted security woven into the development process itself, not bolted on afterward. Here’s how we did it, and which tools actually made the difference.
Starting with secrets — Gitleaks and Azure Key Vault
The first thing we audited was secrets management. Even careful teams make mistakes here — an API key committed to a feature branch, a connection string hardcoded during a late-night debug session. These things happen. The answer isn’t shame; it’s automation.
We integrated Gitleaks directly into the pipeline as a pre-commit and CI scan step. It scans every commit and merge request for exposed credentials, tokens, and private keys before they ever reach the main branch. Within the first week, it caught two instances that would have gone unnoticed.
For secrets storage, we moved everything — API keys, database connection strings, cloud access credentials — into Azure Key Vault. Secrets are fetched at runtime by authorized services only. Nothing sensitive sits in a repo, a config file, or an environment variable that’s logged somewhere. That single change meaningfully reduced the client’s attack surface.
Container security with Trivy
The client’s workloads run in containers, which creates its own category of risk. A clean application sitting on top of a vulnerable base image is still a vulnerable application.
We added Trivy to the pipeline to scan container images for OS-level vulnerabilities, package CVEs, and misconfigurations before anything gets pushed to a registry. It’s fast, it’s accurate, and it fits neatly into a GitLab CI stage without slowing down builds in any meaningful way. If a critical or high-severity CVE is detected, the pipeline fails. No exceptions.
Code quality and vulnerability scanning — SonarQube and GitLab
Security isn’t only about infrastructure. Application code carries its own risks — SQL injection patterns, insecure deserialization, hardcoded values that slip through review.
We integrated SonarQube with quality gates configured to block deployments when code doesn’t meet defined thresholds. Every merge request triggers a scan. Developers see the results inline, in the same interface they’re already using. That immediacy matters — a developer who gets feedback in the same sitting fixes the issue; one who gets it three days later is context-switching cold.
We also enabled GitLab’s built-in vulnerability scanning — SAST, dependency scanning, and secret detection — directly in the pipeline YAML. This gave us a second layer of application-level analysis that integrates cleanly into GitLab’s security dashboard, making it easy for the security team to track findings over time without chasing down reports manually.
Dependency risk — Dependabot and GitHub CodeQL
Third-party libraries are probably the most underestimated risk in modern application development. You write clean code, but you’re still pulling in hundreds of transitive dependencies.
Dependabot handles this automatically — scanning dependencies, identifying known CVEs, and raising pull requests with the fix already staged. We configured it across repositories with severity thresholds so teams weren’t drowning in low-priority noise, but critical patches got flagged immediately.
For deeper semantic analysis on the application code itself, we set up GitHub CodeQL. Unlike a standard SAST scanner, CodeQL reasons about how data flows through the application — it can catch things like user input reaching a sensitive function without proper sanitization, which simpler pattern-matching tools often miss entirely.
IAM and governance — closing the access gap
One finding that came up consistently during the infrastructure review: permissions creep. Projects start with narrow access, teams grow, and suddenly a service account has permissions it acquired six months ago for a task that no longer exists.
We rebuilt IAM roles from scratch using least-privilege as the baseline. Every service, user, and automation process got only what it needed. We also implemented tag-based deny policies so untagged or non-compliant resources are automatically restricted from sensitive operations — governance that enforces itself rather than depending on someone remembering to check.
What shifted for the team
The biggest change wasn’t technical. It was how the development team started relating to security. When Trivy, SonarQube, Gitleaks, and GitLab’s scanners are running on every push, security stops being a thing that happens to your code and starts being part of the feedback loop you’re already in.
Developers caught issues themselves — often before anyone from the security team saw them. That’s the actual goal of a mature DevSecOps pipeline: not more gatekeeping, but earlier, faster, better-quality feedback embedded into the workflow people already use every day.
It doesn’t require a massive team or a complete replatforming. It requires picking the right tools, wiring them into the right stages, and being consistent about what the pipeline will and won’t allow through


