Initial commit

This commit is contained in:
Zhongwei Li
2025-11-29 17:51:02 +08:00
commit ff1f4bd119
252 changed files with 72682 additions and 0 deletions

View File

@@ -0,0 +1,9 @@
# Assets Directory
Place files that will be used in the output Claude produces:
- Templates
- Configuration files
- Images/logos
- Boilerplate code
These files are NOT loaded into context but copied/modified in output.

View File

@@ -0,0 +1,207 @@
# GitHub Actions Workflow for OWASP ZAP Security Scanning
# Place this file in .github/workflows/zap-security-scan.yml
name: OWASP ZAP Security Scan
on:
push:
branches: [main, develop]
pull_request:
branches: [main]
schedule:
# Run weekly security scans on Sunday at 2 AM
- cron: '0 2 * * 0'
workflow_dispatch: # Allow manual triggering
permissions:
contents: read
security-events: write # For uploading SARIF reports
issues: write # For creating security issues
jobs:
zap-baseline-scan:
name: ZAP Baseline Scan (PR/Push)
runs-on: ubuntu-latest
if: github.event_name == 'pull_request' || github.event_name == 'push'
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Run ZAP Baseline Scan
uses: zaproxy/action-baseline@v0.10.0
with:
target: ${{ secrets.STAGING_URL }}
rules_file_name: '.zap/rules.tsv'
cmd_options: '-a -j'
fail_action: true
allow_issue_writing: false
- name: Upload ZAP Scan Report
uses: actions/upload-artifact@v4
if: always()
with:
name: zap-baseline-report
path: |
report_html.html
report_json.json
retention-days: 30
- name: Create Issue on Failure
if: failure()
uses: actions/github-script@v7
with:
script: |
github.rest.issues.create({
owner: context.repo.owner,
repo: context.repo.repo,
title: '🔒 ZAP Baseline Scan Found Security Issues',
body: 'ZAP baseline scan detected security vulnerabilities. Please review the scan report in the workflow artifacts.',
labels: ['security', 'automated']
})
zap-full-scan:
name: ZAP Full Active Scan (Staging)
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/develop' || github.event_name == 'schedule'
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Run ZAP Full Scan
uses: zaproxy/action-full-scan@v0.8.0
with:
target: ${{ secrets.STAGING_URL }}
rules_file_name: '.zap/rules.tsv'
cmd_options: '-a -j -x report.xml'
fail_action: true
allow_issue_writing: true
issue_title: 'ZAP Full Scan: Security Vulnerabilities Detected'
- name: Upload ZAP Full Scan Report
uses: actions/upload-artifact@v4
if: always()
with:
name: zap-full-scan-report
path: |
report_html.html
report_json.json
report.xml
retention-days: 90
- name: Upload SARIF Report to GitHub Security
uses: github/codeql-action/upload-sarif@v3
if: always()
with:
sarif_file: report.xml
zap-api-scan:
name: ZAP API Scan
runs-on: ubuntu-latest
if: github.event_name == 'push' || github.event_name == 'pull_request'
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Run ZAP API Scan
uses: zaproxy/action-api-scan@v0.6.0
with:
target: ${{ secrets.API_URL }}
format: openapi
api_spec_file: './openapi.yaml'
cmd_options: '-a -j'
fail_action: true
- name: Upload API Scan Report
uses: actions/upload-artifact@v4
if: always()
with:
name: zap-api-scan-report
path: |
report_html.html
report_json.json
retention-days: 30
zap-authenticated-scan:
name: ZAP Authenticated Scan
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/develop'
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup Python
uses: actions/setup-python@v5
with:
python-version: '3.11'
- name: Run Authenticated Scan
env:
APP_PASSWORD: ${{ secrets.TEST_USER_PASSWORD }}
TARGET_URL: ${{ secrets.STAGING_URL }}
run: |
python3 scripts/zap_auth_scanner.py \
--target $TARGET_URL \
--auth-type form \
--login-url $TARGET_URL/login \
--username testuser \
--password-env APP_PASSWORD \
--output ./authenticated-scan-report.html
- name: Upload Authenticated Scan Report
uses: actions/upload-artifact@v4
if: always()
with:
name: zap-authenticated-scan-report
path: authenticated-scan-report.*
retention-days: 90
security-gate:
name: Security Gate Check
runs-on: ubuntu-latest
needs: [zap-baseline-scan]
if: always()
steps:
- name: Download Scan Results
uses: actions/download-artifact@v4
with:
name: zap-baseline-report
- name: Check Security Thresholds
run: |
# Install jq for JSON parsing
sudo apt-get update && sudo apt-get install -y jq
# Count high and medium findings
HIGH_COUNT=$(jq '[.site[].alerts[] | select(.risk == "High")] | length' report_json.json)
MEDIUM_COUNT=$(jq '[.site[].alerts[] | select(.risk == "Medium")] | length' report_json.json)
echo "High risk findings: $HIGH_COUNT"
echo "Medium risk findings: $MEDIUM_COUNT"
# Fail if thresholds exceeded
if [ "$HIGH_COUNT" -gt 0 ]; then
echo "❌ Security gate failed: $HIGH_COUNT high-risk vulnerabilities found"
exit 1
fi
if [ "$MEDIUM_COUNT" -gt 10 ]; then
echo "❌ Security gate failed: $MEDIUM_COUNT medium-risk vulnerabilities (max: 10)"
exit 1
fi
echo "✅ Security gate passed"
- name: Post Summary
if: always()
run: |
echo "## Security Scan Summary" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "| Risk Level | Count |" >> $GITHUB_STEP_SUMMARY
echo "|------------|-------|" >> $GITHUB_STEP_SUMMARY
jq -r '.site[].alerts[] | .risk' report_json.json | sort | uniq -c | \
awk '{print "| " $2 " | " $1 " |"}' >> $GITHUB_STEP_SUMMARY

View File

@@ -0,0 +1,226 @@
# GitLab CI/CD Pipeline for OWASP ZAP Security Scanning
# Add this to your .gitlab-ci.yml file
stages:
- security
- report
variables:
ZAP_IMAGE: "zaproxy/zap-stable:latest"
STAGING_URL: "https://staging.example.com"
REPORTS_DIR: "security-reports"
# Baseline scan for all merge requests
zap_baseline_scan:
stage: security
image: docker:latest
services:
- docker:dind
script:
- mkdir -p $REPORTS_DIR
- |
docker run --rm \
-v $(pwd)/$REPORTS_DIR:/zap/wrk/:rw \
$ZAP_IMAGE \
zap-baseline.py \
-t $STAGING_URL \
-r /zap/wrk/baseline-report.html \
-J /zap/wrk/baseline-report.json \
-w /zap/wrk/baseline-report.md \
|| true
- echo "Baseline scan completed"
artifacts:
when: always
paths:
- $REPORTS_DIR/
reports:
junit: $REPORTS_DIR/baseline-report.xml
expire_in: 1 week
only:
- merge_requests
- develop
- main
tags:
- docker
# Full active scan (manual trigger for staging)
zap_full_scan:
stage: security
image: docker:latest
services:
- docker:dind
script:
- mkdir -p $REPORTS_DIR
- |
docker run --rm \
-v $(pwd)/$REPORTS_DIR:/zap/wrk/:rw \
-v $(pwd)/.zap:/zap/config/:ro \
$ZAP_IMAGE \
zap-full-scan.py \
-t $STAGING_URL \
-c /zap/config/rules.tsv \
-r /zap/wrk/full-scan-report.html \
-J /zap/wrk/full-scan-report.json \
-x /zap/wrk/full-scan-report.xml \
|| true
# Check for high-risk findings
- |
if command -v jq &> /dev/null; then
HIGH_COUNT=$(jq '[.site[].alerts[] | select(.risk == "High")] | length' $REPORTS_DIR/full-scan-report.json)
echo "High risk findings: $HIGH_COUNT"
if [ "$HIGH_COUNT" -gt 0 ]; then
echo "❌ Security scan failed: $HIGH_COUNT high-risk vulnerabilities"
exit 1
fi
fi
artifacts:
when: always
paths:
- $REPORTS_DIR/
expire_in: 4 weeks
only:
- develop
when: manual
allow_failure: false
tags:
- docker
# API security scan
zap_api_scan:
stage: security
image: docker:latest
services:
- docker:dind
script:
- mkdir -p $REPORTS_DIR
- |
if [ -f "openapi.yaml" ]; then
docker run --rm \
-v $(pwd)/$REPORTS_DIR:/zap/wrk/:rw \
-v $(pwd):/zap/specs/:ro \
$ZAP_IMAGE \
zap-api-scan.py \
-t $STAGING_URL \
-f openapi \
-d /zap/specs/openapi.yaml \
-r /zap/wrk/api-scan-report.html \
-J /zap/wrk/api-scan-report.json \
|| true
else
echo "OpenAPI specification not found, skipping API scan"
fi
artifacts:
when: always
paths:
- $REPORTS_DIR/
expire_in: 1 week
only:
- merge_requests
- develop
allow_failure: true
tags:
- docker
# Authenticated scan (requires test credentials)
zap_authenticated_scan:
stage: security
image: python:3.11-slim
before_script:
- apt-get update && apt-get install -y docker.io
script:
- mkdir -p $REPORTS_DIR
- |
python3 scripts/zap_auth_scanner.py \
--target $STAGING_URL \
--auth-type form \
--login-url $STAGING_URL/login \
--username $TEST_USERNAME \
--password-env TEST_PASSWORD \
--output $REPORTS_DIR/authenticated-scan-report.html
artifacts:
when: always
paths:
- $REPORTS_DIR/
expire_in: 4 weeks
only:
- develop
when: manual
tags:
- docker
# Security gate - check thresholds
security_gate:
stage: report
image: alpine:latest
before_script:
- apk add --no-cache jq
script:
- |
if [ -f "$REPORTS_DIR/baseline-report.json" ]; then
HIGH_COUNT=$(jq '[.site[].alerts[] | select(.risk == "High")] | length' $REPORTS_DIR/baseline-report.json)
MEDIUM_COUNT=$(jq '[.site[].alerts[] | select(.risk == "Medium")] | length' $REPORTS_DIR/baseline-report.json)
echo "==================================="
echo "Security Scan Results"
echo "==================================="
echo "High risk findings: $HIGH_COUNT"
echo "Medium risk findings: $MEDIUM_COUNT"
echo "==================================="
# Fail on high-risk findings
if [ "$HIGH_COUNT" -gt 0 ]; then
echo "❌ Build failed: High-risk vulnerabilities detected"
exit 1
fi
# Warn on medium-risk findings above threshold
if [ "$MEDIUM_COUNT" -gt 10 ]; then
echo "⚠️ Warning: $MEDIUM_COUNT medium-risk findings (threshold: 10)"
fi
echo "✅ Security gate passed"
else
echo "No scan report found, skipping security gate"
fi
dependencies:
- zap_baseline_scan
only:
- merge_requests
- develop
- main
# Generate consolidated report
generate_report:
stage: report
image: alpine:latest
before_script:
- apk add --no-cache jq curl
script:
- |
echo "# Security Scan Report" > $REPORTS_DIR/summary.md
echo "" >> $REPORTS_DIR/summary.md
echo "**Scan Date:** $(date)" >> $REPORTS_DIR/summary.md
echo "**Target:** $STAGING_URL" >> $REPORTS_DIR/summary.md
echo "" >> $REPORTS_DIR/summary.md
echo "## Findings Summary" >> $REPORTS_DIR/summary.md
echo "" >> $REPORTS_DIR/summary.md
if [ -f "$REPORTS_DIR/baseline-report.json" ]; then
echo "| Risk Level | Count |" >> $REPORTS_DIR/summary.md
echo "|------------|-------|" >> $REPORTS_DIR/summary.md
jq -r '.site[].alerts[] | .risk' $REPORTS_DIR/baseline-report.json | \
sort | uniq -c | awk '{print "| " $2 " | " $1 " |"}' >> $REPORTS_DIR/summary.md
fi
cat $REPORTS_DIR/summary.md
artifacts:
when: always
paths:
- $REPORTS_DIR/summary.md
expire_in: 4 weeks
dependencies:
- zap_baseline_scan
only:
- merge_requests
- develop
- main

View File

@@ -0,0 +1,196 @@
# OWASP ZAP Automation Framework Configuration
# Complete automation workflow for web application security testing
env:
contexts:
- name: WebApp-Security-Scan
urls:
- ${TARGET_URL}
includePaths:
- ${TARGET_URL}.*
excludePaths:
- .*logout.*
- .*signout.*
- .*\\.css
- .*\\.js
- .*\\.png
- .*\\.jpg
- .*\\.gif
- .*\\.svg
authentication:
method: form
parameters:
loginUrl: ${LOGIN_URL}
loginRequestData: username={%username%}&password={%password%}
verification:
method: response
loggedInRegex: "\\QWelcome\\E"
loggedOutRegex: "\\QLogin\\E"
sessionManagement:
method: cookie
parameters:
sessionCookieName: JSESSIONID
users:
- name: test-user
credentials:
username: ${TEST_USERNAME}
password: ${TEST_PASSWORD}
parameters:
failOnError: true
failOnWarning: false
progressToStdout: true
vars:
target_url: ${TARGET_URL}
api_key: ${ZAP_API_KEY}
jobs:
# Environment setup
- type: environment
parameters:
deleteGlobalAlerts: true
updateAddOns: true
# Import OpenAPI specification (if available)
- type: openapi
parameters:
apiFile: ${OPENAPI_SPEC_FILE}
apiUrl: ${TARGET_URL}
targetUrl: ${TARGET_URL}
context: WebApp-Security-Scan
optional: true
# Spider crawling
- type: spider
parameters:
context: WebApp-Security-Scan
user: test-user
maxDuration: 10
maxDepth: 5
maxChildren: 10
acceptCookies: true
handleODataParametersVisited: true
parseComments: true
parseRobotsTxt: true
parseSitemapXml: true
parseSVNEntries: true
parseGit: true
postForm: true
processForm: true
requestWaitTime: 200
# AJAX Spider for JavaScript-heavy applications
- type: spiderAjax
parameters:
context: WebApp-Security-Scan
user: test-user
maxDuration: 10
maxCrawlDepth: 5
numberOfBrowsers: 2
browserId: firefox-headless
clickDefaultElems: true
clickElemsOnce: true
eventWait: 1000
reloadWait: 1000
optional: true
# Wait for passive scanning to complete
- type: passiveScan-wait
parameters:
maxDuration: 5
# Configure passive scan rules
- type: passiveScan-config
parameters:
maxAlertsPerRule: 10
scanOnlyInScope: true
enableTags: true
disableRules:
- 10096 # Timestamp Disclosure (informational)
# Active scanning
- type: activeScan
parameters:
context: WebApp-Security-Scan
user: test-user
policy: Default Policy
maxRuleDurationInMins: 5
maxScanDurationInMins: 30
addQueryParam: false
defaultPolicy: Default Policy
delayInMs: 0
handleAntiCSRFTokens: true
injectPluginIdInHeader: false
scanHeadersAllRequests: false
threadPerHost: 2
# Wait for active scanning to complete
- type: activeScan-wait
# Generate reports
- type: report
parameters:
template: traditional-html
reportDir: ${REPORT_DIR}
reportFile: security-report.html
reportTitle: Web Application Security Assessment
reportDescription: Automated DAST scan using OWASP ZAP
displayReport: false
- type: report
parameters:
template: traditional-json
reportDir: ${REPORT_DIR}
reportFile: security-report.json
reportTitle: Web Application Security Assessment
- type: report
parameters:
template: traditional-xml
reportDir: ${REPORT_DIR}
reportFile: security-report.xml
reportTitle: Web Application Security Assessment
- type: report
parameters:
template: sarif-json
reportDir: ${REPORT_DIR}
reportFile: security-report.sarif
reportTitle: Web Application Security Assessment (SARIF)
optional: true
# Alert filters (false positive suppression)
alertFilters:
- ruleId: 10021
newRisk: Info
url: ".*\\.css|.*\\.js|.*cdn\\..*"
context: WebApp-Security-Scan
- ruleId: 10096
newRisk: Info
url: ".*api\\..*"
parameter: "created_at|updated_at|timestamp"
context: WebApp-Security-Scan
# Scan policies
policies:
- name: Default Policy
defaultStrength: Medium
defaultThreshold: Medium
rules:
- id: 40018 # SQL Injection
strength: High
threshold: Low
- id: 40012 # Cross-Site Scripting (Reflected)
strength: High
threshold: Low
- id: 40014 # Cross-Site Scripting (Persistent)
strength: High
threshold: Low
- id: 90019 # Server-Side Code Injection
strength: High
threshold: Low
- id: 90020 # Remote OS Command Injection
strength: High
threshold: Low

View File

@@ -0,0 +1,192 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
OWASP ZAP Authentication Context Template
Configure this file for form-based, HTTP, or script-based authentication
-->
<configuration>
<context>
<!-- Context Name -->
<name>WebApp-Auth-Context</name>
<desc>Authentication context for web application security testing</desc>
<!-- Enable context -->
<inscope>true</inscope>
<!-- URL Scope Definition -->
<!-- Include all URLs under target domain -->
<incregexes>https://app\.example\.com/.*</incregexes>
<!-- Exclude logout and static content -->
<excregexes>https://app\.example\.com/logout</excregexes>
<excregexes>https://app\.example\.com/signout</excregexes>
<excregexes>https://app\.example\.com/static/.*</excregexes>
<excregexes>.*\.css</excregexes>
<excregexes>.*\.js</excregexes>
<excregexes>.*\.png|.*\.jpg|.*\.gif</excregexes>
<!-- Technology Detection -->
<tech>
<include>Language</include>
<include>Language.JavaScript</include>
<include>OS</include>
<include>OS.Linux</include>
<include>WS</include>
</tech>
<!-- Authentication Configuration -->
<authentication>
<!--
Authentication Types:
- formBasedAuthentication: Traditional login forms
- httpAuthentication: HTTP Basic/Digest/NTLM
- scriptBasedAuthentication: Custom authentication via script
-->
<type>formBasedAuthentication</type>
<!-- Form-Based Authentication -->
<form>
<!-- Login URL -->
<loginurl>https://app.example.com/login</loginurl>
<!-- Login Request Body (POST parameters) -->
<!-- Use {%username%} and {%password%} as placeholders -->
<loginbody>username={%username%}&amp;password={%password%}&amp;csrf_token={%csrf_token%}</loginbody>
<!-- Login Page URL (where login form is displayed) -->
<loginpageurl>https://app.example.com/login</loginpageurl>
</form>
<!-- HTTP Authentication (uncomment if using) -->
<!--
<http>
<realm>Protected Area</realm>
<hostname>app.example.com</hostname>
<port>443</port>
</http>
-->
<!-- Logged-In Indicator (regex pattern that appears when logged in) -->
<!-- This helps ZAP determine if authentication succeeded -->
<loggedin>\QWelcome,\E</loggedin>
<!-- Alternative patterns:
<loggedin>\QLogout\E</loggedin>
<loggedin>\Qdashboard\E</loggedin>
<loggedin>class="user-menu"</loggedin>
-->
<!-- Logged-Out Indicator (regex pattern that appears when logged out) -->
<loggedout>\QYou are not logged in\E</loggedout>
<!-- Alternative patterns:
<loggedout>\QLogin\E</loggedout>
<loggedout>\QSign In\E</loggedout>
-->
<!-- Poll URL for verification (optional) -->
<pollurl>https://app.example.com/api/session/verify</pollurl>
<polldata></polldata>
<pollfreq>60</pollfreq>
</authentication>
<!-- Session Management -->
<sessionManagement>
<!--
Session Management Types:
- cookieBasedSessionManagement: Session via cookies (most common)
- httpAuthSessionManagement: HTTP authentication
- scriptBasedSessionManagement: Custom session handling
-->
<type>cookieBasedSessionManagement</type>
<!-- Session cookies to monitor -->
<sessioncookies>
<cookie>JSESSIONID</cookie>
<cookie>PHPSESSID</cookie>
<cookie>sessionid</cookie>
<cookie>session_token</cookie>
</sessioncookies>
</sessionManagement>
<!-- Test Users -->
<users>
<!-- User 1: Standard test user -->
<user>
<name>testuser</name>
<enabled>true</enabled>
<credentials>
<credential>
<name>username</name>
<value>testuser</value>
</credential>
<credential>
<name>password</name>
<value>TestPassword123!</value>
</credential>
<!-- CSRF token (if needed) -->
<!--
<credential>
<name>csrf_token</name>
<value></value>
</credential>
-->
</credentials>
</user>
<!-- User 2: Admin user (if testing authorization) -->
<user>
<name>adminuser</name>
<enabled>false</enabled>
<credentials>
<credential>
<name>username</name>
<value>adminuser</value>
</credential>
<credential>
<name>password</name>
<value>AdminPassword123!</value>
</credential>
</credentials>
</user>
</users>
<!-- Forced User Mode (for authorization testing) -->
<!--
Enables testing if authenticated user can access resources
they shouldn't have access to
-->
<forcedUserMode>false</forcedUserMode>
<!-- Data Driven Nodes -->
<!--
For testing parameters with different values
-->
<datadrivennodes>
<node>
<name>user_id</name>
<url>https://app.example.com/api/users/{user_id}</url>
</node>
</datadrivennodes>
</context>
<!-- Global Exclude URLs (applied to all contexts) -->
<globalexcludeurl>
<regex>https://.*\.googleapis\.com/.*</regex>
<regex>https://.*\.google-analytics\.com/.*</regex>
<regex>https://.*\.googletagmanager\.com/.*</regex>
<regex>https://cdn\..*</regex>
</globalexcludeurl>
<!-- Anti-CSRF Token Configuration -->
<anticsrf>
<!-- Enable anti-CSRF token handling -->
<enabled>true</enabled>
<!-- Token names to automatically detect and handle -->
<tokennames>
<tokenname>csrf_token</tokenname>
<tokenname>csrftoken</tokenname>
<tokenname>_csrf</tokenname>
<tokenname>authenticity_token</tokenname>
<tokenname>__RequestVerificationToken</tokenname>
</tokennames>
</anticsrf>
</configuration>