Build application security testing into development. Covers static analysis (SAST), dynamic testing (DAST), software composition analysis (SCA), security testing in CI/CD, and triage workflows.
Application security testing (AST) is a family of techniques applied during software development to detect and mitigate security vulnerabilities early and systematically. It matters most when building or maintaining a codebase where security is not an afterthought—especially in CI/CD pipelines, monorepos with multiple services, or systems with frequent releases. SAST, DAST, and SCA are the three pillars of AST: static analysis of source code, dynamic testing of running applications, and analysis of software components and their dependencies.
SAST inspects source code without executing it, identifying security issues at the earliest stage of development. It runs against code in a repository, typically triggered by a pull request or on every commit.
A typical SAST pipeline uses a combination of tools that analyze code for common vulnerabilities like SQL injection, XSS, insecure deserialization, and hard-coded secrets.
name: SAST
on: [pull_request, push]
jobs:
sast:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Set up Java
uses: actions/setup-java@v3
with:
java-version: '17'
distribution: 'temurin'
- name: Run SonarQube Scanner
run: |
mvn sonar:sonar \
-Dsonar.host.url=https://sonar.example.com \
-Dsonar.login=abc123def456 \
-Dsonar.projectKey=myapp \
-Dsonar.sources=src \
-Dsonar.exclusions=**/test/**,**/generated/**
- name: Run CodeQL Analysis
run: |
codeql database create --language=java --source-root=src \
codeql-database
codeql database analyze codeql-database \
--output=codeql-results.sarif \
--format=sarif \
--threads=4
codeql github upload-results --sarif-file=codeql-results.sarif
The sonar:sonar Maven goal uploads results to a SonarQube server with a standard configuration. The codeql database create command generates a CodeQL database from the source tree. Key flags: --language selects the language-specific analysis, --source-root defines the base of the code, and --exclusions filters out test and generated files from analysis.
SAST tools report issues with precise locations: file, line, column, and a description. A typical report entry looks like:
{
"ruleId": "java:S2068",
"message": "Hard-coded password found",
"locations": [
{
"physicalLocation": {
"artifactLocation": {
"uri": "src/main/java/com/example/auth/LoginService.java"
},
"region": {
"startLine": 42,
"startColumn": 15,
"endLine": 42,
"endColumn": 37
}
}
}
],
"properties": {
"severity": "CRITICAL",
"tags": ["security", "best-practice"]
}
}
A common failure mode: SAST reports “Unvalidated input to SQL query” but misses a query that uses a parameterized PreparedStatement because the tool expects Statement or CallableStatement. The fix is to configure the tool to recognize PreparedStatement as valid input—either via a configuration file or command-line flag.
sonar-project.properties to define project-wide rules: sonar.projectKey=myapp
sonar.projectName=My Application
sonar.sources=src
sonar.exclusions=**/test/**,**/generated/**
sonar.java.coveragePlugin=jacoco
sonar.coverage.exclusions=**/test/**,**/generated/**
sonar.scm.provider=git
sonar.git.provider=github
codeql for multiple languages: codeql database create --language=java,python --source-root=. --include=src
codeql config: # .codeql/config
queries:
- security
- path-sensitivity
- performance
SAST-README.md in the root of the repository with a checklist:java:S2068, java:S2069, java:S2070DAST tests a fully deployed, running application by sending real HTTP requests and observing responses. It uncovers runtime vulnerabilities like misconfigured headers, insecure APIs, session management flaws, and broken access control.
DAST is most effective when executed against a staging or preview environment. It can be triggered by a deployment or as part of a post-deployment validation.
# Example: DAST with OWASP ZAP in a GitHub Actions workflow
name: DAST
on: [deployment]
jobs:
zap:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Start web service
run: |
npm start &
sleep 30 # Wait for service to boot
- name: Run OWASP ZAP
run: |
docker run --rm \
-v $(pwd)/zap-results:/zap/reports \
-e ZAP_CONFIG_FILE=/zap/config/zap-config.json \
owasp/zap2docker-weekly \
-daemon \
-host 0.0.0.0 \
-port 8080 \
-config "file:/zap/config/zap-config.json" \
-config "scanner/active/scanPolicy=SecurityPolicy"
- name: Run ZAP Scan
run: |
curl -X POST http://localhost:8080/JSON/core/action/scan/ \
-H "Content-Type: application/json" \
-d '{
"url": "http://localhost:3000",
"scanPolicyName": "SecurityPolicy"
}'
- name: Fetch Results
run: |
curl -X GET http://localhost:8080/JSON/core/view/alerts/ \
-H "Accept: application/json" \
-o alerts.json
- name: Upload Reports
run: |
mkdir -p reports/zap
cp alerts.json reports/zap/
cp -r /zap/reports/*.html reports/zap/
tar -czf reports/zap.tar.gz -C reports/zap .
gh upload reports/zap.tar.gz
The docker run command starts ZAP in daemon mode, with a configuration file that defines scan policies and core settings. The curl command triggers a scan on a specific URL, and alerts.json contains a list of discovered vulnerabilities.
ZAP returns a standard JSON response for alerts:
{
"alerts": [
{
"alert": "XSS - Reflected",
"risk": "High",
"confidence": "Medium",
"description": "The application is vulnerable to reflected XSS attacks.",
"param": "search",
"attack": "test<script>alert(1)</script>",
"evidence": "<input type=\"text\" name=\"search\" value=\"test<script>alert(1)</script>\" />",
"uri": "https://staging.myapp.com/search",
"source": "ZAP",
"cwe": 79,
"owasp": "A3",
"solution": "Use input validation and output encoding."
}
]
}
Each alert includes param for the vulnerable HTTP parameter, attack with the test payload, and evidence showing the actual HTML response where the attack was detected.
zap-config.json to define default settings: {
"file": {
"output": "/zap/reports"
},
"scanner": {
"active": {
"scanPolicy": "SecurityPolicy"
}
},
"scanPolicy": {
"SecurityPolicy": {
"scan": [
"all",
"xss",
"sqlinjection",
"csrf",
"insecure-headers"
],
"config": {
"xss": {
"minLevel": "High",
"maxTime": 60
}
}
}
}
}
load phase: simulate 100 concurrent users before scanning.Content-Security-Policy unless explicitly configured. The fix is to add a customHeaders rule in the scan policy.SCA identifies and analyzes third-party libraries and dependencies in a project. It detects known vulnerabilities in dependencies, licenses, and outdated components.
SCA is typically run after dependency resolution (e.g., npm install, mvn dependency:resolve, pip install).
# Example: SCA with Snyk in CI
name: SCA
on: [pull_request, push]
jobs:
snyk:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up Node.js
uses: actions/setup-node@v3
with:
node-version: '18'
- name: Install dependencies
run: npm install
- name: Run Snyk
run: |
snyk test --file=package.json \
--json \
--output=results.json \
--org=myorg \
--project-name=myapp
- name: Check for Critical Vulnerabilities
run: |
jq -r '.[].vulnerabilities[] | select(.severity == "critical") | .id' results.json | \
xargs -I{} snyk monitor --file=package.json --id={}
- name: Upload Snyk Report
run: |
snyk monitor --file=package.json
snyk report --file=results.json --format=html --output=report.html
gh upload report.html
The snyk test command analyzes package.json and outputs a JSON report. It includes severity, title, description, references, and fixes.
A typical SCA result:
{
"vulnerabilities": [
{
"id": "SNYK-JS-EXPRESS-123456",
"title": "Express: Remote Code Execution in Body Parser",
"severity": "critical",
"description": "A vulnerability in express-body-parser allows RCE via crafted JSON.",
"package": "express",
"version": "4.18.2",
"fixedIn": [
"4.19.0",
"4.18.3"
],
"references": [
{
"url": "https://github.com/expressjs/express/pull/4523",
"title": "PR #4523: Add JSON body parser security hardening"
},
{
"url": "https://nvd.nist.gov/vuln/detail/CVE-2023-12345",
"title": "CVE-2023-12345: RCE in Express"
}
],
"cvss": {
"score": 9.8,
"vector": "AV:N/AC:L/PR:N/UI:R/S:C/C:High/I:High/A:High"
},
"from": [
"package.json",
"package-lock.json"
],
"install": {
"path": "node_modules/express"
}
}
]
}
Key failure modes:
lodash but misses a lodash update in a package.json that wasn’t resolved. The fix is to run npm install before snyk test.1.2.3, but the SCA tool resolves it to 1.2.3 and finds a package-lock.json with 1.2.3, but the project uses 1.2.3 in package.json and 1.2.3 in yarn.lock. The tool picks the wrong version.axios, but the fix is available in 0.27.2, and the project uses 0.27.0. The fixedIn list is correct, but the from field shows package.json and yarn.lock, so the fix is not applied.main branch.snyk monitor to track dependencies over time and detect new vulnerabilities.SCA-README.md with:snyk CLI to generate a snyk.yml configuration file: # snyk.yml
org: myorg
project: myapp
monitor:
enabled: true
branch: main
schedule: "0 0 * * *"
test:
severity: "critical"
output: "json"
file: "package.json"
The most effective AST pipeline combines all three. A CI job might run:
Results are aggregated into a single report, and failures in any component block the merge. A pull request might show:
LoginService.java/searchexpress and axiosEach tool runs independently but contributes to a unified view of the application’s security posture.
This page was rewritten on 16 October 2026. It replaced a templated version that was not specific to its own title. How these pages are written and checked. If anything here is wrong, tell us at [email protected] and we will correct it.
We use cookies for analytics (Google Analytics) and advertising (Google AdSense) to improve your experience and support free content. Privacy Policy