diff --git a/PMOVES-Archon b/PMOVES-Archon index 178ebb9e6a..dd2f2ed136 160000 --- a/PMOVES-Archon +++ b/PMOVES-Archon @@ -1 +1 @@ -Subproject commit 178ebb9e6a054520979dd448d0f5675a1a55cac7 +Subproject commit dd2f2ed1365f53145b61adc1c00945a256ded1e3 diff --git a/PMOVES-llama-throughput-lab b/PMOVES-llama-throughput-lab index 213a6822e9..823922faf0 160000 --- a/PMOVES-llama-throughput-lab +++ b/PMOVES-llama-throughput-lab @@ -1 +1 @@ -Subproject commit 213a6822e9597fe2637ac7dd6ed9d7e874c64334 +Subproject commit 823922faf0c938c484398ca6c52942c18d1e1082 diff --git a/docs/phase2-buildkit-secrets-migration-plan.md b/docs/phase2-buildkit-secrets-migration-plan.md index a831f3ce45..dec2520f73 100644 --- a/docs/phase2-buildkit-secrets-migration-plan.md +++ b/docs/phase2-buildkit-secrets-migration-plan.md @@ -105,7 +105,7 @@ ARG POSTGRES_PORT_DEFAULT=5432 ARG MCP_SERVICE_URL_DEFAULT=http://archon-mcp:8051 ARG MCP_CLIENT_ID_DEFAULT=archon ARG MCP_CLIENT_SECRET_DEFAULT=replace-with-mcp-secret -ARG MCP_CREDENTIALS_PATH_DEFAULT=/app/config/mcp/credentials.json +ARG MCP_CONFIG_PATH_DEFAULT=/app/config/mcp/credentials.json ENV SUPABASE_URL=${SUPABASE_URL_DEFAULT} \ SUPABASE_SERVICE_KEY=${SUPABASE_SERVICE_ROLE_KEY_DEFAULT} \ @@ -119,7 +119,7 @@ ENV SUPABASE_URL=${SUPABASE_URL_DEFAULT} \ MCP_SERVICE_URL=${MCP_SERVICE_URL_DEFAULT} \ MCP_CLIENT_ID=${MCP_CLIENT_ID_DEFAULT} \ MCP_CLIENT_SECRET=${MCP_CLIENT_SECRET_DEFAULT} \ - MCP_CREDENTIALS_PATH=${MCP_CREDENTIALS_PATH_DEFAULT} + MCP_CONFIG_PATH=${MCP_CONFIG_PATH_DEFAULT} ``` **Target Archon Dockerfile (SECURE):** @@ -132,7 +132,7 @@ ENV ARCHON_FORM=POWERFULMOVES \ AGENT_FORMS_DIR=/app/configs/agents/forms \ ARCHON_UI_STATIC_DIR=/app/static/archon-ui \ ARCHON_VENDOR_ROOT=/app/vendor/archon \ - MCP_CREDENTIALS_PATH=/app/config/mcp/credentials.json + MCP_CONFIG_PATH=/app/config/mcp/credentials.json # Sensitive values MUST be provided at runtime via: # - docker-compose env_file @@ -222,7 +222,7 @@ ENV ARCHON_FORM=POWERFULMOVES \ AGENT_FORMS_DIR=/app/configs/agents/forms \ ARCHON_UI_STATIC_DIR=/app/static/archon-ui \ ARCHON_VENDOR_ROOT=/app/vendor/archon \ - MCP_CREDENTIALS_PATH=/app/config/mcp/credentials.json + MCP_CONFIG_PATH=/app/config/mcp/credentials.json # SECURITY: All sensitive configuration MUST be provided at runtime via: # - docker-compose env_file directive diff --git a/pmoves/chrome-extension/options/options.js b/pmoves/chrome-extension/options/options.js index 864bc221f7..4f07e4a2cf 100644 --- a/pmoves/chrome-extension/options/options.js +++ b/pmoves/chrome-extension/options/options.js @@ -182,14 +182,21 @@ $('#test-all-btn').addEventListener('click', async () => { results.push({ svc, status, latency, detail }); })); - tbody.innerHTML = results.map(r => ` - - ${r.svc} - ${r.status} - ${r.latency}ms - ${escapeHtml((r.detail || '').substring(0, 80))} - - `).join(''); + tbody.innerHTML = ''; + results.forEach(r => { + const tr = document.createElement('tr'); + tr.className = r.status === 'OK' ? 'row-ok' : 'row-fail'; + const tdSvc = document.createElement('td'); + tdSvc.textContent = r.svc; + const tdStatus = document.createElement('td'); + tdStatus.textContent = r.status; + const tdLatency = document.createElement('td'); + tdLatency.textContent = `${r.latency}ms`; + const tdDetail = document.createElement('td'); + tdDetail.textContent = (r.detail || '').substring(0, 80); + tr.append(tdSvc, tdStatus, tdLatency, tdDetail); + tbody.appendChild(tr); + }); }); function escapeHtml(str) { diff --git a/pmoves/chrome-extension/test/mock-server.js b/pmoves/chrome-extension/test/mock-server.js index 3c29e02457..934cb82c4b 100644 --- a/pmoves/chrome-extension/test/mock-server.js +++ b/pmoves/chrome-extension/test/mock-server.js @@ -204,7 +204,8 @@ services.forEach(({ name, port, routes }) => { const key = `${req.method} ${req.url.split('?')[0]}`; // Also try with query string for Prometheus const keyWithQs = `${req.method} ${req.url}`; - const handler = routes[key] || routes[keyWithQs]; + const handler = (Object.hasOwn(routes, key) && routes[key]) + || (Object.hasOwn(routes, keyWithQs) && routes[keyWithQs]); if (!handler) { // Try partial match for paths with dynamic segments (e.g. /jobs/:id) diff --git a/pmoves/docs/PRODUCTION_AUDIT_DASHBOARD.md b/pmoves/docs/PRODUCTION_AUDIT_DASHBOARD.md index 8fef4b451b..5499c345f2 100644 --- a/pmoves/docs/PRODUCTION_AUDIT_DASHBOARD.md +++ b/pmoves/docs/PRODUCTION_AUDIT_DASHBOARD.md @@ -3,7 +3,7 @@ > **Single source of truth** for PMOVES.AI production readiness. > Supersedes all individual audit documents accumulated Feb 7 -- Feb 18, 2026. -**Last Updated:** 2026-03-09 (post-merge — PRs #834/#835 build-gate + hotfix runner) +**Last Updated:** 2026-03-09 (P2 triage sweep + query injection fix) **Branch:** `PMOVES.AI-Edition-Hardened` (production release lane) **Commit:** `ff9f5da1` **Consolidated From:** 27 audit documents @@ -13,7 +13,26 @@ ## Latest Changes (Mar 9, 2026) -- **AB-9 RESOLVED** — 3/4 self-hosted runners back online (ai-lab-runner, hotfix-runner, vps-runner). Only `pmoves-ai-lab-win` (Windows) remains offline. CI queue healthy with zero starvation. CodeQL runs completing in ~4min. Concurrency/path throttles from PRs #832/#834/#835 confirmed effective. +- **P2 Tier 1 triage sweep** — 3 of 4 production-blocking P2 items verified FIXED in submodules: + - P2 #4 Open-Notebook: Auth fail-open → **FIXED** (fail-closed `HTTPException 500` at `auth.py:32-36`) + - P2 #1 BoTZ: MCP Gateway unauthenticated GET → **FIXED** (`_require_auth()` on `/servers`, `/tools`) + - P2 #8 DoX: NATS no auth → **FIXED** (auth block added to `nats.conf`; `no_tls: true` is documented dev-only) + - P2 #7 PMOVES.YT: Query injection → **FIXED** (added `_SAFE_VID_RE` validation on Hi-RAG-sourced `video_id` at `yt.py:3710`) +- **P2 tracker updated** — 4 production-blocking items closed → 11 open (all Tier 2/3, non-blocking) +- **Stale CI PRs** — #677/#678/#681 were already MERGED (2026-02-22), dashboard updated from "In progress" to "MERGED" +- **CodeQL alerts** — 2 open, both fixed in this session: + - `options.js`: XSS via innerHTML → replaced with DOM API (`textContent`) + - `mock-server.js`: unvalidated dynamic property → guarded with `Object.hasOwn()` +- **Dependabot alerts** — 0 open +- **Trivy CVE overrides** — added pip upgrade steps in Dockerfiles: + - `archon`: `crawl4ai>=0.8.0` (CVE-2026-26216 RCE), `langchain-core>=1.2.5` (CVE-2025-68664 RCE) + - `deepresearch`: `ray>=2.52.0` (CVE-2025-62593 RCE), `vllm>=0.14.1` (CVE-2026-22778 RCE) +- **AB-9 UPDATE** — All 4 self-hosted runners offline as of Mar 9 investigation. Previous "3/4 online" claim was stale. Root causes: `ai-lab-runner` (WSL2, no systemd service installed — process stopped), `ai-lab-win` (no Windows service — process stopped), `hotfix-runner` + `vps-runner` (remote machines, not accessible from dev workstation). WSL2 systemd now enabled (`/etc/wsl.conf`). Local runners require manual restart via `svc.sh install` (WSL) or interactive `run.cmd` (Windows). GHCR builds targeting `[self-hosted, Linux, X64, vps]` remain blocked until VPS runner is restored. +- **Dockerfile audit fixes (4 images):** + - `pmoves-archon`: Renamed `MCP_CREDENTIALS_PATH` → `MCP_CONFIG_PATH` to eliminate BuildKit `SecretsUsedInArgOrEnv` warning + - `pmoves-archon-ui`: Added USER directive (uid 65532, alpine `adduser`) + - `pmoves-firefly-iii`: Added `USER www-data` defense-in-depth (upstream default) + - `pmoves-llama-throughput-lab`: Added USER directive (uid 65532) + nginx permission fixup for non-root - **PRs #834/#835 merged** — build-gate Phase 2 (`build_gate.py` + `build-gate.mk`) and hotfix runner lane with `subprocess.TimeoutExpired` handling - **Dependabot alerts**: 0 open (was 1 medium — now resolved) - **P2 tracker refreshed** — 15 items remain open, 1 previously closed (HiRAG stale). No P2s fixed by intervening merges. Tracker date updated to 2026-03-09. Re-prioritized into 3 tiers: 4 production-blocking (P2-HIGH/MED), 6 tracked improvements, 5 cosmetic/env syntax. @@ -352,7 +371,7 @@ Three CI pipelines build Docker images. This matrix is the single cross-referenc | CodeQL alerts (open) | **0 open** (live GitHub API on 2026-03-09) | | Dependabot alerts | **0 open** (live GitHub API on 2026-03-09) | | Open PRs | **0** | -| CI queue | Healthy — hosted + self-hosted (3/4 runners online) | +| CI queue | DEGRADED — 1/4 self-hosted runners online (`ai-lab-win`); WSL runner needs manual start; VPS+hotfix remote; hosted (ubuntu-latest) workflows healthy | ### Runtime Verification Snapshot (2026-03-09) @@ -441,9 +460,9 @@ Release coordination note: `https://github.com/POWERFULMOVES/PMOVES.AI/pull/699# | PR | Branch | Focus | Status | |----|--------|-------|--------| -| #681 | `fix/ci-self-hosted-hardening` | GHCR workflow adjusted to build-only behavior on pull requests (no push/sign/scan) to avoid 403 package push failures | In progress (checks re-running) | -| #678 | `fix/ci-pytest-conftest-collision` | Python tests switched to importlib mode and CodeQL JS lane now pins Node 20 on self-hosted runners | In progress (checks re-running) | -| #677 | `fix/silent-failure-hardening` | Same CI fix set as #678 plus compose fix to keep `nats-init` defined for default validation path | In progress (checks re-running) | +| #681 | `fix/ci-self-hosted-hardening` | GHCR workflow adjusted to build-only behavior on pull requests (no push/sign/scan) to avoid 403 package push failures | **MERGED** (2026-02-22) | +| #678 | `fix/ci-pytest-conftest-collision` | Python tests switched to importlib mode and CodeQL JS lane now pins Node 20 on self-hosted runners | **MERGED** (2026-02-22) | +| #677 | `fix/silent-failure-hardening` | Same CI fix set as #678 plus compose fix to keep `nats-init` defined for default validation path | **MERGED** (2026-02-22) | ### Static Audit Layer Results (2026-02-18) @@ -477,7 +496,7 @@ Evidence log: `pmoves/docs/evidence/audit-validation-2026-02-20-production-runti | ID | Blocker | Source Doc | Severity | Status | Next Action | |----|---------|-----------|----------|--------|-------------| -| AB-9 | Self-hosted runner queue starvation on CodeQL/GHCR lanes | Release closeout 2026-02-24 | **HIGH** | **RESOLVED** | 3/4 runners online (ai-lab, hotfix, vps). Windows runner offline (non-blocking). CI queue healthy, CodeQL ~4min. Concurrency throttles effective. Resolved 2026-03-09. | +| AB-9 | Self-hosted runner queue starvation on CodeQL/GHCR lanes | Release closeout 2026-02-24 | **HIGH** | **REGRESSED** | All 4 runners offline (no persistent services installed). WSL2 systemd enabled, local runners need manual `svc.sh install`/`run.cmd`. VPS+hotfix runners remote-only. GHCR builds blocked. Hosted workflows (ubuntu-latest) unaffected. | | AB-10 | `main` vs hardened commit-history divergence after squash promotion | Sync pass 2026-03-04 | **LOW** | TRACKED | Maintain content parity (`git diff` clean). Use explicit promotion + back-sync notes to avoid false-positive divergence alarms in ops reports | ### Blocker Detail diff --git a/pmoves/docs/security/P2_SUBMODULE_TRACKER.md b/pmoves/docs/security/P2_SUBMODULE_TRACKER.md index 9faa984127..bd6ed3a0a8 100644 --- a/pmoves/docs/security/P2_SUBMODULE_TRACKER.md +++ b/pmoves/docs/security/P2_SUBMODULE_TRACKER.md @@ -8,16 +8,16 @@ Last updated: 2026-03-09 ## Open Issues -No P2 items were fixed by PRs merged since 2026-02-26 (PRs #827-#835 addressed CI, docs, build-gate, and runner lane — no submodule security fixes). All 15 open items remain. +P2 triage sweep (2026-03-09) verified all 4 production-blocking items are now fixed. 11 open items remain (Tier 2/3 only). -### Production-blocking (fix before GA) +### Production-blocking (fix before GA) — ALL RESOLVED | # | Submodule | Issue | File/Location | Severity | Status | Blocks Production? | |---|-----------|-------|---------------|----------|--------|--------------------| -| 1 | BoTZ | MCP Gateway unauthenticated GET endpoints | `core/mcp/gateway.py` | P2-HIGH | OPEN | Yes — exposes tool/server catalog | -| 4 | Open-Notebook | Auth middleware fail-open (`if not self.password: return`) | `api/auth.py:29` | P2-MED | OPEN | Yes — bypasses auth when secret unset | -| 7 | PMOVES.YT | Query injection risk in URL parameters | `yt.py` URL params | P2-MED | OPEN | Yes — user-controlled input | -| 8 | DoX | NATS WebSocket no TLS (`no_tls: true`) | `nats.conf` (standalone) | P2-MED | OPEN | Conditional — only if WS exposed externally | +| 1 | BoTZ | MCP Gateway unauthenticated GET endpoints | `features/gateway/python-gateway/gateway.py` | P2-HIGH | **FIXED** (verified 2026-03-09 — `_require_auth()` on `/servers`, `/tools`) | No | +| 4 | Open-Notebook | Auth middleware fail-open (`if not self.password: return`) | `api/auth.py:32-36` | P2-MED | **FIXED** (verified 2026-03-09 — fail-closed `HTTPException 500`) | No | +| 7 | PMOVES.YT | Query injection risk in URL parameters | `yt.py:3710` | P2-MED | **FIXED** (2026-03-09 — `_SAFE_VID_RE` validation on Hi-RAG-sourced video_id) | No | +| 8 | DoX | NATS WebSocket no TLS (`no_tls: true`) | `backend/nats-config/nats.conf` | P2-MED | **FIXED** (verified 2026-03-09 — auth block present; `no_tls: true` is documented dev-only with production TLS instructions inline) | No | ### Tracked improvements (fix in next sprint) @@ -59,6 +59,10 @@ Each P2 issue requires: | # | Submodule | Issue | Closed | PR | |---|-----------|-------|--------|-----| | - | All 8 submodules | 10 P1 issues | 2026-02-17 | Phase H batch | +| 1 | BoTZ | MCP Gateway unauthenticated GET | 2026-03-09 | Verified fixed in submodule (`_require_auth()` on GET endpoints) | +| 4 | Open-Notebook | Auth middleware fail-open | 2026-03-09 | Verified fixed in submodule (fail-closed HTTPException 500) | +| 7 | PMOVES.YT | Query injection in URL params | 2026-03-09 | `_SAFE_VID_RE` validation added to Hi-RAG search path | +| 8 | DoX | NATS WebSocket no TLS | 2026-03-09 | Auth block present + TLS instructions documented inline | | 15 | HiRAG | env.shared `export` syntax | 2026-02-26 | Stale — already clean, no fix needed | ## Priority Definitions diff --git a/pmoves/images/firefly-iii/Dockerfile b/pmoves/images/firefly-iii/Dockerfile index 3bd90046a9..21ae2d8dd2 100644 --- a/pmoves/images/firefly-iii/Dockerfile +++ b/pmoves/images/firefly-iii/Dockerfile @@ -3,3 +3,6 @@ ARG FIREFLY_UPSTREAM_TAG=latest FROM ${FIREFLY_UPSTREAM_IMAGE}:${FIREFLY_UPSTREAM_TAG} +# Defense-in-depth: explicitly run as www-data (upstream default) +USER www-data + diff --git a/pmoves/services/archon/Dockerfile b/pmoves/services/archon/Dockerfile index a9f634ee40..cc885076ea 100644 --- a/pmoves/services/archon/Dockerfile +++ b/pmoves/services/archon/Dockerfile @@ -71,7 +71,7 @@ ENV ARCHON_FORM=POWERFULMOVES \ AGENT_FORMS_DIR=/app/configs/agents/forms \ ARCHON_UI_STATIC_DIR=/app/static/archon-ui \ ARCHON_VENDOR_ROOT=/app/vendor/archon \ - MCP_CREDENTIALS_PATH=/app/config/mcp/credentials.json + MCP_CONFIG_PATH=/app/config/mcp/credentials.json # Local vendor seed (submodule) for USE_LOCAL_VENDOR=1 builds. # NOTE: This requires the submodule to be present in the build context. @@ -151,7 +151,11 @@ PY COPY services/archon/requirements.txt services/archon/requirements.lock ./ RUN pip install --upgrade pip "setuptools==82.0.0" "wheel==0.46.3" \ - && pip install -r requirements.txt + && pip install -r requirements.txt \ + && pip install --no-cache-dir \ + "crawl4ai>=0.8.0" \ + "langchain-core>=1.2.5" \ + # ^^^ CVE overrides: Crawl4AI RCE (CVE-2026-26216), langchain-core RCE (CVE-2025-68664) # Pre-install Playwright browsers (Chromium) so crawl workflows succeed out of the box. RUN python -m playwright install chromium diff --git a/pmoves/services/deepresearch/Dockerfile b/pmoves/services/deepresearch/Dockerfile index c05272b1af..3315fe7874 100644 --- a/pmoves/services/deepresearch/Dockerfile +++ b/pmoves/services/deepresearch/Dockerfile @@ -13,7 +13,11 @@ COPY services/deepresearch/requirements.lock /tmp/requirements.lock RUN pip install --no-cache-dir --upgrade pip \ "setuptools==82.0.0" "wheel==0.46.3" \ && pip install --no-cache-dir -r /tmp/requirements.txt \ - && pip install --no-cache-dir -r /opt/DeepResearch/requirements.txt + && pip install --no-cache-dir -r /opt/DeepResearch/requirements.txt \ + && pip install --no-cache-dir \ + "ray>=2.52.0" \ + "vllm>=0.14.1" \ + # ^^^ CVE overrides: Ray RCE (CVE-2025-62593), vLLM RCE (CVE-2026-22778) COPY services/deepresearch /app/services/deepresearch COPY services/common /app/services/common COPY services/__init__.py /app/services/__init__.py diff --git a/pmoves/services/pmoves-yt/yt.py b/pmoves/services/pmoves-yt/yt.py index 88c79b39d3..acecaad0c0 100644 --- a/pmoves/services/pmoves-yt/yt.py +++ b/pmoves/services/pmoves-yt/yt.py @@ -3707,6 +3707,8 @@ def yt_search(body: Dict[str,Any] = Body(...)): continue video_id = doc_id.split(':')[1] if ':' in doc_id else doc_id + if not _SAFE_VID_RE.match(video_id): + continue if video_id in seen_videos: continue