From cd79242b3ebc12b2af16390ee4cfde4f3b62dcb3 Mon Sep 17 00:00:00 2001 From: lintsinghua Date: Tue, 16 Dec 2025 20:00:00 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E4=B8=80=E9=94=AE=E9=83=A8=E7=BD=B2?= =?UTF-8?q?=E6=B2=99=E7=AE=B1=20+=20Docker=20=E9=95=9C=E5=83=8F=E5=8F=91?= =?UTF-8?q?=E5=B8=83=E5=B7=A5=E4=BD=9C=E6=B5=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 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 --- .github/workflows/docker-publish.yml | 129 ++++++++++++++++++ .../app/services/agent/agents/orchestrator.py | 21 ++- .../app/services/agent/agents/verification.py | 35 +++-- backend/pyproject.toml | 6 +- docker-compose.yml | 5 +- 5 files changed, 179 insertions(+), 17 deletions(-) create mode 100644 .github/workflows/docker-publish.yml diff --git a/.github/workflows/docker-publish.yml b/.github/workflows/docker-publish.yml new file mode 100644 index 0000000..2656f76 --- /dev/null +++ b/.github/workflows/docker-publish.yml @@ -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 diff --git a/backend/app/services/agent/agents/orchestrator.py b/backend/app/services/agent/agents/orchestrator.py index 6d39491..a67b117 100644 --- a/backend/app/services/agent/agents/orchestrator.py +++ b/backend/app/services/agent/agents/orchestrator.py @@ -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: diff --git a/backend/app/services/agent/agents/verification.py b/backend/app/services/agent/agents/verification.py index c9206e9..10d3597 100644 --- a/backend/app/services/agent/agents/verification.py +++ b/backend/app/services/agent/agents/verification.py @@ -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: # 如果没有最终结果,使用原始发现 diff --git a/backend/pyproject.toml b/backend/pyproject.toml index e30f72d..a900bb7 100644 --- a/backend/pyproject.toml +++ b/backend/pyproject.toml @@ -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", diff --git a/docker-compose.yml b/docker-compose.yml index 484ac3c..6078ac0 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -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: