Skip to content

feat(cipher): Cipher Memory MCP bridge + Claude config unification - #621

Merged
POWERFULMOVES merged 145 commits into
mainfrom
PMOVES.AI-Edition-Hardened
Feb 16, 2026
Merged

POWERFULMOVES merged 145 commits into
mainfrom
PMOVES.AI-Edition-Hardened

Conversation

@POWERFULMOVES

Copy link
Copy Markdown
Owner

Summary

  • Cipher MCP Bridge (pmoves-cipher-mcp/): Python MCP server (stdio transport) bridging Claude Code CLI to Cipher Memory via HTTP. 4 tools: store, search, store_reasoning, reasoning_patterns.
  • Docker service (cipher-api): Added to pmoves/docker-compose.yml on port 8096 (agents profile), sharing existing Neo4j + NATS. Hardened with *tier-agent-hardened anchor.
  • Claude Code config (.claude/mcp.json): Registers the MCP server for automatic tool availability.
  • Skill definition (.claude/skills/pmoves-cipher-memory/): SKILL.md with usage patterns, categories, memory hints + skill.json manifest.
  • Hooks wiring (.claude/settings.json): Registered existing damage-control PreToolUse hooks (Bash/Edit/Write) that were on disk but unregistered.
  • Context docs: Updated services-catalog.md, submodules.md, and CLAUDE.md with Cipher Memory entries.

Architecture

Claude Code CLI ──stdio──► cipher_mcp (Python MCP) ──HTTP──► cipher-api (Node.js/Neo4j)
                                   │
                             NATS announce
                             health loop

Files Changed (20 files, +1458 lines)

Action Path
Create pmoves-cipher-mcp/ (11 files: server, tools, client, health, announcer, common, registry, main, pyproject, README)
Create .claude/mcp.json
Create .claude/skills/pmoves-cipher-memory/SKILL.md
Create .claude/skills/pmoves-cipher-memory/skill.json
Modify .claude/settings.json — hooks wiring
Modify pmoves/docker-compose.yml — cipher-api service
Modify .gitignore — cipher MCP entries
Modify .claude/CLAUDE.md, .claude/context/services-catalog.md, .claude/context/submodules.md

Test plan

  • git submodule status Pmoves-cipher — shows 51eea54 (main)
  • Python imports: from cipher_mcp.server import app — OK
  • docker compose --profile agents config --services | grep cipher — shows cipher-api
  • All JSON validated: mcp.json, skill.json, settings.json
  • Full smoke test after cipher-api container build (requires Neo4j + NATS running)
  • Verify MCP tools appear in Claude Code CLI tool list

🤖 Generated with Claude Code

Codex Agent and others added 30 commits February 6, 2026 09:46
Add two new submodules that were created during Supabase enhancement and
VPS-related PRs but were missing from .gitmodules:

- PMOVES-Headscale: Tailscale control server for VPS coordination
  (branch: PMOVES.AI-Edition-Hardened)
- PMOVES-supabase: Self-hosted Supabase integration components

Also updates:
- scripts/with-env.sh: Load env.tier-supabase in tier sequence
- env.tier-supabase.example: Supabase environment template (copy to env.tier-supabase)
- .gitignore: Add negation pattern to allow env.tier-*.example files
- docs/SUBMODULE_LIST.md: Document new submodules

Users should copy env.tier-supabase.example to env.tier-supabase and
fill in their actual values before running bring-up.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Add supabase to all tier environment handling targets so bring-up scripts
automatically create and populate Supabase credentials:

- bootstrap-tier-envs: Creates env.tier-supabase from example
- populate-tier-envs: Auto-generates JWT secret, anon key, service role key,
  realtime secret, and postgres password via openssl rand
- check-tier-envs: Validates env.tier-supabase exists before services start

- .gitignore: Add negation pattern to allow env.tier-*.example files

This ensures Supabase auth credentials are properly configured during
automated bring-up without manual intervention.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Fix ROOT_DIR calculation in with-env.sh to handle sourced/executed cases
- Fix load_env_file variable expansion for ${VAR:-default} syntax
- Remove duplicate /pmoves path from tier env file loading
- Update env.tier-supabase.example with complete Supabase configuration
- Fix POSTGRES_DB in env.shared (cataclysm_pmoves → pmoves)

This resolves environment variable issues that were preventing
Supabase services from starting during incremental bring-up.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Fix supabase-bootstrap to use correct database credentials (pmoves/pmoves)
- Fix supabase-bootstrap to use TCP connection (-h localhost) for auth
- Fix seed path from supabase/seeds to supabase/initdb
- Add NEO4J_server_config_strict__validation_enabled=false for Neo4j 5.x

These fixes address issues encountered during Supabase bring-up on
PMOVES.AI-Edition-Hardened branch where:
1. Migrations failed with "peer authentication" errors
2. Seed scripts weren't found (wrong directory path)
3. Neo4j 5.x failed with "Unrecognized setting: PASSWORD"

Related: PMOVES-supabase submodule integration
Document common issues encountered during Supabase bring-up on
PMOVES.AI-Edition-Hardened branch and their fixes:

- PostgreSQL peer authentication errors (need -h localhost)
- Missing schemas and roles (manual setup required)
- Kong migration bootstrap requirements
- Neo4j 5.x config validation issues
- Storage service region requirements

Includes complete bring-up sequence for fresh deployments.
- PMOVES-Archon: Switched from feat/personas-clean-rebase to Hardened
- PMOVES-ToKenism-Multi: Reset to origin/PMOVES.AI-Edition-Hardened
- PMOVES-supabase: Re-registered as proper gitlink submodule
- PMOVES-DoX: Now on PMOVES.AI-Edition-Hardened (external switch)
- PMOVES-BoTZ nested in DoX: On PMOVES.AI-Edition-Hardened-DoX

This ensures all submodules track the production hardened branch
for consistent deployment and CI/CD alignment.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Document Supabase submodule update procedure
- Add critical environment loading fix for docker compose
- Document Realtime service requirements (64-byte SECRET_KEY_BASE)
- Add submodule branch alignment table (2026-02-06)
- Note PMOVES-Headscale lacks Hardened branch (main only)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- PMOVES-Headscale: Now on PMOVES.AI-Edition-Hardened (user created)
- Update docs to reflect all submodules on Hardened

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Clarify working directory for environment loading commands
- Add branch-only update path in CI/CD procedure
- Fix SUPABASE_REALTIME_SECRET variable naming documentation
- Add env file creation instruction (cp from example)
- Rename section to "Key Submodules" (not complete list)
- Add verification commands for submodule alignment
- Fix PMOVES-DoX submodule reference update
- Note PMOVES-DoX is external submodule

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Replace exposed Venice API key with placeholder. Users must generate
their own key at https://venice.ai

Security: P0 - Actual API key was exposed in repository
Risk: Unauthorized API usage, cost implications

Action Required:
- Revoke exposed key: kTBQ__hQVA8zSwilSNF8e5q6oEGtW9f8mlxCFZBqXh
- Generate new key for production use if needed

Addresses PR-1 from Production Hardening Tracker

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Codex Agent <codex-agent@example.com>
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Implements production-ready remote access platform with Headscale (self-hosted Tailscale) + RustDesk + BoTZ MCP integration.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
CHIT code audit tracking document - all CHIT/GEOMETRY BUS/Evoswarm code verified present on Hardened

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Fixes CI cancellation issue by correcting runner label from [self-hosted, vps] to [self-hosted, ai-lab]
Add pre-flight validation for LLM provider API keys to catch
configuration issues before docker compose attempts to start services.

Changes:
- Add check for core LLM providers (OpenAI, Anthropic, Groq, Gemini, Ollama)
- Warn if no LLM providers are configured
- Display status of optional API keys (ElevenLabs, Voyage, Cohere, Cloudflare)
- Detect empty/placeholder API key values

Resolves CROSS_PLATFORM_TASKS.md - Issue K3 (Priority 2).

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Add platform-specific documentation for bringing up PMOVES.AI on WSL2
with Docker Desktop.

Topics covered:
- Prerequisites and Docker Desktop configuration
- Known WSL2 issues and solutions (bind mounts, network labels, env expansion)
- Step-by-step bring-up procedure
- Service URLs for WSL2 environment
- Troubleshooting common WSL2 problems
- Path mapping and firewall considerations
- GPU passthrough setup

Resolves CROSS_PLATFORM_TASKS.md - Documentation Task D1.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit addresses critical issues found during PR review:

**PR #592 - TensorZero Port Consistency**
- Fix internal container-to-container communication to use port 3000
  (not host port 3030) across all services
- Files updated: gateway-agent, deepresearch, agent-zero, n8n flows,
  tokenism-simulator, mcp configs, docker-compose overrides

**PR #593 - ClickHouse Configuration**
- Fix ClickHouse URL format to include embedded credentials
- Remove unused separate USER/PASSWORD/DB environment variables

**PR #595 - Documentation Port Fixes**
- Correct Grafana port from 3000 to 3002 in documentation

**PR #590 - Docker Compose --project-directory**
- Add --project-directory to DC variable for WSL2 compatibility
- Convert inline docker compose commands to use $(DC) variable
- Fix up-supabase, backup, brand-defaults, and neo4j targets

**PR #594 - Network Cleanup with Error Handling**
- Add clean-networks target with improved error reporting
- Provides warnings instead of silent failures
- Integrated into 'up' target as pre-flight check

**New Automated Tool**
- Add port-consistency-check.sh script for validation
- Can detect and auto-fix port inconsistencies

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit incorporates the remaining changes from open PRs that were
not included in the initial port consistency fix commit:

**From PR #590 (WSL2 --project-directory)**
- Add --project-directory to apply_migrations_docker.sh
- Add --project-directory to smoke-tests.sh
- Add --project-directory to validate-phase1-hardening.sh
- Remove unsafe 'cd' pattern, use explicit paths instead

**From PR #591 (API Key Validation)**
- Add provider API key validation to env_check.sh
- Checks for OpenAI, Anthropic, Groq, Gemini, Ollama
- Warns about empty/placeholder API keys
- Provides helpful configuration tips

**From PR #587 (Security Update)**
- Update python-multipart to 0.0.22 (CVE-2026-24486 fix)
- Applied to agentgym-rl-coordinator and Enhanced Media Stack plans
- NOTE: Transformers constraint kept at <4.50.0 for FlagEmbedding

All superseded PRs can now be closed.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
… PMOVES.AI-Edition-Hardened

# Conflicts:
#	pmoves/Makefile
- Created comprehensive production readiness audit for PMOVES.AI-Edition-Hardened
- Documented submodule branch alignment issues and PR requirements
- Created PMOVES-DoX PR #96 for PostgreSQL 17 compatibility fixes
- All critical submodules verified on PMOVES.AI-Edition-Hardened branches
- Migrated e2b components from vendor/ paths to forked submodules
- Investigation confirmed no code was lost during migration
- Vendor directories were empty with no local git history
- Commented out entries preserved for branch transition reference
- All code preserved in forked repositories:
  - PMOVES-E2B-Danger-Room
  - PMOVES-E2B-Danger-Room-Desktop
  - pmoves-e2b-mcp-server
  - PMOVES-Danger-infra
  - PMOVES-E2b-Spells

Fixes git submodule errors when running:
  git submodule update --init --recursive

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Bumps python-multipart from 0.0.20 to 0.0.22

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Created PMOVES-DoX #96 for PostgreSQL 17 compatibility
- Created PMOVES-BoTZ #51 for TensorZero 2026 migration
- Created PMOVES-Agent-Zero #3 for PMOVES.AI integration patterns
- Fixed PMOVES-tensorzero to PMOVES.AI-Edition-Hardened branch
- Fixed Pmoves-hyperdimensions to PMOVES.AI-Edition-Hardened branch
- Verified PMOVES-Wealth on origin/main (fork with upstream)
- Cleaned up orphan e2b submodule reference
- Created PMOVES.AI-Edition-Hardened-DoX variant branches where needed
if line.startswith(f"{label}="):
lines[i] = f"{label}={value}"
updated = True
print(f"Updated: {label}")

Check failure

Code scanning / CodeQL

Clear-text logging of sensitive information

This expression logs [sensitive data (secret)](1) as clear text.

Copilot Autofix

AI 7 months ago

In general, the problem is that data derived from a secrets source (cgp["points"]) is being logged directly. To fix this without changing core functionality, we should avoid printing sensitive or potentially sensitive identifiers and instead log only non-sensitive, aggregate information (e.g., counts) or use redacted placeholders. The goal is to preserve some feedback to the operator that the script is doing work, while ensuring that no secret-related identifiers from secrets are written to output.

The best minimal change here is to remove or neutralize the per-secret log lines on 31 and 39 so they do not interpolate label. We can replace them with generic messages that do not include label, such as "Updated 1 secret entry" or similar, or even omit those logs entirely and rely on the existing final summary line 45, which already logs only the count of credentials: len(secrets) (a non-sensitive integer). Given the desire to keep progress indications, a safe compromise is to print generic messages without referencing label. Concretely:

  • In pmoves/scripts/update_env_from_cgp.py, change line 31 from print(f"Updated: {label}") to print("Updated one credential entry") (or just remove it).
  • Similarly change line 39 from print(f"Updated (empty): {label}") to print("Updated one previously empty credential entry").
    No new imports or methods are required; we simply adjust the string literals passed to print to avoid using tainted data.
Suggested changeset 1
pmoves/scripts/update_env_from_cgp.py

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/pmoves/scripts/update_env_from_cgp.py b/pmoves/scripts/update_env_from_cgp.py
--- a/pmoves/scripts/update_env_from_cgp.py
+++ b/pmoves/scripts/update_env_from_cgp.py
@@ -28,7 +28,7 @@
         if line.startswith(f"{label}="):
             lines[i] = f"{label}={value}"
             updated = True
-            print(f"Updated: {label}")
+            print("Updated one credential entry")
             break
 
     if not updated and f"{label}=" in env_content:
@@ -36,7 +36,7 @@
         for i, line in enumerate(lines):
             if line == f"{label}=":
                 lines[i] = f"{label}={value}"
-                print(f"Updated (empty): {label}")
+                print("Updated one previously empty credential entry")
                 break
 
 # Write back
EOF
@@ -28,7 +28,7 @@
if line.startswith(f"{label}="):
lines[i] = f"{label}={value}"
updated = True
print(f"Updated: {label}")
print("Updated one credential entry")
break

if not updated and f"{label}=" in env_content:
@@ -36,7 +36,7 @@
for i, line in enumerate(lines):
if line == f"{label}=":
lines[i] = f"{label}={value}"
print(f"Updated (empty): {label}")
print("Updated one previously empty credential entry")
break

# Write back
Copilot is powered by AI and may make mistakes. Always verify output.
Unable to commit as this autofix suggestion is now outdated
for i, line in enumerate(lines):
if line == f"{label}=":
lines[i] = f"{label}={value}"
print(f"Updated (empty): {label}")

Check failure

Code scanning / CodeQL

Clear-text logging of sensitive information

This expression logs [sensitive data (secret)](1) as clear text.

Copilot Autofix

AI 7 months ago

Generally, to fix clear-text logging of sensitive information, remove sensitive values (and often identifiers closely tied to them) from logs, or replace them with non-sensitive, non-identifying summaries (counts, generic messages, or redacted forms). Any code path that takes data from a secrets structure and passes it into a logging or printing function should be treated as suspect.

In this script, the label is derived from a secrets file and used in two log messages: print(f"Updated: {label}") and print(f"Updated (empty): {label}"). To preserve existing functionality while improving security, we can change these prints to avoid including the label. Since the primary function of the log is to show progress, we can instead print a generic message like "Updated a secret from CGP" and "Updated an empty secret from CGP", or similar wording, without interpolating label. This requires only small modifications to the two print statements within the loop (lines 31 and 39) in pmoves/scripts/update_env_from_cgp.py; no new imports or helper methods are needed.

Concretely:

  • In the first update branch (lines 28–32), replace print(f"Updated: {label}") with a non-identifying message, e.g. print("Updated a secret from CGP").
  • In the second branch for empty values (lines 36–40), replace print(f"Updated (empty): {label}") with something like print("Updated an empty secret from CGP").

No other changes are necessary to maintain behavior: the environment file content is unchanged; only log verbosity/contents are reduced.


Suggested changeset 1
pmoves/scripts/update_env_from_cgp.py

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/pmoves/scripts/update_env_from_cgp.py b/pmoves/scripts/update_env_from_cgp.py
--- a/pmoves/scripts/update_env_from_cgp.py
+++ b/pmoves/scripts/update_env_from_cgp.py
@@ -28,7 +28,7 @@
         if line.startswith(f"{label}="):
             lines[i] = f"{label}={value}"
             updated = True
-            print(f"Updated: {label}")
+            print("Updated a secret from CGP")
             break
 
     if not updated and f"{label}=" in env_content:
@@ -36,7 +36,7 @@
         for i, line in enumerate(lines):
             if line == f"{label}=":
                 lines[i] = f"{label}={value}"
-                print(f"Updated (empty): {label}")
+                print("Updated an empty secret from CGP")
                 break
 
 # Write back
EOF
@@ -28,7 +28,7 @@
if line.startswith(f"{label}="):
lines[i] = f"{label}={value}"
updated = True
print(f"Updated: {label}")
print("Updated a secret from CGP")
break

if not updated and f"{label}=" in env_content:
@@ -36,7 +36,7 @@
for i, line in enumerate(lines):
if line == f"{label}=":
lines[i] = f"{label}={value}"
print(f"Updated (empty): {label}")
print("Updated an empty secret from CGP")
break

# Write back
Copilot is powered by AI and may make mistakes. Always verify output.
Unable to commit as this autofix suggestion is now outdated

try:
# Create cache directory (must be inside try block for error handling)
cache_dir.mkdir(parents=True, exist_ok=True)

Check failure

Code scanning / CodeQL

Uncontrolled data used in path expression

This path depends on a [user-provided value](1).

Copilot Autofix

AI 7 months ago

In general, to fix uncontrolled data in path expressions, either (1) constrain user input to a safe subset (e.g., a whitelist of allowed characters or patterns), or (2) build the path under a fixed root and verify the normalized result stays within that root. Here the user-controlled value is not intended to be an arbitrary filesystem path; it is a Hugging Face model ID. The most compatible fix is to sanitize and validate hf_id before using it as a directory name, ensuring we only use safe characters and a reasonable maximum length, while still allowing all typical HF model IDs.

Concretely, we can introduce a small helper function, e.g. def _safe_model_cache_key(model_id: str) -> str, that:

  • Replaces / with -- (as currently).
  • Replaces or removes any characters outside a safe allow‑list (letters, digits, -, _, ., :).
  • Optionally truncates the resulting name to a reasonable length to avoid filesystem issues.
    Then we use this sanitized key when constructing cache_dir, i.e. safe_key = _safe_model_cache_key(hf_id) and cache_dir = Path(HF_HUB_CACHE) / "models" / safe_key. This preserves existing behavior for normal IDs while ensuring that even malicious values cannot inject problematic path characters.

All changes occur in pmoves/services/hf-mcp-server/main.py. We only need to add the helper function (no new imports beyond the standard library that is already in use) and adjust the cache_dir assignment to use it.

Suggested changeset 1
pmoves/services/hf-mcp-server/main.py

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/pmoves/services/hf-mcp-server/main.py b/pmoves/services/hf-mcp-server/main.py
--- a/pmoves/services/hf-mcp-server/main.py
+++ b/pmoves/services/hf-mcp-server/main.py
@@ -44,6 +44,27 @@
     level=logging.INFO,
     format="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
 )
+
+
+def _safe_model_cache_key(model_id: str) -> str:
+    """
+    Sanitize a model identifier so it can be safely used as a single
+    directory name under the model cache root.
+
+    This is not meant to restrict which Hugging Face models can be
+    requested; it only constrains how we map the ID onto the local
+    filesystem to avoid using untrusted strings directly as paths.
+    """
+    # Start from the normalized HF ID: replace path separators first.
+    key = model_id.replace("/", "--")
+
+    # Allow only a conservative set of characters in directory names.
+    allowed = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_.:"
+    key = "".join(c if c in allowed else "_" for c in key)
+
+    # Avoid excessively long directory names which could cause OS errors.
+    # 128 characters should be enough for realistic HF model IDs.
+    return key[:128]
 logger = logging.getLogger(__name__)
 
 # Environment variables
@@ -498,7 +519,9 @@
         hf_id = model_id
         model_data = {}
 
-    cache_dir = Path(HF_HUB_CACHE) / "models" / hf_id.replace("/", "--")
+    # Sanitize the model identifier before using it as a directory name.
+    safe_cache_key = _safe_model_cache_key(hf_id)
+    cache_dir = Path(HF_HUB_CACHE) / "models" / safe_cache_key
 
     try:
         # Create cache directory (must be inside try block for error handling)
EOF
@@ -44,6 +44,27 @@
level=logging.INFO,
format="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
)


def _safe_model_cache_key(model_id: str) -> str:
"""
Sanitize a model identifier so it can be safely used as a single
directory name under the model cache root.

This is not meant to restrict which Hugging Face models can be
requested; it only constrains how we map the ID onto the local
filesystem to avoid using untrusted strings directly as paths.
"""
# Start from the normalized HF ID: replace path separators first.
key = model_id.replace("/", "--")

# Allow only a conservative set of characters in directory names.
allowed = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_.:"
key = "".join(c if c in allowed else "_" for c in key)

# Avoid excessively long directory names which could cause OS errors.
# 128 characters should be enough for realistic HF model IDs.
return key[:128]
logger = logging.getLogger(__name__)

# Environment variables
@@ -498,7 +519,9 @@
hf_id = model_id
model_data = {}

cache_dir = Path(HF_HUB_CACHE) / "models" / hf_id.replace("/", "--")
# Sanitize the model identifier before using it as a directory name.
safe_cache_key = _safe_model_cache_key(hf_id)
cache_dir = Path(HF_HUB_CACHE) / "models" / safe_cache_key

try:
# Create cache directory (must be inside try block for error handling)
Copilot is powered by AI and may make mistakes. Always verify output.
Unable to commit as this autofix suggestion is now outdated

cache_dir = Path(HF_HUB_CACHE) / "models" / model_id.replace("/", "--")

if not cache_dir.exists():

Check failure

Code scanning / CodeQL

Uncontrolled data used in path expression

This path depends on a [user-provided value](1).

Copilot Autofix

AI 7 months ago

In general, the fix is to sanitize or validate any user-controlled value before using it to construct filesystem paths. For this case, we only need the cache subdirectory derived from model_id to be a safe name under HF_HUB_CACHE / "models". We can achieve this by (1) mapping the model ID to a directory-friendly name (already partially done with .replace("/", "--")), and (2) ensuring that the resulting name is free of problematic characters and does not become an absolute path or contain path separators. One robust approach is to reduce the name to a limited character set (for example, alphanumerics, dash, underscore, and dot), substituting anything else with a safe character like _. This preserves functionality (different model_ids will still map to deterministic directory names) while eliminating potential path traversal or weird-platform edge cases.

The single best fix here without changing existing behavior much is to introduce a small helper to "sanitize" the model-id-derived directory name, and then use that sanitized name instead of raw model_id.replace("/", "--") in hf_model_convert_gguf. Since we can only edit the shown file, we’ll:

  • Add an import re at the top (using only standard library).
  • Define a helper function, e.g. _safe_model_cache_dir_name(model_id: str) -> str, that:
    • Replaces / with -- (keeping existing behavior).
    • Uses a regex to replace any character not in [A-Za-z0-9._-] with _.
    • Optionally strips leading dots to avoid hidden or special names like ...
    • Ensures we never accidentally introduce path separators.
  • Change the construction of cache_dir in hf_model_convert_gguf (line 611) to use this helper instead of inlining model_id.replace("/", "--").

All changes will be within pmoves/services/hf-mcp-server/main.py. No other files are modified.

Suggested changeset 1
pmoves/services/hf-mcp-server/main.py

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/pmoves/services/hf-mcp-server/main.py b/pmoves/services/hf-mcp-server/main.py
--- a/pmoves/services/hf-mcp-server/main.py
+++ b/pmoves/services/hf-mcp-server/main.py
@@ -27,6 +27,7 @@
 from enum import Enum
 from pathlib import Path
 from typing import Any, Dict, List, Optional
+import re
 
 import aiohttp
 from fastapi import FastAPI, HTTPException
@@ -590,6 +591,26 @@
     return results
 
 
+def _safe_model_cache_dir_name(model_id: str) -> str:
+    """
+    Generate a safe directory name for a model ID under the cache root.
+
+    This preserves the existing convention of replacing "/" with "--",
+    and additionally restricts the resulting name to a safe character set.
+    """
+    # Preserve existing mapping from "org/model" -> "org--model"
+    name = model_id.replace("/", "--")
+    # Replace any character that is not alphanumeric, dot, underscore, or dash
+    name = re.sub(r"[^A-Za-z0-9._-]", "_", name)
+    # Avoid special/hidden names like ".", "..", or those starting with them
+    while name.startswith("."):
+        name = name[1:]
+    # Fallback to a generic name if everything was stripped
+    if not name:
+        name = "model"
+    return name
+
+
 async def hf_model_convert_gguf(
     model_id: str,
     quantize: str = "q4_0",
@@ -608,7 +629,8 @@
     # This is a placeholder - actual GGUF conversion requires llama.cpp
     # In production, this would spawn a conversion job or call an external service
 
-    cache_dir = Path(HF_HUB_CACHE) / "models" / model_id.replace("/", "--")
+    safe_dir_name = _safe_model_cache_dir_name(model_id)
+    cache_dir = Path(HF_HUB_CACHE) / "models" / safe_dir_name
 
     if not cache_dir.exists():
         raise HTTPException(
EOF
@@ -27,6 +27,7 @@
from enum import Enum
from pathlib import Path
from typing import Any, Dict, List, Optional
import re

import aiohttp
from fastapi import FastAPI, HTTPException
@@ -590,6 +591,26 @@
return results


def _safe_model_cache_dir_name(model_id: str) -> str:
"""
Generate a safe directory name for a model ID under the cache root.

This preserves the existing convention of replacing "/" with "--",
and additionally restricts the resulting name to a safe character set.
"""
# Preserve existing mapping from "org/model" -> "org--model"
name = model_id.replace("/", "--")
# Replace any character that is not alphanumeric, dot, underscore, or dash
name = re.sub(r"[^A-Za-z0-9._-]", "_", name)
# Avoid special/hidden names like ".", "..", or those starting with them
while name.startswith("."):
name = name[1:]
# Fallback to a generic name if everything was stripped
if not name:
name = "model"
return name


async def hf_model_convert_gguf(
model_id: str,
quantize: str = "q4_0",
@@ -608,7 +629,8 @@
# This is a placeholder - actual GGUF conversion requires llama.cpp
# In production, this would spawn a conversion job or call an external service

cache_dir = Path(HF_HUB_CACHE) / "models" / model_id.replace("/", "--")
safe_dir_name = _safe_model_cache_dir_name(model_id)
cache_dir = Path(HF_HUB_CACHE) / "models" / safe_dir_name

if not cache_dir.exists():
raise HTTPException(
Copilot is powered by AI and may make mistakes. Always verify output.
Unable to commit as this autofix suggestion is now outdated
hunnibear and others added 18 commits February 15, 2026 12:58
…udit

hardened: layered local bring-up + archon/env + monitoring alignment
@github-actions

Copy link
Copy Markdown
Contributor

Docker Hardening Validation

Hardening Validation Report

Validated: Mon 16 Feb 2026 12:21:05 AM UTC

Services Checked

PMOVES.AI Docker Hardening Validation

[INFO] Checking: pmoves/docker-compose.hardened.yml

[INFO] Validating: hi-rag-gateway-v2
[PASS] Non-root user: 65532:65532
[PASS] Read-only filesystem
[PASS] All capabilities dropped
[PASS] No-new-privileges enabled
[WARN] No resource limits

[INFO] Validating: extract-worker
[PASS] Non-root user: 65532:65532
[PASS] Read-only filesystem
[PASS] All capabilities dropped
[PASS] No-new-privileges enabled
[WARN] No resource limits

[INFO] Validating: langextract
[PASS] Non-root user: 65532:65532
[PASS] Read-only filesystem
[PASS] All capabilities dropped
[PASS] No-new-privileges enabled
[WARN] No resource limits

[INFO] Validating: presign
[PASS] Non-root user: 65532:65532
[PASS] Read-only filesystem
[PASS] All capabilities dropped
[PASS] No-new-privileges enabled
[WARN] No resource limits

[INFO] Validating: render-webhook
[PASS] Non-root user: 65532:65532
[PASS] Read-only filesystem
[PASS] All capabilities dropped
[PASS] No-new-privileges enabled
[WARN] No resource limits

[INFO] Validating: retrieval-eval
[PASS] Non-root user: 65532:65532
[PASS] Read-only filesystem
[PASS] All capabilities dropped
[PASS] No-new-privileges enabled
[WARN] No resource limits

[INFO] Validating: pdf-ingest
[PASS] Non-root user: 65532:65532
[PASS] Read-only filesystem
[PASS] All capabilities dropped
[PASS] No-new-privileges enabled
[WARN] No resource limits

[INFO] Validating: jellyfin-bridge
[PASS] Non-root user: 65532:65532
[PASS] Read-only filesystem
[PASS] All capabilities dropped
[PASS] No-new-privileges enabled
[WARN] No resource limits

[INFO] Validating: invidious-companion-proxy
[PASS] Non-root user: 65532:65532
[PASS] Read-only filesystem
[PASS] All capabilities dropped
[PASS] No-new-privileges enabled
[WARN] No resource limits

[INFO] Validating: ffmpeg-whisper
[PASS] Non-root user: 65532:65532
[PASS] Read-only filesystem
[PASS] All capabilities dropped
[PASS] No-new-privileges enabled
[WARN] No resource limits

[INFO] Validating: media-video
[PASS] Non-root user: 65532:65532
[PASS] Read-only filesystem
[PASS] All capabilities dropped
[PASS] No-new-privileges enabled
[WARN] No resource limits

[INFO] Validating: media-audio
[PASS] Non-root user: 65532:65532
[PASS] Read-only filesystem
[PASS] All capabilities dropped
[PASS] No-new-privileges enabled
[WARN] No resource limits

[INFO] Validating: hi-rag-gateway-v2-gpu
[PASS] Non-root user: 65532:65532
[PASS] Read-only filesystem
[PASS] All capabilities dropped
[PASS] No-new-privileges enabled
[WARN] No resource limits

[INFO] Validating: hi-rag-gateway-gpu
[PASS] Non-root user: 65532:65532
[PASS] Read-only filesystem
[PASS] All capabilities dropped
[PASS] No-new-privileges enabled
[WARN] No resource limits

[INFO] Validating: deepresearch
[PASS] Non-root user: 65532:65532
[PASS] Read-only filesystem
[PASS] All capabilities dropped
[PASS] No-new-privileges enabled
[WARN] No resource limits

[INFO] Validating: supaserch
[PASS] Non-root user: 65532:65532
[PASS] Read-only filesystem
[PASS] All capabilities dropped
[PASS] No-new-privileges enabled
[WARN] No resource limits

[INFO] Validating: publisher-discord
[PASS] Non-root user: 65532:65532
[PASS] Read-only filesystem
[PASS] All capabilities dropped
[PASS] No-new-privileges enabled
[WARN] No resource limits

[INFO] Validating: mesh-agent
[PASS] Non-root user: 65532:65532
[PASS] Read-only filesystem
[PASS] All capabilities dropped
[PASS] No-new-privileges enabled
[WARN] No resource limits

[INFO] Validating: nats-echo-req
[PASS] Non-root user: 65532:65532
[PASS] Read-only filesystem
[PASS] All capabilities dropped
[PASS] No-new-privileges enabled
[WARN] No resource limits

[INFO] Validating: nats-echo-res
[PASS] Non-root user: 65532:65532
[PASS] Read-only filesystem
[PASS] All capabilities dropped
[PASS] No-new-privileges enabled
[WARN] No resource limits

[INFO] Validating: publisher
[PASS] Non-root user: 65532:65532
[PASS] Read-only filesystem
[PASS] All capabilities dropped
[PASS] No-new-privileges enabled
[WARN] No resource limits

[INFO] Validating: analysis-echo
[PASS] Non-root user: 65532:65532
[PASS] Read-only filesystem
[PASS] All capabilities dropped
[PASS] No-new-privileges enabled
[WARN] No resource limits

[INFO] Validating: graph-linker
[PASS] Non-root user: 65532:65532
[PASS] Read-only filesystem
[PASS] All capabilities dropped
[PASS] No-new-privileges enabled
[WARN] No resource limits

[INFO] Validating: comfy-watcher
[PASS] Non-root user: 65532:65532
[PASS] Read-only filesystem
[PASS] All capabilities dropped
[PASS] No-new-privileges enabled
[WARN] No resource limits

[INFO] Validating: grayjay-plugin-host
[PASS] Non-root user: 65532:65532
[PASS] Read-only filesystem
[PASS] All capabilities dropped
[PASS] No-new-privileges enabled
[WARN] No resource limits

[INFO] Validating: agent-zero
[PASS] Non-root user: 65532:65532
[PASS] Read-only filesystem
[PASS] All capabilities dropped
[PASS] No-new-privileges enabled
[WARN] No resource limits

[INFO] Validating: archon
[PASS] Non-root user: 65532:65532
[PASS] Read-only filesystem
[PASS] All capabilities dropped
[PASS] No-new-privileges enabled
[WARN] No resource limits

[INFO] Validating: channel-monitor
[PASS] Non-root user: 65532:65532
[PASS] Read-only filesystem
[PASS] All capabilities dropped
[PASS] No-new-privileges enabled
[WARN] No resource limits

[INFO] Validating: pmoves-yt
[PASS] Non-root user: 65532:65532
[PASS] Read-only filesystem
[PASS] All capabilities dropped
[PASS] No-new-privileges enabled
[WARN] No resource limits

[INFO] Validating: notebook-sync
[PASS] Non-root user: 65532:65532
[PASS] Read-only filesystem
[PASS] All capabilities dropped
[PASS] No-new-privileges enabled
[WARN] No resource limits

[INFO] Validating: supabase_service_role_key
[WARN] No user directive
[WARN] No read_only directive
[WARN] No cap_drop: ["ALL"]
[WARN] No no-new-privileges
[WARN] No resource limits

[INFO] Validating: supabase_jwt_secret
[WARN] No user directive
[WARN] No read_only directive
[WARN] No cap_drop: ["ALL"]
[WARN] No no-new-privileges
[WARN] No resource limits

======================================
Summary: 120 passed, 40 warnings, 0 errors

@POWERFULMOVES
POWERFULMOVES merged commit ba606b7 into main Feb 16, 2026
37 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants