NCSG

Automated Dependency Scanning with Trivy and Email Alerts on Bitbucket Cloud

Learn how to automate dependency scanning with Trivy on Bitbucket Cloud, send email alerts via Postmark when vulnerabilities are detected.

Automated Dependency Scanning with Trivy and Email Alerts on Bitbucket Cloud

Sun Mar 15 - Written by: Nuwan

Vulnerabilities in third-party packages are one of the most common attack surfaces in current applications. Your code might be clean, but the libraries you depend on may not be. Setting up automated dependency scanning is one of the highest-value, lowest-effort security controls you can put in place, and in this post I’ll walk you through exactly how I did it at work using Trivy, Bitbucket Pipelines, and Postmark.

By the end of this guide you’ll have a scheduled pipeline that scans your project dependencies and generates an email alert with the full findings attached when vulnerabilities are detected.


What is Trivy?

Trivy is an open-source vulnerability scanner by Aqua Security. It supports a wide range of targets including filesystems, container images, Git repositories, and more. For dependency scanning it supports most major ecosystems out of the box npm, pip, Composer, Go, Maven and others.

One important limitation worth calling out upfront: Trivy cannot detect malicious packages. It checks your dependencies against known vulnerability databases, but it does not cross-reference against known malicious packages.


The Setup at a Glance

Here’s what we’re building:

  • Trivy runs as a scheduled Bitbucket Pipeline on each project.
  • It scans the filesystem for vulnerabilities across all severity levels.
  • On findings, the pipeline captures the exit code and conditionally triggers an email alert.
  • The email is sent via Postmark using Atlassian’s email-notify pipe.
  • The full scan results are attached as a JSON file.
  • Your security team triages the alert, then either raises it with the corresponding team for patching or adds it to an ignored list if the vulnerability is not exploitable

Prerequisites

Before you start, make sure you have:

  • A Bitbucket Cloud workspace with Pipelines enabled.
  • A Postmark account with a verified sender signature.
  • The following repository variables configured in Bitbucket:
    • POSTMARK_USERNAME
    • POSTMARK_PASSWORD
    • POSTMARK_HOST

Tip: If you’re rolling this out across multiple repositories, set these as workspace-level variables so you don’t have to repeat them per repository.


The Pipeline Configuration

The custom pipeline consists of two steps: one to run the scan, and one to send the email. Here’s the full configuration:

- step: &dependency-scan
    name: Dependency scan
    image: aquasec/trivy:latest
    script:
      - trivy fs --exit-code 1 --scanners vuln --skip-dirs "node_modules,vendor" --ignore-unfixed --format json --output results.json .
    after-script:
      - echo "TRIVY_EXIT_CODE=$BITBUCKET_EXIT_CODE" >> $BITBUCKET_PIPELINES_VARIABLES_PATH
    on-fail:
      strategy: ignore
    artifacts:
      - results.json
    output-variables:
      - TRIVY_EXIT_CODE

- step: &send-email
    name: Send Email
    condition:
      state:
        TRIVY_EXIT_CODE == 1
    script:
      - pipe: atlassian/email-notify:<version>
        variables:
          USERNAME: $POSTMARK_USERNAME
          PASSWORD: $POSTMARK_PASSWORD
          FROM: 'security@example.com'
          TO: 'security@exampl.com'
          HOST: $POSTMARK_HOST
          SUBJECT: 'Dependency vulnerabilities detected in "${BITBUCKET_REPO_SLUG}"'
          ATTACHMENTS: 'results.json'

Let’s break down what each part does.


Step 1: The Dependency Scan

image: aquasec/trivy:latest

Trivy runs as a Docker container using the official Aqua Security image. No installation needed, Bitbucket Pipelines pulls it and runs it directly.

trivy fs --exit-code 1 --scanners vuln --skip-dirs "node_modules,vendor" --ignore-unfixed --format json --output results.json .

Breaking down the flags:

FlagPurpose
fsFilesystem scan mode — scans the project directory
--exit-code 1Returns exit code 1 if vulnerabilities are found
--scanners vulnOnly run vulnerability checks (skips secrets, misconfigs, etc.)
--skip-dirs "node_modules,vendor"Avoids scanning installed packages directly (update this based on the language)
--ignore-unfixedSkips vulnerabilities that don’t have a fix available yet
--format jsonOutputs results as JSON
--output results.jsonSaves the report to a file

No --severity flag is passed here, which means all severity levels are captured — CRITICAL, HIGH, MEDIUM, LOW, and UNKNOWN. This is intentional. We want full visibility and let the security team decide what’s worth acting on during triage.

Capturing the Exit Code

after-script:
  - echo "TRIVY_EXIT_CODE=$BITBUCKET_EXIT_CODE" >> $BITBUCKET_PIPELINES_VARIABLES_PATH
on-fail:
  strategy: ignore

This is the key piece that makes the two-step flow work. Bitbucket’s after-script block runs regardless of whether the step passes or fails. Here we capture Trivy’s exit code into a pipeline output variable.

on-fail: strategy: ignore ensures the pipeline doesn’t stop when Trivy finds vulnerabilities, we want it to continue to the email step.

Passing the Report as an Artifact

artifacts:
  - results.json
output-variables:
  - TRIVY_EXIT_CODE

The JSON report is declared as an artifact so it’s available to the next step. TRIVY_EXIT_CODE is declared as an output variable so the second step can read it.


Step 2: Sending the Email Alert

condition:
  state:
    TRIVY_EXIT_CODE == 1

This condition ensures the email step only runs when Trivy actually found something. If the scan comes back clean, this step is skipped entirely.

pipe: atlassian/email-notify:<version>
variables:
  USERNAME: $POSTMARK_USERNAME
  PASSWORD: $POSTMARK_PASSWORD
  FROM: 'security@example.com'
  TO: 'security@example.com'
  HOST: $POSTMARK_HOST
  SUBJECT: 'Dependency vulnerabilities detected in "${BITBUCKET_REPO_SLUG}"'
  ATTACHMENTS: 'results.json'

Atlassian’s email-notify pipe handles the email delivery. We’re using Postmark as the SMTP provider, the credentials are stored as Bitbucket repository variables so they’re never hardcoded.

$BITBUCKET_REPO_SLUG is a built-in Bitbucket variable that resolves to the repository name, making the email subject automatically descriptive when you’re running this across multiple repositories.

The full JSON scan results are attached to the email, giving the security team everything they need without having to dig into pipeline logs.


Scheduling the Pipeline

Rather than running on every push, this is set up as a scheduled build in Bitbucket. Scheduled pipelines can be configured under Pipelines > Schedules(at the top right).

The custom pipeline is referenced in bitbucket-pipelines.yml like this:

pipelines:
  custom:
    dependency-scan:
      - step: *dependency-scan
      - step: *send-email

You can then schedule dependency-scan to run daily, weekly, or whatever cadence makes sense for your team.


Wrapping Up

This setup gives you continuous dependency scanning with zero developer friction, it runs on a schedule and only alerts when there’s something to act on. It took minimal effort to configure and covers different types of projects without any ecosystem-specific tuning.

If you’re on Bitbucket Cloud and don’t have dependency scanning in place yet, this is a solid starting point. The full pipeline config above is copy-paste ready, update the email-notify pipe version to the latest by checking here, swap in your SMTP credentials and you’re good to go.