Skip to content

Commit 4e97bd5

Browse files
committed
feat(security): add pre-decode payload size check and key_type validation
Security improvements from code review: 1. Pre-decode payload size check: - Check base64 string length BEFORE decoding to prevent memory exhaustion - Quick rejection if base64 > 13.3MB (would decode to > 10MB) - Prevents attacker from forcing server to decode large payloads into memory 2. Input validation for key_type: - Validate key_type is 'legacy' or 'modern' at API layer - Prevents potential issues from unexpected values Addresses code review security findings. Signed-off-by: Scott R. Shinn <scott@atomicorp.com>
1 parent 72b7f09 commit 4e97bd5

File tree

1 file changed

+55
-10
lines changed

1 file changed

+55
-10
lines changed

server/chelon-service.py

Lines changed: 55 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -146,18 +146,62 @@ def _handle_signing(operation):
146146
# Parse request
147147
try:
148148
data = request.get_json()
149-
except Exception:
149+
except Exception as e:
150+
logger.error(f"[{request_id}] Invalid JSON: {e}")
150151
return jsonify({'error': 'Invalid JSON', 'request_id': request_id}), 400
151-
152+
152153
if not data:
153154
return jsonify({'error': 'Empty request body', 'request_id': request_id}), 400
154155

155-
raw_data_b64 = data.get('data')
156-
payload_size = len(raw_data_b64) if raw_data_b64 else 0
156+
raw_data_b64 = data.get('data')
157+
158+
# DoS Protection: Check base64 string size BEFORE decoding to prevent memory exhaustion
159+
# Base64 encoding increases size by ~33%, so 13.3MB base64 = ~10MB decoded
160+
if raw_data_b64:
161+
base64_size = len(raw_data_b64)
162+
# Quick rejection: if base64 string is > 13.3MB, decoded will be > 10MB
163+
if base64_size > 13981013: # ~13.3MB in bytes
164+
latency = time.time() - start_time
165+
audit_logger.log_signing(
166+
token_id=token_info['token_id'],
167+
operation=operation,
168+
key_used=None,
169+
data_hash=f"size:{base64_size}",
170+
success=False,
171+
client_ip=request.remote_addr,
172+
request_id=request_id,
173+
latency=latency,
174+
payload_size=base64_size,
175+
error='Payload too large (pre-decode check)'
176+
)
177+
return jsonify({'error': 'Payload too large (base64 size exceeds limit)', 'request_id': request_id}), 413
157178

158-
# DoS Protection: Limit payload size
159-
# 10MB limit on actual decoded data (not base64-encoded size)
160-
if raw_data_b64 and payload_size > 10 * 1024 * 1024: # 10MB limit
179+
# Decode and validate payload
180+
try:
181+
if not raw_data_b64:
182+
raise ValueError("Missing 'data' field")
183+
raw_data = base64.b64decode(raw_data_b64)
184+
except Exception as e:
185+
logger.error(f"[{request_id}] Failed to decode data: {e}")
186+
latency = time.time() - start_time
187+
audit_logger.log_signing(
188+
token_id=token_info['token_id'],
189+
operation=operation,
190+
key_used=None,
191+
data_hash=None,
192+
success=False,
193+
client_ip=request.remote_addr,
194+
request_id=request_id,
195+
latency=latency,
196+
error=f"Invalid base64 data: {str(e)}"
197+
)
198+
return jsonify({'error': f'Invalid base64 data: {str(e)}', 'request_id': request_id}), 400
199+
200+
# Calculate actual decoded payload size
201+
payload_size = len(raw_data)
202+
203+
# Secondary check: verify decoded size is within limit
204+
if payload_size > 10 * 1024 * 1024: # 10MB limit
161205
latency = time.time() - start_time
162206
audit_logger.log_signing(
163207
token_id=token_info['token_id'],
@@ -173,10 +217,11 @@ def _handle_signing(operation):
173217
)
174218
return jsonify({'error': f'Payload too large (decoded size: {payload_size} bytes, limit: 10MB)', 'request_id': request_id}), 413
175219

176-
if not raw_data_b64:
177-
return jsonify({'error': 'Missing "data" field', 'request_id': request_id}), 400
178-
220+
# Validate key_type parameter
179221
key_type = data.get('key_type', 'legacy')
222+
if key_type not in ['legacy', 'modern']:
223+
logger.warning(f"[{request_id}] Invalid key_type: {key_type}")
224+
return jsonify({'error': f'Invalid key_type: {key_type}. Must be "legacy" or "modern"', 'request_id': request_id}), 400
180225

181226
sign_target = None
182227
data_id = None

0 commit comments

Comments
 (0)