feat: 一键部署沙箱 + Docker 镜像发布工作流
- docker-compose: 移除沙箱 profiles 配置,支持一键 docker compose up -d - pyproject.toml: 迁移 dev-dependencies 到 dependency-groups (PEP 735) - 新增 docker-publish.yml 工作流,支持手动发布镜像(不创建 tag) - 优化 orchestrator 和 verification agent 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
f71b8da7df
commit
cd79242b3e
|
|
@ -0,0 +1,129 @@
|
|||
name: Docker Publish
|
||||
|
||||
# 只构建并推送 Docker 镜像,不创建 Release 或 Tag
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
tag:
|
||||
description: '镜像标签 (例如: latest, dev, v3.0.0)'
|
||||
required: true
|
||||
default: 'latest'
|
||||
type: string
|
||||
build_frontend:
|
||||
description: '构建前端镜像'
|
||||
required: false
|
||||
type: boolean
|
||||
default: true
|
||||
build_backend:
|
||||
description: '构建后端镜像'
|
||||
required: false
|
||||
type: boolean
|
||||
default: true
|
||||
build_sandbox:
|
||||
description: '构建沙箱镜像'
|
||||
required: false
|
||||
type: boolean
|
||||
default: true
|
||||
|
||||
jobs:
|
||||
build-and-push:
|
||||
name: 构建并推送镜像
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
|
||||
steps:
|
||||
- name: 检出代码
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: 设置 Node.js
|
||||
if: ${{ github.event.inputs.build_frontend == 'true' }}
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '20'
|
||||
|
||||
- name: 安装 pnpm
|
||||
if: ${{ github.event.inputs.build_frontend == 'true' }}
|
||||
uses: pnpm/action-setup@v4
|
||||
with:
|
||||
version: 9
|
||||
|
||||
- name: 安装前端依赖
|
||||
if: ${{ github.event.inputs.build_frontend == 'true' }}
|
||||
working-directory: ./frontend
|
||||
run: pnpm install --frozen-lockfile
|
||||
|
||||
- name: 构建前端项目
|
||||
if: ${{ github.event.inputs.build_frontend == 'true' }}
|
||||
working-directory: ./frontend
|
||||
run: pnpm build
|
||||
env:
|
||||
VITE_USE_LOCAL_DB: 'true'
|
||||
|
||||
- name: 登录到 GitHub Container Registry
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: 设置 QEMU
|
||||
uses: docker/setup-qemu-action@v3
|
||||
|
||||
- name: 设置 Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
- name: 构建并推送前端 Docker 镜像
|
||||
if: ${{ github.event.inputs.build_frontend == 'true' }}
|
||||
uses: docker/build-push-action@v5
|
||||
with:
|
||||
context: ./frontend
|
||||
file: ./frontend/Dockerfile
|
||||
push: true
|
||||
platforms: linux/amd64,linux/arm64
|
||||
tags: |
|
||||
ghcr.io/${{ github.repository_owner }}/deepaudit-frontend:${{ github.event.inputs.tag }}
|
||||
cache-from: type=gha,scope=frontend
|
||||
cache-to: type=gha,mode=max,scope=frontend
|
||||
|
||||
- name: 构建并推送后端 Docker 镜像
|
||||
if: ${{ github.event.inputs.build_backend == 'true' }}
|
||||
uses: docker/build-push-action@v5
|
||||
with:
|
||||
context: ./backend
|
||||
file: ./backend/Dockerfile
|
||||
push: true
|
||||
platforms: linux/amd64,linux/arm64
|
||||
tags: |
|
||||
ghcr.io/${{ github.repository_owner }}/deepaudit-backend:${{ github.event.inputs.tag }}
|
||||
cache-from: type=gha,scope=backend
|
||||
cache-to: type=gha,mode=max,scope=backend
|
||||
|
||||
- name: 构建并推送沙箱 Docker 镜像
|
||||
if: ${{ github.event.inputs.build_sandbox == 'true' }}
|
||||
uses: docker/build-push-action@v5
|
||||
with:
|
||||
context: ./docker/sandbox
|
||||
file: ./docker/sandbox/Dockerfile
|
||||
push: true
|
||||
platforms: linux/amd64,linux/arm64
|
||||
tags: |
|
||||
ghcr.io/${{ github.repository_owner }}/deepaudit-sandbox:${{ github.event.inputs.tag }}
|
||||
cache-from: type=gha,scope=sandbox
|
||||
cache-to: type=gha,mode=max,scope=sandbox
|
||||
|
||||
- name: 输出镜像信息
|
||||
run: |
|
||||
echo "## 镜像已推送到 GHCR" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
if [ "${{ github.event.inputs.build_frontend }}" == "true" ]; then
|
||||
echo "- \`ghcr.io/${{ github.repository_owner }}/deepaudit-frontend:${{ github.event.inputs.tag }}\`" >> $GITHUB_STEP_SUMMARY
|
||||
fi
|
||||
if [ "${{ github.event.inputs.build_backend }}" == "true" ]; then
|
||||
echo "- \`ghcr.io/${{ github.repository_owner }}/deepaudit-backend:${{ github.event.inputs.tag }}\`" >> $GITHUB_STEP_SUMMARY
|
||||
fi
|
||||
if [ "${{ github.event.inputs.build_sandbox }}" == "true" ]; then
|
||||
echo "- \`ghcr.io/${{ github.repository_owner }}/deepaudit-sandbox:${{ github.event.inputs.tag }}\`" >> $GITHUB_STEP_SUMMARY
|
||||
fi
|
||||
|
|
@ -893,17 +893,32 @@ Action Input: {{"参数": "值"}}
|
|||
|
||||
if same_file and (same_line or similar_desc or same_type):
|
||||
# Update existing with new info (e.g. verification results)
|
||||
# Prefer verified data over unverified
|
||||
merged = {**existing_f, **normalized_new}
|
||||
# 🔥 FIX: Smart merge - don't overwrite good data with empty values
|
||||
merged = dict(existing_f) # Start with existing data
|
||||
for key, value in normalized_new.items():
|
||||
# Only overwrite if new value is meaningful
|
||||
if value is not None and value != "" and value != 0:
|
||||
merged[key] = value
|
||||
elif key not in merged or merged[key] is None:
|
||||
# Fill in missing fields even with empty values
|
||||
merged[key] = value
|
||||
|
||||
# Keep the better title
|
||||
if normalized_new.get("title") and len(normalized_new.get("title", "")) > len(existing_f.get("title", "")):
|
||||
merged["title"] = normalized_new["title"]
|
||||
# Keep verified status if either is verified
|
||||
if existing_f.get("is_verified") or normalized_new.get("is_verified"):
|
||||
merged["is_verified"] = True
|
||||
# 🔥 FIX: Preserve non-zero line numbers
|
||||
if existing_f.get("line_start") and not normalized_new.get("line_start"):
|
||||
merged["line_start"] = existing_f["line_start"]
|
||||
# 🔥 FIX: Preserve vulnerability_type
|
||||
if existing_f.get("vulnerability_type") and not normalized_new.get("vulnerability_type"):
|
||||
merged["vulnerability_type"] = existing_f["vulnerability_type"]
|
||||
|
||||
self._all_findings[i] = merged
|
||||
found = True
|
||||
logger.info(f"[Orchestrator] Merged finding: {new_file}:{new_line} ({new_type})")
|
||||
logger.info(f"[Orchestrator] Merged finding: {new_file}:{merged.get('line_start', 0)} ({merged.get('vulnerability_type', '')})")
|
||||
break
|
||||
|
||||
if not found:
|
||||
|
|
|
|||
|
|
@ -667,31 +667,50 @@ class VerificationAgent(BaseAgent):
|
|||
|
||||
# 处理最终结果
|
||||
verified_findings = []
|
||||
|
||||
|
||||
# 🔥 Robustness: If LLM returns empty findings but we had input, fallback to original
|
||||
llm_findings = []
|
||||
if final_result and "findings" in final_result:
|
||||
llm_findings = final_result["findings"]
|
||||
|
||||
|
||||
if not llm_findings and findings_to_verify:
|
||||
logger.warning(f"[{self.name}] LLM returned empty findings despite {len(findings_to_verify)} inputs. Falling back to originals.")
|
||||
# Fallback to logic below (else branch)
|
||||
final_result = None
|
||||
final_result = None
|
||||
|
||||
if final_result and "findings" in final_result:
|
||||
# 🔥 DEBUG: Log what LLM returned for verdict diagnosis
|
||||
verdicts_debug = [(f.get("file_path", "?"), f.get("verdict"), f.get("confidence")) for f in final_result["findings"]]
|
||||
logger.info(f"[{self.name}] LLM returned verdicts: {verdicts_debug}")
|
||||
|
||||
for f in final_result["findings"]:
|
||||
# 🔥 FIX: Normalize verdict - handle missing/empty verdict
|
||||
verdict = f.get("verdict")
|
||||
if not verdict or verdict not in ["confirmed", "likely", "uncertain", "false_positive"]:
|
||||
# Try to infer verdict from other fields
|
||||
if f.get("is_verified") is True:
|
||||
verdict = "confirmed"
|
||||
elif f.get("confidence", 0) >= 0.8:
|
||||
verdict = "likely"
|
||||
elif f.get("confidence", 0) <= 0.3:
|
||||
verdict = "false_positive"
|
||||
else:
|
||||
verdict = "uncertain"
|
||||
logger.warning(f"[{self.name}] Missing/invalid verdict for {f.get('file_path', '?')}, inferred as: {verdict}")
|
||||
|
||||
verified = {
|
||||
**f,
|
||||
"is_verified": f.get("verdict") == "confirmed" or (
|
||||
f.get("verdict") == "likely" and f.get("confidence", 0) >= 0.8
|
||||
"verdict": verdict, # 🔥 Ensure verdict is set
|
||||
"is_verified": verdict == "confirmed" or (
|
||||
verdict == "likely" and f.get("confidence", 0) >= 0.8
|
||||
),
|
||||
"verified_at": datetime.now(timezone.utc).isoformat() if f.get("verdict") in ["confirmed", "likely"] else None,
|
||||
"verified_at": datetime.now(timezone.utc).isoformat() if verdict in ["confirmed", "likely"] else None,
|
||||
}
|
||||
|
||||
|
||||
# 添加修复建议
|
||||
if not verified.get("recommendation"):
|
||||
verified["recommendation"] = self._get_recommendation(f.get("vulnerability_type", ""))
|
||||
|
||||
|
||||
verified_findings.append(verified)
|
||||
else:
|
||||
# 如果没有最终结果,使用原始发现
|
||||
|
|
|
|||
|
|
@ -202,10 +202,10 @@ exclude_lines = [
|
|||
"if TYPE_CHECKING:",
|
||||
]
|
||||
|
||||
# ============ UV Configuration ============
|
||||
# ============ Dependency Groups (PEP 735) ============
|
||||
|
||||
[tool.uv]
|
||||
dev-dependencies = [
|
||||
[dependency-groups]
|
||||
dev = [
|
||||
"pytest>=7.4.0",
|
||||
"pytest-asyncio>=0.21.0",
|
||||
"pytest-cov>=4.1.0",
|
||||
|
|
|
|||
|
|
@ -111,14 +111,13 @@ services:
|
|||
- deepaudit-network
|
||||
|
||||
# 沙箱镜像构建服务 (漏洞验证必须)
|
||||
# 注意: 此服务仅用于构建镜像,不会持续运行
|
||||
# 注意: 此服务仅用于构建镜像,构建完成后自动退出
|
||||
sandbox:
|
||||
build:
|
||||
context: ./docker/sandbox
|
||||
dockerfile: Dockerfile
|
||||
image: deepaudit/sandbox:latest
|
||||
profiles:
|
||||
- build-only
|
||||
restart: "no"
|
||||
command: echo "Sandbox image built successfully"
|
||||
|
||||
networks:
|
||||
|
|
|
|||
Loading…
Reference in New Issue