Integrating AWS Automated Security Helper with Azure DevOps Pipelines for Pull Request Security Scanning

Integrating AWS Automated Security Helper with Azure DevOps Pipelines for Pull Request Security Scanning

S

Stewart Moreland

Security vulnerabilities discovered late in the development cycle are expensive and disruptive to fix. By integrating security scanning directly into your pull request workflow, you can catch issues early when they're easiest and cheapest to resolve. AWS Automated Security Helper (ASH) provides a comprehensive suite of open-source security scanning tools that can be seamlessly integrated into Azure DevOps pipelines.

What is AWS Automated Security Helper (ASH)?

AWS Automated Security Helper is an extensible, open-source security scanning orchestration engine that combines multiple industry-standard tools to provide comprehensive coverage across your entire codebase. Rather than managing individual security tools separately, ASH provides a unified interface that runs multiple scanners in parallel and aggregates results into a single report.

Comprehensive Security Coverage

ASH integrates multiple specialized security tools to provide comprehensive coverage:

ASH Security Tool Coverage

Code Security Tools:

  • git-secrets: Detects API keys, passwords, and AWS credentials
  • Bandit: Python security issue detection
  • Semgrep: Multi-language static analysis (Python, JavaScript, Go, C#, Java, Bash)
  • ESLint: JavaScript security linting

Vulnerability & Dependency Management:

  • Grype: Vulnerability scanner for multiple languages
  • Syft: Software Bill of Materials (SBOM) generation
  • npm-audit: JavaScript dependency vulnerability scanning

Infrastructure Security:

  • Checkov: Terraform and CloudFormation security scanning
  • cfn-nag: CloudFormation template security analysis
  • cdk-nag: AWS CDK security validation

Why Azure DevOps + ASH Integration Matters

The Challenge: Security Debt and Late-Stage Discoveries

Traditional security scanning often happens after code deployment, leading to:

📊 Cost of Late Security Discovery
0%
$100baseline
Development Phase Fix Cost
+1400%
$1,50015x more
Testing Phase Fix Cost
+9900%
$10,000100x more
Production Fix Cost

The Solution: Shift-Left Security with ASH

By integrating ASH into Azure DevOps pull request workflows, you can:

  1. Catch vulnerabilities early in the development cycle
  2. Automate security reviews without slowing down development
  3. Provide immediate feedback to developers
  4. Enforce security standards before code reaches production
  5. Generate compliance reports automatically

Architecture Overview

The integration follows a container-first approach that ensures consistency across environments:

Loading diagram...

Key Components:

  • Azure DevOps Pipeline: Orchestrates the security scanning workflow
  • ASH Container: Provides consistent scanning environment
  • Security Gates: Automated decision points based on scan results
  • Feedback Loop: Immediate results delivered to pull request

Prerequisites and Setup

Azure DevOps Requirements

yaml
# Required Azure DevOps permissions
Project Permissions:
- Build: Edit build pipelines
- Code: Contribute to pull requests
- Pipeline: Use build pipeline
Repository Permissions:
- Read: Source code access
- Contribute: Create/update pipelines
- Pull Request Review: Comment on PRs

Agent Pool Configuration

Ensure your Azure DevOps agents have the necessary capabilities:

bash
# Install Docker on Ubuntu agent
sudo apt-get update
sudo apt-get install -y docker.io
sudo systemctl start docker
sudo systemctl enable docker
# Add the pipeline user to docker group
sudo usermod -aG docker $USER
# Verify Docker installation
docker --version
💡 Container Requirements

ASH requires the ability to run Linux containers. Ensure your Azure DevOps agents have Docker or another OCI-compatible container runtime installed and properly configured.

Pipeline Implementation

Basic Pipeline Configuration

Create a new pipeline file (azure-pipelines-security.yml) in your repository:

yaml
# Azure DevOps Pipeline for ASH Security Scanning
name: Security-Scan-$(Date:yyyyMMdd)-$(Rev:r)
trigger: none # Only run on PR
pr:
branches:
include:
- main
- develop
- feature/*
paths:
exclude:
- docs/*
- README.md
pool:
vmImage: 'ubuntu-latest'
variables:
- name: ASH_VERSION
value: 'v2.0.1' # Use latest stable version
- name: ASH_OUTPUT_DIR
value: '$(Agent.TempDirectory)/ash-results'
- name: ASH_SOURCE_DIR
value: '$(Build.SourcesDirectory)'
stages:
- stage: SecurityScan
displayName: 'Security Vulnerability Scan'
jobs:
- job: ASHScan
displayName: 'Run ASH Security Analysis'
timeoutInMinutes: 30
steps:
- checkout: self
fetchDepth: 1
clean: true
- task: Bash@3
displayName: 'Setup ASH Environment'
inputs:
targetType: 'inline'
script: |
set -e
echo "Setting up ASH environment..."
# Create output directory
mkdir -p $(ASH_OUTPUT_DIR)
# Set permissions for Docker access
sudo chmod 666 /var/run/docker.sock
# Verify Docker is available
docker --version
echo "Environment setup complete"
- task: Bash@3
displayName: 'Clone and Setup ASH'
inputs:
targetType: 'inline'
script: |
set -e
echo "Cloning ASH repository..."
# Clone ASH repository
git clone https://github.com/awslabs/automated-security-helper.git /tmp/ash
cd /tmp/ash
# Checkout specific version for stability
git checkout $(ASH_VERSION)
# Make ASH executable
chmod +x ./ash
echo "ASH setup complete"
- task: Bash@3
displayName: 'Run ASH Security Scan'
inputs:
targetType: 'inline'
script: |
set -e
cd /tmp/ash
echo "Starting ASH security scan..."
echo "Scanning directory: $(ASH_SOURCE_DIR)"
echo "Output directory: $(ASH_OUTPUT_DIR)"
# Run ASH with comprehensive scanning
./ash \
--source-dir "$(ASH_SOURCE_DIR)" \
--output-dir "$(ASH_OUTPUT_DIR)" \
--format json \
--preserve-report \
--no-cleanup \
--force
echo "ASH scan completed"
- task: Bash@3
displayName: 'Process ASH Results'
condition: always() # Run even if previous step fails
inputs:
targetType: 'inline'
script: |
set -e
echo "Processing ASH scan results..."
# Check if results file exists
RESULTS_FILE="$(ASH_OUTPUT_DIR)/aggregated_results.json"
if [ -f "$RESULTS_FILE" ]; then
echo "Results file found: $RESULTS_FILE"
# Count vulnerabilities by severity
CRITICAL_COUNT=$(jq '[.vulnerabilities[]? | select(.severity == "CRITICAL")] | length' "$RESULTS_FILE" || echo "0")
HIGH_COUNT=$(jq '[.vulnerabilities[]? | select(.severity == "HIGH")] | length' "$RESULTS_FILE" || echo "0")
MEDIUM_COUNT=$(jq '[.vulnerabilities[]? | select(.severity == "MEDIUM")] | length' "$RESULTS_FILE" || echo "0")
LOW_COUNT=$(jq '[.vulnerabilities[]? | select(.severity == "LOW")] | length' "$RESULTS_FILE" || echo "0")
echo "Critical vulnerabilities: $CRITICAL_COUNT"
echo "High vulnerabilities: $HIGH_COUNT"
echo "Medium vulnerabilities: $MEDIUM_COUNT"
echo "Low vulnerabilities: $LOW_COUNT"
# Set pipeline variables for later use
echo "##vso[task.setvariable variable=CriticalCount;isOutput=true]$CRITICAL_COUNT"
echo "##vso[task.setvariable variable=HighCount;isOutput=true]$HIGH_COUNT"
echo "##vso[task.setvariable variable=MediumCount;isOutput=true]$MEDIUM_COUNT"
echo "##vso[task.setvariable variable=LowCount;isOutput=true]$LOW_COUNT"
# Fail pipeline if critical vulnerabilities found
if [ "$CRITICAL_COUNT" -gt 0 ]; then
echo "##vso[task.logissue type=error]Critical vulnerabilities found! Failing the build."
echo "##vso[task.complete result=Failed;]Critical security vulnerabilities detected"
elif [ "$HIGH_COUNT" -gt 5 ]; then
echo "##vso[task.logissue type=warning]High number of high-severity vulnerabilities found"
echo "##vso[task.complete result=SucceededWithIssues;]Multiple high-severity vulnerabilities detected"
else
echo "Security scan passed with acceptable risk level"
fi
else
echo "##vso[task.logissue type=warning]No results file found"
fi
- task: PublishBuildArtifacts@1
displayName: 'Publish Security Reports'
condition: always()
inputs:
PathtoPublish: '$(ASH_OUTPUT_DIR)'
ArtifactName: 'SecurityReports'
publishLocation: 'Container'
- task: PublishTestResults@2
displayName: 'Publish Security Test Results'
condition: always()
inputs:
testResultsFormat: 'JUnit'
testResultsFiles: '$(ASH_OUTPUT_DIR)/**/*.xml'
failTaskOnFailedTests: false
testRunTitle: 'ASH Security Scan Results'

Advanced Pipeline with Security Gates

For production environments, implement additional security gates and reporting:

yaml
# Advanced Azure DevOps Pipeline with ASH Security Scanning
name: Advanced-Security-Pipeline-$(Date:yyyyMMdd)-$(Rev:r)
trigger: none
pr:
branches:
include:
- main
- develop
- release/*
- feature/*
resources:
repositories:
- repository: security-templates
type: git
name: shared-pipelines/security-templates
ref: refs/heads/main
extends:
template: security-pipeline-template.yml@security-templates
parameters:
enableAdvancedScanning: true
securityGates:
critical: 0
high: 3
medium: 10
variables:
- group: SecurityScanConfig
- name: BRANCH_NAME
value: $[replace(variables['Build.SourceBranchName'], '/', '-')]
stages:
- stage: PreScan
displayName: 'Pre-Scan Validation'
jobs:
- job: ValidateEnvironment
steps:
- script: |
echo "Validating scan environment..."
# Check for sensitive files that shouldn't be scanned
find . -name "*.pem" -o -name "*.key" -o -name "secrets.json" | head -10
displayName: 'Environment Validation'
- stage: SecurityScan
displayName: 'Comprehensive Security Scan'
dependsOn: PreScan
jobs:
- job: ASHScan
displayName: 'ASH Multi-Tool Security Analysis'
timeoutInMinutes: 45
steps:
- checkout: self
fetchDepth: 0 # Full history for better analysis
- task: Cache@2
displayName: 'Cache ASH Docker Images'
inputs:
key: 'ash-docker-cache-v1'
path: '/tmp/ash-cache'
- task: Bash@3
displayName: 'Advanced ASH Scan with Custom Rules'
inputs:
targetType: 'inline'
script: |
set -e
# Enhanced ASH configuration
export ASH_OCI_RUNNER=docker
export ASH_IMAGE_NAME=automated-security-helper:$(ASH_VERSION)
# Clone ASH with caching
if [ ! -d "/tmp/ash" ]; then
git clone --depth 1 --branch $(ASH_VERSION) \
https://github.com/awslabs/automated-security-helper.git /tmp/ash
fi
cd /tmp/ash
# Custom security rules for organization
mkdir -p custom-rules
cat > custom-rules/.securityignore << 'EOF'
# Ignore test files
**/test/**
**/tests/**
**/*test*
# Ignore documentation
**/docs/**
**/*.md
EOF
# Run comprehensive scan
./ash \
--source-dir "$(Build.SourcesDirectory)" \
--output-dir "$(Agent.TempDirectory)/ash-results" \
--format json \
--preserve-report \
--force \
--offline-semgrep-rulesets "p/security-audit,p/ci,p/secrets" \
--debug
echo "Advanced ASH scan completed"
- task: PowerShell@2
displayName: 'Generate Security Dashboard'
inputs:
targetType: 'inline'
script: |
$resultsPath = "$(Agent.TempDirectory)/ash-results"
$resultsFile = "$resultsPath/aggregated_results.json"
if (Test-Path $resultsFile) {
$results = Get-Content $resultsFile | ConvertFrom-Json
# Generate HTML report
$htmlReport = @"
<!DOCTYPE html>
<html>
<head>
<title>Security Scan Report - $(Build.BuildNumber)</title>
<style>
body { font-family: Arial, sans-serif; margin: 20px; }
.header { background: #2196F3; color: white; padding: 20px; }
.critical { color: #f44336; font-weight: bold; }
.high { color: #ff9800; font-weight: bold; }
.medium { color: #ffeb3b; color: black; }
.low { color: #4caf50; }
table { width: 100%; border-collapse: collapse; margin: 20px 0; }
th, td { border: 1px solid #ddd; padding: 12px; text-align: left; }
th { background-color: #f2f2f2; }
</style>
</head>
<body>
<div class="header">
<h1>Security Scan Report</h1>
<p>Build: $(Build.BuildNumber) | Branch: $(Build.SourceBranchName) | Date: $(Get-Date)</p>
</div>
<h2>Vulnerability Summary</h2>
<!-- Report content would be generated here -->
</body>
</html>
"@
$htmlReport | Out-File "$resultsPath/security-report.html"
Write-Host "Security dashboard generated"
}
- stage: SecurityGates
displayName: 'Security Quality Gates'
dependsOn: SecurityScan
condition: always()
jobs:
- job: EvaluateSecurityGates
displayName: 'Evaluate Security Policy Compliance'
steps:
- download: current
artifact: SecurityReports
- task: Bash@3
displayName: 'Security Policy Evaluation'
inputs:
targetType: 'inline'
script: |
# Implement security policy evaluation logic
# This could integrate with external policy engines
echo "Evaluating security policies..."
# Example: Check against organizational security standards
POLICY_VIOLATIONS=0
# Policy: No critical vulnerabilities in production branches
if [ "$(Build.SourceBranch)" == "refs/heads/main" ]; then
if [ "$(CriticalCount)" -gt 0 ]; then
echo "POLICY VIOLATION: Critical vulnerabilities in main branch"
POLICY_VIOLATIONS=$((POLICY_VIOLATIONS + 1))
fi
fi
if [ $POLICY_VIOLATIONS -gt 0 ]; then
echo "##vso[task.complete result=Failed;]Security policy violations detected"
else
echo "All security policies satisfied"
fi
- stage: Reporting
displayName: 'Security Reporting & Notifications'
dependsOn: SecurityGates
condition: always()
jobs:
- job: SecurityReporting
steps:
- task: PublishHtmlReport@1
displayName: 'Publish Security Dashboard'
inputs:
reportDir: '$(Agent.TempDirectory)/ash-results'
tabName: 'Security Analysis'

Pull Request Integration

Configure the pipeline to provide immediate feedback on pull requests:

yaml
# PR Comment Task
- task: GitHubComment@0
displayName: 'Post Security Results to PR'
condition: always()
inputs:
gitHubConnection: 'GitHub-Connection'
repositoryName: '$(Build.Repository.Name)'
comment: |
## 🔒 Security Scan Results
**Build:** $(Build.BuildNumber)
**Branch:** $(Build.SourceBranchName)
**Status:** $(Agent.JobStatus)
### Vulnerability Summary
- 🔴 Critical: $(CriticalCount)
- 🟠 High: $(HighCount)
- 🟡 Medium: $(MediumCount)
- 🟢 Low: $(LowCount)
### Security Tools Used
- ✅ Static Code Analysis (Semgrep, Bandit, ESLint)
- ✅ Dependency Scanning (Grype, npm-audit)
- ✅ Infrastructure Security (Checkov, cfn-nag)
- ✅ Secret Detection (git-secrets)
📋 [View Detailed Report]($(System.TeamFoundationCollectionUri)$(System.TeamProject)/_build/results?buildId=$(Build.BuildId)&view=artifacts)

Best Practices and Optimization

Performance Optimization

yaml
# Cache ASH containers and vulnerability databases
- task: Cache@2
displayName: 'Cache ASH Components'
inputs:
key: 'ash-v2 | "$(Agent.OS)" | $(ASH_VERSION)'
restoreKeys: |
ash-v2 | "$(Agent.OS)"
ash-v2
path: |
/tmp/ash-cache
~/.cache/ash

Security Configuration

Environment Variables and Secrets Management:

yaml
variables:
- group: ASH-Security-Config # Secure variable group
- name: ASH_CONFIG_FILE
value: '$(Pipeline.Workspace)/ash-config.json'
# Secure configuration file
- task: DownloadSecureFile@1
name: ashConfig
displayName: 'Download ASH Configuration'
inputs:
secureFile: 'ash-security-config.json'
- script: |
cp $(ashConfig.secureFilePath) $(ASH_CONFIG_FILE)
chmod 600 $(ASH_CONFIG_FILE)
displayName: 'Setup Secure Configuration'

Error Handling and Retry Logic

yaml
# Robust error handling
- task: Bash@3
displayName: 'ASH Scan with Retry Logic'
retryCountOnTaskFailure: 3
inputs:
targetType: 'inline'
script: |
set -e
MAX_RETRIES=3
RETRY_DELAY=30
for i in $(seq 1 $MAX_RETRIES); do
echo "Attempt $i of $MAX_RETRIES"
if /tmp/ash/ash --source-dir "$(Build.SourcesDirectory)" --output-dir "$(ASH_OUTPUT_DIR)"; then
echo "ASH scan completed successfully"
break
else
if [ $i -eq $MAX_RETRIES ]; then
echo "ASH scan failed after $MAX_RETRIES attempts"
exit 1
else
echo "ASH scan failed, retrying in $RETRY_DELAY seconds..."
sleep $RETRY_DELAY
fi
fi
done

Integration with Security Tools and Workflows

SARIF Integration for Advanced Reporting

ASH can generate SARIF (Static Analysis Results Interchange Format) output for better integration with security tools:

yaml
# Generate SARIF output for Security tab integration
- script: |
# Convert ASH JSON output to SARIF format
python3 << 'EOF'
import json
import sys
from datetime import datetime
def convert_to_sarif(ash_results_file, sarif_output_file):
with open(ash_results_file, 'r') as f:
ash_data = json.load(f)
sarif_template = {
"version": "2.1.0",
"$schema": "https://json.schemastore.org/sarif-2.1.0.json",
"runs": [{
"tool": {
"driver": {
"name": "AWS Automated Security Helper",
"version": "2.0.1",
"informationUri": "https://github.com/awslabs/automated-security-helper"
}
},
"results": []
}]
}
# Convert ASH findings to SARIF format
for vulnerability in ash_data.get('vulnerabilities', []):
sarif_result = {
"ruleId": vulnerability.get('id', 'unknown'),
"level": map_severity(vulnerability.get('severity', 'info')),
"message": {
"text": vulnerability.get('description', '')
},
"locations": [{
"physicalLocation": {
"artifactLocation": {
"uri": vulnerability.get('file', '')
},
"region": {
"startLine": vulnerability.get('line', 1)
}
}
}]
}
sarif_template["runs"][0]["results"].append(sarif_result)
with open(sarif_output_file, 'w') as f:
json.dump(sarif_template, f, indent=2)
def map_severity(ash_severity):
mapping = {
'CRITICAL': 'error',
'HIGH': 'error',
'MEDIUM': 'warning',
'LOW': 'note'
}
return mapping.get(ash_severity.upper(), 'note')
convert_to_sarif('$(ASH_OUTPUT_DIR)/aggregated_results.json', '$(ASH_OUTPUT_DIR)/results.sarif')
EOF
displayName: 'Convert to SARIF Format'
# Publish SARIF results for Security tab
- task: PublishBuildArtifacts@1
displayName: 'Publish SARIF Results'
inputs:
PathtoPublish: '$(ASH_OUTPUT_DIR)/results.sarif'
ArtifactName: 'CodeAnalysisLogs'

Integration with External Security Platforms

📚 DevSecOps Best Practices
Part 1 of 3
Current
Next →

Webhook Integration for Security Orchestration:

yaml
# Send results to external security platforms
- task: Bash@3
displayName: 'Send Results to Security Platform'
condition: always()
inputs:
targetType: 'inline'
script: |
# Send to DefectDojo, OWASP Dependency-Track, etc.
curl -X POST "$(SECURITY_PLATFORM_URL)/api/v2/import-scan/" \
-H "Authorization: Token $(SECURITY_PLATFORM_TOKEN)" \
-F "scan_type=ASH Security Scan" \
-F "file=@$(ASH_OUTPUT_DIR)/aggregated_results.json" \
-F "engagement=$(ENGAGEMENT_ID)" \
-F "verified=true"

Troubleshooting Common Issues

Container Runtime Issues

💡 Docker Permission Issues

If you encounter Docker permission errors, ensure the pipeline agent user is added to the docker group and that the Docker socket has appropriate permissions.

Common Docker Issues and Solutions:

bash
# Issue: Permission denied accessing Docker socket
# Solution: Fix Docker socket permissions
sudo chmod 666 /var/run/docker.sock
# Issue: ASH container build fails
# Solution: Clear Docker cache and rebuild
docker system prune -f
docker build --no-cache -t ash:latest .
# Issue: Container out of memory
# Solution: Increase container memory limits
./ash --build-target ci # Use CI-optimized build target

Performance Issues

Large Repository Optimization:

yaml
# Optimize for large repositories
variables:
- name: ASH_SCAN_TIMEOUT
value: '3600' # 1 hour timeout
- name: ASH_PARALLEL_JOBS
value: '4' # Parallel scanning jobs
# Selective scanning for large repos
- script: |
# Only scan modified files in PRs
if [ "$BUILD_REASON" == "PullRequest" ]; then
git diff --name-only HEAD~1 HEAD > changed-files.txt
ASH_SOURCE_DIR=$(mktemp -d)
while IFS= read -r file; do
mkdir -p "$ASH_SOURCE_DIR/$(dirname "$file")"
cp "$file" "$ASH_SOURCE_DIR/$file" 2>/dev/null || true
done < changed-files.txt
echo "##vso[task.setvariable variable=ASH_SOURCE_DIR]$ASH_SOURCE_DIR"
fi
displayName: 'Optimize Scan Scope for PRs'

Measuring Success and ROI

Key Metrics to Track

Security Metrics Over Time

Track improvement in security posture

Security KPIs to Monitor:

  1. Vulnerability Trend Analysis

    • Critical vulnerabilities per release
    • Time to vulnerability remediation
    • Security debt accumulation rate
  2. Process Efficiency Metrics

    • Scan execution time trends
    • False positive rates
    • Developer adoption rates
  3. Business Impact Metrics

    • Reduced security incidents
    • Faster security review cycles
    • Compliance audit success rates

Generating Executive Reports

yaml
# Generate executive security dashboard
- task: PowerShell@2
displayName: 'Generate Executive Security Report'
inputs:
targetType: 'inline'
script: |
# Create executive summary report
$reportData = @{
BuildNumber = "$(Build.BuildNumber)"
Repository = "$(Build.Repository.Name)"
Branch = "$(Build.SourceBranchName)"
ScanDate = Get-Date
SecurityPosture = @{
Critical = $(CriticalCount)
High = $(HighCount)
Medium = $(MediumCount)
Low = $(LowCount)
}
ComplianceStatus = if ($(CriticalCount) -eq 0) { "COMPLIANT" } else { "NON_COMPLIANT" }
RiskLevel = switch ($(CriticalCount)) {
0 { if ($(HighCount) -gt 5) { "MEDIUM" } else { "LOW" } }
default { "HIGH" }
}
}
$reportData | ConvertTo-Json -Depth 3 | Out-File "$(ASH_OUTPUT_DIR)/executive-report.json"
# Send to metrics collection system
Invoke-RestMethod -Uri "$(METRICS_ENDPOINT)" -Method POST -Body ($reportData | ConvertTo-Json) -ContentType "application/json"

Conclusion and Next Steps

Integrating AWS Automated Security Helper with Azure DevOps pipelines provides a comprehensive, automated approach to shift-left security that catches vulnerabilities early in the development cycle. By implementing the patterns and practices outlined in this guide, you can:

✅ Reduce Security Debt: Catch vulnerabilities before they reach production
✅ Improve Developer Experience: Provide immediate, actionable security feedback
✅ Enhance Compliance: Automated evidence collection for security audits
✅ Scale Security Practices: Consistent security scanning across all projects
✅ Optimize Development Velocity: Security gates that enhance rather than hinder productivity

  1. Start Small: Begin with a pilot project to validate the integration
  2. Customize Security Policies: Tailor vulnerability thresholds to your organization's risk tolerance
  3. Integrate with Security Tools: Connect ASH results to your security orchestration platform
  4. Train Development Teams: Provide training on interpreting and acting on security scan results
  5. Monitor and Optimize: Continuously improve scan performance and accuracy

The combination of Azure DevOps and AWS Automated Security Helper provides a powerful foundation for implementing DevSecOps practices that scale with your organization while maintaining the agility that modern development teams require.