From cbf0da9ee058f44af7c07d46774a4a5b8074213f Mon Sep 17 00:00:00 2001 From: AlexFucuson9 Date: Sat, 11 Jul 2026 20:10:33 +0700 Subject: [PATCH] fix: add usedforsecurity=False to hashlib md5/sha1 calls in skills and web_server MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit hashlib.md5() and hashlib.sha1() without usedforsecurity=False crash on FIPS-enabled systems (RHEL 8/9, Ubuntu FIPS) with ValueError: "EVP_DigestInit_ex disabled for FIPS". All uses are non-security (cache keys, deduplication, file hashing). Affected files: - tools/skills_sync.py: _dir_hash() — 1 md5 call - tools/skills_hub.py: cache key generation — 5 md5 calls - hermes_cli/web_server.py: action name slugs — 2 sha1 calls Refs: #56715, #56716, #56719 (prior FIPS fixes in other modules) --- hermes_cli/web_server.py | 4 ++-- tools/skills_hub.py | 10 +++++----- tools/skills_sync.py | 2 +- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/hermes_cli/web_server.py b/hermes_cli/web_server.py index ecd47ab294307..2603bbcefe949 100644 --- a/hermes_cli/web_server.py +++ b/hermes_cli/web_server.py @@ -11137,7 +11137,7 @@ def _mcp_install_action_name(name: str) -> str: re-click or a second catalog install doesn't overwrite the first's tracked process/log while its git clone is still running.""" slug = re.sub(r"[^a-z0-9]+", "-", name.lower()).strip("-")[:48] or "server" - digest = hashlib.sha1(name.encode()).hexdigest()[:8] + digest = hashlib.sha1(name.encode(), usedforsecurity=False).hexdigest()[:8] action = f"mcp-install-{slug}-{digest}" _ACTION_LOG_FILES.setdefault(action, f"action-{action}.log") return action @@ -12076,7 +12076,7 @@ def _hub_action_name(verb: str, key: str) -> str: (readable) + hash (collision-proof) keys each action to its own row. """ slug = re.sub(r"[^a-z0-9]+", "-", key.lower()).strip("-")[:48] or "skill" - digest = hashlib.sha1(key.encode()).hexdigest()[:8] + digest = hashlib.sha1(key.encode(), usedforsecurity=False).hexdigest()[:8] name = f"skills-{verb}-{slug}-{digest}" _ACTION_LOG_FILES.setdefault(name, f"action-{name}.log") return name diff --git a/tools/skills_hub.py b/tools/skills_hub.py index 9883b720ee025..d5b59c907dfbc 100644 --- a/tools/skills_hub.py +++ b/tools/skills_hub.py @@ -1267,7 +1267,7 @@ def _parse_identifier(self, identifier: str) -> Optional[dict]: } def _parse_index(self, index_url: str) -> Optional[dict]: - cache_key = f"well_known_index_{hashlib.md5(index_url.encode()).hexdigest()}" + cache_key = f"well_known_index_{hashlib.md5(index_url.encode(), usedforsecurity=False).hexdigest()}" cached = _read_index_cache(cache_key) if isinstance(cached, dict) and isinstance(cached.get("skills"), list): return cached @@ -1532,7 +1532,7 @@ def search(self, query: str, limit: int = 10) -> List[SkillMeta]: # entries; the sitemap walks the full ~20k+ catalog. return self._sitemap_catalog(limit) - cache_key = f"skills_sh_search_{hashlib.md5(f'{query}|{limit}'.encode()).hexdigest()}" + cache_key = f"skills_sh_search_{hashlib.md5(f'{query}|{limit}'.encode(), usedforsecurity=False).hexdigest()}" cached = _read_index_cache(cache_key) if cached is not None: return [SkillMeta(**item) for item in cached][:limit] @@ -1759,7 +1759,7 @@ def _meta_from_search_item(self, item: dict) -> Optional[SkillMeta]: ) def _fetch_detail_page(self, identifier: str) -> Optional[dict]: - cache_key = f"skills_sh_detail_{hashlib.md5(identifier.encode()).hexdigest()}" + cache_key = f"skills_sh_detail_{hashlib.md5(identifier.encode(), usedforsecurity=False).hexdigest()}" cached = _read_index_cache(cache_key) if isinstance(cached, dict): return cached @@ -2253,7 +2253,7 @@ def search(self, query: str, limit: int = 10) -> List[SkillMeta]: # Non-empty query catalog miss, or catalog walker failure: fall back to # the lightweight listing API for a best-effort response. - cache_key = f"clawhub_search_listing_v1_{hashlib.md5(query.encode()).hexdigest()}_{limit}" + cache_key = f"clawhub_search_listing_v1_{hashlib.md5(query.encode(), usedforsecurity=False).hexdigest()}_{limit}" cached = _read_index_cache(cache_key) if cached is not None: return self._finalize_search_results( @@ -2359,7 +2359,7 @@ def inspect(self, identifier: str) -> Optional[SkillMeta]: ) def _search_catalog(self, query: str, limit: int = 10) -> List[SkillMeta]: - cache_key = f"clawhub_search_catalog_v1_{hashlib.md5(f'{query}|{limit}'.encode()).hexdigest()}" + cache_key = f"clawhub_search_catalog_v1_{hashlib.md5(f'{query}|{limit}'.encode(), usedforsecurity=False).hexdigest()}" cached = _read_index_cache(cache_key) if cached is not None: return [SkillMeta(**s) for s in cached][:limit] diff --git a/tools/skills_sync.py b/tools/skills_sync.py index 2c0f41c47a62d..381e1ade1d030 100644 --- a/tools/skills_sync.py +++ b/tools/skills_sync.py @@ -231,7 +231,7 @@ def _compute_relative_dest(skill_dir: Path, bundled_dir: Path) -> Path: def _dir_hash(directory: Path) -> str: """Compute a hash of all file contents in a directory for change detection.""" - hasher = hashlib.md5() + hasher = hashlib.md5(usedforsecurity=False) try: for fpath in sorted(directory.rglob("*")): if fpath.is_file():