Scan for Vulnerabilities

Octopus.Script exported 2025-11-02 by mcasperson belongs to ‘SBOM’ category.

This step extracts the Docker image, finds any bom.json files, and scans them for vulnerabilities using Trivy.

Parameters

When steps based on the template are included in a project’s deployment process, the parameters below can be set.

Application package containing the SBOM to scan

Sbom.Package =

Script body

Steps based on this template will execute the following PowerShell script.

Write-Host "Pulling Trivy Docker Image"
Write-Host "##octopus[stdout-verbose]"
docker pull ghcr.io/aquasecurity/trivy
Write-Host "##octopus[stdout-default]"

$SUCCESS = 0

Write-Host "##octopus[stdout-verbose]"
Get-ChildItem -Path "." | Out-String
Write-Host "##octopus[stdout-default]"

# Find all bom.json files
$bomFiles = Get-ChildItem -Path "." -Filter "bom.json" -Recurse -File

if ($bomFiles.Count -eq 0) {
    Write-Host "No bom.json files found in the current directory."
    exit 0
}

foreach ($file in $bomFiles) {
    Write-Host "Scanning $($file.FullName)"

    # Delete any existing report file
    if (Test-Path "$($file.FullName)/depscan-bom.json") {
        Remove-Item "$($file.FullName)/depscan-bom.json" -Force
    }

    # Generate the report, capturing the output
    try {
        $OUTPUT = docker run --rm -v "$($file.FullName):/input/$($file.Name)" ghcr.io/aquasecurity/trivy sbom -q "/input/$($file.Name)"
        $exitCode = $LASTEXITCODE
    }
    catch {
        $OUTPUT = $_.Exception.Message
        $exitCode = 1
    }

    # Run again to generate the JSON output in the same directory as the bom.json file
    docker run --rm -v "$($file.DirectoryName):/output" -v "$($file.FullName):/input/$($file.Name)" ghcr.io/aquasecurity/trivy sbom -q -f json -o /output/depscan-bom.json "/input/$($file.Name)"

    # Parse JSON output to count vulnerabilities
    $jsonContent = Get-Content -Path "$($file.DirectoryName)/depscan-bom.json" | ConvertFrom-Json
    $CRITICAL = ($jsonContent.Results | ForEach-Object { $_.Vulnerabilities } | Where-Object { $_.Severity -eq "CRITICAL" }).Count
    $HIGH = ($jsonContent.Results | ForEach-Object { $_.Vulnerabilities } | Where-Object { $_.Severity -eq "HIGH" }).Count

    if ("#{Octopus.Environment.Name}" -eq "Security") {
        Write-Highlight "🟥 $CRITICAL critical vulnerabilities"
        Write-Highlight "🟧 $HIGH high vulnerabilities"
    }

    # Set success to 1 if exit code is not zero
    if ($exitCode -ne 0) {
        $SUCCESS = 1
    }

    # Print the output
    $OUTPUT | ForEach-Object {
        if ($_.Length -gt 0) {
            Write-Host $_
        }
    }
}

# Find all depscan-bom.json files recursively
$depscanFiles = Get-ChildItem -Path "." -Filter "depscan-bom.json" -Recurse -File

if ($depscanFiles.Count -gt 0) {
    $zipPath = "$PWD/depscan-bom.zip"

    # Remove existing zip if present
    if (Test-Path $zipPath) {
        Remove-Item $zipPath -Force
    }

    # Create a temporary directory structure and copy files with relative paths
    $tempDir = "$PWD/temp_zip"

    if (Test-Path $tempDir) {
        Remove-Item $tempDir -Recurse -Force
    }

    New-Item -ItemType Directory -Path $tempDir -Force | Out-Null

    foreach ($file in $depscanFiles) {
        $relativePath = $file.FullName.Substring($PWD.Path.Length + 1)
        $targetPath = Join-Path $tempDir $relativePath
        $targetDir = Split-Path $targetPath -Parent

        Write-Host "Adding $relativePath to zip"

        if (-not (Test-Path $targetDir)) {
            New-Item -ItemType Directory -Path $targetDir -Force | Out-Null
        }

        Copy-Item $file.FullName -Destination $targetPath
    }

    # Compress with relative paths
    Compress-Archive -Path "$tempDir/*" -DestinationPath $zipPath

    # Cleanup temp directory
    Remove-Item $tempDir -Recurse -Force

    # Octopus Deploy artifact
    New-OctopusArtifact $zipPath

} else {
    Write-Host "No depscan-bom.json files found to zip."
}

# Cleanup
for ($i = 1; $i -le 10; $i++) {
    try {
        if (Test-Path "bundle") {
            Set-ItemProperty -Path "bundle" -Name IsReadOnly -Value $false -Recurse -ErrorAction SilentlyContinue
            Remove-Item -Path "bundle" -Recurse -Force -ErrorAction Stop
            break
        }
    }
    catch {
        Write-Host "Attempting to clean up files"
        Start-Sleep -Seconds 1
    }
}

# Set Octopus variable
Set-OctopusVariable -Name "VerificationResult" -Value $SUCCESS

exit 0

Provided under the Apache License version 2.0.

Report an issue

To use this template in Octopus Deploy, copy the JSON below and paste it into the Library → Step templates → Import dialog.

{
  "Id": "a38bfff8-8dde-4dd6-9fd0-c90bb4709d5a",
  "Name": "Scan for Vulnerabilities",
  "Description": "This step extracts the Docker image, finds any bom.json files, and scans them for vulnerabilities using Trivy.",
  "Version": 3,
  "ExportedAt": "2025-11-02T21:42:33.662Z",
  "ActionType": "Octopus.Script",
  "Author": "mcasperson",
  "Packages": [
    {
      "Id": "9b495093-0020-4df8-bc44-2fd65ab13943",
      "Name": "application",
      "PackageId": "",
      "FeedId": null,
      "AcquisitionLocation": "Server",
      "Properties": {
        "Extract": "True",
        "SelectionMode": "deferred",
        "PackageParameterName": "Sbom.Package"
      }
    }
  ],
  "Parameters": [
    {
      "Id": "9cea163a-a048-4ff5-9405-56fdcb9015c7",
      "Name": "Sbom.Package",
      "Label": "Application package containing the SBOM to scan",
      "HelpText": null,
      "DefaultValue": "",
      "DisplaySettings": {
        "Octopus.ControlType": "Package"
      }
    }
  ],
  "Properties": {
    "OctopusUseBundledTooling": "False",
    "Octopus.Action.Script.ScriptSource": "Inline",
    "Octopus.Action.Script.Syntax": "PowerShell",
    "Octopus.Action.Script.ScriptBody": "Write-Host \"Pulling Trivy Docker Image\"\nWrite-Host \"##octopus[stdout-verbose]\"\ndocker pull ghcr.io/aquasecurity/trivy\nWrite-Host \"##octopus[stdout-default]\"\n\n$SUCCESS = 0\n\nWrite-Host \"##octopus[stdout-verbose]\"\nGet-ChildItem -Path \".\" | Out-String\nWrite-Host \"##octopus[stdout-default]\"\n\n# Find all bom.json files\n$bomFiles = Get-ChildItem -Path \".\" -Filter \"bom.json\" -Recurse -File\n\nif ($bomFiles.Count -eq 0) {\n    Write-Host \"No bom.json files found in the current directory.\"\n    exit 0\n}\n\nforeach ($file in $bomFiles) {\n    Write-Host \"Scanning $($file.FullName)\"\n\n    # Delete any existing report file\n    if (Test-Path \"$($file.FullName)/depscan-bom.json\") {\n        Remove-Item \"$($file.FullName)/depscan-bom.json\" -Force\n    }\n\n    # Generate the report, capturing the output\n    try {\n        $OUTPUT = docker run --rm -v \"$($file.FullName):/input/$($file.Name)\" ghcr.io/aquasecurity/trivy sbom -q \"/input/$($file.Name)\"\n        $exitCode = $LASTEXITCODE\n    }\n    catch {\n        $OUTPUT = $_.Exception.Message\n        $exitCode = 1\n    }\n\n    # Run again to generate the JSON output in the same directory as the bom.json file\n    docker run --rm -v \"$($file.DirectoryName):/output\" -v \"$($file.FullName):/input/$($file.Name)\" ghcr.io/aquasecurity/trivy sbom -q -f json -o /output/depscan-bom.json \"/input/$($file.Name)\"\n\n    # Parse JSON output to count vulnerabilities\n    $jsonContent = Get-Content -Path \"$($file.DirectoryName)/depscan-bom.json\" | ConvertFrom-Json\n    $CRITICAL = ($jsonContent.Results | ForEach-Object { $_.Vulnerabilities } | Where-Object { $_.Severity -eq \"CRITICAL\" }).Count\n    $HIGH = ($jsonContent.Results | ForEach-Object { $_.Vulnerabilities } | Where-Object { $_.Severity -eq \"HIGH\" }).Count\n\n    if (\"#{Octopus.Environment.Name}\" -eq \"Security\") {\n        Write-Highlight \"🟥 $CRITICAL critical vulnerabilities\"\n        Write-Highlight \"🟧 $HIGH high vulnerabilities\"\n    }\n\n    # Set success to 1 if exit code is not zero\n    if ($exitCode -ne 0) {\n        $SUCCESS = 1\n    }\n\n    # Print the output\n    $OUTPUT | ForEach-Object {\n        if ($_.Length -gt 0) {\n            Write-Host $_\n        }\n    }\n}\n\n# Find all depscan-bom.json files recursively\n$depscanFiles = Get-ChildItem -Path \".\" -Filter \"depscan-bom.json\" -Recurse -File\n\nif ($depscanFiles.Count -gt 0) {\n    $zipPath = \"$PWD/depscan-bom.zip\"\n\n    # Remove existing zip if present\n    if (Test-Path $zipPath) {\n        Remove-Item $zipPath -Force\n    }\n\n    # Create a temporary directory structure and copy files with relative paths\n    $tempDir = \"$PWD/temp_zip\"\n\n    if (Test-Path $tempDir) {\n        Remove-Item $tempDir -Recurse -Force\n    }\n\n    New-Item -ItemType Directory -Path $tempDir -Force | Out-Null\n\n    foreach ($file in $depscanFiles) {\n        $relativePath = $file.FullName.Substring($PWD.Path.Length + 1)\n        $targetPath = Join-Path $tempDir $relativePath\n        $targetDir = Split-Path $targetPath -Parent\n\n        Write-Host \"Adding $relativePath to zip\"\n\n        if (-not (Test-Path $targetDir)) {\n            New-Item -ItemType Directory -Path $targetDir -Force | Out-Null\n        }\n\n        Copy-Item $file.FullName -Destination $targetPath\n    }\n\n    # Compress with relative paths\n    Compress-Archive -Path \"$tempDir/*\" -DestinationPath $zipPath\n\n    # Cleanup temp directory\n    Remove-Item $tempDir -Recurse -Force\n\n    # Octopus Deploy artifact\n    New-OctopusArtifact $zipPath\n\n} else {\n    Write-Host \"No depscan-bom.json files found to zip.\"\n}\n\n# Cleanup\nfor ($i = 1; $i -le 10; $i++) {\n    try {\n        if (Test-Path \"bundle\") {\n            Set-ItemProperty -Path \"bundle\" -Name IsReadOnly -Value $false -Recurse -ErrorAction SilentlyContinue\n            Remove-Item -Path \"bundle\" -Recurse -Force -ErrorAction Stop\n            break\n        }\n    }\n    catch {\n        Write-Host \"Attempting to clean up files\"\n        Start-Sleep -Seconds 1\n    }\n}\n\n# Set Octopus variable\nSet-OctopusVariable -Name \"VerificationResult\" -Value $SUCCESS\n\nexit 0"
  },
  "Category": "SBOM",
  "HistoryUrl": "https://github.com/OctopusDeploy/Library/commits/master/step-templates//opt/buildagent/work/a381802920158308/step-templates/sbom-scan.json",
  "Website": "/step-templates/a38bfff8-8dde-4dd6-9fd0-c90bb4709d5a",
  "Logo": "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",
  "$Meta": {
    "Type": "ActionTemplate"
  }
}

History

Page updated on Sunday, November 2, 2025