Dev.to Security 🔐 Cybersecurity 👁 1 📖 9 min read

DevSecOps in Practice: Securing Java and Spring Boot Delivery Pipelines

DevSecOps is often described as "shifting security left," but in practice many teams still experience security as a gate at the end of the pipeline. A scanner runs, a report lands in a shared folder, and a developer disc

DevSecOps is often described as "shifting security left," but in practice many teams still experience security as a gate at the end of the pipeline. A scanner runs, a report lands in a shared folder, and a developer discovers three weeks later that a critical dependency has been waiting to be fixed the whole time. The goal of DevSecOps is not to add more gates. It is to make security a property of the delivery pipeline itself, so that the safe path is also the fastest path.

This article walks through the practices I use when building Java and Spring Boot services in regulated environments. It covers how to automate dependency and secret scanning, how to enforce policy as code, how to harden container images, how to protect the runtime, and how to make the whole system produce evidence that auditors and incident responders can rely on.

What DevSecOps Actually Means

DevSecOps extends DevOps by treating security as a shared responsibility across development, operations, and security teams. Three ideas make it work:

  1. Automate the checks. Manual reviews do not scale to dozens of deployments a day. Every check that can be automated should be.
  2. Fail fast, but with useful feedback. A build that fails with a clear explanation and a suggested fix is far more useful than a report generated next month.
  3. Make the secure path the default. Templates, base images, and shared pipeline stages should be secure out of the box, so that teams do not need to remember every rule.

The rest of this article applies these ideas to concrete stages of the delivery pipeline.

Stage 1: Secure Coding Feedback in the IDE

The earliest feedback comes from the developer's own environment. Static analysis plugins for IntelliJ IDEA or VS Code can flag common issues such as SQL built with string concatenation, unsafe deserialization, or hardcoded credentials as the developer types. These tools are useful, but they only help if the team agrees on a shared rule set. Commit a configuration file to the repository so that everyone runs the same rules.

The most common class of vulnerabilities in Java services is still injection. Compare these two approaches:

// Vulnerable: user input concatenated into SQL
public List<Customer> findByEmail(String email) {
    String sql = "SELECT * FROM customers WHERE email = '" + email + "'";
    return jdbcTemplate.query(sql, customerRowMapper);
}

// Safe: parameter binding
public List<Customer> findByEmail(String email) {
    return jdbcTemplate.query(
            "SELECT * FROM customers WHERE email = ?",
            customerRowMapper,
            email);
}

A static analysis rule can detect the first pattern automatically. Make it a blocking check in the pipeline rather than a warning that everyone learns to ignore.

Stage 2: Secret Detection Before the Commit Leaves the Machine

Secrets in source control are one of the most common causes of breaches, and they are expensive to clean up because the history of the repository keeps them alive. Prevent them at three points:

  • Pre-commit hooks that scan staged changes for API keys, tokens, and private keys.
  • Pipeline scanning on every push, using a dedicated secret scanner such as Gitleaks or TruffleHog.
  • Repository-level protection that blocks pushes containing known secret patterns.

A simple pre-commit configuration with Gitleaks looks like this:

# .pre-commit-config.yaml
repos:
  - repo: https://github.com/gitleaks/gitleaks
    rev: v8.18.4
    hooks:
      - id: gitleaks

When a secret is detected, the response should be rotation first and cleanup second. Assume the credential is compromised, rotate it in the secrets manager, and then remove it from history if needed.

Applications should never read secrets from files in the repository. Load them at runtime from a secrets manager such as AWS Secrets Manager, Google Secret Manager, OCI Vault, or HashiCorp Vault:

@Configuration
public class DatabaseConfig {

    @Bean
    DataSource dataSource(
            @Value("${db.url}") String url,
            @Value("${db.username}") String username,
            @Value("${db.password}") String password) {
        HikariDataSource ds = new HikariDataSource();
        ds.setJdbcUrl(url);
        ds.setUsername(username);
        ds.setPassword(password);
        return ds;
    }
}

In this example, the password is resolved from the environment or from a secrets integration at startup. The value never appears in a file under version control. Spring Cloud Config, the AWS Secrets Manager integration, or the Vault Spring integration can supply these properties without changing application code.

Stage 3: Dependency and Supply Chain Scanning

Most of the code running in a typical Spring Boot service was written by someone else. That is an advantage, but it also means that a single vulnerable transitive dependency can expose your entire system. Dependency scanning should happen on every build.

The OWASP Dependency-Check Maven plugin is a common choice for Java projects:

<plugin>
    <groupId>org.owasp</groupId>
    <artifactId>dependency-check-maven</artifactId>
    <version>10.0.4</version>
    <executions>
        <execution>
            <goals>
                <goal>check</goal>
            </goals>
        </execution>
    </executions>
    <configuration>
        <failBuildOnCVSS>7</failBuildOnCVSS>
        <suppressionFiles>
            <suppressionFile>owasp-suppressions.xml</suppressionFile>
        </suppressionFiles>
    </configuration>
</plugin>

The failBuildOnCVSS setting stops the build when a dependency has a high-severity known vulnerability. Suppressions should be explicit, reviewed, and time-limited. A suppression without an expiration date tends to become permanent.

Beyond scanning, three supply chain practices reduce risk:

  • Pin versions. Use explicit versions in the build file and review updates instead of accepting floating ranges.
  • Generate an SBOM. A Software Bill of Materials, produced with CycloneDX for example, lets you answer quickly whether a newly disclosed vulnerability affects any of your services.
  • Verify artifacts. Sign container images and build artifacts, and verify signatures before deployment.

Stage 4: Static and Dynamic Analysis in the Pipeline

Static Application Security Testing (SAST) analyzes source code for vulnerability patterns. Dynamic Application Security Testing (DAST) probes a running application from the outside. Both have a role, and both produce noise if they are configured carelessly.

For SAST, focus on rules that map to real risk in your context, such as injection, broken authentication, path traversal, and insecure deserialization. Triage new findings on every pull request, and track the trend over time rather than fixating on the total count.

For DAST, run a baseline scan against a deployed test environment on a schedule, and run a more thorough scan before major releases. Keep the scan credentials limited to test accounts with the minimum necessary permissions.

Stage 5: Container Hardening

Containers package your application together with its operating system libraries, and each layer can carry vulnerabilities. Harden the image in three ways.

Use minimal base images. A distroless or slim Java runtime image contains far fewer packages than a full operating system image, which means fewer vulnerabilities to patch.

FROM maven:3.9-eclipse-temurin-21 AS build
WORKDIR /workspace
COPY pom.xml .
RUN mvn -B dependency:go-offline
COPY src ./src
RUN mvn -B -DskipTests package

FROM gcr.io/distroless/java21-debian12:nonroot
WORKDIR /app
COPY --from=build /workspace/target/payments-service.jar app.jar
EXPOSE 8080
ENTRYPOINT ["java", "-XX:MaxRAMPercentage=75.0", "-jar", "/app/app.jar"]

This multi-stage build keeps the build tools out of the final image, and the nonroot variant of the distroless base runs the process as a non-root user.

Scan the image. Run Trivy or Grype against the built image in the pipeline, and fail the build on critical vulnerabilities that have a fix available.

Set runtime restrictions. In Kubernetes, configure the pod to run as non-root, drop Linux capabilities, and mount the root filesystem as read-only where possible:

securityContext:
  runAsNonRoot: true
  allowPrivilegeEscalation: false
  readOnlyRootFilesystem: true
  capabilities:
    drop:
      - ALL

Stage 6: Policy as Code

Security rules that live only in a document are difficult to enforce consistently. Policy as code expresses those rules in a form that the pipeline and the cluster can evaluate automatically. Open Policy Agent (OPA) with Rego, or Kyverno for Kubernetes, are common choices.

A simple example is a rule that rejects any deployment without resource limits, which protects other tenants from a runaway service. In Kyverno, a policy might look like this:

apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
  name: require-resource-limits
spec:
  validationFailureAction: Enforce
  rules:
    - name: check-limits
      match:
        any:
          - resources:
              kinds:
                - Pod
      validate:
        message: "CPU and memory limits are required."
        pattern:
          spec:
            containers:
              - resources:
                  limits:
                    memory: "?*"
                    cpu: "?*"

The advantage of policy as code is that it is versioned, reviewed, and tested like any other code. When a policy changes, the change is visible in history, and the same rule applies in every environment.

Stage 7: Runtime Protection and Observability

No pipeline catches everything, so the system needs to detect and respond to suspicious behavior at runtime. Three capabilities matter most.

Structured security logging. Log authentication failures, authorization denials, and sensitive actions in a structured format, with a correlation identifier. Make sure that logs never contain tokens, passwords, or full personal data.

@Component
public class SecurityEventLogger {

    private static final Logger log = LoggerFactory.getLogger("security");

    public void authorizationDenied(String principal, String resource, String action) {
        log.warn("event=AUTHZ_DENIED principal={} resource={} action={} correlationId={}",
                principal, resource, action, MDC.get("correlationId"));
    }
}

Rate limiting and abuse detection. Limit login attempts and sensitive endpoints, and alert on unusual patterns such as many denials from one client in a short window.

Runtime threat detection. Tools such as Falco can detect unexpected process execution, shell access in containers, or writes to sensitive paths, and send alerts to the security team.

Connect these signals to your incident response process. An alert that nobody reads provides no protection.

Stage 8: Evidence and Compliance

DevSecOps is not complete until you can prove that your controls work. Compliance frameworks such as PCI DSS, SOC 2, and ISO 27001 require evidence, and the most efficient evidence is the kind the pipeline generates automatically:

  • Build logs showing that scans ran and their results
  • Records of who approved each deployment and when
  • Policy evaluation results for each release
  • Access reviews generated from identity provider data
  • Vulnerability management metrics, such as mean time to remediate by severity

Store this evidence in a system that the delivery team cannot silently modify, and retain it according to your regulatory requirements. When an auditor asks how a control works, the answer should be a link to the pipeline definition and a sample of its output, not a meeting to reconstruct what happened.

Measuring DevSecOps Maturity

Metrics keep the program honest. Useful measures include:

  • Mean time to remediate critical vulnerabilities, by service
  • Percentage of services with automated scanning in their pipeline
  • Secret detection rate before and after pre-commit hooks were introduced
  • Deployment failure rate caused by security policy, which shows whether the controls are too strict or too loose
  • Exception count and age, to ensure suppressions do not accumulate

Review these metrics regularly with both engineering and security leadership. The goal is to improve the system over time, not to produce a perfect score on day one.

Common Pitfalls

Several patterns undermine DevSecOps programs in practice:

  • Scanning without action. A dashboard full of unresolved findings trains teams to ignore security signals. Set clear ownership and service-level targets for remediation.
  • Too many blocking rules at once. Introducing dozens of strict gates on the first day causes teams to find workarounds. Start with the highest-impact checks and expand gradually.
  • Security as a separate team's pipeline. If developers cannot see or fix the findings themselves, the feedback loop is too slow. Give teams direct access to results and guidance.
  • Ignoring the human path. Developers under deadline pressure will choose the fastest route. Make sure the secure route is also the easiest one.

Key Takeaways

  • DevSecOps makes security a property of the delivery pipeline, not a separate phase at the end.
  • Automate secret detection, dependency scanning, and static analysis, and make the critical checks blocking.
  • Keep secrets out of source control and load them from a secrets manager at runtime.
  • Harden container images with minimal bases, non-root execution, and image scanning.
  • Express security policy as code so it is versioned, reviewed, and enforced consistently.
  • Monitor runtime behavior and connect alerts to a real response process.
  • Generate compliance evidence automatically from the pipeline itself.
  • Measure progress with metrics that reveal both the strength of controls and the friction they create.

DevSecOps works best when it feels like an improvement to the delivery process rather than an obstacle to it. Start with one pipeline, add secret detection and dependency scanning this week, and expand from there. Each automated check removes a class of risk without asking anyone to remember a rule.

📰 Read the original article on Dev.to Security

Originally published by Dev.to Security. Aggregated on AIWithGhost for educational purposes — full credit and traffic to the original publisher.