Handle duplicate desktop launches #39
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Security hardening | |
| on: | |
| pull_request: | |
| push: | |
| branches: [main] | |
| schedule: | |
| - cron: "23 4 * * 1" | |
| workflow_dispatch: | |
| permissions: | |
| contents: read | |
| jobs: | |
| python: | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v6 | |
| - uses: actions/setup-python@v6 | |
| with: | |
| python-version: "3.12" | |
| cache: pip | |
| - name: Install CPU runtime and development dependencies | |
| run: | | |
| python -m pip install --upgrade pip | |
| python -m pip install --index-url https://download.pytorch.org/whl/cpu "torch>=2.4.0" | |
| python -m pip install -e ".[dev,office]" | |
| - name: Import and lint | |
| run: | | |
| python -c "import shogun.app; import gensui.app" | |
| # Full-tree lint has substantial pre-existing debt. Gate the security | |
| # boundary and deployment-path files introduced by this remediation. | |
| python -m ruff check \ | |
| shogun/api/a2a.py \ | |
| shogun/api/control_plane_auth.py \ | |
| shogun/api/gensui_config.py \ | |
| shogun/api/infrastructure_auth.py \ | |
| shogun/api/telemetry.py \ | |
| shogun/config.py \ | |
| shogun/integrations/a2a_client.py \ | |
| shogun/services/ssrf_guard.py \ | |
| shogun/services/provider_credentials.py \ | |
| shogun/nexus/security/outbound.py \ | |
| shogun/telemetry \ | |
| telemetry_service \ | |
| gensui/app.py \ | |
| gensui/config.py \ | |
| tests/test_ssrf_guard.py \ | |
| tests/test_gensui_frontend.py \ | |
| tests/test_install_telemetry.py \ | |
| tests/test_telemetry_ingestion.py \ | |
| tests/test_red_team_hardening.py | |
| - name: Security regression tests | |
| # The repository-wide suite has unrelated pre-existing failures. Keep | |
| # this required gate scoped to the remediation's security regressions. | |
| run: | | |
| python -m pytest -q \ | |
| tests/test_ssrf_guard.py \ | |
| tests/test_gensui_frontend.py \ | |
| tests/test_install_telemetry.py \ | |
| tests/test_telemetry_ingestion.py \ | |
| tests/test_red_team_hardening.py | |
| python scripts/check-telemetry-privacy.py | |
| frontends: | |
| runs-on: ubuntu-latest | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| include: | |
| - name: Tenshu | |
| directory: frontend | |
| lockfile: frontend/package-lock.json | |
| - name: Gensui | |
| directory: gensui/frontend | |
| lockfile: gensui/frontend/package-lock.json | |
| name: Frontend - ${{ matrix.name }} | |
| defaults: | |
| run: | |
| working-directory: ${{ matrix.directory }} | |
| steps: | |
| - uses: actions/checkout@v6 | |
| - uses: actions/setup-node@v6 | |
| with: | |
| node-version: "22" | |
| cache: npm | |
| cache-dependency-path: ${{ matrix.lockfile }} | |
| package-manager-cache: true | |
| - run: npm ci | |
| - name: High/Critical dependency gate | |
| run: npm run audit:security | |
| # The repository has pre-existing full-tree lint debt. Keep the complete | |
| # lint command available while gating newly added security boundary code. | |
| - run: npm run lint:security | |
| - run: npm run build | |
| - name: XSS regression tests | |
| if: matrix.directory == 'frontend' | |
| run: npm run test:security | |
| repository-security: | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v6 | |
| - name: Trivy repository, secret, and misconfiguration scan | |
| uses: aquasecurity/trivy-action@57a97c7e7821a5776cebc9bb87c984fa69cba8f1 | |
| with: | |
| scan-type: fs | |
| scan-ref: . | |
| scanners: vuln,secret,misconfig | |
| severity: HIGH,CRITICAL | |
| ignore-unfixed: true | |
| trivyignores: .trivyignore.yaml | |
| exit-code: "1" | |
| telemetry-service-container: | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 30 | |
| steps: | |
| - uses: actions/checkout@v6 | |
| - name: Build telemetry service | |
| run: docker build --file telemetry_service/Dockerfile --tag shogun-telemetry:ci . | |
| - name: Scan telemetry image | |
| uses: aquasecurity/trivy-action@57a97c7e7821a5776cebc9bb87c984fa69cba8f1 | |
| with: | |
| image-ref: shogun-telemetry:ci | |
| severity: HIGH,CRITICAL | |
| ignore-unfixed: true | |
| exit-code: "1" | |
| - name: Run non-root health test | |
| run: | | |
| docker volume create telemetry-ci-data | |
| docker run -d --name telemetry-ci \ | |
| --read-only --tmpfs /tmp:rw,noexec,nosuid,size=64m \ | |
| --cap-drop ALL --security-opt no-new-privileges:true \ | |
| -e TELEMETRY_ENVIRONMENT=development \ | |
| -e TELEMETRY_DATABASE_URL=sqlite:////app/data/telemetry.db \ | |
| -e TELEMETRY_HMAC_SECRET=ci-only-hmac-secret-01234567890123456789 \ | |
| -e TELEMETRY_IDENTITY_PROXY_SECRET=ci-only-proxy-secret-012345678901234567 \ | |
| -p 127.0.0.1:8080:8080 \ | |
| -v telemetry-ci-data:/app/data \ | |
| shogun-telemetry:ci | |
| for attempt in $(seq 1 30); do | |
| curl --fail --silent http://127.0.0.1:8080/health && break | |
| sleep 2 | |
| done | |
| curl --fail --silent http://127.0.0.1:8080/v1/schema | |
| test "$(docker exec telemetry-ci whoami)" = "telemetry" | |
| - name: Telemetry service logs | |
| if: failure() | |
| run: docker logs telemetry-ci || true | |
| - name: Clean up telemetry service | |
| if: always() | |
| run: | | |
| docker rm -f telemetry-ci || true | |
| docker volume rm telemetry-ci-data || true | |
| gensui-container: | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 30 | |
| steps: | |
| - uses: actions/checkout@v6 | |
| - name: Build Gensui | |
| run: docker build --file gensui/Dockerfile --tag shogun-gensui:ci . | |
| - name: Scan Gensui image | |
| uses: aquasecurity/trivy-action@57a97c7e7821a5776cebc9bb87c984fa69cba8f1 | |
| with: | |
| image-ref: shogun-gensui:ci | |
| severity: HIGH,CRITICAL | |
| ignore-unfixed: true | |
| exit-code: "1" | |
| - name: Run hardened Gensui smoke test | |
| run: | | |
| docker volume create gensui-ci-data | |
| docker volume create gensui-ci-logs | |
| docker run -d --name gensui-ci \ | |
| --read-only --tmpfs /tmp:rw,noexec,nosuid,size=256m \ | |
| --cap-drop ALL --security-opt no-new-privileges:true \ | |
| -e GENSUI_JWT_SECRET=ci-only-random-jwt-secret-0123456789 \ | |
| -e GENSUI_ADMIN_PASSWORD=ci-only-random-admin-password \ | |
| -p 127.0.0.1:8787:8787 \ | |
| -v gensui-ci-data:/app/data -v gensui-ci-logs:/app/logs \ | |
| shogun-gensui:ci | |
| for attempt in $(seq 1 60); do | |
| curl --fail --silent http://127.0.0.1:8787/api/gensui/health && break | |
| sleep 2 | |
| done | |
| curl --fail --silent http://127.0.0.1:8787/ | |
| curl --fail --silent http://127.0.0.1:8787/agents | |
| login_json="$(curl --fail --silent \ | |
| -H 'Content-Type: application/json' \ | |
| -d '{"email":"admin@gensui.local","password":"ci-only-random-admin-password"}' \ | |
| http://127.0.0.1:8787/api/gensui/auth/login)" | |
| admin_token="$(python -c 'import json,sys; print(json.load(sys.stdin)["token"])' <<<"$login_json")" | |
| curl --fail --silent \ | |
| -H "Authorization: Bearer $admin_token" \ | |
| http://127.0.0.1:8787/api/gensui/dashboard | |
| test "$(docker exec gensui-ci whoami)" = "gensui" | |
| docker exec gensui-ci sh -c "grep -R '/api/gensui' /app/frontend/dist/assets >/dev/null" | |
| if docker exec gensui-ci sh -c "grep -R '/api/v1' /app/frontend/dist/assets >/dev/null"; then | |
| echo "Gensui bundle unexpectedly references the Tenshu API prefix" >&2 | |
| exit 1 | |
| fi | |
| - name: Gensui logs | |
| if: failure() | |
| run: docker logs gensui-ci || true | |
| - name: Clean up Gensui | |
| if: always() | |
| run: | | |
| docker rm -f gensui-ci || true | |
| docker volume rm gensui-ci-data gensui-ci-logs || true | |
| shogun-server-container: | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 60 | |
| steps: | |
| - uses: actions/checkout@v6 | |
| - name: Build Shogun Server | |
| run: docker build --tag shogun-server:ci . | |
| - name: Prepare Shogun Server environment | |
| run: | | |
| cp .env.server.example .env.server | |
| sed -i 's/change-me-postgres-password/ci-postgres-password/' .env.server | |
| sed -i 's/change-me-to-a-random-64-char-string/ci-application-secret-0123456789abcdefghijklmnopqrstuvwxyz/' .env.server | |
| sed -i 's/change-me-to-an-independent-random-64-char-string/ci-vault-secret-0123456789abcdefghijklmnopqrstuvwxyz/' .env.server | |
| sed -i 's/change-me-to-an-independent-infrastructure-admin-token/ci-infrastructure-token-0123456789abcdefghijklmnopqrstuvwxyz/' .env.server | |
| - name: Scan Shogun image | |
| uses: aquasecurity/trivy-action@57a97c7e7821a5776cebc9bb87c984fa69cba8f1 | |
| with: | |
| image-ref: shogun-server:ci | |
| severity: HIGH,CRITICAL | |
| ignore-unfixed: true | |
| exit-code: "1" | |
| - name: Start Shogun Server profile | |
| run: | | |
| docker tag shogun-server:ci shogun-server:local | |
| docker compose --env-file .env.server -f docker-compose.server.yml up -d --no-build | |
| for attempt in $(seq 1 120); do | |
| curl --fail --silent http://127.0.0.1:8000/api/v1/health && break | |
| sleep 2 | |
| done | |
| curl --fail --silent http://127.0.0.1:8000/ | |
| curl --fail --silent http://127.0.0.1:8000/setup | |
| test "$(docker exec shogun-server whoami)" = "shogun" | |
| - name: Launch Mado Chromium as the runtime user | |
| run: | | |
| docker exec shogun-server python -c "import asyncio; from playwright.async_api import async_playwright; exec('async def smoke():\n async with async_playwright() as p:\n browser = await p.chromium.launch(headless=True)\n page = await browser.new_page()\n await page.set_content(\"<title>mado-smoke</title>\")\n assert await page.title() == \"mado-smoke\"\n await browser.close()\nasyncio.run(smoke())')" | |
| - name: Shogun Server logs | |
| if: failure() | |
| run: docker compose --env-file .env.server -f docker-compose.server.yml logs | |
| - name: Clean up Shogun Server | |
| if: always() | |
| run: docker compose --env-file .env.server -f docker-compose.server.yml down --volumes |