
Security Scan
- 46 installs
- 41 repo stars
- Updated July 6, 2026
- aws-samples/sample-agent-skills-for-builders
security-scan is a Claude skill that runs multi-tool security and compliance scanning on AWS CDK projects, aggregating Viperlight, license, Trivy, and ASH scans.
About
This skill runs security and compliance scanning on AWS CDK projects using multiple tools. A developer uses it before deployment to synthesize CloudFormation, run Viperlight code analysis, check dependency licenses, scan containers with Trivy, and run ASH for aggregated SAST, IaC, and secret analysis. It ends with a summary and an HTML report and treats all 9 steps as mandatory.
- Multi-tool security and compliance scanning for AWS CDK projects
- Runs Viperlight, license check, Trivy, and ASH (SAST/IaC/secret) scans
- 9 mandatory steps ending in summary and HTML reports
Security Scan by the numbers
- 46 all-time installs (skills.sh)
- Ranked #1,362 of 2,203 Security skills by installs in the Skillselion catalog
- Data as of Jul 30, 2026 (Skillselion catalog sync)
security-scan capabilities & compatibility
- Capabilities
- cost estimator · create install scripts
- Works with
- aws
- Use cases
- security audit · devops
- Pricing
- Free
What security-scan says it does
Comprehensive security and compliance scanning for AWS CDK projects.
IMPORTANT: All 9 steps are mandatory. Do not skip steps regardless of time constraints.
npx skills add https://github.com/aws-samples/sample-agent-skills-for-builders --skill security-scanAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 46 |
|---|---|
| repo stars | ★ 41 |
| Last updated | July 6, 2026 |
| Repository | aws-samples/sample-agent-skills-for-builders ↗ |
What it does
Run multi-tool security and compliance scanning on an AWS CDK project before deployment and generate review reports.
Who is it for?
Developers running pre-deployment security and compliance audits on AWS CDK projects.
When should I use this skill?
When running security audits on CDK projects, checking license compliance, or scanning container images before deployment.
What you get
A findings summary and HTML report covering code security, license compliance, container vulnerabilities, and aggregated SAST/IaC/secret analysis.
- security findings summary report
- HTML security report
By the numbers
- 9 mandatory scan steps
- 4 scanning tools (Viperlight, license, Trivy, ASH)
Files
Security Scan
Comprehensive AWS CDK project security and compliance scanning with multi-tool analysis.
When to Apply
Reference this skill when:
- Running security audits on CDK projects
- Checking license compliance
- Scanning container images for vulnerabilities
- Running aggregated SAST/IaC/secret analysis (via ASH)
- Generating security reports for review
How It Works
IMPORTANT: All 9 steps are mandatory. Do not skip steps regardless of time constraints.
1. Architecture Diagram - Capture system architecture 2. CDK Synthesis - Generate CloudFormation templates 3. Viperlight Scan - Code security analysis 4. License Check - Dependency license compliance 5. Trivy Scan - Container vulnerability scanning 6. ASH Scan - Automated Security Helper (aggregated SAST/IaC/secret scanners) 7. Deployment Verification - Validate deployment readiness 8. Summary Report - Generate findings summary 9. HTML Report - Create visual report
Prerequisites
- AWS CDK project
- Viperlight CLI installed
- Trivy CLI installed
- ASH (Automated Security Helper) - installed via
uvxfrom https://github.com/awslabs/automated-security-helper - Docker (required for ASH container mode)
Usage
# Run security scan
"Run security scan on my CDK project"
"Check this project for vulnerabilities"
"Generate security compliance report"Output
Results saved to security-scan-results/{TIMESTAMP}/:
- Raw scan outputs per tool
clean-results/- Parsed findingssecurity-report.html- Visual report
Enforcement Rules
- Execute ALL scan commands (no skipping)
- Fix issues found (don't just comment them)
- Non-interactive execution only
- Continue pipeline on individual step failures
References
- Architecture Diagram
- CDK Synthesis
- Viperlight Scan
- License Check
- Trivy Scan
- ASH Scan
- Deployment Verification
- Report Generation
Security Scan Skill
A comprehensive security and compliance scanning solution for AWS CDK projects, providing multi-tool analysis including code security, license compliance, container vulnerability scanning, and aggregated SAST/IaC/secret analysis.
Installation
npx skills add https://github.com/aws-samples/sample-agent-skills-for-builders --skill security-scanQuick Start
Trigger the skill with natural language prompts:
# Security audits
"Run security scan on my CDK project"
"Check this project for vulnerabilities"
# Compliance checks
"Generate security compliance report"
"Verify license compliance for all dependencies"
# Aggregated SAST/IaC/secret scanning
"Run ASH aggregated security scan"Prerequisites
Before using this skill, ensure you have the following installed:
- AWS CDK - AWS Cloud Development Kit for infrastructure as code
- Viperlight - Code security scanner
npm install -g viperlight- Trivy - Container vulnerability scanner
brew install trivy # macOS
# or
apt-get install trivy # Linux- ASH (Automated Security Helper) - Aggregated security scanner from AWS Labs (awslabs/automated-security-helper) that bundles bandit, checkov, semgrep, grype, and other tools. Requires Python 3.10+, Docker (running), and
uv. See references/ash-scan.md for the pinned install command.
What the Skill Does
The security-scan skill executes a 9-step comprehensive security workflow:
1. Architecture Diagram - Visualizes your system architecture 2. CDK Synthesis - Generates CloudFormation templates 3. Viperlight Scan - Detects code security issues 4. License Check - Verifies dependency license compliance 5. Trivy Scan - Scans container images for vulnerabilities 6. ASH Scan - Runs Automated Security Helper (aggregates SAST, IaC, and secret scanners) 7. Deployment Verification - Validates deployment readiness 8. Summary Report - Creates a findings summary 9. HTML Report - Generates a visual HTML report
All 9 steps execute sequentially. The workflow continues even if individual steps encounter failures.
Output Structure
Security scan results are saved to security-scan-results/{TIMESTAMP}/ with the following structure:
security-scan-results/2024-01-15-14-30-45/
├── viperlight-output.json
├── license-report.json
├── trivy-report.json
├── ash-report.json
├── deployment-check.json
├── clean-results/
│ ├── vulnerabilities.json
│ ├── license-issues.json
│ └── best-practices-violations.json
├── architecture-diagram.png
└── security-report.htmlOpen security-report.html in your browser to view a visual summary of all findings.
Usage Examples
Basic Security Scan
User: "Run a complete security scan on my CDK project"The skill will execute all 9 scanning steps and generate reports.
License Compliance Check
User: "Check if all dependencies have compliant licenses"Focuses on license scanning and generates a compliance report.
Vulnerability Assessment
User: "Scan container images for vulnerabilities before deployment"Runs Trivy container scanning and generates vulnerability findings.
File Structure
skills/security-scan/
├── SKILL.md # Skill definition and trigger rules
├── README.md # This file
├── references/
│ ├── architecture-diagram.md
│ ├── cdk-synthesis.md
│ ├── viperlight-scan.md
│ ├── license-check.md
│ ├── trivy-scan.md
│ ├── ash-scan.md
│ ├── deployment-verification.md
│ └── report-generation.md
└── scripts/
└── [automation scripts, if present]Consult the reference files for detailed information on each scanning tool and step.
Key Features
- Multi-tool analysis - Combines industry-standard security tools
- Comprehensive reports - HTML visual reports with findings
- License compliance - Ensures all dependencies meet licensing requirements
- Container security - Scans images for known vulnerabilities
- Aggregated security scanning - ASH consolidates findings from multiple SAST/IaC/secret scanners
- Non-blocking failures - Pipeline continues even if individual scans fail
Interpreting Results
Check the generated security-report.html for a visual overview of:
- High priority vulnerabilities - Require immediate attention
- License violations - Dependencies with non-compliant licenses
- SAST / IaC / secret findings - From ASH-aggregated scanners (bandit, checkov, semgrep, grype, etc.)
- Deployment blockers - Issues preventing safe deployment
Review the JSON reports in clean-results/ for programmatic access to findings.
Next Steps
1. Install all prerequisites (see Prerequisites section) 2. Navigate to your AWS CDK project 3. Trigger the skill with a natural language prompt 4. Review the generated security report 5. Address any high-priority findings 6. Re-run the scan to verify fixes
Support
For detailed tool-specific documentation, see the references/ directory included with this skill.
架构图获取
目标
获取项目的架构图,用于安全审查和文档提交。
执行步骤
Step 1: 获取扫描目录
source security-scan-results/.current-scan
echo "输出目录: ${SCAN_DIR}/01-architecture/"Step 2: 查找架构图
架构图通常在 README.md 文档前面部分展示,常见位置:
docs/images/architecture.pngdocs/images/architecture.jpgdocs/architecture.pngarchitecture.pngimages/architecture.png
检查 README 中的架构图引用:
grep -E "!\[.*\]\(.*\.(png|jpg|svg)" README.md | head -5自动查找架构图文件:
# 查找可能的架构图文件
ARCH_FILE=$(find "$PROJECT_ROOT" -type f \( -name "architecture*.png" -o -name "architecture*.jpg" -o -name "architecture*.svg" \) \
-not -path "*/node_modules/*" -not -path "*/cdk.out/*" -not -path "*/.git/*" | head -1)
if [ -z "$ARCH_FILE" ]; then
# 如果没找到 architecture 命名的文件,查找 docs/images 下的图片
ARCH_FILE=$(find "$PROJECT_ROOT/docs" -type f \( -name "*.png" -o -name "*.jpg" -o -name "*.svg" \) 2>/dev/null | head -1)
fi
echo "找到架构图: $ARCH_FILE"Step 3: 复制架构图到结果目录
找到架构图后,复制到扫描结果目录和 result 目录:
if [ -n "$ARCH_FILE" ]; then
# 获取文件扩展名
EXT="${ARCH_FILE##*.}"
# 复制到 01-architecture 目录
cp "$ARCH_FILE" "${SCAN_DIR}/01-architecture/diagram.${EXT}"
# 同时复制到 result 目录(用于最终提交)
cp "$ARCH_FILE" "${SCAN_DIR}/result/architecture.${EXT}"
echo "架构图已复制"
else
echo "未找到架构图文件,请手动指定路径"
fi注意: 如果自动查找未找到架构图,请根据项目实际情况手动指定路径。
Step 4: 验证架构图
确认架构图已复制:
ls -la "${SCAN_DIR}/01-architecture/"
ls -la "${SCAN_DIR}/result/"完成标志
- [ ] 架构图文件已复制到
01-architecture/diagram.* - [ ] 架构图文件已复制到
result/architecture.* - [ ] 如果项目没有架构图,记录说明并继续下一步
记录用时:
echo "STEP_01_END=$(date +%s)" >> security-scan-results/.current-scan下一步
完成后继续执行 02-cdk-synth.md
ASH (Automated Security Helper) 扫描
🚫 禁止跳过此步骤
绝对禁止以任何理由跳过此步骤!
- ❌ 禁止因 "ASH 未安装" 而跳过 → 必须先安装再执行
- ❌ 禁止因 "扫描时间长" 而跳过 → 必须等待完成(15-30分钟是正常的)
- ❌ 禁止因 "Docker 镜像构建失败" 而跳过 → 必须排查并解决
- ❌ 禁止创建 skipped.txt 文件 → 必须执行实际扫描
如果遇到问题,必须解决问题后继续执行,而不是跳过。
目标
使用 ASH 工具扫描 AWS 安全最佳实践合规性。
前置条件
条件 1: 检查基础环境依赖
- Python 3.10+
- Docker(必须运行中)
- uv (Python package manager)
条件 2: 安装 ASH
# Install uv if you don't have it
curl -sSf https://astral.sh/uv/install.sh | sh
# Create an alias for ASH (使用固定版本 v3.1.5 避免构建问题)
alias ash='uvx --from "git+https://github.com/awslabs/automated-security-helper.git@v3.1.5" ash'重要:必须使用 Docker 模式
ASH 扫描必须使用 --mode container,禁止使用 --mode local。原因:
- Container 模式将所有扫描工具打包在 Docker 镜像中,无需本地安装
- Local 模式需要手动安装 bandit、checkov、semgrep、grype 等多个工具
- Container 模式保证环境一致性,避免本地环境差异导致的问题
执行前请确认:
# 确认 Docker 正在运行
docker info > /dev/null 2>&1 && echo "Docker is running" || echo "Docker is NOT running"Docker 镜像构建问题处理
如果首次运行 ASH 时 Docker 镜像构建失败(如 uv build 错误),可以尝试以下方法:
1. 清理并重试:
docker rmi automated-security-helper:non-root
ash --mode container --output-dir output/注意:遇到问题时必须解决问题,不能切换到 local 模式,不能使用已有镜像。
重要规则
1. 先清理再扫描:扫描前删除 node_modules 和 cdk.out 目录,避免扫描大量第三方文件 2. 先初始化再配置再扫描:ash config init → 配置 suppressions → 运行扫描 3. 代码修复优先:能通过代码修复的问题必须修复,无法修复的误报才添加到 suppressions 4. 修复-重扫循环:发现问题后修复或更新配置,重新扫描直到通过 5. 结果归档:扫描通过后,将配置和结果文件拷贝到 result 目录
输出目录结构
06-ash/
├── output/ # ASH 扫描输出目录
│ ├── reports/
│ │ ├── ash.html
│ │ ├── ash.summary.md
│ │ └── ...
│ └── ash_aggregated_results.json
└── fix-report.md # 修复报告(如有)
# 扫描通过后归档到:
result/ash/
├── ash.yaml # 配置文件副本
├── ash.md # 扫描摘要报告
└── ash.html执行步骤
Step 1: 获取扫描目录
source security-scan-results/.current-scan
mkdir -p "${SCAN_DIR}/06-ash"Step 2: 清理第三方依赖和构建输出目录
删除 node_modules、cdk.out 和 .next 目录,避免扫描大量第三方文件和构建产物(扫描后可通过 pnpm install 恢复):
# 删除所有 node_modules 目录
find "$PROJECT_ROOT" -name "node_modules" -type d -prune -exec rm -rf {} + 2>/dev/null
# 删除 cdk.out 目录
rm -rf "$PROJECT_ROOT/cdk.out"
# 删除所有 .next 目录(Next.js 构建输出)
find "$PROJECT_ROOT" -name ".next" -type d -prune -exec rm -rf {} + 2>/dev/nullStep 3: 确认 ASH 已安装
# 使用固定版本运行
uvx --from "git+https://github.com/awslabs/automated-security-helper.git@v3.1.5" ash --versionStep 4: 初始化并配置 ASH
ash config init编辑生成的 .ash/.ash.yaml。
⚠️ 配置文件格式要求:
1. fail_on_findings 必须在顶层,不能放在 global_settings 里 2. suppressions 的 path 必须使用完整路径,不支持通配符 ** 3. 每个需要 suppress 的文件都要单独添加一条规则
正确的配置文件格式:
# yaml-language-server: $schema=https://raw.githubusercontent.com/awslabs/automated-security-helper/refs/heads/main/automated_security_helper/schemas/AshConfig.json
project_name: your-project-name
fail_on_findings: true # 必须在顶层!
global_settings:
severity_threshold: MEDIUM
ignore_paths:
- path: 'node_modules/'
reason: 'Third-party dependencies'
- path: 'cdk.out/'
reason: 'CDK build output'
- path: '.next/'
reason: 'Next.js build output'
suppressions:
# 每个文件单独添加规则,使用完整路径
- rule_id: 'CKV_DOCKER_2'
path: 'src/infrastructure/lambda/my-function/Dockerfile'
reason: 'Lambda containers do not need HEALTHCHECK'
ash_plugin_modules: []❌ 错误示例(不要这样写):
global_settings:
fail_on_findings: true # 错误!不能放在 global_settings 里
suppressions:
- rule_id: 'CKV_DOCKER_2'
path: 'src/infrastructure/**' # 错误!不支持通配符Step 5: 执行 ASH 扫描
必须使用 `ASH_CONFIG` 环境变量指定配置文件:
export ASH_CONFIG=".ash/.ash.yaml"
ash --mode container --output-dir "${SCAN_DIR}/06-ash/output" 2>&1 | tee "${SCAN_DIR}/06-ash/scan-log.txt"Step 6: 分析并修复问题
检查 ${SCAN_DIR}/06-ash/output/reports/ 下的报告(推荐查看 ash.html 或 ash.summary.md)。
处理原则:
- 能修复的问题 → 修改代码
- 确认是误报 → 添加到
.ash/.ash.yaml的 suppressions
常见可修复问题
| 问题 | 修复方案 |
|---|---|
IAM 权限过宽 (* resource) | 限制到具体 ARN |
| S3 未加密 | 添加 encryption: s3.BucketEncryption.S3_MANAGED |
| S3 公开访问 | 添加 blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL |
| 安全组 0.0.0.0/0 | 限制到 VPC CIDR |
Suppressions 添加原则
重要:不要预先添加 suppressions! 只有在扫描发现问题后,经过分析确认是误报,才添加对应的 suppression。
添加 suppression 的流程: 1. 运行扫描,查看报告中的具体问题 2. 分析每个问题,判断是真实问题还是误报 3. 真实问题 → 修复代码 4. 确认是误报 → 添加 suppression 并记录理由
Lambda/ECS Dockerfile 常见误报
对于 Lambda 和 ECS 容器的 Dockerfile,以下检查通常是误报:
| Rule ID | 说明 | Reason |
|---|---|---|
CKV_DOCKER_2 | HEALTHCHECK 检查 | Lambda/ECS 容器由 AWS 管理生命周期,不需要 Docker HEALTHCHECK |
CKV_DOCKER_3 | USER 检查 | Lambda 容器应使用默认用户以获得正确的文件系统权限 |
dockerfile.security.missing-user.missing-user | 同上 | 同上 |
⚠️ 必须为每个 Dockerfile 单独添加 suppression 规则:
global_settings:
suppressions:
# Lambda Dockerfile 1
- rule_id: 'CKV_DOCKER_2'
path: 'src/infrastructure/lambda/function-a/Dockerfile'
reason: 'Lambda containers do not need HEALTHCHECK - AWS Lambda manages container lifecycle'
- rule_id: 'CKV_DOCKER_3'
path: 'src/infrastructure/lambda/function-a/Dockerfile'
reason: 'Lambda containers should use default user for proper file system permissions'
- rule_id: 'dockerfile.security.missing-user.missing-user'
path: 'src/infrastructure/lambda/function-a/Dockerfile'
reason: 'Lambda containers should use default user for proper file system permissions'
# Lambda Dockerfile 2
- rule_id: 'CKV_DOCKER_2'
path: 'src/infrastructure/lambda/function-b/Dockerfile'
reason: 'Lambda containers do not need HEALTHCHECK - AWS Lambda manages container lifecycle'
# ... 为每个 Dockerfile 重复添加Step 7: 重新扫描验证
修复后重新扫描,重复 Step 5-6 直到无 CRITICAL/HIGH 问题:
export ASH_CONFIG=".ash/.ash.yaml"
ash --mode container --output-dir "${SCAN_DIR}/06-ash/output"Step 8: 归档结果
mkdir -p "${SCAN_DIR}/result/ash"
cp .ash/.ash.yaml "${SCAN_DIR}/result/ash/ash.yaml"
cp "${SCAN_DIR}/06-ash/output/reports/ash.html" "${SCAN_DIR}/result/ash/" 2>/dev/null || trueStep 9: 生成结果报告
生成 ${SCAN_DIR}/result/ash/ash.md,包含: 1. 扫描摘要(通过/失败状态) 2. .ash.yaml 中 suppressions 列表的所有内容和对应的理由 3. 包含 fix-report.md 中的所有改动说明(如有)
完成标志
- [ ]
.ash/.ash.yaml配置文件已创建 - [ ]
${SCAN_DIR}/06-ash/output/目录存在 - [ ] 无 CRITICAL 级别问题
- [ ] 无 HIGH 级别问题(或已添加 suppression 并记录理由)
- [ ]
${SCAN_DIR}/result/ash/目录已创建 - [ ]
${SCAN_DIR}/result/ash/ash.md已生成 - [ ]
${SCAN_DIR}/result/ash/ash.md包含 "Suppressions 配置" 章节(列出所有 suppressions 及理由) - [ ]
${SCAN_DIR}/result/ash/ash.yaml已生成 - [ ]
${SCAN_DIR}/result/ash/ash.html已生成 - [ ] 如有修复,
fix-report.md已更新
记录用时:
echo "STEP_06_END=$(date +%s)" >> security-scan-results/.current-scan下一步
完成后继续执行 07-deploy-verify.md
CDK Synth 扫描
目标
生成 CDK CloudFormation 模板,验证 CDK 代码能正确合成。
前置条件
- Node.js 和 pnpm/npm
- AWS CDK CLI (
npm install -g aws-cdk) - Docker(用于构建 Python Lambda)
执行步骤
Step 1: 获取扫描目录
source security-scan-results/.current-scanStep 2: 登录 ECR Public
aws ecr-public get-login-password --region us-east-1 | docker login --username AWS --password-stdin public.ecr.aws注意: 必须使用 us-east-1,与项目部署 region 无关。
Step 3: 安装依赖
根据项目使用的包管理器安装依赖。
⚠️ pnpm 10+: 如遇 approve-builds 提示,需先在 package.json 中添加 pnpm.onlyBuiltDependencies 配置以确保非交互式执行。
# 检测并使用正确的包管理器
if [ -f "pnpm-lock.yaml" ]; then
pnpm install
elif [ -f "yarn.lock" ]; then
yarn install
elif [ -f "package-lock.json" ]; then
npm install
else
npm install
fiStep 4: 执行 CDK Synth
首次执行时保存为 output_origin.txt,后续修复重试时覆盖 output.txt:
# 首次执行:保存原始输出
if [ ! -f "${SCAN_DIR}/02-cdk-synth/output_origin.txt" ]; then
npx cdk synth 2>&1 | tee "${SCAN_DIR}/02-cdk-synth/output_origin.txt"
else
# 修复后重试:覆盖 output.txt
npx cdk synth 2>&1 | tee "${SCAN_DIR}/02-cdk-synth/output.txt"
fiStep 5: 提取 CloudFormation 模板
根据是否存在 output.txt 选择输出源:
# 如果有修复后的 output.txt 则用它,否则用原始输出
if [ -f "${SCAN_DIR}/02-cdk-synth/output.txt" ]; then
OUTPUT_FILE="${SCAN_DIR}/02-cdk-synth/output.txt"
else
OUTPUT_FILE="${SCAN_DIR}/02-cdk-synth/output_origin.txt"
fi
# 提取从 Metadata: 开始的内容(trim 前导空格后匹配)
awk '{line=$0; gsub(/^[[:space:]]+/, "", line)} line ~ /^Metadata:/ {found=1} found' "${OUTPUT_FILE}" > "${SCAN_DIR}/result/cdk-synth.txt"Step 6: 检查结果
- 成功:
cdk-synth.txt存在且以Metadata:开头 - 失败: 输出文件包含
Error、Exception、failed
常见错误:
| 错误 | 修复 |
|---|---|
| ECR Public 403 | Step 1 登录 + docker buildx prune -f |
| Cannot find module | 重新安装依赖 |
| Cannot retrieve context | cdk context --clear |
| Missing context | 检查 README 中的部署参数要求 |
输出文件说明
| 文件 | 说明 |
|---|---|
output_origin.txt | 首次执行的原始输出(保留不变) |
output.txt | 最新执行的输出(修复后会覆盖) |
完成标志
- [ ]
output_origin.txt或者output.txt无错误 - [ ]
cdk-synth.txt存在且内容有效 - [ ] 如有修复,创建
fix-report.md
记录用时:
echo "STEP_02_END=$(date +%s)" >> security-scan-results/.current-scan下一步
继续执行 03-viperlight-scan.md
部署验证
🚫 禁止跳过此步骤
绝对禁止以任何理由跳过此步骤!
- ❌ 禁止因 "之前扫描有问题" 而跳过 → 必须执行部署
- ❌ 禁止因 "Stack 已存在" 而跳过 → 必须重新执行
cdk deploy - ❌ 禁止因 "时间限制" 而跳过 → 必须等待部署完成
- ❌ 禁止仅检查 Stack 状态就跳过 → 必须实际执行部署命令
- ❌ 禁止创建 skipped.txt 文件 → 必须执行实际部署
如果部署失败,必须排查原因、修复代码后重新部署,直到成功为止。
目标
验证当前代码能够成功部署到 AWS。
前置条件
- 所有前置扫描已完成
- 所有 CRITICAL/HIGH 问题已修复
- 已完成前置准备 (确认 Region 和 Email)
执行步骤
Step 1: 获取扫描配置并设置 Region
source security-scan-results/.current-scan
export AWS_REGION=${SCAN_REGION} # 设置部署目标 Region
echo "Region: ${SCAN_REGION}"
echo "Email: ${SCAN_EMAIL}"Step 1.5: 检查 ACM 证书
阅读项目 README 或 install 脚本,确认部署是否需要 ACM 证书参数。
如需证书: 1. 检查现有证书:aws acm list-certificates --region ${SCAN_REGION} 2. 若无可用证书,按项目文档在当前 region 创建一个新的 ACM 证书 3. 保存到配置:echo "ACM_CERT_ARN=<arn>" >> security-scan-results/.current-scan,部署时使用
如不需要证书:直接执行 Step 2
Step 2: 执行部署
⚠️ 重要:禁止使用交互式命令!
通常 install.sh 脚本包含交互式 read 命令获取 email,必须直接使用 npx cdk deploy 命令:
# 直接使用 cdk deploy,具体参数请参考 readme 或者 install.sh 等安装脚本中的定义。禁止使用 install.sh 的交互式输入, 如下为一个示例:
npx cdk deploy --all --context Email=${SCAN_EMAIL} --require-approval never部署方式: 1. 全新部署: 如果没有现有 Stack,执行上述命令 2. 更新部署: 如果有现有 Stack,同样执行上述命令进行更新
重要:
- 使用
.current-scan中记录的SCAN_EMAIL和SCAN_REGION - 必须等待部署完成,不能中途跳过
Step 3: 验证部署成功
部署成功的必要条件 (全部满足才算成功):
1. ✅ 部署输出的 Outputs 内容不为空 2. ✅ Stack 状态为 CREATE_COMPLETE 或 UPDATE_COMPLETE
验证 Stack 状态:
aws cloudformation describe-stacks --stack-name <STACK_NAME> \
--query "Stacks[0].StackStatus" --output text只有看到以上所有标志,才算部署成功。
重要提示Important !!!: 如果部署失败,请查找失败原因, 修复代码后重新部署, 直到成功为止。
常见问题
Stack 回滚: 检查 CloudFormation 事件日志, 修复问题后重新部署。 ARM架构兼容问题导致部署失败: 请实现多架构兼容部署,需要在x86,ARM 机器上都能正确部署。
BedrockAgentCore ARM64 限制
改用 docker buildx 构建 ARM64 镜像。
完成标志
- [ ] 已执行 `cdk deploy` 命令(不能仅检查stack之前的状态)
- [ ] 部署需要全部成功,不允许部分成功就跳过
- [ ] Stack 状态为 CREATE_COMPLETE 或 UPDATE_COMPLETE
- [ ] 生成 deploy-report.md
记录用时:
echo "STEP_07_END=$(date +%s)" >> security-scan-results/.current-scan下一步
检查完成标志,所有内容检查通过后,再继续执行 08-generate-summary.md
License 许可证检查
目标
扫描项目中所有语言的第三方依赖库许可证,生成合规性报告,识别潜在风险许可证。
预批准许可证列表 (Amazon Open Source Policy)
✅ 低风险 (可自由使用)
| 许可证 | 变体 |
|---|---|
| Apache-2.0 | ImageMagick |
| BSD | Apache-1.1, BSD-3-Clause, BSD-2-Clause, BSD-2-Clause-FreeBSD, BSD-3-Clause-Attribution, BSD-1-Clause, BSD-Source-Code, 0BSD, EDL, OLDAP-2.8 |
| MIT | MIT-0, ISC, X11, BouncyCastle, Boost, PostgreSQL, PIL, curl, NTP, HPND, HPND-sell-variant, metamail |
| Zlib | zlib-acknowledgement, libpng, bzip2, Spencer-94 |
| Python-2.0 / PSF-2.0 | Python-2.1.1 |
| 其他 | OpenSSL, JSON, libjpeg, WTFPL, Unicode-, CC0-1.0, Unlicense, CC-BY-, Ruby, BlueOak-1.0.0, SIL-OFL-1.1, HDF5 |
⚠️ 中风险 (未修改包可批准)
| 许可证 | 说明 |
|---|---|
| MPL-2.0 | Mozilla Public License - 修改的文件需开源 |
| CDDL-1.0/1.1 | Common Development and Distribution License |
| EPL-2.0 | Eclipse Public License |
❌ 高风险 (需法务审查)
| 许可证 | 说明 |
|---|---|
| GPL-, AGPL-, LGPL-* | Copyleft 许可证 |
| CC-BY-NC-* | 非商业限制 |
| UNKNOWN / UNLICENSED | 未知许可证 |
执行步骤
Step 1: 获取扫描目录
source security-scan-results/.current-scan
mkdir -p "${SCAN_DIR}/04-license"Step 2: 扫描 Node.js 依赖
重要: 必须扫描 nodejs-dirs.txt 中列出的所有目录,确保每个包含 node_modules 的目录都被单独扫描并生成对应的 CSV 文件。
find "$PROJECT_ROOT" -name "package.json" -not -path "*/node_modules/*" -not -path "*/cdk.out/*" > "${SCAN_DIR}/04-license/nodejs-dirs.txt"
cat "${SCAN_DIR}/04-license/nodejs-dirs.txt" | while read pkg; do
dir=$(dirname "$pkg")
name=$(echo "$dir" | tr '/' '-' | sed 's/^.-//')
[ "$dir" = "." ] && name="root"
if [ -d "${dir}/node_modules" ]; then
echo "Scanning ${dir}..."
npx license-checker --start "$dir" --csv > "${SCAN_DIR}/04-license/nodejs-${name}.csv" 2>/dev/null
fi
done验证要求:
- 检查
nodejs-dirs.txt中的每个目录 - 对于有
node_modules的目录,确认生成了对应的nodejs-*.csv文件 - 记录哪些目录被扫描、哪些目录因无
node_modules而跳过
Step 3: 扫描 Python 依赖
重要: 必须扫描 python-dirs.txt 中列出的所有 requirements.txt 文件,确保所有 Python 依赖都被安装并扫描。
find "$PROJECT_ROOT" -name "requirements.txt" -not -path "*/node_modules/*" -not -path "*/cdk.out/*" > "${SCAN_DIR}/04-license/python-dirs.txt"
python3 -m venv /tmp/license-scan-venv
source /tmp/license-scan-venv/bin/activate
pip install pip-licenses -q
cat "${SCAN_DIR}/04-license/python-dirs.txt" | while read req; do
echo "Installing dependencies from: $req"
pip install -r "$req" -q 2>/dev/null || true
done
pip-licenses --format=csv --with-urls > "${SCAN_DIR}/04-license/python.csv"
deactivate
rm -rf /tmp/license-scan-venv验证要求:
- 检查
python-dirs.txt中的每个requirements.txt文件 - 确认所有文件中的依赖都被安装到虚拟环境
- 确认
python.csv包含所有 Python 依赖的许可证信息
Step 3.1: 扫描 C# 依赖 (可选)
如果项目包含 .csproj 文件,执行 C# 依赖扫描:
# 检查是否有 C# 项目
CSPROJ_COUNT=$(find "$PROJECT_ROOT" -name "*.csproj" -not -path "*/node_modules/*" -not -path "*/cdk.out/*" 2>/dev/null | wc -l)
if [ "$CSPROJ_COUNT" -gt 0 ]; then
echo "Found $CSPROJ_COUNT C# projects, scanning..."
# 安装工具 (如未安装)
dotnet tool install --global nuget-license 2>/dev/null || true
# 扫描并生成 CSV
nuget-license --input . --output-type csv > "${SCAN_DIR}/04-license/csharp.csv" 2>/dev/null || true
echo "C# license scan completed"
else
echo "No C# projects found, skipping..."
fiStep 4: 使用脚本生成报告
重要: 使用独立的 Python 脚本自动生成报告。脚本位于 skill 目录的 scripts/ 子目录中。
# 获取 skill 目录路径(假设 skill 安装在 .claude/skills/pcsr-scan/ 下)
SKILL_DIR="${HOME}/.claude/skills/pcsr-scan"
# 如果是项目级 skill,使用相对路径
if [ -f ".claude/skills/pcsr-scan/scripts/generate-license-report.py" ]; then
SKILL_DIR=".claude/skills/pcsr-scan"
fi
python3 "${SKILL_DIR}/scripts/generate-license-report.py" "${SCAN_DIR}"脚本会自动: 1. 解析所有 CSV 文件 (Node.js, Python, C#) 2. 分类许可证风险等级 3. 生成完整的 markdown 报告
排除项
项目自身的 package 没有说明 license 的情况不计入第三方依赖统计。
常见问题
| 问题 | 处理 |
|---|---|
| 双许可证 (如 MIT OR GPL) | 选择更宽松的许可证 |
| UNKNOWN 许可证 | 检查包的 GitHub/npm/PyPI 页面确认 |
| MPL-2.0 包 | 未修改可使用,记录在报告中 |
| 项目自身包显示 UNLICENSED | 非第三方依赖,不计入风险统计 |
Step 5: AI 审查并优化报告
脚本生成报告后,需要 AI 审查"需要关注的依赖"部分,提供更详细的分析和处理建议:
1. 读取生成的 ${SCAN_DIR}/04-license/license.md 2. 对于每个中/高风险依赖,分析:
- 该包的实际用途和必要性
- 是否为双许可证(如 MIT OR GPL),选择更宽松的许可证
- 是否未修改使用(MPL-2.0 未修改可接受)
- 是否需要法务审查
3. 更新"需要关注的依赖"表格,添加详细说明列
示例格式:
### 中风险许可证 (MPL-2.0)
| 包名 | 版本 | 许可证 | 说明 |
|------|------|--------|------|
| certifi | 2026.1.4 | MPL-2.0 | Python SSL 证书包,未修改使用,✅ 可接受 |
| tqdm | 4.66.5 | MIT + MPL-2.0 | 双许可证,选择 MIT,✅ 可接受 |4. 更新结论部分,确认所有中风险依赖已评估通过 5. 保存更新后的报告到 ${SCAN_DIR}/04-license/license.md 和 ${SCAN_DIR}/result/license.md
完成标志
- [ ]
${SCAN_DIR}/04-license/license.md存在 - [ ]
${SCAN_DIR}/result/license.md存在 - [ ] 无高风险许可证 (或已记录豁免)
- [ ] 中风险依赖已 AI 审查并添加处理建议
- [ ] Node.js 目录验证:
nodejs-dirs.txt中所有目录已逐个检查,有node_modules的目录均已生成对应 CSV 文件 - [ ] Python 目录验证:
python-dirs.txt中所有requirements.txt文件已逐个安装依赖 - [ ] 报告完整性验证:
license.md中的"完整依赖列表"包含通过 Python 脚本生成的全部所有依赖(Node.js 和 Python)
记录用时:
echo "STEP_04_END=$(date +%s)" >> security-scan-results/.current-scan下一步
完成后继续执行 05-trivy-scan.md
生成扫描汇总报告
目标
汇总所有扫描结果,生成最终报告供提交。
执行步骤
Step 1: 获取扫描目录和时间信息
source security-scan-results/.current-scan
echo "扫描目录: ${SCAN_DIR}"Step 2: 计算各步骤用时
读取 .current-scan 中的时间戳,计算每步用时:
# 格式化时间函数(秒数转为 Xm Ys 格式)
format_duration() {
local seconds=$1
if [ $seconds -ge 60 ]; then
local mins=$((seconds / 60))
local secs=$((seconds % 60))
echo "${mins}m ${secs}s"
else
echo "${seconds}s"
fi
}
# 计算各步骤用时
STEP_01_DURATION=$((STEP_01_END - SCAN_START_TIME))
STEP_02_DURATION=$((STEP_02_END - STEP_01_END))
STEP_03_DURATION=$((STEP_03_END - STEP_02_END))
STEP_04_DURATION=$((STEP_04_END - STEP_03_END))
STEP_05_DURATION=$((STEP_05_END - STEP_04_END))
STEP_06_DURATION=$((STEP_06_END - STEP_05_END))
STEP_07_DURATION=$((STEP_07_END - STEP_06_END))
# 总用时
TOTAL_DURATION=$((STEP_07_END - SCAN_START_TIME))Step 3: 确认所有扫描文件存在
ls -la "${SCAN_DIR}/"应包含以下目录:
01-architecture/02-cdk-synth/03-viperlight/04-license/05-trivy/06-ash/07-deploy/
Step 4: 生成汇总报告
创建 ${SCAN_DIR}/SUMMARY.md,包含用时统计:
# Security Scan Summary Report
## 扫描信息
| 项目 | 值 |
|------|-----|
| 扫描时间 | [扫描开始时间] |
| 项目名称 | [项目名称] |
| 分支 | [git branch] |
| Commit | [git commit hash] |
| 扫描目录 | ${SCAN_DIR} |
| **总用时** | **[总用时,如 1h 23m 45s]** |
## 各步骤用时
| 步骤 | 扫描类型 | 用时 | 状态 |
|------|---------|------|------|
| 01 | Architecture | [用时] | ✅/❌ |
| 02 | CDK Synth | [用时] | ✅/❌ |
| 03 | Viperlight | [用时] | ✅/❌ |
| 04 | License | [用时] | ✅/❌ |
| 05 | Trivy | [用时] | ✅/❌ |
| 06 | ASH | [用时] | ✅/❌ |
| 07 | Deploy | [用时] | ✅/❌ |
## 扫描结果概览
| 扫描类型 | 状态 | 修复数 | 跳过数 | 说明 |
|---------|------|-------|-------|------|
| CDK Synth | ✅/❌ | 0 | 0 | [说明] |
| Viperlight | ✅/❌ | 0 | 0 | [说明] |
| ASH | ✅/❌ | 0 | 0 | [说明] |
| Trivy | ✅/❌ | 0 | 0 | [说明] |
| Architecture | ✅/❌ | - | - | [说明] |
| License | ✅/❌ | - | - | [说明] |
| Deploy | ✅/❌ | 0 | 0 | [说明] |
## 修复记录汇总
...用时格式说明:
- 小于 60 秒:显示为
45s - 60 秒以上:显示为
1m 23s - 60 分钟以上:显示为
1h 23m 45s
Step 5: 更新汇总报告
根据实际扫描结果,编辑 ${SCAN_DIR}/SUMMARY.md: 1. 填写每个扫描的实际状态 2. 统计各步骤的修复数量 3. 记录豁免的问题及原因 4. 确认文件清单完整
完成标志
- [ ]
SUMMARY.md已创建并填写完整 - [ ] 各步骤用时已计算并填充(从
.current-scan读取STEP_0X_END计算,非占位符) - [ ] 所有扫描文件已确认存在
- [ ] 报告已准备好提交
---
生成 HTML 汇总报告
目标
将所有扫描结果整合到一个单独的 HTML 文件中,使用现代化的 Tab 导航界面,方便查看和分享。
执行步骤
Step 1: 获取扫描目录
source security-scan-results/.current-scan
echo "扫描目录: ${SCAN_DIR}"Step 2: 确认所有结果文件存在
ls -la "${SCAN_DIR}/result/"
ls -la "${SCAN_DIR}/result/ash/"应包含以下文件:
SUMMARY.md- 汇总报告result/architecture.png- 架构图result/cdk-synth.txt- CDK 模板result/viperlight.txt- Viperlight 扫描结果result/license.md- 许可证报告result/trivy.md- Trivy 扫描摘要result/ash/ash.md- ASH 报告 (Markdown)result/ash/ash.html- ASH 报告 (HTML)result/ash/ash.yaml- ASH 配置07-deploy/output.txt- 部署日志
Step 3: 使用脚本生成 HTML 报告
重要: 使用独立的 Python 脚本自动生成 HTML 报告。脚本位于 skill 目录的 scripts/ 子目录中。
# 获取 skill 目录路径(假设 skill 安装在 .claude/skills/security-scan/ 下)
SKILL_DIR="${HOME}/.claude/skills/security-scan"
# 如果是项目级 skill,使用相对路径
if [ -f ".claude/skills/security-scan/scripts/generate-html-report.py" ]; then
SKILL_DIR=".claude/skills/security-scan"
fi
python3 "${SKILL_DIR}/scripts/generate-html-report.py" "${SCAN_DIR}"脚本会自动: 1. 读取所有扫描结果文件 2. 将架构图转换为 base64 嵌入 3. 生成带有 Tab 导航的现代化 HTML 报告 4. 输出到 ${SCAN_DIR}/report.html
Step 4: 验证生成的报告
ls -la "${SCAN_DIR}/report.html"Step 5: 记录用时
echo "STEP_09_END=$(date +%s)" >> security-scan-results/.current-scan完成标志
- [ ]
${SCAN_DIR}/report.html已生成
下一步
无 - 这是最后一步
扫描流程已全部完成,report.html 是最终的汇总报告,可以直接打开查看所有扫描结果。
---
扫描完成
恭喜!安全扫描流程已全部完成。
最终报告文件:${SCAN_DIR}/report.html
可以直接在浏览器中打开查看所有扫描结果。
Trivy 容器镜像扫描
目标
使用 Trivy 扫描 Docker 镜像中的安全漏洞,包括基础镜像的系统包和应用依赖。
前置条件
- 已安装 Docker
- 已安装 Trivy CLI(安装: https://github.com/aquasecurity/trivy)
- 已登录 ECR Public(避免 403 错误)
aws ecr-public get-login-password --region us-east-1 | docker login --username AWS --password-stdin public.ecr.aws重要规则
1. 必须使用镜像扫描:所有 Dockerfile 都必须构建镜像后使用 trivy image 扫描,禁止使用 trivy fs 文件系统扫描(不完整) 2. 顺序构建:按顺序构建镜像,利用 Docker 层缓存(多个镜像共享基础镜像时更高效) 3. 修复循环:发现漏洞后必须修复并重新构建、重新扫描,直到无 CRITICAL/HIGH 漏洞 4. output.txt 规则:仅在发现漏洞并修复后才生成 output.txt,无漏洞时只保留 output_origin.txt 5. 自动修复:发现漏洞后必须立即自动修复,无需询问用户确认。只有在漏洞无法修复(如上游无修复版本)时才记录豁免说明 6. 最小改动原则(同 00-full-scan.md 规则 5):只修复安全问题,不改变业务逻辑
- ✅ 直接修复:改动范围小(≤3 个文件,≤100 行代码)
- ⏭️ 跳过并记录:需要修改业务实现逻辑或改动范围大
7. 构建失败重试:镜像构建失败时最多重试 1 次,仍失败则跳过并记录
扫描方式说明
| 命令 | 扫描对象 | 内容 | 使用场景 |
|---|---|---|---|
trivy image | Docker 镜像 | 基础镜像系统包 + 应用依赖 | 必须使用 |
trivy fs | 本地文件系统 | 仅应用依赖文件 | ❌ 禁止使用 |
本流程强制使用 `trivy image` 扫描构建后的镜像,确保扫描完整性。
输出目录结构
05-trivy/
├── {image-name-1}/
│ ├── output_origin.txt # 首次扫描原始输出(始终保留)
│ └── output.txt # 修复后的扫描输出(仅当有漏洞需修复时创建)
├── {image-name-2}/
│ └── ...
└── fix-report.md # 修复报告(如有修复)执行步骤
Step 1: 登录 ECR Public
aws ecr-public get-login-password --region us-east-1 | docker login --username AWS --password-stdin public.ecr.awsStep 2: 查找所有 Dockerfile
find "$PROJECT_ROOT" -name "Dockerfile*" -not -path "*/node_modules/*" -not -path "*/cdk.out/*" -not -path "*/.git/*"Step 3: 顺序构建并扫描所有镜像
按顺序构建镜像(利用 Docker 层缓存,多个镜像共享基础镜像时更高效):
为每个 Dockerfile 执行:
# 创建输出目录
mkdir -p "${SCAN_DIR}/05-trivy/${image_name}"
# 构建镜像
docker build -t "trivy-scan-${image_name}:latest" -f "$dockerfile" "$dir"
# 扫描镜像(仅生成 output_origin.txt)
trivy image --severity HIGH,CRITICAL "trivy-scan-${image_name}:latest" > "${SCAN_DIR}/05-trivy/${image_name}/output_origin.txt" 2>&1为什么顺序构建更好:
- 多个镜像共享相同基础镜像(如
python:3.11-slim)时,Docker 会缓存共享层 - 第一个镜像拉取基础镜像后,后续镜像直接使用缓存
- 避免并行构建时的资源竞争和磁盘 I/O 瓶颈
Step 4: 分析扫描结果
检查每个镜像的 output_origin.txt:
- CRITICAL: 必须立即修复
- HIGH: 应该修复
Step 5: 修复-重建-重扫循环
发现漏洞后必须执行以下循环,直到无漏洞:
发现漏洞 → 修复代码/依赖 → 重新构建镜像 → 重新扫描 → 检查结果
↑ ↓
└──────────── 仍有漏洞 ←─────────────────────────────┘修复后重新扫描:
# 1. 修复 Dockerfile 或依赖文件
# 2. 重新构建镜像
docker build -t "trivy-scan-${image_name}:latest" -f "$dockerfile" "$dir"
# 3. 重新扫描,生成 output.txt
trivy image --severity HIGH,CRITICAL "trivy-scan-${image_name}:latest" > "${SCAN_DIR}/05-trivy/${image_name}/output.txt" 2>&1
# 4. 检查是否还有漏洞,如有则重复步骤 1-3常见修复方式:
| 问题类型 | 修复方案 |
|---|---|
| 基础镜像漏洞 | 更新 FROM 到最新版本或使用 Alpine |
| 系统包漏洞 | 添加 RUN apk upgrade 或 apt-get upgrade |
| Python 依赖漏洞 | 更新 requirements.txt 中的版本 |
| Node.js 依赖漏洞 | 更新 package.json 中的版本 |
Step 6: 清理镜像
扫描完成后删除所有临时镜像:
docker rmi "trivy-scan-portal:latest" "trivy-scan-tag-image:latest" ...
# 或批量删除
docker images | grep "trivy-scan-" | awk '{print $3}' | xargs docker rmiStep 7: 生成结果报告
生成 ${SCAN_DIR}/result/trivy.md,仅包含: 1. 扫描摘要表格(镜像名、CRITICAL/HIGH 数量、状态) 2. 豁免漏洞说明(如有豁免)
注意: 修复记录和下一步建议放在 fix-report.md 中,不要放在 result/trivy.md 中。
完成标志
- [ ] 所有 Dockerfile 都已构建镜像并扫描
- [ ] 每个镜像目录下都有
output_origin.txt - [ ] 无 CRITICAL 级别漏洞
- [ ] 无 HIGH 级别漏洞(或已记录豁免理由)
- [ ]
${SCAN_DIR}/result/trivy.md已生成 - [ ] 如有修复,
fix-report.md已创建,对应镜像有output.txt - [ ] 所有临时镜像已清理
记录用时:
echo "STEP_05_END=$(date +%s)" >> security-scan-results/.current-scan下一步
完成后继续执行 06-ash-scan.md
Viperlight 安全扫描
目标
使用 Viperlight 扫描代码中的敏感信息、密钥泄露、安全漏洞。
前置条件
- 已安装 Viperlight CLI(安装参考: https://w.amazon.com/bin/view/AWS/Teams/GlobalServicesSecurity/Tools/Viperlight/)
执行步骤
Step 1: 获取扫描目录并确认工具
source security-scan-results/.current-scan
viperlight --versionStep 2: 检查配置文件
确保项目根目录有以下配置文件:
`.viperlightrc` - 如不存在则创建:
{"failOn":"low","all":true}`.viperlightignore` - 常见忽略目录:
.github/
cdk.out/*
node_modules/
coverage/
.*/package-lock.json
public/*
.next/
dist/
build/
*.lockStep 3: 确定扫描目标目录
必须使用 `--target` 指定核心源代码目录,不要扫描整个项目。
首先确定项目的源代码目录(按优先级检查):
# 常见源代码目录,按优先级检查
SCAN_TARGETS=""
# 检查常见目录是否存在(使用 PROJECT_ROOT 绝对路径)
for dir in src lib lambda functions app packages; do
if [ -d "$PROJECT_ROOT/$dir" ]; then
SCAN_TARGETS="$SCAN_TARGETS $PROJECT_ROOT/$dir/"
fi
done
# 如果没有找到常见目录,扫描项目根目录
if [ -z "$SCAN_TARGETS" ]; then
SCAN_TARGETS="$PROJECT_ROOT"
fi
echo "扫描目标目录: $SCAN_TARGETS"示例: 对于典型 CDK 项目,扫描目标可能是 src/infrastructure/ src/portal/
Step 4: 执行扫描
首次执行时保存为 output_origin.txt,后续修复重试时覆盖 output.txt:
# 首次执行:保存原始输出
if [ ! -f "${SCAN_DIR}/03-viperlight/output_origin.txt" ]; then
viperlight scan --target $SCAN_TARGETS > "${SCAN_DIR}/03-viperlight/output_origin.txt" 2>&1
cp "${SCAN_DIR}/03-viperlight/output_origin.txt" "${SCAN_DIR}/03-viperlight/output.txt"
else
# 修复后重试:覆盖 output.txt
viperlight scan --target $SCAN_TARGETS > "${SCAN_DIR}/03-viperlight/output.txt" 2>&1
fiStep 5: 检查状态并修复
grep -E "Status:" "${SCAN_DIR}/03-viperlight/output.txt"Status: PASSED→ 扫描通过,跳到 Step 5Status: FAILED→ 必须修复所有问题直到 PASSED
⚠️ 修复原则:代码修复优先,只有在确认是误报或无法修复时**,才使用 # nosec 注释
常见问题修复方式:
| 问题类型 | 修复方式 |
|---|---|
| 硬编码密钥/密码 | 移除并使用环境变量或 Secrets Manager |
| AWS Access Key | 立即轮换,从代码移除,使用 IAM Role |
| 私钥文件 | 移出仓库,添加到 .gitignore |
try_except_pass (B110) | 改为 except Exception as e: print(f"Warning: {e}") |
subprocess (B603/B404) | 确认输入可信后添加 # nosec B603 注释 |
注意: # nosec 必须在同一行末尾,并附带理由说明。
修复后重新扫描直到 PASSED(会覆盖 output.txt,保留 output_origin.txt):
viperlight scan --target $SCAN_TARGETS > "${SCAN_DIR}/03-viperlight/output.txt" 2>&1Step 6: 生成结果文件
echo "$ viperlight scan --target $SCAN_TARGETS" > "${SCAN_DIR}/result/viperlight.txt"
echo '' >> "${SCAN_DIR}/result/viperlight.txt"
sed 's/\x1b\[[0-9;]*m//g' "${SCAN_DIR}/03-viperlight/output.txt" >> "${SCAN_DIR}/result/viperlight.txt"Step 7: 生成修复报告(如有修复)
如果进行了代码修复,创建 ${SCAN_DIR}/03-viperlight/fix-report.md,记录:
- 发现的问题(文件位置、问题描述)
- 修复方案(diff 格式)
- 验证结果
输出文件说明
| 文件 | 说明 |
|---|---|
output_origin.txt | 首次执行的原始输出(保留不变,记录原始问题) |
output.txt | 最新执行的输出(修复后会覆盖,记录最终状态) |
完成标志
- [ ]
${SCAN_DIR}/result/viperlight.txt存在且包含Status: PASSED - [ ] 如有修复,
fix-report.md已创建
记录用时:
echo "STEP_03_END=$(date +%s)" >> security-scan-results/.current-scan下一步
完成后继续执行 04-license-check.md
#!/usr/bin/env python3
"""
Generate HTML security scan report with modern tabbed interface.
Usage: python generate-html-report.py <scan_dir>
"""
import sys
import os
import base64
from pathlib import Path
def read_file(filepath):
"""Read file content, return empty string if not exists."""
try:
return Path(filepath).read_text(encoding='utf-8')
except Exception:
return ''
def read_binary(filepath):
"""Read binary file and return base64 encoded string."""
try:
with open(filepath, 'rb') as f:
return base64.b64encode(f.read()).decode('utf-8')
except Exception:
return ''
def escape_html(text):
"""Escape HTML special characters."""
return text.replace('&', '&').replace('<', '<').replace('>', '>')
def get_image_mime(filepath):
"""Get image MIME type from extension."""
ext = Path(filepath).suffix.lower()
return {'.png': 'image/png', '.jpg': 'image/jpeg', '.jpeg': 'image/jpeg', '.svg': 'image/svg+xml', '.gif': 'image/gif'}.get(ext, 'image/png')
def extract_project_name(summary_md):
"""Extract project name from SUMMARY.md content."""
import re
# 查找 | 项目名称 | xxx | 格式
match = re.search(r'\|\s*项目名称\s*\|\s*([^|]+)\s*\|', summary_md)
if match:
return match.group(1).strip()
return None
def generate_html(scan_dir):
"""Generate the complete HTML report."""
scan_path = Path(scan_dir)
scan_name = scan_path.name
# Read all content files
summary_md = read_file(scan_path / 'SUMMARY.md')
# Extract project name from SUMMARY.md
project_name = extract_project_name(summary_md) or scan_name
cdk_synth = read_file(scan_path / 'result' / 'cdk-synth.txt')
viperlight = read_file(scan_path / 'result' / 'viperlight.txt')
license_md = read_file(scan_path / 'result' / 'license.md')
trivy_md = read_file(scan_path / 'result' / 'trivy.md')
ash_md = read_file(scan_path / 'result' / 'ash' / 'ash.md')
ash_html = read_file(scan_path / 'result' / 'ash' / 'ash.html')
ash_yaml = read_file(scan_path / 'result' / 'ash' / 'ash.yaml')
deploy_log = read_file(scan_path / '07-deploy' / 'output.txt')
# Read architecture image
arch_img = ''
arch_mime = 'image/png'
for ext in ['.png', '.jpg', '.jpeg', '.svg']:
arch_path = scan_path / 'result' / f'architecture{ext}'
if arch_path.exists():
arch_img = read_binary(arch_path)
arch_mime = get_image_mime(arch_path)
break
# Generate HTML
html = f'''<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Security Scan Report - {scan_name}</title>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/github.min.css">
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/highlight.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/marked/12.0.0/marked.min.js"></script>
<style>
:root {{
--bg-primary: #ffffff;
--bg-secondary: #f6f8fa;
--bg-tertiary: #eef1f4;
--border-color: #d0d7de;
--text-primary: #1f2328;
--text-secondary: #656d76;
--accent-blue: #0969da;
--accent-green: #1a7f37;
--accent-yellow: #9a6700;
--accent-red: #cf222e;
--accent-purple: #8250df;
}}
* {{
margin: 0;
padding: 0;
box-sizing: border-box;
}}
body {{
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Noto Sans', Helvetica, Arial, sans-serif;
background: var(--bg-primary);
color: var(--text-primary);
line-height: 1.6;
min-height: 100vh;
}}
.header {{
background: linear-gradient(135deg, var(--bg-secondary) 0%, var(--bg-tertiary) 100%);
border-bottom: 1px solid var(--border-color);
padding: 24px 32px;
position: sticky;
top: 0;
z-index: 100;
backdrop-filter: blur(10px);
box-shadow: 0 1px 3px rgba(0,0,0,0.08);
}}
.header h1 {{
font-size: 24px;
font-weight: 600;
display: flex;
align-items: center;
gap: 12px;
}}
.header h1::before {{
content: '🛡️';
font-size: 28px;
}}
.header .scan-id {{
font-size: 14px;
color: var(--text-secondary);
margin-top: 4px;
font-family: 'SF Mono', Consolas, monospace;
}}
.tabs-container {{
display: flex;
background: var(--bg-secondary);
border-bottom: 1px solid var(--border-color);
padding: 0 16px;
overflow-x: auto;
scrollbar-width: thin;
}}
.tabs-container::-webkit-scrollbar {{
height: 6px;
}}
.tabs-container::-webkit-scrollbar-thumb {{
background: var(--border-color);
border-radius: 3px;
}}
.tab-btn {{
padding: 12px 20px;
background: transparent;
border: none;
color: var(--text-secondary);
cursor: pointer;
font-size: 14px;
font-weight: 500;
white-space: nowrap;
position: relative;
transition: all 0.2s ease;
}}
.tab-btn:hover {{
color: var(--text-primary);
background: var(--bg-tertiary);
}}
.tab-btn.active {{
color: var(--accent-blue);
}}
.tab-btn.active::after {{
content: '';
position: absolute;
bottom: 0;
left: 0;
right: 0;
height: 2px;
background: var(--accent-blue);
border-radius: 2px 2px 0 0;
}}
.tab-btn .icon {{
margin-right: 8px;
}}
.sub-tabs {{
display: flex;
background: var(--bg-tertiary);
border-bottom: 1px solid var(--border-color);
padding: 0 16px;
}}
.sub-tab-btn {{
padding: 8px 16px;
background: transparent;
border: none;
color: var(--text-secondary);
cursor: pointer;
font-size: 13px;
font-weight: 500;
transition: all 0.2s ease;
border-radius: 6px 6px 0 0;
}}
.sub-tab-btn:hover {{
color: var(--text-primary);
}}
.sub-tab-btn.active {{
color: var(--accent-purple);
background: var(--bg-secondary);
}}
.content {{
padding: 24px 32px;
max-width: 1400px;
margin: 0 auto;
}}
.tab-content {{
display: none;
animation: fadeIn 0.3s ease;
}}
.tab-content.active {{
display: block;
}}
@keyframes fadeIn {{
from {{ opacity: 0; transform: translateY(10px); }}
to {{ opacity: 1; transform: translateY(0); }}
}}
/* Markdown Styles */
.markdown-body {{
color: var(--text-primary);
}}
.markdown-body h1 {{
font-size: 28px;
font-weight: 600;
margin: 0 0 16px 0;
padding-bottom: 12px;
border-bottom: 1px solid var(--border-color);
}}
.markdown-body h2 {{
font-size: 22px;
font-weight: 600;
margin: 32px 0 16px 0;
padding-bottom: 8px;
border-bottom: 1px solid var(--border-color);
}}
.markdown-body h3 {{
font-size: 18px;
font-weight: 600;
margin: 24px 0 12px 0;
}}
.markdown-body p {{
margin: 12px 0;
}}
.markdown-body table {{
width: 100%;
border-collapse: collapse;
margin: 16px 0;
font-size: 14px;
}}
.markdown-body th {{
background: var(--bg-tertiary);
padding: 12px 16px;
text-align: left;
font-weight: 600;
border: 1px solid var(--border-color);
}}
.markdown-body td {{
padding: 10px 16px;
border: 1px solid var(--border-color);
}}
.markdown-body tr:hover td {{
background: var(--bg-secondary);
}}
.markdown-body code {{
background: var(--bg-tertiary);
padding: 2px 6px;
border-radius: 4px;
font-family: 'SF Mono', Consolas, monospace;
font-size: 13px;
}}
.markdown-body pre {{
background: var(--bg-tertiary);
padding: 16px;
border-radius: 8px;
overflow-x: auto;
margin: 16px 0;
}}
.markdown-body pre code {{
background: transparent;
padding: 0;
}}
.markdown-body ul, .markdown-body ol {{
margin: 12px 0;
padding-left: 24px;
}}
.markdown-body li {{
margin: 6px 0;
}}
.markdown-body hr {{
border: none;
border-top: 1px solid var(--border-color);
margin: 24px 0;
}}
.markdown-body blockquote {{
border-left: 4px solid var(--accent-blue);
padding-left: 16px;
margin: 16px 0;
color: var(--text-secondary);
}}
.markdown-body a {{
color: var(--accent-blue);
text-decoration: none;
}}
.markdown-body a:hover {{
text-decoration: underline;
}}
/* Code Block */
.code-block {{
background: var(--bg-tertiary);
border: 1px solid var(--border-color);
border-radius: 8px;
overflow: hidden;
}}
.code-header {{
background: var(--bg-secondary);
padding: 8px 16px;
border-bottom: 1px solid var(--border-color);
display: flex;
justify-content: space-between;
align-items: center;
}}
.code-header span {{
font-size: 13px;
color: var(--text-secondary);
font-family: 'SF Mono', Consolas, monospace;
}}
.code-content {{
padding: 16px;
overflow-x: auto;
max-height: 70vh;
overflow-y: auto;
}}
.code-content pre {{
margin: 0;
font-family: 'SF Mono', Consolas, monospace;
font-size: 13px;
line-height: 1.5;
white-space: pre;
}}
/* Image Container */
.image-container {{
text-align: center;
padding: 24px;
background: var(--bg-secondary);
border-radius: 8px;
border: 1px solid var(--border-color);
}}
.image-container img {{
max-width: 100%;
height: auto;
border-radius: 4px;
}}
.image-container .caption {{
margin-top: 12px;
color: var(--text-secondary);
font-size: 14px;
}}
/* ASH HTML iframe */
.ash-html-frame {{
width: 100%;
height: 80vh;
border: 1px solid var(--border-color);
border-radius: 8px;
background: #fff;
}}
/* Status badges */
.status-pass {{ color: var(--accent-green); }}
.status-fail {{ color: var(--accent-red); }}
.status-warn {{ color: var(--accent-yellow); }}
/* Footer */
.footer {{
text-align: center;
padding: 24px;
color: var(--text-secondary);
font-size: 13px;
border-top: 1px solid var(--border-color);
margin-top: 48px;
}}
/* Responsive */
@media (max-width: 768px) {{
.header {{ padding: 16px; }}
.content {{ padding: 16px; }}
.tab-btn {{ padding: 10px 14px; font-size: 13px; }}
}}
</style>
</head>
<body>
<div class="header">
<h1>Security Scan Report</h1>
<div class="scan-id">{project_name}</div>
</div>
<div class="tabs-container">
<button class="tab-btn active" data-tab="summary"><span class="icon">📋</span>Summary</button>
<button class="tab-btn" data-tab="architecture"><span class="icon">🏗️</span>Architecture</button>
<button class="tab-btn" data-tab="cdk-synth"><span class="icon">☁️</span>CDK Synth</button>
<button class="tab-btn" data-tab="viperlight"><span class="icon">🔍</span>Viperlight</button>
<button class="tab-btn" data-tab="license"><span class="icon">📜</span>License</button>
<button class="tab-btn" data-tab="trivy"><span class="icon">🐳</span>Trivy</button>
<button class="tab-btn" data-tab="ash"><span class="icon">🛡️</span>ASH</button>
<button class="tab-btn" data-tab="deploy"><span class="icon">🚀</span>Deploy</button>
</div>
<div id="ash-subtabs" class="sub-tabs" style="display: none;">
<button class="sub-tab-btn active" data-subtab="ash-md">ash.md</button>
<button class="sub-tab-btn" data-subtab="ash-html">ash.html</button>
<button class="sub-tab-btn" data-subtab="ash-yaml">ash.yaml</button>
</div>
<div class="content">
<!-- Summary Tab -->
<div id="tab-summary" class="tab-content active">
<div class="markdown-body" id="summary-content"></div>
</div>
<!-- Architecture Tab -->
<div id="tab-architecture" class="tab-content">
<div class="image-container">
{f'<img src="data:{arch_mime};base64,{arch_img}" alt="Architecture Diagram">' if arch_img else '<p>No architecture diagram found.</p>'}
<div class="caption">Project Architecture Diagram</div>
</div>
</div>
<!-- CDK Synth Tab -->
<div id="tab-cdk-synth" class="tab-content">
<div class="code-block">
<div class="code-header">
<span>cdk-synth.txt</span>
<span>CloudFormation Template</span>
</div>
<div class="code-content">
<pre><code class="language-yaml">{escape_html(cdk_synth) if cdk_synth else 'No CDK synth output found.'}</code></pre>
</div>
</div>
</div>
<!-- Viperlight Tab -->
<div id="tab-viperlight" class="tab-content">
<div class="code-block">
<div class="code-header">
<span>viperlight.txt</span>
<span>Security Scan Results</span>
</div>
<div class="code-content">
<pre><code>{escape_html(viperlight) if viperlight else 'No Viperlight output found.'}</code></pre>
</div>
</div>
</div>
<!-- License Tab -->
<div id="tab-license" class="tab-content">
<div class="markdown-body" id="license-content"></div>
</div>
<!-- Trivy Tab -->
<div id="tab-trivy" class="tab-content">
<div class="markdown-body" id="trivy-content"></div>
</div>
<!-- ASH Tab -->
<div id="tab-ash" class="tab-content">
<div id="subtab-ash-md" class="sub-tab-content active">
<div class="markdown-body" id="ash-md-content"></div>
</div>
<div id="subtab-ash-html" class="sub-tab-content" style="display: none;">
<iframe class="ash-html-frame" srcdoc="{escape_html(ash_html).replace(chr(34), '"')}" sandbox="allow-same-origin"></iframe>
</div>
<div id="subtab-ash-yaml" class="sub-tab-content" style="display: none;">
<div class="code-block">
<div class="code-header">
<span>ash.yaml</span>
<span>ASH Configuration</span>
</div>
<div class="code-content">
<pre><code class="language-yaml">{escape_html(ash_yaml) if ash_yaml else 'No ASH config found.'}</code></pre>
</div>
</div>
</div>
</div>
<!-- Deploy Tab -->
<div id="tab-deploy" class="tab-content">
<div class="code-block">
<div class="code-header">
<span>deploy-output.txt</span>
<span>Deployment Log</span>
</div>
<div class="code-content">
<pre><code>{escape_html(deploy_log) if deploy_log else 'No deployment log found.'}</code></pre>
</div>
</div>
</div>
</div>
<div class="footer">
Generated by Security Scan Power | {scan_name}
</div>
<script>
// Markdown content
const markdownContent = {{
summary: {repr(summary_md)},
license: {repr(license_md)},
trivy: {repr(trivy_md)},
ashMd: {repr(ash_md)}
}};
// Render markdown
function renderMarkdown(elementId, content) {{
const element = document.getElementById(elementId);
if (element && content) {{
element.innerHTML = marked.parse(content);
element.querySelectorAll('pre code').forEach((block) => {{
hljs.highlightElement(block);
}});
}}
}}
// Initialize markdown rendering
document.addEventListener('DOMContentLoaded', function() {{
marked.setOptions({{
breaks: true,
gfm: true
}});
renderMarkdown('summary-content', markdownContent.summary);
renderMarkdown('license-content', markdownContent.license);
renderMarkdown('trivy-content', markdownContent.trivy);
renderMarkdown('ash-md-content', markdownContent.ashMd);
// Highlight code blocks
document.querySelectorAll('pre code').forEach((block) => {{
hljs.highlightElement(block);
}});
}});
// Tab switching
document.querySelectorAll('.tab-btn').forEach(btn => {{
btn.addEventListener('click', function() {{
const tabId = this.dataset.tab;
// Update active tab button
document.querySelectorAll('.tab-btn').forEach(b => b.classList.remove('active'));
this.classList.add('active');
// Update active tab content
document.querySelectorAll('.tab-content').forEach(c => c.classList.remove('active'));
document.getElementById('tab-' + tabId).classList.add('active');
// Show/hide ASH subtabs
const ashSubtabs = document.getElementById('ash-subtabs');
ashSubtabs.style.display = tabId === 'ash' ? 'flex' : 'none';
}});
}});
// ASH sub-tab switching
document.querySelectorAll('.sub-tab-btn').forEach(btn => {{
btn.addEventListener('click', function() {{
const subtabId = this.dataset.subtab;
// Update active sub-tab button
document.querySelectorAll('.sub-tab-btn').forEach(b => b.classList.remove('active'));
this.classList.add('active');
// Update active sub-tab content
document.querySelectorAll('.sub-tab-content').forEach(c => c.style.display = 'none');
document.getElementById('subtab-' + subtabId).style.display = 'block';
}});
}});
</script>
</body>
</html>'''
return html
def main():
if len(sys.argv) < 2:
print('Usage: python generate-html-report.py <scan_dir>')
sys.exit(1)
scan_dir = sys.argv[1]
# Input validation: path length limit
if len(scan_dir) > 4096:
print('Error: Path too long (max 4096 characters)')
sys.exit(1)
if not Path(scan_dir).exists():
print(f'Error: Directory not found: {scan_dir}')
sys.exit(1)
html = generate_html(scan_dir)
output_path = Path(scan_dir) / 'report.html'
output_path.write_text(html, encoding='utf-8')
print(f'✅ HTML report generated: {output_path}')
if __name__ == '__main__':
main()
#!/usr/bin/env python3
"""
Generate license compliance report from CSV scan results.
Usage: python generate-license-report.py <scan_dir>
"""
import csv
import sys
import os
from pathlib import Path
# Pre-approved licenses (Amazon Open Source Policy)
LOW_RISK = {
'Apache-2.0', 'Apache Software License', 'Apache License 2.0',
'MIT', 'MIT License', 'MIT-0', 'MIT-CMU',
'BSD', 'BSD License', 'BSD-2-Clause', 'BSD-3-Clause', 'BSD-1-Clause', '0BSD',
'ISC', 'PSF-2.0', 'Python-2.0', 'Unlicense', 'CC0-1.0',
'Zlib', 'libpng', 'bzip2', 'OpenSSL', 'JSON', 'WTFPL',
'BlueOak-1.0.0', 'SIL-OFL-1.1', 'Ruby', 'curl', 'NTP', 'HPND',
}
MEDIUM_RISK = {'MPL-2.0', 'Mozilla Public License 2.0 (MPL 2.0)', 'CDDL-1.0', 'CDDL-1.1', 'EPL-2.0'}
HIGH_RISK_PATTERNS = ['GPL', 'AGPL', 'LGPL', 'CC-BY-NC', 'UNKNOWN']
def classify_license(license_str):
"""Classify license risk level."""
if not license_str or license_str == 'UNLICENSED':
return 'skip' # Project's own package
# Check for dual licenses - pick the more permissive one
if ' OR ' in license_str:
parts = license_str.replace('(', '').replace(')', '').split(' OR ')
for part in parts:
if any(l in part for l in LOW_RISK):
return 'low'
# Check high risk first
for pattern in HIGH_RISK_PATTERNS:
if pattern in license_str.upper() and 'MIT' not in license_str:
return 'high'
# Check medium risk
for m in MEDIUM_RISK:
if m in license_str:
return 'medium'
# Check low risk
for l in LOW_RISK:
if l in license_str:
return 'low'
# Dual licenses with semicolon
if ';' in license_str:
return 'low' # Usually permissive options
return 'low' # Default to low for unknown but not flagged
def parse_nodejs_csv(filepath):
"""Parse Node.js license-checker CSV output."""
deps = []
with open(filepath, 'r', encoding='utf-8') as f:
reader = csv.reader(f)
next(reader) # Skip header
for row in reader:
if len(row) >= 3:
deps.append({
'name': row[0],
'license': row[1],
'url': row[2] if row[2] else ''
})
return deps
def parse_python_csv(filepath):
"""Parse pip-licenses CSV output."""
deps = []
with open(filepath, 'r', encoding='utf-8') as f:
reader = csv.reader(f)
next(reader) # Skip header
for row in reader:
if len(row) >= 4:
deps.append({
'name': row[0],
'version': row[1],
'license': row[2],
'url': row[3] if row[3] else ''
})
return deps
def parse_csharp_csv(filepath):
"""Parse nuget-license CSV output."""
deps = []
with open(filepath, 'r', encoding='utf-8') as f:
reader = csv.reader(f)
next(reader) # Skip header
for row in reader:
if len(row) >= 3:
deps.append({
'name': row[0],
'version': row[1] if len(row) > 1 else '',
'license': row[2] if len(row) > 2 else '',
'url': row[3] if len(row) > 3 else ''
})
return deps
def generate_report(scan_dir):
"""Generate the full license report."""
license_dir = Path(scan_dir) / '04-license'
# Collect all dependencies
all_nodejs = []
all_python = []
all_csharp = []
# Parse Node.js CSVs
for csv_file in license_dir.glob('nodejs-*.csv'):
deps = parse_nodejs_csv(csv_file)
source = csv_file.stem.replace('nodejs-', '')
for d in deps:
d['source'] = source
all_nodejs.extend(deps)
# Parse Python CSV
python_csv = license_dir / 'python.csv'
if python_csv.exists():
all_python = parse_python_csv(python_csv)
# Parse C# CSV
csharp_csv = license_dir / 'csharp.csv'
if csharp_csv.exists():
all_csharp = parse_csharp_csv(csharp_csv)
# Read scanned directories
nodejs_dirs = []
python_dirs = []
nodejs_dirs_file = license_dir / 'nodejs-dirs.txt'
if nodejs_dirs_file.exists():
nodejs_dirs = [l.strip() for l in nodejs_dirs_file.read_text().splitlines() if l.strip()]
python_dirs_file = license_dir / 'python-dirs.txt'
if python_dirs_file.exists():
python_dirs = [l.strip() for l in python_dirs_file.read_text().splitlines() if l.strip()]
# Classify and count
low, medium, high, skipped = 0, 0, 0, 0
attention_needed = []
for d in all_nodejs + all_python + all_csharp:
risk = classify_license(d['license'])
if risk == 'low':
low += 1
elif risk == 'medium':
medium += 1
attention_needed.append(d)
elif risk == 'high':
high += 1
attention_needed.append(d)
else:
skipped += 1
total = low + medium + high
# Generate markdown
lines = []
lines.append('# License Compliance Report\n')
lines.append('## 扫描摘要\n')
lines.append('| 指标 | 数量 |')
lines.append('|------|------|')
lines.append(f'| 总依赖数 | {total} |')
lines.append(f'| Node.js | {len(all_nodejs) - sum(1 for d in all_nodejs if classify_license(d["license"]) == "skip")} |')
lines.append(f'| Python | {len(all_python)} |')
if all_csharp:
lines.append(f'| C# | {len(all_csharp)} |')
lines.append('')
# Scanned directories
lines.append('### 扫描目录\n')
lines.append('**Node.js:**')
for d in nodejs_dirs:
dir_path = os.path.dirname(d) if d != './package.json' else '.'
lines.append(f'- `{dir_path}`')
lines.append('')
lines.append('**Python:**')
for d in python_dirs:
dir_path = os.path.dirname(d)
lines.append(f'- `{dir_path}`')
if all_csharp:
lines.append('')
lines.append('**C#:** 项目根目录')
lines.append('')
# Risk assessment
status = '✅ 通过' if high == 0 else '❌ 有风险' if high > 0 else '⚠️ 需关注'
lines.append('## 风险评估\n')
lines.append(f'### 总体状态: {status}\n')
lines.append('| 风险等级 | 数量 | 占比 |')
lines.append('|---------|------|------|')
lines.append(f'| ✅ 低风险 | {low} | {low*100//total if total else 0}% |')
lines.append(f'| ⚠️ 中风险 | {medium} | {medium*100//total if total else 0}% |')
lines.append(f'| ❌ 高风险 | {high} | {high*100//total if total else 0}% |')
lines.append('')
# Attention needed
if attention_needed:
lines.append('## ⚠️ 需要关注的依赖\n')
lines.append('| 包名 | 许可证 | 风险等级 | 处理建议 |')
lines.append('|------|--------|---------|---------|')
for d in attention_needed:
risk = classify_license(d['license'])
level = '⚠️ 中' if risk == 'medium' else '❌ 高'
suggestion = '双许可证,选择 MIT' if ' OR ' in d['license'] else '未修改可使用' if risk == 'medium' else '需法务审查'
lines.append(f'| {d["name"]} | {d["license"]} | {level} | {suggestion} |')
lines.append('')
return lines, all_nodejs, all_python, all_csharp
def generate_dep_tables(all_nodejs, all_python, all_csharp):
"""Generate dependency tables."""
lines = []
lines.append('## 完整依赖列表\n')
# Group Node.js by source
by_source = {}
for d in all_nodejs:
src = d.get('source', 'unknown')
if src not in by_source:
by_source[src] = []
by_source[src].append(d)
for source, deps in sorted(by_source.items()):
valid_deps = [d for d in deps if classify_license(d['license']) != 'skip']
lines.append(f'### Node.js 依赖 ({source}) - {len(valid_deps)} 个\n')
lines.append('| 包名 | 许可证 | 仓库 |')
lines.append('|------|--------|------|')
for d in valid_deps:
lines.append(f'| {d["name"]} | {d["license"]} | {d["url"]} |')
lines.append('')
# Python
if all_python:
lines.append(f'### Python 依赖 - {len(all_python)} 个\n')
lines.append('| 包名 | 版本 | 许可证 | URL |')
lines.append('|------|------|--------|-----|')
for d in all_python:
lines.append(f'| {d["name"]} | {d["version"]} | {d["license"]} | {d["url"]} |')
lines.append('')
# C#
if all_csharp:
lines.append(f'### C# 依赖 - {len(all_csharp)} 个\n')
lines.append('| 包名 | 版本 | 许可证 | URL |')
lines.append('|------|------|--------|-----|')
for d in all_csharp:
lines.append(f'| {d["name"]} | {d.get("version", "")} | {d["license"]} | {d.get("url", "")} |')
lines.append('')
# Conclusion
lines.append('---\n')
lines.append('## 结论\n')
lines.append('✅ **许可证合规检查通过** (自动生成)')
return lines
def main():
if len(sys.argv) < 2:
print('Usage: python generate-license-report.py <scan_dir>')
sys.exit(1)
scan_dir = sys.argv[1]
# Input validation: path length limit
if len(scan_dir) > 4096:
print('Error: Path too long (max 4096 characters)')
sys.exit(1)
license_dir = Path(scan_dir) / '04-license'
# Generate report
header_lines, all_nodejs, all_python, all_csharp = generate_report(scan_dir)
table_lines = generate_dep_tables(all_nodejs, all_python, all_csharp)
# Write to file
output = '\n'.join(header_lines + table_lines)
(license_dir / 'license.md').write_text(output)
print(f'✅ Report generated: {license_dir}/license.md')
if __name__ == '__main__':
main()
Related skills
FAQ
Which scanners does this skill run?
Viperlight for code security, a dependency license check, Trivy for container vulnerabilities, and ASH for aggregated SAST, IaC, and secret analysis.
Can steps be skipped to save time?
No. All 9 steps are mandatory and must not be skipped regardless of time constraints.