Skip to content

Handle duplicate desktop launches #39

Handle duplicate desktop launches

Handle duplicate desktop launches #39

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