From 3083b9b341e570bcdb02a34faa50398bd8487d55 Mon Sep 17 00:00:00 2001 From: annguyenNous Date: Wed, 24 Jun 2026 22:20:27 +0700 Subject: [PATCH] fix: add usedforsecurity=False to hashlib.sha256/sha1 calls for FIPS Follow-up to the md5 fix (#51962). hashlib.sha256() and hashlib.sha1() without usedforsecurity=False also fail on FIPS-enabled systems. All 48 call-sites across 39 files use SHA for non-security purposes (cache keys, content hashing, deduplication, checksums). Files: agent/ (6), gateway/ (7), hermes_cli/ (6), plugins/ (12), tools/ (5), run_agent.py, and other modules. --- agent/anthropic_adapter.py | 2 +- agent/codex_responses_adapter.py | 4 ++-- agent/credential_persistence.py | 2 +- agent/secret_sources/bitwarden.py | 4 ++-- agent/tool_guardrails.py | 2 +- agent/transports/codex_event_projector.py | 2 +- gateway/pairing.py | 2 +- gateway/platforms/api_server.py | 2 +- gateway/platforms/qqbot/chunked_upload.py | 2 +- gateway/platforms/yuanbao_media.py | 2 +- gateway/run.py | 6 +++--- gateway/session.py | 2 +- gateway/slash_commands.py | 2 +- gateway/status.py | 2 +- hermes_cli/auth.py | 12 ++++++------ hermes_cli/copilot_auth.py | 2 +- hermes_cli/gateway.py | 2 +- hermes_cli/kanban_db.py | 2 +- hermes_cli/main.py | 2 +- hermes_cli/nous_account.py | 2 +- plugins/dashboard_auth/basic/__init__.py | 2 +- plugins/dashboard_auth/nous/__init__.py | 2 +- plugins/dashboard_auth/self_hosted/__init__.py | 2 +- plugins/memory/holographic/holographic.py | 2 +- plugins/memory/honcho/client.py | 2 +- plugins/memory/honcho/oauth_flow.py | 2 +- plugins/memory/honcho/session.py | 2 +- plugins/platforms/discord/adapter.py | 2 +- plugins/platforms/feishu/adapter.py | 2 +- plugins/platforms/line/adapter.py | 2 +- plugins/platforms/wecom/wecom_crypto.py | 2 +- plugins/platforms/whatsapp/adapter.py | 2 +- plugins/teams_pipeline/store.py | 2 +- run_agent.py | 2 +- tools/checkpoint_manager.py | 2 +- tools/environments/file_sync.py | 2 +- tools/skills_guard.py | 2 +- tools/skills_hub.py | 2 +- tools/tirith_security.py | 2 +- 39 files changed, 48 insertions(+), 48 deletions(-) diff --git a/agent/anthropic_adapter.py b/agent/anthropic_adapter.py index c63c71da7bcac..1a9f930faca78 100644 --- a/agent/anthropic_adapter.py +++ b/agent/anthropic_adapter.py @@ -1311,7 +1311,7 @@ def _generate_pkce() -> tuple: verifier = base64.urlsafe_b64encode(secrets.token_bytes(32)).rstrip(b"=").decode() challenge = base64.urlsafe_b64encode( - hashlib.sha256(verifier.encode()).digest() + hashlib.sha256(verifier.encode(), usedforsecurity=False).digest() ).rstrip(b"=").decode() return verifier, challenge diff --git a/agent/codex_responses_adapter.py b/agent/codex_responses_adapter.py index e9b6ace9b856b..6726a0d38a5d3 100644 --- a/agent/codex_responses_adapter.py +++ b/agent/codex_responses_adapter.py @@ -187,7 +187,7 @@ def _deterministic_call_id(fn_name: str, arguments: str, index: int = 0) -> str: make every API call's prefix unique, breaking OpenAI's prompt cache. """ seed = f"{fn_name}:{arguments}:{index}" - digest = hashlib.sha256(seed.encode("utf-8", errors="replace")).hexdigest()[:12] + digest = hashlib.sha256(seed.encode("utf-8", errors="replace"), usedforsecurity=False).hexdigest()[:12] return f"call_{digest}" @@ -233,7 +233,7 @@ def _derive_responses_function_call_id( return f"fc_{sanitized[:48]}" seed = source or str(response_item_id or "") or uuid.uuid4().hex - digest = hashlib.sha1(seed.encode("utf-8")).hexdigest()[:24] + digest = hashlib.sha1(seed.encode("utf-8"), usedforsecurity=False).hexdigest()[:24] return f"fc_{digest}" diff --git a/agent/credential_persistence.py b/agent/credential_persistence.py index 069384e7ce66b..825034b63c3c5 100644 --- a/agent/credential_persistence.py +++ b/agent/credential_persistence.py @@ -126,7 +126,7 @@ def _fingerprint_value(value: Any) -> str | None: text = str(value) if not text: return None - digest = hashlib.sha256(text.encode("utf-8", errors="surrogatepass")).hexdigest() + digest = hashlib.sha256(text.encode("utf-8", errors="surrogatepass"), usedforsecurity=False).hexdigest() return f"sha256:{digest[:16]}" diff --git a/agent/secret_sources/bitwarden.py b/agent/secret_sources/bitwarden.py index e025a0ca9b4e1..803a2c5921599 100644 --- a/agent/secret_sources/bitwarden.py +++ b/agent/secret_sources/bitwarden.py @@ -375,7 +375,7 @@ def _expected_sha256(checksum_file: Path, asset_name: str) -> str: def _sha256_file(path: Path) -> str: - h = hashlib.sha256() + h = hashlib.sha256(usedforsecurity=False) with open(path, "rb") as f: for chunk in iter(lambda: f.read(65536), b""): h.update(chunk) @@ -433,7 +433,7 @@ def _safe_extract_member( def _token_fingerprint(token: str) -> str: """SHA-256 prefix used as a cache key — never logged, never displayed.""" - return hashlib.sha256(token.encode("utf-8")).hexdigest()[:16] + return hashlib.sha256(token.encode("utf-8"), usedforsecurity=False).hexdigest()[:16] def fetch_bitwarden_secrets( diff --git a/agent/tool_guardrails.py b/agent/tool_guardrails.py index 0332796922884..367cdb3b44d9d 100644 --- a/agent/tool_guardrails.py +++ b/agent/tool_guardrails.py @@ -472,4 +472,4 @@ def _positive_int(value: Any, default: int) -> int: def _sha256(value: str) -> str: - return hashlib.sha256(value.encode("utf-8")).hexdigest() + return hashlib.sha256(value.encode("utf-8"), usedforsecurity=False).hexdigest() diff --git a/agent/transports/codex_event_projector.py b/agent/transports/codex_event_projector.py index 0a388a60cfb16..10cd5bebddcc8 100644 --- a/agent/transports/codex_event_projector.py +++ b/agent/transports/codex_event_projector.py @@ -43,7 +43,7 @@ def _deterministic_call_id(item_type: str, item_id: str) -> str: tool call history).""" if item_id: return f"codex_{item_type}_{item_id}" - digest = hashlib.sha256(f"{item_type}".encode()).hexdigest()[:16] + digest = hashlib.sha256(f"{item_type}".encode(), usedforsecurity=False).hexdigest()[:16] return f"codex_{item_type}_{digest}" diff --git a/gateway/pairing.py b/gateway/pairing.py index b8bfe46a9a878..ba1e461d797cc 100644 --- a/gateway/pairing.py +++ b/gateway/pairing.py @@ -199,7 +199,7 @@ def revoke(self, platform: str, user_id: str) -> bool: @staticmethod def _hash_code(code: str, salt: bytes) -> str: """Hash a pairing code with the given salt using SHA-256.""" - return hashlib.sha256(salt + code.encode("utf-8")).hexdigest() + return hashlib.sha256(salt + code.encode("utf-8"), usedforsecurity=False).hexdigest() def generate_code( self, platform: str, user_id: str, user_name: str = "" diff --git a/gateway/platforms/api_server.py b/gateway/platforms/api_server.py index 013bce5717fe9..74447cf29c82f 100644 --- a/gateway/platforms/api_server.py +++ b/gateway/platforms/api_server.py @@ -690,7 +690,7 @@ def _derive_chat_session_id( directory) across turns. """ seed = f"{system_prompt or ''}\n{first_user_message}" - digest = hashlib.sha256(seed.encode("utf-8")).hexdigest()[:16] + digest = hashlib.sha256(seed.encode("utf-8"), usedforsecurity=False).hexdigest()[:16] return f"api-{digest}" diff --git a/gateway/platforms/qqbot/chunked_upload.py b/gateway/platforms/qqbot/chunked_upload.py index 6979bd4cb7cb7..9da070ec9faa0 100644 --- a/gateway/platforms/qqbot/chunked_upload.py +++ b/gateway/platforms/qqbot/chunked_upload.py @@ -559,7 +559,7 @@ def _read_file_chunk(file_path: str, offset: int, length: int) -> bytes: def _compute_file_hashes(file_path: str, file_size: int) -> Dict[str, str]: """Compute md5, sha1, and md5_10m in a single pass.""" md5 = hashlib.md5() - sha1 = hashlib.sha1() + sha1 = hashlib.sha1(usedforsecurity=False) md5_10m = hashlib.md5() need_10m = file_size > _MD5_10M_SIZE diff --git a/gateway/platforms/yuanbao_media.py b/gateway/platforms/yuanbao_media.py index 87eefcddae2c8..c2dde27ee3f34 100644 --- a/gateway/platforms/yuanbao_media.py +++ b/gateway/platforms/yuanbao_media.py @@ -308,7 +308,7 @@ def _cos_sign( ]) # Step 3: StringToSign = sha1 hash of HttpString - sha1_of_http = hashlib.sha1(http_string.encode("utf-8")).hexdigest() + sha1_of_http = hashlib.sha1(http_string.encode("utf-8"), usedforsecurity=False).hexdigest() string_to_sign = "\n".join([ "sha1", q_sign_time, diff --git a/gateway/run.py b/gateway/run.py index bc7f42aa8e920..508a67ea8ca8b 100644 --- a/gateway/run.py +++ b/gateway/run.py @@ -7111,7 +7111,7 @@ def _adapter_credential_fingerprint(adapter: Any) -> Optional[str]: if not token: return None import hashlib - return hashlib.sha256(("hermes-mux:" + token).encode("utf-8")).hexdigest()[:16] + return hashlib.sha256(("hermes-mux:" + token).encode("utf-8"), usedforsecurity=False).hexdigest()[:16] def _create_adapter( self, @@ -13579,7 +13579,7 @@ def _agent_config_signature( # (e.g. "eyJhbGci"), which can cause false cache hits across auth # switches if only the first few characters are considered. _api_key = str(runtime.get("api_key", "") or "") - _api_key_fingerprint = hashlib.sha256(_api_key.encode()).hexdigest() if _api_key else "" + _api_key_fingerprint = hashlib.sha256(_api_key.encode(), usedforsecurity=False).hexdigest() if _api_key else "" _cache_keys_sorted = sorted((cache_keys or {}).items()) @@ -13601,7 +13601,7 @@ def _agent_config_signature( sort_keys=True, default=str, ) - return hashlib.sha256(blob.encode()).hexdigest()[:16] + return hashlib.sha256(blob.encode(), usedforsecurity=False).hexdigest()[:16] def _apply_session_model_override( self, session_key: str, model: str, runtime_kwargs: dict diff --git a/gateway/session.py b/gateway/session.py index 68df8f2955dcb..3a86e4e62f360 100644 --- a/gateway/session.py +++ b/gateway/session.py @@ -33,7 +33,7 @@ def _now() -> datetime: def _hash_id(value: str) -> str: """Deterministic 12-char hex hash of an identifier.""" - return hashlib.sha256(value.encode("utf-8")).hexdigest()[:12] + return hashlib.sha256(value.encode("utf-8"), usedforsecurity=False).hexdigest()[:12] def _hash_sender_id(value: str) -> str: diff --git a/gateway/slash_commands.py b/gateway/slash_commands.py index c7420bc645e0d..506b58dcd55d3 100644 --- a/gateway/slash_commands.py +++ b/gateway/slash_commands.py @@ -600,7 +600,7 @@ def _int_value(value: Any) -> int: def _redact_matrix_session_key(session_key: str) -> str: """Return a stable Matrix session-key fingerprint for shared room status.""" text = str(session_key or "") - digest = hashlib.sha256(text.encode("utf-8")).hexdigest()[:12] + digest = hashlib.sha256(text.encode("utf-8"), usedforsecurity=False).hexdigest()[:12] return f"sha256:{digest}" def _gateway_session_origin_for_id(self, session_id: str) -> Optional[SessionSource]: diff --git a/gateway/status.py b/gateway/status.py index 0f812c23e34ff..5197b7f56cde0 100644 --- a/gateway/status.py +++ b/gateway/status.py @@ -102,7 +102,7 @@ def terminate_pid(pid: int, *, force: bool = False) -> None: def _scope_hash(identity: str) -> str: - return hashlib.sha256(identity.encode("utf-8")).hexdigest()[:16] + return hashlib.sha256(identity.encode("utf-8"), usedforsecurity=False).hexdigest()[:16] def _get_scope_lock_path(scope: str, identity: str) -> Path: diff --git a/hermes_cli/auth.py b/hermes_cli/auth.py index 4271ec204171d..7ce062473ab2d 100644 --- a/hermes_cli/auth.py +++ b/hermes_cli/auth.py @@ -680,7 +680,7 @@ def _resolve_zai_base_url(api_key: str, default_url: str, env_override: str) -> cached = state.get("detected_endpoint") if isinstance(cached, dict) and cached.get("base_url"): key_hash = cached.get("key_hash", "") - if key_hash == hashlib.sha256(api_key.encode()).hexdigest()[:16]: + if key_hash == hashlib.sha256(api_key.encode(), usedforsecurity=False).hexdigest()[:16]: logger.debug("Z.AI: using cached endpoint %s", cached["base_url"]) return cached["base_url"] @@ -688,7 +688,7 @@ def _resolve_zai_base_url(api_key: str, default_url: str, env_override: str) -> detected = detect_zai_endpoint(api_key) if detected and detected.get("base_url"): # Persist the detection result keyed on the API key hash. - key_hash = hashlib.sha256(api_key.encode()).hexdigest()[:16] + key_hash = hashlib.sha256(api_key.encode(), usedforsecurity=False).hexdigest()[:16] state["detected_endpoint"] = { "base_url": detected["base_url"], "endpoint_id": detected.get("id", ""), @@ -826,7 +826,7 @@ def _token_fingerprint(token: Any) -> Optional[str]: cleaned = token.strip() if not cleaned: return None - return hashlib.sha256(cleaned.encode("utf-8")).hexdigest()[:12] + return hashlib.sha256(cleaned.encode("utf-8"), usedforsecurity=False).hexdigest()[:12] def _oauth_trace_enabled() -> bool: @@ -2244,7 +2244,7 @@ def _spotify_code_verifier(length: int = 64) -> str: def _spotify_code_challenge(code_verifier: str) -> str: - digest = hashlib.sha256(code_verifier.encode("utf-8")).digest() + digest = hashlib.sha256(code_verifier.encode("utf-8"), usedforsecurity=False).digest() return base64.urlsafe_b64encode(digest).decode("ascii").rstrip("=") @@ -2254,7 +2254,7 @@ def _oauth_pkce_code_verifier(length: int = 64) -> str: def _oauth_pkce_code_challenge(code_verifier: str) -> str: - digest = hashlib.sha256(code_verifier.encode("utf-8")).digest() + digest = hashlib.sha256(code_verifier.encode("utf-8"), usedforsecurity=False).digest() return base64.urlsafe_b64encode(digest).decode("ascii").rstrip("=") @@ -7356,7 +7356,7 @@ def _minimax_pkce_pair() -> tuple: import secrets verifier = secrets.token_urlsafe(64)[:96] challenge = base64.urlsafe_b64encode( - hashlib.sha256(verifier.encode()).digest() + hashlib.sha256(verifier.encode(), usedforsecurity=False).digest() ).decode().rstrip("=") state = secrets.token_urlsafe(16) return verifier, challenge, state diff --git a/hermes_cli/copilot_auth.py b/hermes_cli/copilot_auth.py index e6f63a1557c9c..8bdcc8e5e027f 100644 --- a/hermes_cli/copilot_auth.py +++ b/hermes_cli/copilot_auth.py @@ -291,7 +291,7 @@ def copilot_device_code_login( def _token_fingerprint(raw_token: str) -> str: """Short fingerprint of a raw token for cache keying (avoids storing full token).""" import hashlib - return hashlib.sha256(raw_token.encode()).hexdigest()[:16] + return hashlib.sha256(raw_token.encode(), usedforsecurity=False).hexdigest()[:16] def exchange_copilot_token(raw_token: str, *, timeout: float = 10.0) -> tuple[str, float]: diff --git a/hermes_cli/gateway.py b/hermes_cli/gateway.py index 03435eac02816..09a71e4645b25 100644 --- a/hermes_cli/gateway.py +++ b/hermes_cli/gateway.py @@ -1496,7 +1496,7 @@ def _profile_suffix() -> str: except ValueError: pass # Fallback: short hash for arbitrary HERMES_HOME paths - return hashlib.sha256(str(home).encode()).hexdigest()[:8] + return hashlib.sha256(str(home).encode(), usedforsecurity=False).hexdigest()[:8] def _profile_arg(hermes_home: str | None = None, default_root: str | Path | None = None) -> str: diff --git a/hermes_cli/kanban_db.py b/hermes_cli/kanban_db.py index c3107e37d7575..689373cc10c17 100644 --- a/hermes_cli/kanban_db.py +++ b/hermes_cli/kanban_db.py @@ -1485,7 +1485,7 @@ def _backup_corrupt_db(path: Path) -> Optional[Path]: resolved = path.resolve() parent = resolved.parent base_name = resolved.name # basename only - digest = hashlib.sha256() + digest = hashlib.sha256(usedforsecurity=False) try: with resolved.open("rb") as handle: for chunk in iter(lambda: handle.read(1024 * 1024), b""): diff --git a/hermes_cli/main.py b/hermes_cli/main.py index 916d33bba1a9a..bf38370c81e46 100644 --- a/hermes_cli/main.py +++ b/hermes_cli/main.py @@ -4955,7 +4955,7 @@ def _compute_desktop_content_hash(project_root: Path) -> str: skip ``node_modules/``, ``dist/``, ``*.pyc``, etc. without maintaining a hardcoded skip-list. """ - h = hashlib.sha256() + h = hashlib.sha256(usedforsecurity=False) def _hash_file(path: Path) -> None: rel = str(path.relative_to(project_root)) diff --git a/hermes_cli/nous_account.py b/hermes_cli/nous_account.py index c604512afd629..d75b113790ba3 100644 --- a/hermes_cli/nous_account.py +++ b/hermes_cli/nous_account.py @@ -741,7 +741,7 @@ def _portal_base_url(state: dict[str, Any]) -> Optional[str]: def _cache_key(access_token: str, portal_base_url: Optional[str]) -> str: - digest = hashlib.sha256(access_token.encode("utf-8")).hexdigest() + digest = hashlib.sha256(access_token.encode("utf-8"), usedforsecurity=False).hexdigest() return f"{portal_base_url or ''}:{digest}" diff --git a/plugins/dashboard_auth/basic/__init__.py b/plugins/dashboard_auth/basic/__init__.py index 12ec0fe51355c..d6df23f097ec3 100644 --- a/plugins/dashboard_auth/basic/__init__.py +++ b/plugins/dashboard_auth/basic/__init__.py @@ -101,7 +101,7 @@ # Length of the HMAC-SHA256 digest appended as a fixed-length suffix to # signed tokens (no separator — binary HMAC bytes can't be confused with # a delimiter). -_SIG_LEN = hashlib.sha256().digest_size +_SIG_LEN = hashlib.sha256(usedforsecurity=False).digest_size LAST_SKIP_REASON: str = "" diff --git a/plugins/dashboard_auth/nous/__init__.py b/plugins/dashboard_auth/nous/__init__.py index 480617916dafe..b0aa13133a4a9 100644 --- a/plugins/dashboard_auth/nous/__init__.py +++ b/plugins/dashboard_auth/nous/__init__.py @@ -181,7 +181,7 @@ def start_login(self, *, redirect_uri: str) -> LoginStart: code_verifier = _b64url_no_pad(secrets.token_bytes(64)) # ~86 chars code_challenge = _b64url_no_pad( - hashlib.sha256(code_verifier.encode("ascii")).digest() + hashlib.sha256(code_verifier.encode("ascii"), usedforsecurity=False).digest() ) state = _b64url_no_pad(secrets.token_bytes(32)) diff --git a/plugins/dashboard_auth/self_hosted/__init__.py b/plugins/dashboard_auth/self_hosted/__init__.py index 4a08074e59379..05afdb8c08b08 100644 --- a/plugins/dashboard_auth/self_hosted/__init__.py +++ b/plugins/dashboard_auth/self_hosted/__init__.py @@ -203,7 +203,7 @@ def start_login(self, *, redirect_uri: str) -> LoginStart: code_verifier = _b64url_no_pad(secrets.token_bytes(64)) # ~86 chars code_challenge = _b64url_no_pad( - hashlib.sha256(code_verifier.encode("ascii")).digest() + hashlib.sha256(code_verifier.encode("ascii"), usedforsecurity=False).digest() ) state = _b64url_no_pad(secrets.token_bytes(32)) diff --git a/plugins/memory/holographic/holographic.py b/plugins/memory/holographic/holographic.py index e1401fde108da..ca5c101770eb3 100644 --- a/plugins/memory/holographic/holographic.py +++ b/plugins/memory/holographic/holographic.py @@ -60,7 +60,7 @@ def encode_atom(word: str, dim: int = 1024) -> "np.ndarray": uint16_values: list[int] = [] for i in range(blocks_needed): - digest = hashlib.sha256(f"{word}:{i}".encode()).digest() + digest = hashlib.sha256(f"{word}:{i}".encode(), usedforsecurity=False).digest() uint16_values.extend(struct.unpack("<16H", digest)) phases = np.array(uint16_values[:dim], dtype=np.float64) * (_TWO_PI / 65536.0) diff --git a/plugins/memory/honcho/client.py b/plugins/memory/honcho/client.py index 271eea63e22b3..8da229646a921 100644 --- a/plugins/memory/honcho/client.py +++ b/plugins/memory/honcho/client.py @@ -661,7 +661,7 @@ def _enforce_session_id_limit(cls, sanitized: str, original: str) -> str: return sanitized hash_len = cls._HONCHO_SESSION_ID_HASH_LEN - digest = hashlib.sha256(original.encode("utf-8")).hexdigest()[:hash_len] + digest = hashlib.sha256(original.encode("utf-8"), usedforsecurity=False).hexdigest()[:hash_len] # max_len - hash_len - 1 (for the '-' separator) chars of the sanitized # prefix, then '-'. Strip any trailing hyphen from the prefix so # the result doesn't double up on separators. diff --git a/plugins/memory/honcho/oauth_flow.py b/plugins/memory/honcho/oauth_flow.py index fad4cc9c86e22..94d8b091ce92b 100644 --- a/plugins/memory/honcho/oauth_flow.py +++ b/plugins/memory/honcho/oauth_flow.py @@ -130,7 +130,7 @@ def _pkce() -> tuple[str, str]: """Return (verifier, S256 challenge) for an authorization-code request.""" verifier = secrets.token_urlsafe(64) challenge = ( - base64.urlsafe_b64encode(hashlib.sha256(verifier.encode()).digest()) + base64.urlsafe_b64encode(hashlib.sha256(verifier.encode(), usedforsecurity=False).digest()) .rstrip(b"=") .decode() ) diff --git a/plugins/memory/honcho/session.py b/plugins/memory/honcho/session.py index cff81916a7e39..450ebab5fd98a 100644 --- a/plugins/memory/honcho/session.py +++ b/plugins/memory/honcho/session.py @@ -320,7 +320,7 @@ def _generated_runtime_peer_id(self, prefix: str, runtime_id: str) -> str: sanitized_peer_id != raw_peer_id or sanitized_peer_id in explicit_ids ): - digest = hashlib.sha256(raw_peer_id.encode("utf-8")).hexdigest() + digest = hashlib.sha256(raw_peer_id.encode("utf-8"), usedforsecurity=False).hexdigest() for hash_len in _PEER_ID_HASH_ESCALATION_LENGTHS: candidate = f"{sanitized_peer_id}-{digest[:hash_len]}" if candidate not in explicit_ids: diff --git a/plugins/platforms/discord/adapter.py b/plugins/platforms/discord/adapter.py index ca31426cc18c6..5d36cf8dcd14c 100644 --- a/plugins/platforms/discord/adapter.py +++ b/plugins/platforms/discord/adapter.py @@ -1244,7 +1244,7 @@ def _desired_command_sync_fingerprint(self) -> str: ] desired.sort(key=lambda item: (item.get("type", 1), item.get("name", ""))) payload = json.dumps(desired, sort_keys=True, separators=(",", ":")) - return hashlib.sha256(payload.encode("utf-8")).hexdigest() + return hashlib.sha256(payload.encode("utf-8"), usedforsecurity=False).hexdigest() def _command_sync_skip_reason(self, app_id: Any, fingerprint: str) -> Optional[str]: entry = self._read_command_sync_state().get(self._command_sync_state_key(app_id)) diff --git a/plugins/platforms/feishu/adapter.py b/plugins/platforms/feishu/adapter.py index bf3c49d3b8679..863d27dfe7864 100644 --- a/plugins/platforms/feishu/adapter.py +++ b/plugins/platforms/feishu/adapter.py @@ -3441,7 +3441,7 @@ def _is_webhook_signature_valid(self, headers: Any, body_bytes: bytes) -> bool: try: body_str = body_bytes.decode("utf-8", errors="replace") content = f"{timestamp}{nonce}{self._encrypt_key}{body_str}" - computed = hashlib.sha256(content.encode("utf-8")).hexdigest() + computed = hashlib.sha256(content.encode("utf-8"), usedforsecurity=False).hexdigest() return hmac.compare_digest(computed, signature) except Exception: logger.debug("[Feishu] Signature verification raised an exception", exc_info=True) diff --git a/plugins/platforms/line/adapter.py b/plugins/platforms/line/adapter.py index 130bb2e2c38cb..67b81f55c9121 100644 --- a/plugins/platforms/line/adapter.py +++ b/plugins/platforms/line/adapter.py @@ -753,7 +753,7 @@ async def connect(self) -> bool: try: from gateway.status import acquire_scoped_lock # Use a hash of the token so we don't write the secret to disk. - tok_hash = hashlib.sha256(self.channel_access_token.encode()).hexdigest()[:16] + tok_hash = hashlib.sha256(self.channel_access_token.encode(), usedforsecurity=False).hexdigest()[:16] if not acquire_scoped_lock("line", tok_hash): self._set_fatal_error( "lock_conflict", diff --git a/plugins/platforms/wecom/wecom_crypto.py b/plugins/platforms/wecom/wecom_crypto.py index f984ca80c3eb0..37943339c5d5d 100644 --- a/plugins/platforms/wecom/wecom_crypto.py +++ b/plugins/platforms/wecom/wecom_crypto.py @@ -60,7 +60,7 @@ def decode(cls, decrypted: bytes) -> bytes: def _sha1_signature(token: str, timestamp: str, nonce: str, encrypt: str) -> str: parts = sorted([token, timestamp, nonce, encrypt]) - return hashlib.sha1("".join(parts).encode("utf-8")).hexdigest() + return hashlib.sha1("".join(parts).encode("utf-8"), usedforsecurity=False).hexdigest() class WXBizMsgCrypt: diff --git a/plugins/platforms/whatsapp/adapter.py b/plugins/platforms/whatsapp/adapter.py index 5c3d6bbb82372..85b8924c0f871 100644 --- a/plugins/platforms/whatsapp/adapter.py +++ b/plugins/platforms/whatsapp/adapter.py @@ -279,7 +279,7 @@ def _file_content_hash(path: Path) -> str: """ import hashlib try: - return hashlib.sha256(path.read_bytes()).hexdigest()[:16] + return hashlib.sha256(path.read_bytes(), usedforsecurity=False).hexdigest()[:16] except OSError: return "" diff --git a/plugins/teams_pipeline/store.py b/plugins/teams_pipeline/store.py index ceab28cb7eff5..e4cfba9ba3884 100644 --- a/plugins/teams_pipeline/store.py +++ b/plugins/teams_pipeline/store.py @@ -110,7 +110,7 @@ def build_notification_receipt_key(cls, notification: Dict[str, Any]) -> str: if explicit_id: return f"id:{explicit_id}" canonical = json.dumps(notification, sort_keys=True, separators=(",", ":")) - digest = hashlib.sha256(canonical.encode("utf-8")).hexdigest() + digest = hashlib.sha256(canonical.encode("utf-8"), usedforsecurity=False).hexdigest() return f"sha256:{digest}" def has_notification_receipt(self, receipt_key: str) -> bool: diff --git a/run_agent.py b/run_agent.py index 63050980934b2..2ecab05476855 100644 --- a/run_agent.py +++ b/run_agent.py @@ -4390,7 +4390,7 @@ def _materialize_data_url_for_vision(image_url: str) -> tuple[str, Optional[Path return str(path), path def _describe_image_for_anthropic_fallback(self, image_url: str, role: str) -> str: - cache_key = hashlib.sha256(str(image_url or "").encode("utf-8")).hexdigest() + cache_key = hashlib.sha256(str(image_url or "").encode("utf-8"), usedforsecurity=False).hexdigest() cached = self._anthropic_image_fallback_cache.get(cache_key) if cached: return cached diff --git a/tools/checkpoint_manager.py b/tools/checkpoint_manager.py index 720973b67e0be..7001ffba02d26 100644 --- a/tools/checkpoint_manager.py +++ b/tools/checkpoint_manager.py @@ -200,7 +200,7 @@ def _normalize_path(path_value: str) -> Path: def _project_hash(working_dir: str) -> str: """Deterministic per-project hash: sha256(abs_path)[:16].""" abs_path = str(_normalize_path(working_dir)) - return hashlib.sha256(abs_path.encode()).hexdigest()[:16] + return hashlib.sha256(abs_path.encode(), usedforsecurity=False).hexdigest()[:16] def _store_path(base: Optional[Path] = None) -> Path: diff --git a/tools/environments/file_sync.py b/tools/environments/file_sync.py index 89f712693fef8..a5d4c6476d9d5 100644 --- a/tools/environments/file_sync.py +++ b/tools/environments/file_sync.py @@ -93,7 +93,7 @@ def unique_parent_dirs(files: list[tuple[str, str]]) -> list[str]: def _sha256_file(path: str) -> str: """Return hex SHA-256 digest of a file.""" - h = hashlib.sha256() + h = hashlib.sha256(usedforsecurity=False) with open(path, "rb") as f: for chunk in iter(lambda: f.read(65536), b""): h.update(chunk) diff --git a/tools/skills_guard.py b/tools/skills_guard.py index a1a8606d6d479..abc9c5e995c38 100644 --- a/tools/skills_guard.py +++ b/tools/skills_guard.py @@ -774,7 +774,7 @@ def content_hash(skill_path: Path) -> str: one on an in-memory bundle), so any change to the hash shape MUST land in both places at once. """ - h = hashlib.sha256() + h = hashlib.sha256(usedforsecurity=False) if skill_path.is_dir(): for f in sorted(skill_path.rglob("*")): if f.is_file(): diff --git a/tools/skills_hub.py b/tools/skills_hub.py index cf99b5b9d297c..2547b8b78fba6 100644 --- a/tools/skills_hub.py +++ b/tools/skills_hub.py @@ -3439,7 +3439,7 @@ def uninstall_skill(skill_name: str) -> Tuple[bool, str]: def bundle_content_hash(bundle: SkillBundle) -> str: """Compute a deterministic hash for an in-memory skill bundle.""" - h = hashlib.sha256() + h = hashlib.sha256(usedforsecurity=False) for rel_path in sorted(bundle.files): # Include the path so swapping file contents between two paths # changes the hash (avoids filename-swap evading update detection). diff --git a/tools/tirith_security.py b/tools/tirith_security.py index 757ff5c450391..e230619eecddf 100644 --- a/tools/tirith_security.py +++ b/tools/tirith_security.py @@ -316,7 +316,7 @@ def _verify_checksum(archive_path: str, checksums_path: str, archive_name: str) logger.warning("No checksum entry for %s", archive_name) return False - sha = hashlib.sha256() + sha = hashlib.sha256(usedforsecurity=False) with open(archive_path, "rb") as f: for chunk in iter(lambda: f.read(8192), b""): sha.update(chunk)