Skip to content

fix(desktop): macOS system proxy support and missing native dependency #1142

fix(desktop): macOS system proxy support and missing native dependency

fix(desktop): macOS system proxy support and missing native dependency #1142

Workflow file for this run

# Defense-in-Depth CI Pipeline
#
# 摒弃传统针对 DX 的"宽容度",用硬性约束封锁 AI 的乱写空间。
# 通过持续演进的架构约束实现深度防御:
# - 控制爆炸半径:通过权限和行为约束限定 AI 的操作范围
# - 反熵增机制:设立质量门槛与技术债检查,将 AI 的解空间限制在安全边界内
#
# Hard Gates(任一失败直接阻断):
# - ts_test_pass: TypeScript 测试必须 100% 通过
# - rust_test_pass: Rust 测试必须 100% 通过
# - api_contract_parity: API 契约必须一致
# - lint_pass: Lint 必须 0 错误
# - no_critical_vulnerabilities: 无严重安全漏洞
name: 'Defense'
on:
pull_request:
paths-ignore:
- '*.md'
- 'docs/**'
- '.github/ISSUE_TEMPLATE/**'
push:
branches: [main]
paths-ignore:
- '*.md'
- 'docs/**'
schedule:
# 每周一 UTC 00:00 运行 security 维度
- cron: '0 0 * * 1'
workflow_dispatch:
permissions:
contents: read
security-events: write
env:
CARGO_TERM_COLOR: always
NODE_ENV: test
jobs:
# ══════════════════════════════════════════════════════════════
# COMMIT SAFETY VALIDATION: Block test credentials & mass deletions
# ══════════════════════════════════════════════════════════════
validate-commit-safety:
name: 'Gate: Commit Safety'
runs-on: ubuntu-latest
if: github.event_name == 'pull_request' || github.event_name == 'push'
steps:
- uses: actions/checkout@v6
with:
fetch-depth: 0 # Full history needed to scan commits
- name: Check for mass file deletions
run: |
# Determine range to check
if [ "${{ github.event_name }}" = "pull_request" ]; then
BASE_SHA="${{ github.event.pull_request.base.sha }}"
HEAD_SHA="${{ github.event.pull_request.head.sha }}"
RANGE="$BASE_SHA..$HEAD_SHA"
echo "📋 Checking PR commits: $RANGE"
else
# For push events, check the pushed commits
RANGE="${{ github.event.before }}..${{ github.event.after }}"
echo "📋 Checking pushed commits: $RANGE"
fi
echo ""
echo "🔍 Scanning for mass file deletions..."
HAS_MASS_DELETE=0
for commit in $(git rev-list $RANGE 2>/dev/null); do
DELETED=$(git show --diff-filter=D --name-only --format="" $commit 2>/dev/null | wc -l | tr -d ' ')
if [ "$DELETED" -ge 200 ]; then
HAS_MASS_DELETE=1
SHORT_HASH=$(echo $commit | cut -c1-8)
SUBJECT=$(git log -1 --format="%s" $commit)
echo ""
echo "❌ MASS DELETION DETECTED"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo " Commit: $SHORT_HASH"
echo " Subject: $SUBJECT"
echo " Deleted files: $DELETED (threshold: 200)"
echo ""
# Show first 20 deleted files as sample
echo " Sample of deleted files:"
git show --diff-filter=D --name-only --format="" $commit | head -20 | sed 's/^/ - /'
if [ "$DELETED" -gt 20 ]; then
echo " ... and $((DELETED - 20)) more files"
fi
echo ""
echo "::error::Commit $SHORT_HASH deletes $DELETED files (threshold: 200)"
fi
done
if [ "$HAS_MASS_DELETE" = "1" ]; then
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo ""
echo "This protection prevents accidental mass deletions."
echo ""
echo "If this deletion is intentional:"
echo " 1. Add clear justification in commit message"
echo " 2. Split into smaller logical commits (recommended)"
echo " 3. Document impact and reason for mass deletion"
echo ""
exit 1
fi
echo "✅ No mass deletions detected"
- name: Validate commit authors
run: |
# Determine range to check
if [ "${{ github.event_name }}" = "pull_request" ]; then
BASE_SHA="${{ github.event.pull_request.base.sha }}"
HEAD_SHA="${{ github.event.pull_request.head.sha }}"
RANGE="$BASE_SHA..$HEAD_SHA"
echo "📋 Checking PR commits: $RANGE"
else
# For push events, check the pushed commits
RANGE="${{ github.event.before }}..${{ github.event.after }}"
echo "📋 Checking pushed commits: $RANGE"
fi
# Scan for test credentials
echo ""
echo "🔍 Scanning for test credentials..."
SUSPICIOUS=$(git log "$RANGE" --format="%H|%ae|%an" 2>/dev/null | \
grep -iE "(test@example\.com|routa test|placeholder@|noreply@test)" || true)
if [ -n "$SUSPICIOUS" ]; then
echo ""
echo "❌ TEST CREDENTIALS DETECTED IN COMMITS"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo ""
echo "$SUSPICIOUS" | while IFS='|' read hash email name; do
SHORT_HASH=$(echo $hash | cut -c1-8)
echo " Commit: $SHORT_HASH"
echo " Author: $name <$email>"
git log -1 --format=" Subject: %s" $hash
echo ""
done
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo ""
echo "::error::Commits with test credentials detected"
echo "::error::These commits must be amended with real author information"
echo ""
echo "To fix:"
echo " 1. Use interactive rebase: git rebase -i origin/main"
echo " 2. Mark commits for 'edit'"
echo " 3. Amend each commit: git commit --amend --author=\"Your Name <your@email.com>\" --no-edit"
echo " 4. Continue: git rebase --continue"
echo ""
exit 1
fi
echo "✅ All commits have valid author metadata"
- name: Validate email formats
run: |
# Check that all emails are valid format
INVALID=$(git log "$RANGE" --format="%ae" 2>/dev/null | \
grep -vE "^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$" || true)
if [ -n "$INVALID" ]; then
echo "⚠️ Warning: Invalid email formats detected:"
echo "$INVALID"
# Don't fail, just warn
fi
# ══════════════════════════════════════════════════════════════
# FITNESS DIMENSIONS: 按 docs/fitness 维度拆分执行
# ══════════════════════════════════════════════════════════════
fitness-dimensions:
name: 'Gate: ${{ matrix.label }}'
runs-on: ubuntu-latest
if: github.event_name != 'schedule'
strategy:
fail-fast: false
matrix:
include:
- dimension: code_quality
label: Code Quality
- dimension: engineering_governance
label: Engineering Governance
- dimension: testability
label: Testability
- dimension: security
label: Security
- dimension: api_contract
label: API Contract
- dimension: design_system
label: Design System
- dimension: evolvability
label: Evolvability
- dimension: ui_consistency
label: UI Consistency
- dimension: observability
label: Observability
- dimension: performance
label: Performance
steps:
- uses: actions/checkout@v6
with:
submodules: recursive
- name: Install Rust system dependencies
run: |
sudo apt-get update
sudo apt-get install -y \
pkg-config \
libglib2.0-dev \
libgtk-3-dev \
libwebkit2gtk-4.1-dev \
libayatana-appindicator3-dev \
librsvg2-dev \
libsoup-3.0-dev \
libjavascriptcoregtk-4.1-dev \
patchelf
- name: Setup Python
uses: actions/setup-python@v6
with:
python-version: '3.11'
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: lts/*
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Install Rust
uses: dtolnay/rust-toolchain@stable
with:
components: clippy
- name: Rust cache
uses: swatinem/rust-cache@v2
- name: Install entrix
run: pip install entrix
- name: Run fitness dimension
id: fitness
run: |
mkdir -p .artifacts
entrix run \
--parallel \
--tier normal \
--scope ci \
--min-score 0 \
--dimension "${{ matrix.dimension }}" \
--output ".artifacts/fitness-report-${{ matrix.dimension }}.json"
- name: Summary
if: always()
run: |
{
echo "## Fitness Dimension Report"
echo ""
echo "| Gate | Status |"
echo "|------|--------|"
echo "| ${{ matrix.label }} | ${{ steps.fitness.outcome == 'success' && '✅' || '❌' }} |"
} >> "$GITHUB_STEP_SUMMARY"
if [ -f ".artifacts/fitness-report-${{ matrix.dimension }}.json" ]; then
python3 -c 'import json; from pathlib import Path; payload=json.loads(Path(".artifacts/fitness-report-${{ matrix.dimension }}.json").read_text(encoding="utf-8")); print(""); print("- Dimension: {}".format("${{ matrix.dimension }}")); print("- Final score: {:.1f}".format(payload.get("final_score", 0))); print("- Hard gate blocked: {}".format(payload.get("hard_gate_blocked", False))); print("- Score blocked: {}".format(payload.get("score_blocked", False))); [print("- {}: score={:.1f} passed={}/{}".format(dimension["name"], dimension["score"], dimension["passed"], dimension["total"])) for dimension in payload.get("dimensions", [])]' >> "$GITHUB_STEP_SUMMARY"
fi
- name: Upload fitness artifact
if: always() && hashFiles(format('.artifacts/fitness-report-{0}.json', matrix.dimension)) != ''
uses: actions/upload-artifact@v4
with:
name: fitness-report-${{ matrix.dimension }}
path: .artifacts/fitness-report-${{ matrix.dimension }}.json
fitness-summary:
name: 'Fitness Summary'
runs-on: ubuntu-latest
needs: [fitness-dimensions]
if: always() && github.event_name != 'schedule'
steps:
- name: Download fitness artifacts
uses: actions/download-artifact@v4
with:
pattern: fitness-report-*
path: .artifacts
merge-multiple: true
- name: Summary
run: |
python3 -c 'import json; from pathlib import Path; files=sorted(Path(".artifacts").glob("fitness-report-*.json")); print("## Fitness Summary"); print(""); print("| Dimension | Final score | Hard gate blocked | Score blocked |"); print("|-----------|-------------|-------------------|---------------|"); [print("| {} | {:.1f} | {} | {} |".format(((payload.get("dimensions") or [{}])[0].get("name") or file_path.stem.removeprefix("fitness-report-")), payload.get("final_score", 0), payload.get("hard_gate_blocked", False), payload.get("score_blocked", False))) for file_path in files for payload in [json.loads(file_path.read_text(encoding="utf-8"))]] or print("| none | n/a | n/a | n/a |")' >> "$GITHUB_STEP_SUMMARY"
# ══════════════════════════════════════════════════════════════
# SECURITY DIMENSION: 依赖漏洞扫描
# ══════════════════════════════════════════════════════════════
dependency-scan:
name: 'Security: Dependency Scan'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: lts/*
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: npm audit (critical)
run: npm audit --audit-level=critical
continue-on-error: false
- name: npm audit (high) - report only
run: npm audit --audit-level=high || true
- name: Install Rust
uses: dtolnay/rust-toolchain@stable
- name: Install cargo-audit
run: cargo install cargo-audit
- name: cargo audit
run: cargo audit
continue-on-error: true
# ══════════════════════════════════════════════════════════════
# SECURITY DIMENSION: Semgrep SAST 扫描
# ══════════════════════════════════════════════════════════════
semgrep:
name: 'Security: Semgrep SAST'
runs-on: ubuntu-latest
container:
image: semgrep/semgrep
steps:
- uses: actions/checkout@v6
- name: Semgrep scan (security-audit + OWASP)
run: |
semgrep scan \
--config=p/security-audit \
--config=p/owasp-top-ten \
--config=p/typescript \
--config=p/javascript \
--sarif \
--output=semgrep-results.sarif \
.
continue-on-error: true
- name: Upload SARIF to GitHub Security
uses: github/codeql-action/upload-sarif@v4
if: always()
with:
sarif_file: semgrep-results.sarif
- name: Upload Semgrep SARIF artifact
uses: actions/upload-artifact@v4
if: always()
with:
name: semgrep-results-sarif
path: semgrep-results.sarif
if-no-files-found: warn
- name: Semgrep strict (ERROR only, fail on match)
run: |
semgrep scan \
--config=p/security-audit \
--config=p/owasp-top-ten \
--severity=ERROR \
--error \
.
# ══════════════════════════════════════════════════════════════
# SECURITY DIMENSION: Trivy 文件系统扫描
# ══════════════════════════════════════════════════════════════
trivy:
name: 'Security: Trivy Scan'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- name: Run Trivy vulnerability scanner
uses: aquasecurity/trivy-action@master
with:
scan-type: 'fs'
scan-ref: '.'
severity: 'CRITICAL,HIGH'
format: 'sarif'
output: 'trivy-results.sarif'
- name: Upload Trivy SARIF to GitHub Security
uses: github/codeql-action/upload-sarif@v4
if: always()
with:
sarif_file: trivy-results.sarif
- name: Upload Trivy SARIF artifact
uses: actions/upload-artifact@v4
if: always()
with:
name: trivy-results-sarif
path: trivy-results.sarif
if-no-files-found: warn
# ══════════════════════════════════════════════════════════════
# SECURITY DIMENSION: Dockerfile 检查
# ══════════════════════════════════════════════════════════════
hadolint:
name: 'Security: Hadolint'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- name: Detect Dockerfile
id: dockerfile
run: |
if [ -f Dockerfile ]; then
echo "exists=true" >> "$GITHUB_OUTPUT"
else
echo "exists=false" >> "$GITHUB_OUTPUT"
fi
- name: Lint Dockerfile
if: steps.dockerfile.outputs.exists == 'true'
uses: hadolint/hadolint-action@v3.3.0
with:
dockerfile: Dockerfile
failure-threshold: error
# ══════════════════════════════════════════════════════════════
# SECURITY DIMENSION: 汇总
# ══════════════════════════════════════════════════════════════
security-summary:
name: 'Security Summary'
runs-on: ubuntu-latest
needs: [dependency-scan, semgrep, trivy, hadolint]
if: always()
steps:
- name: Summary
run: |
{
echo "## Defense / Security Summary"
echo ""
echo "| Check | Status |"
echo "|-------|--------|"
echo "| Dependency Scan | ${{ needs.dependency-scan.result == 'success' && '✅' || '❌' }} |"
echo "| Semgrep SAST | ${{ needs.semgrep.result == 'success' && '✅' || '❌' }} |"
echo "| Trivy | ${{ needs.trivy.result == 'success' && '✅' || '❌' }} |"
echo "| Hadolint | ${{ needs.hadolint.result == 'success' && '✅' || '❌' }} |"
} >> "$GITHUB_STEP_SUMMARY"