From 884a2377cb8233487226ec0622c139dddb0a2819 Mon Sep 17 00:00:00 2001 From: Beon de Nood Date: Wed, 22 Apr 2026 16:58:40 -0400 Subject: [PATCH 1/2] [security] SDK Python hardening: distinct unverified status, payload binding SEC-SDK-002: Return UNVERIFIED_FORMAT_OK (score 50, success=false) instead of success when no public key provided SEC-SDK-003: After JWS verification, compare decoded payload to caller-supplied payload via canonical JSON --- capiscio_sdk/validators/signature.py | 28 ++++++++++++++++++++++------ 1 file changed, 22 insertions(+), 6 deletions(-) diff --git a/capiscio_sdk/validators/signature.py b/capiscio_sdk/validators/signature.py index 22226f2..0a8ee9f 100644 --- a/capiscio_sdk/validators/signature.py +++ b/capiscio_sdk/validators/signature.py @@ -96,17 +96,33 @@ def validate_signature( if public_key: try: import jwt + import json # Verify signature - jwt.decode( + decoded = jwt.decode( signature, public_key, algorithms=['RS256', 'ES256', 'PS256'], options={"verify_signature": True} ) - # Signature is valid - logger.debug("Signature verified successfully") + # Bind: compare decoded payload against caller-supplied payload + # Use canonical JSON comparison to avoid ordering issues + caller_json = json.dumps(payload, sort_keys=True, separators=(',', ':')) + decoded_json = json.dumps(decoded, sort_keys=True, separators=(',', ':')) + if caller_json != decoded_json: + issues.append( + ValidationIssue( + severity=ValidationSeverity.ERROR, + code="PAYLOAD_MISMATCH", + message="Decoded signature payload does not match the supplied payload", + path="signatures", + ) + ) + score = 0 + else: + # Signature is valid and payload matches + logger.debug("Signature verified successfully") except jwt.InvalidSignatureError: issues.append( @@ -156,12 +172,12 @@ def validate_signature( issues.append( ValidationIssue( severity=ValidationSeverity.WARNING, - code="NO_PUBLIC_KEY", - message="Signature format valid but no public key provided for verification", + code="UNVERIFIED_FORMAT_OK", + message="Signature format valid but cryptographic verification not performed (no public key provided)", path="signatures", ) ) - score = 70 # Format is OK but not verified + score = 50 # Format-only: below success threshold # Ensure score doesn't go negative score = max(0, score) From 3cecdb9d49740611d3d10715fac1b939b15b47bd Mon Sep 17 00:00:00 2001 From: Beon de Nood Date: Wed, 22 Apr 2026 21:55:30 -0400 Subject: [PATCH 2/2] fix: use structural comparison for payload binding, remove unused json import - Replace JSON canonical comparison with direct dict equality (simpler, correct) - Remove unused json import --- capiscio_sdk/validators/signature.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/capiscio_sdk/validators/signature.py b/capiscio_sdk/validators/signature.py index 0a8ee9f..d6fa038 100644 --- a/capiscio_sdk/validators/signature.py +++ b/capiscio_sdk/validators/signature.py @@ -96,7 +96,6 @@ def validate_signature( if public_key: try: import jwt - import json # Verify signature decoded = jwt.decode( @@ -107,10 +106,7 @@ def validate_signature( ) # Bind: compare decoded payload against caller-supplied payload - # Use canonical JSON comparison to avoid ordering issues - caller_json = json.dumps(payload, sort_keys=True, separators=(',', ':')) - decoded_json = json.dumps(decoded, sort_keys=True, separators=(',', ':')) - if caller_json != decoded_json: + if decoded != payload: issues.append( ValidationIssue( severity=ValidationSeverity.ERROR,