From 9996b9a5a2ae2e95c028387e0a58636c76eb0418 Mon Sep 17 00:00:00 2001 From: Bartosz Blizniak Date: Wed, 10 Jun 2026 12:54:51 +0100 Subject: [PATCH 1/3] fix(security): resolve code scanning alerts in JWT and checksum handling --- cloudsmith_cli/core/credentials/oidc/cache.py | 18 ++++++++++-------- cloudsmith_cli/core/download.py | 2 +- cloudsmith_cli/core/utils.py | 2 +- 3 files changed, 12 insertions(+), 10 deletions(-) diff --git a/cloudsmith_cli/core/credentials/oidc/cache.py b/cloudsmith_cli/core/credentials/oidc/cache.py index a0892ca0..95ff485c 100644 --- a/cloudsmith_cli/core/credentials/oidc/cache.py +++ b/cloudsmith_cli/core/credentials/oidc/cache.py @@ -7,6 +7,7 @@ from __future__ import annotations +import base64 import hashlib import json import logging @@ -41,15 +42,16 @@ def _cache_key(api_host: str, org: str, service_slug: str) -> str: def _decode_jwt_exp(token: str) -> float | None: - """Decode the exp claim from a JWT without verification.""" - try: - import jwt + """Read the exp claim from a JWT payload. - payload = jwt.decode( - token, - options={"verify_signature": False}, - algorithms=["RS256", "ES256", "HS256"], - ) + The token is only inspected to determine a cache TTL; it is never used to + make an authorization decision, so the signature is deliberately not + verified (the API rejects tampered tokens regardless). + """ + try: + payload_segment = token.split(".")[1] + padded = payload_segment + "=" * (-len(payload_segment) % 4) + payload = json.loads(base64.urlsafe_b64decode(padded)) exp = payload.get("exp") if exp is not None: return float(exp) diff --git a/cloudsmith_cli/core/download.py b/cloudsmith_cli/core/download.py index 578131a3..8c753b67 100644 --- a/cloudsmith_cli/core/download.py +++ b/cloudsmith_cli/core/download.py @@ -583,7 +583,7 @@ def _verify_checksum(filepath: str, expected: str) -> bool: # Try SHA1 if len(expected) == 40: - sha1_hash = hashlib.sha1() + sha1_hash = hashlib.sha1(usedforsecurity=False) with open(filepath, "rb") as f: for chunk in iter(lambda: f.read(4096), b""): sha1_hash.update(chunk) diff --git a/cloudsmith_cli/core/utils.py b/cloudsmith_cli/core/utils.py index d97a8fc3..6d863910 100644 --- a/cloudsmith_cli/core/utils.py +++ b/cloudsmith_cli/core/utils.py @@ -35,7 +35,7 @@ def read_file(*path): def calculate_file_md5(filepath, blocksize=2**20): """Calculate an MD5 hash for a file.""" - checksum = hashlib.md5() + checksum = hashlib.md5(usedforsecurity=False) with click.open_file(filepath, "rb") as f: From 09330c879e174068d2c10df292cdfd136934ffcc Mon Sep 17 00:00:00 2001 From: Bartosz Blizniak Date: Wed, 10 Jun 2026 13:08:30 +0100 Subject: [PATCH 2/3] fix(security): use bounded split when parsing JWT payload --- cloudsmith_cli/core/credentials/oidc/cache.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/cloudsmith_cli/core/credentials/oidc/cache.py b/cloudsmith_cli/core/credentials/oidc/cache.py index 95ff485c..9e9d2c37 100644 --- a/cloudsmith_cli/core/credentials/oidc/cache.py +++ b/cloudsmith_cli/core/credentials/oidc/cache.py @@ -48,8 +48,11 @@ def _decode_jwt_exp(token: str) -> float | None: make an authorization decision, so the signature is deliberately not verified (the API rejects tampered tokens regardless). """ + parts = token.split(".", 2) + if len(parts) != 3: + return None try: - payload_segment = token.split(".")[1] + payload_segment = parts[1] padded = payload_segment + "=" * (-len(payload_segment) % 4) payload = json.loads(base64.urlsafe_b64decode(padded)) exp = payload.get("exp") From 6f6b4ada4d1fe0e900990fd10433fac3f98f68d2 Mon Sep 17 00:00:00 2001 From: Bartosz Blizniak Date: Wed, 10 Jun 2026 15:27:41 +0100 Subject: [PATCH 3/3] fix(security): revert to PyJWT for JWT expiry parsing --- cloudsmith_cli/core/credentials/oidc/cache.py | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/cloudsmith_cli/core/credentials/oidc/cache.py b/cloudsmith_cli/core/credentials/oidc/cache.py index 9e9d2c37..2888e004 100644 --- a/cloudsmith_cli/core/credentials/oidc/cache.py +++ b/cloudsmith_cli/core/credentials/oidc/cache.py @@ -7,7 +7,6 @@ from __future__ import annotations -import base64 import hashlib import json import logging @@ -48,13 +47,10 @@ def _decode_jwt_exp(token: str) -> float | None: make an authorization decision, so the signature is deliberately not verified (the API rejects tampered tokens regardless). """ - parts = token.split(".", 2) - if len(parts) != 3: - return None try: - payload_segment = parts[1] - padded = payload_segment + "=" * (-len(payload_segment) % 4) - payload = json.loads(base64.urlsafe_b64decode(padded)) + import jwt + + payload = jwt.decode(token, options={"verify_signature": False}) exp = payload.get("exp") if exp is not None: return float(exp)