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