Skip to content

Commit cd79242

Browse files
lintsinghuaclaude
andcommitted
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 <[email protected]>
1 parent f71b8da commit cd79242

File tree

5 files changed

+179
-17
lines changed

5 files changed

+179
-17
lines changed
Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
name: Docker Publish
2+
3+
# 只构建并推送 Docker 镜像,不创建 Release 或 Tag
4+
on:
5+
workflow_dispatch:
6+
inputs:
7+
tag:
8+
description: '镜像标签 (例如: latest, dev, v3.0.0)'
9+
required: true
10+
default: 'latest'
11+
type: string
12+
build_frontend:
13+
description: '构建前端镜像'
14+
required: false
15+
type: boolean
16+
default: true
17+
build_backend:
18+
description: '构建后端镜像'
19+
required: false
20+
type: boolean
21+
default: true
22+
build_sandbox:
23+
description: '构建沙箱镜像'
24+
required: false
25+
type: boolean
26+
default: true
27+
28+
jobs:
29+
build-and-push:
30+
name: 构建并推送镜像
31+
runs-on: ubuntu-latest
32+
33+
permissions:
34+
contents: read
35+
packages: write
36+
37+
steps:
38+
- name: 检出代码
39+
uses: actions/checkout@v4
40+
41+
- name: 设置 Node.js
42+
if: ${{ github.event.inputs.build_frontend == 'true' }}
43+
uses: actions/setup-node@v4
44+
with:
45+
node-version: '20'
46+
47+
- name: 安装 pnpm
48+
if: ${{ github.event.inputs.build_frontend == 'true' }}
49+
uses: pnpm/action-setup@v4
50+
with:
51+
version: 9
52+
53+
- name: 安装前端依赖
54+
if: ${{ github.event.inputs.build_frontend == 'true' }}
55+
working-directory: ./frontend
56+
run: pnpm install --frozen-lockfile
57+
58+
- name: 构建前端项目
59+
if: ${{ github.event.inputs.build_frontend == 'true' }}
60+
working-directory: ./frontend
61+
run: pnpm build
62+
env:
63+
VITE_USE_LOCAL_DB: 'true'
64+
65+
- name: 登录到 GitHub Container Registry
66+
uses: docker/login-action@v3
67+
with:
68+
registry: ghcr.io
69+
username: ${{ github.actor }}
70+
password: ${{ secrets.GITHUB_TOKEN }}
71+
72+
- name: 设置 QEMU
73+
uses: docker/setup-qemu-action@v3
74+
75+
- name: 设置 Docker Buildx
76+
uses: docker/setup-buildx-action@v3
77+
78+
- name: 构建并推送前端 Docker 镜像
79+
if: ${{ github.event.inputs.build_frontend == 'true' }}
80+
uses: docker/build-push-action@v5
81+
with:
82+
context: ./frontend
83+
file: ./frontend/Dockerfile
84+
push: true
85+
platforms: linux/amd64,linux/arm64
86+
tags: |
87+
ghcr.io/${{ github.repository_owner }}/deepaudit-frontend:${{ github.event.inputs.tag }}
88+
cache-from: type=gha,scope=frontend
89+
cache-to: type=gha,mode=max,scope=frontend
90+
91+
- name: 构建并推送后端 Docker 镜像
92+
if: ${{ github.event.inputs.build_backend == 'true' }}
93+
uses: docker/build-push-action@v5
94+
with:
95+
context: ./backend
96+
file: ./backend/Dockerfile
97+
push: true
98+
platforms: linux/amd64,linux/arm64
99+
tags: |
100+
ghcr.io/${{ github.repository_owner }}/deepaudit-backend:${{ github.event.inputs.tag }}
101+
cache-from: type=gha,scope=backend
102+
cache-to: type=gha,mode=max,scope=backend
103+
104+
- name: 构建并推送沙箱 Docker 镜像
105+
if: ${{ github.event.inputs.build_sandbox == 'true' }}
106+
uses: docker/build-push-action@v5
107+
with:
108+
context: ./docker/sandbox
109+
file: ./docker/sandbox/Dockerfile
110+
push: true
111+
platforms: linux/amd64,linux/arm64
112+
tags: |
113+
ghcr.io/${{ github.repository_owner }}/deepaudit-sandbox:${{ github.event.inputs.tag }}
114+
cache-from: type=gha,scope=sandbox
115+
cache-to: type=gha,mode=max,scope=sandbox
116+
117+
- name: 输出镜像信息
118+
run: |
119+
echo "## 镜像已推送到 GHCR" >> $GITHUB_STEP_SUMMARY
120+
echo "" >> $GITHUB_STEP_SUMMARY
121+
if [ "${{ github.event.inputs.build_frontend }}" == "true" ]; then
122+
echo "- \`ghcr.io/${{ github.repository_owner }}/deepaudit-frontend:${{ github.event.inputs.tag }}\`" >> $GITHUB_STEP_SUMMARY
123+
fi
124+
if [ "${{ github.event.inputs.build_backend }}" == "true" ]; then
125+
echo "- \`ghcr.io/${{ github.repository_owner }}/deepaudit-backend:${{ github.event.inputs.tag }}\`" >> $GITHUB_STEP_SUMMARY
126+
fi
127+
if [ "${{ github.event.inputs.build_sandbox }}" == "true" ]; then
128+
echo "- \`ghcr.io/${{ github.repository_owner }}/deepaudit-sandbox:${{ github.event.inputs.tag }}\`" >> $GITHUB_STEP_SUMMARY
129+
fi

backend/app/services/agent/agents/orchestrator.py

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -893,17 +893,32 @@ async def run_with_cancel_check():
893893

894894
if same_file and (same_line or similar_desc or same_type):
895895
# Update existing with new info (e.g. verification results)
896-
# Prefer verified data over unverified
897-
merged = {**existing_f, **normalized_new}
896+
# 🔥 FIX: Smart merge - don't overwrite good data with empty values
897+
merged = dict(existing_f) # Start with existing data
898+
for key, value in normalized_new.items():
899+
# Only overwrite if new value is meaningful
900+
if value is not None and value != "" and value != 0:
901+
merged[key] = value
902+
elif key not in merged or merged[key] is None:
903+
# Fill in missing fields even with empty values
904+
merged[key] = value
905+
898906
# Keep the better title
899907
if normalized_new.get("title") and len(normalized_new.get("title", "")) > len(existing_f.get("title", "")):
900908
merged["title"] = normalized_new["title"]
901909
# Keep verified status if either is verified
902910
if existing_f.get("is_verified") or normalized_new.get("is_verified"):
903911
merged["is_verified"] = True
912+
# 🔥 FIX: Preserve non-zero line numbers
913+
if existing_f.get("line_start") and not normalized_new.get("line_start"):
914+
merged["line_start"] = existing_f["line_start"]
915+
# 🔥 FIX: Preserve vulnerability_type
916+
if existing_f.get("vulnerability_type") and not normalized_new.get("vulnerability_type"):
917+
merged["vulnerability_type"] = existing_f["vulnerability_type"]
918+
904919
self._all_findings[i] = merged
905920
found = True
906-
logger.info(f"[Orchestrator] Merged finding: {new_file}:{new_line} ({new_type})")
921+
logger.info(f"[Orchestrator] Merged finding: {new_file}:{merged.get('line_start', 0)} ({merged.get('vulnerability_type', '')})")
907922
break
908923

909924
if not found:

backend/app/services/agent/agents/verification.py

Lines changed: 27 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -667,31 +667,50 @@ def has_valid_file_path(finding: Dict) -> bool:
667667

668668
# 处理最终结果
669669
verified_findings = []
670-
670+
671671
# 🔥 Robustness: If LLM returns empty findings but we had input, fallback to original
672672
llm_findings = []
673673
if final_result and "findings" in final_result:
674674
llm_findings = final_result["findings"]
675-
675+
676676
if not llm_findings and findings_to_verify:
677677
logger.warning(f"[{self.name}] LLM returned empty findings despite {len(findings_to_verify)} inputs. Falling back to originals.")
678678
# Fallback to logic below (else branch)
679-
final_result = None
679+
final_result = None
680680

681681
if final_result and "findings" in final_result:
682+
# 🔥 DEBUG: Log what LLM returned for verdict diagnosis
683+
verdicts_debug = [(f.get("file_path", "?"), f.get("verdict"), f.get("confidence")) for f in final_result["findings"]]
684+
logger.info(f"[{self.name}] LLM returned verdicts: {verdicts_debug}")
685+
682686
for f in final_result["findings"]:
687+
# 🔥 FIX: Normalize verdict - handle missing/empty verdict
688+
verdict = f.get("verdict")
689+
if not verdict or verdict not in ["confirmed", "likely", "uncertain", "false_positive"]:
690+
# Try to infer verdict from other fields
691+
if f.get("is_verified") is True:
692+
verdict = "confirmed"
693+
elif f.get("confidence", 0) >= 0.8:
694+
verdict = "likely"
695+
elif f.get("confidence", 0) <= 0.3:
696+
verdict = "false_positive"
697+
else:
698+
verdict = "uncertain"
699+
logger.warning(f"[{self.name}] Missing/invalid verdict for {f.get('file_path', '?')}, inferred as: {verdict}")
700+
683701
verified = {
684702
**f,
685-
"is_verified": f.get("verdict") == "confirmed" or (
686-
f.get("verdict") == "likely" and f.get("confidence", 0) >= 0.8
703+
"verdict": verdict, # 🔥 Ensure verdict is set
704+
"is_verified": verdict == "confirmed" or (
705+
verdict == "likely" and f.get("confidence", 0) >= 0.8
687706
),
688-
"verified_at": datetime.now(timezone.utc).isoformat() if f.get("verdict") in ["confirmed", "likely"] else None,
707+
"verified_at": datetime.now(timezone.utc).isoformat() if verdict in ["confirmed", "likely"] else None,
689708
}
690-
709+
691710
# 添加修复建议
692711
if not verified.get("recommendation"):
693712
verified["recommendation"] = self._get_recommendation(f.get("vulnerability_type", ""))
694-
713+
695714
verified_findings.append(verified)
696715
else:
697716
# 如果没有最终结果,使用原始发现

backend/pyproject.toml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -202,10 +202,10 @@ exclude_lines = [
202202
"if TYPE_CHECKING:",
203203
]
204204

205-
# ============ UV Configuration ============
205+
# ============ Dependency Groups (PEP 735) ============
206206

207-
[tool.uv]
208-
dev-dependencies = [
207+
[dependency-groups]
208+
dev = [
209209
"pytest>=7.4.0",
210210
"pytest-asyncio>=0.21.0",
211211
"pytest-cov>=4.1.0",

docker-compose.yml

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -111,14 +111,13 @@ services:
111111
- deepaudit-network
112112

113113
# 沙箱镜像构建服务 (漏洞验证必须)
114-
# 注意: 此服务仅用于构建镜像,不会持续运行
114+
# 注意: 此服务仅用于构建镜像,构建完成后自动退出
115115
sandbox:
116116
build:
117117
context: ./docker/sandbox
118118
dockerfile: Dockerfile
119119
image: deepaudit/sandbox:latest
120-
profiles:
121-
- build-only
120+
restart: "no"
122121
command: echo "Sandbox image built successfully"
123122

124123
networks:

0 commit comments

Comments
 (0)