Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion agent/anthropic_adapter.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
4 changes: 2 additions & 2 deletions agent/codex_responses_adapter.py
Original file line number Diff line number Diff line change
Expand Up @@ -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}"


Expand Down Expand Up @@ -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}"


Expand Down
2 changes: 1 addition & 1 deletion agent/credential_persistence.py
Original file line number Diff line number Diff line change
Expand Up @@ -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]}"


Expand Down
4 changes: 2 additions & 2 deletions agent/secret_sources/bitwarden.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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(
Expand Down
2 changes: 1 addition & 1 deletion agent/tool_guardrails.py
Original file line number Diff line number Diff line change
Expand Up @@ -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()
2 changes: 1 addition & 1 deletion agent/transports/codex_event_projector.py
Original file line number Diff line number Diff line change
Expand Up @@ -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}"


Expand Down
2 changes: 1 addition & 1 deletion gateway/pairing.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 = ""
Expand Down
2 changes: 1 addition & 1 deletion gateway/platforms/api_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -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}"


Expand Down
2 changes: 1 addition & 1 deletion gateway/platforms/qqbot/chunked_upload.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion gateway/platforms/yuanbao_media.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
6 changes: 3 additions & 3 deletions gateway/run.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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())

Expand All @@ -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
Expand Down
2 changes: 1 addition & 1 deletion gateway/session.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
2 changes: 1 addition & 1 deletion gateway/slash_commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -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]:
Expand Down
2 changes: 1 addition & 1 deletion gateway/status.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
12 changes: 6 additions & 6 deletions hermes_cli/auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -680,15 +680,15 @@ 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"]

# Probe — may take up to ~8s per endpoint.
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", ""),
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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("=")


Expand All @@ -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("=")


Expand Down Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion hermes_cli/copilot_auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -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]:
Expand Down
2 changes: 1 addition & 1 deletion hermes_cli/gateway.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
2 changes: 1 addition & 1 deletion hermes_cli/kanban_db.py
Original file line number Diff line number Diff line change
Expand Up @@ -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""):
Expand Down
2 changes: 1 addition & 1 deletion hermes_cli/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand Down
2 changes: 1 addition & 1 deletion hermes_cli/nous_account.py
Original file line number Diff line number Diff line change
Expand Up @@ -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}"


Expand Down
2 changes: 1 addition & 1 deletion plugins/dashboard_auth/basic/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 = ""
Expand Down
2 changes: 1 addition & 1 deletion plugins/dashboard_auth/nous/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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))

Expand Down
2 changes: 1 addition & 1 deletion plugins/dashboard_auth/self_hosted/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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))

Expand Down
2 changes: 1 addition & 1 deletion plugins/memory/holographic/holographic.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
2 changes: 1 addition & 1 deletion plugins/memory/honcho/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 '-<hash>'. Strip any trailing hyphen from the prefix so
# the result doesn't double up on separators.
Expand Down
2 changes: 1 addition & 1 deletion plugins/memory/honcho/oauth_flow.py
Original file line number Diff line number Diff line change
Expand Up @@ -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()
)
Expand Down
2 changes: 1 addition & 1 deletion plugins/memory/honcho/session.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
2 changes: 1 addition & 1 deletion plugins/platforms/discord/adapter.py
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand Down
2 changes: 1 addition & 1 deletion plugins/platforms/feishu/adapter.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
2 changes: 1 addition & 1 deletion plugins/platforms/line/adapter.py
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
2 changes: 1 addition & 1 deletion plugins/platforms/wecom/wecom_crypto.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
2 changes: 1 addition & 1 deletion plugins/platforms/whatsapp/adapter.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 ""

Expand Down
2 changes: 1 addition & 1 deletion plugins/teams_pipeline/store.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
2 changes: 1 addition & 1 deletion run_agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion tools/checkpoint_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
Loading