From Source to Artifact: Securing the Modern Java Supply Chain

As Java developers, we've moved far beyond writing code in isolation. Modern applications are built by assembling dozens of dependencies, containerizing them, and deploying through complex pipelines. Each of these steps introduces risk. A secure software supply chain is the practice of hardening each link in this chain—from your first git commit to the running application in production.

What is a Software Supply Chain, and Why Should Java Developers Care?

Think of your application's journey like a manufacturing assembly line:

  • Raw Materials: Your code + Open-source dependencies (Maven artifacts)
  • Assembly: Build process (Maven/Gradle) + Containerization (Docker)
  • Quality Control: Testing, Scanning, Validation
  • Shipping: Container registry to runtime environment

A breach at any point can lead to compromised software. For Java developers, this means understanding and securing:

  1. Dependencies (Your Maven pom.xml)
  2. Build Process (Your CI/CD pipeline)
  3. Container Images (Your Dockerfile and base images)
  4. Deployment Artifacts (Your signed, verified containers)

The Java Developer's Reality: The Dependency Blind Spot

Consider this typical pom.xml snippet:

<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>2.7.0</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.13.3</version>
</dependency>
<!-- Plus 50+ transitive dependencies -->
</dependencies>

How many of those 50+ transitive dependencies have known vulnerabilities? Are any of them malicious? Without proper controls, you're shipping risk into production.

Building a Secure Java Supply Chain: A Practical Approach

1. Secure Your Dependencies at the Source

Start by validating every component you bring into your application.

Use Maven/Gradle Plugins for Vulnerability Scanning:

<!-- OWASP Dependency Check Maven Plugin -->
<plugin>
<groupId>org.owasp</groupId>
<artifactId>dependency-check-maven</artifactId>
<version>8.2.1</version>
<executions>
<execution>
<goals>
<goal>check</goal>
</goals>
</execution>
</executions>
</plugin>

Run it as part of your build:

mvn dependency-check:check
# Build fails if critical vulnerabilities found

Use Software Bill of Materials (SBOM):
Generate an SBOM to know exactly what's in your application:

mvn org.cyclonedx:cyclone-dx-maven-plugin:makeAggregateBom

This creates a bom.json that lists every component, creating transparency for security teams.

2. Harden Your Build Pipeline

Your CI/CD pipeline should be a secure, repeatable process.

Example GitLab CI Pipeline with Security Gates:

# .gitlab-ci.yml
stages:
- test
- security-scan
- build
- deploy
dependency-check:
stage: security-scan
image: maven:3.8-openjdk-17
script:
- mvn dependency-check:check -DfailBuildOnCVSS=7
artifacts:
reports:
dependency_scanning: target/dependency-check-report.html
sbom-generation:
stage: security-scan
script:
- mvn org.cyclonedx:cyclone-dx-maven-plugin:makeAggregateBom
container-scan:
stage: security-scan
image: docker:latest
services:
- docker:dind
script:
- docker build -t my-app:$CI_COMMIT_SHA .
- docker scan my-app:$CI_COMMIT_SHA --severity high --fail-on high

3. Secure Your Container Images

Your Dockerfile choices matter significantly:

# INSECURE - Common anti-patterns
FROM openjdk:8-jre # Old, vulnerable base image
COPY target/my-app.jar /app/
EXPOSE 8080
ENTRYPOINT ["java", "-jar", "/app/my-app.jar"]
# SECURE - Hardened approach
FROM eclipse-temurin:17-jre-alpine@sha256:a1fd...  # Minimal, specific, pinned
# Create non-root user
RUN addgroup -S spring && adduser -S spring -G spring
USER spring
# Copy application
COPY --chown=spring:spring target/my-app.jar /app/my-app.jar
# Use JVM security flags
ENTRYPOINT ["java", \
"-Djava.security.egd=file:/dev/./urandom", \
"-XX:+UseContainerSupport", \
"-jar", "/app/my-app.jar"]

4. Sign and Verify Your Artifacts

Use Cosign to sign your container images:

# Build and sign your image
docker build -t myregistry.com/my-team/my-app:1.0.0 .
cosign sign -key cosign.key myregistry.com/my-team/my-app:1.0.0
# In your deployment pipeline, verify the signature
cosign verify -key cosign.pub myregistry.com/my-team/my-app:1.0.0

Integrating Supply Chain Security Tools

Sigstore for Java Artifacts:
Sign your Maven artifacts with Sigstore:

# During your release process
mvn clean deploy -Dgpg.skip=true
jarsigner -keystore my-keystore.jks target/*.jar my-alias

GitHub Actions Workflow for Supply Chain Security:

name: Secure Supply Chain
on: [push, pull_request]
jobs:
security-scan:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up JDK
uses: actions/setup-java@v3
with:
java-version: '17'
distribution: 'temurin'
- name: Dependency vulnerability check
run: mvn dependency-check:check
- name: Generate SBOM
run: mvn org.cyclonedx:cyclone-dx-maven-plugin:makeAggregateBom
- name: Build and scan container
run: |
docker build -t my-app:${{ github.sha }} .
docker scan my-app:${{ github.sha }} --fail-on high
- name: Sign container
uses: sigstore/cosign-installer@main
with:
cosign-release: 'v2.0.0'
run: cosign sign -key ${{ secrets.COSIGN_PRIVATE_KEY }} my-app:${{ github.sha }}

The Complete Secure Java Supply Chain

When properly implemented, your supply chain looks like this:

  1. Develop: Code in feature branch
  2. Verify:
    • Dependencies scanned on PR
    • SAST tools run on code
    • SBOM generated
  3. Build:
    • Container built from minimal, pinned base image
    • Image scanned for vulnerabilities
    • Image signed with cryptographic signature
  4. Deploy:
    • Signature verified at deployment
    • Artifact deployed to production

Conclusion: Shifting Security Left in Java Development

Securing the Java supply chain isn't about adding burdensome processes—it's about integrating security seamlessly into your existing workflow. By taking ownership of your dependencies, hardening your build pipeline, and verifying your artifacts, you transform from being a potential attack vector to becoming a security-aware developer.

The modern Java developer doesn't just write business logic; they ensure their entire delivery chain—from the first Maven dependency to the final container image—is secure, transparent, and trustworthy. This approach not only protects your applications but also builds confidence with your security team and customers.


Tools to explore: OWASP Dependency Check, CycloneDX, Syft, Grype, Trivy, Cosign, Sigstore, and your cloud provider's native container scanning services.

Leave a Reply

Your email address will not be published. Required fields are marked *


Macro Nepal Helper