7
7
import json
8
8
import logging
9
9
import os
10
+ import re
11
+ import secrets
10
12
13
+ from pathlib import Path
11
14
from typing import Any , Dict
12
15
13
16
import requests
18
21
class SentryClient :
19
22
"""Client for authenticated API calls to the Sentry monolith."""
20
23
21
- def __init__ (self , base_url : str = "http://localhost:8000" , shared_secret : str | None = None ) -> None :
24
+ def __init__ (self , base_url : str ) -> None :
22
25
self .base_url = base_url .rstrip ("/" )
23
- self .shared_secret = shared_secret or os .getenv ("LAUNCHPAD_RPC_SHARED_SECRET" )
24
-
26
+ self .shared_secret = os .getenv ("LAUNCHPAD_RPC_SHARED_SECRET" )
25
27
if not self .shared_secret :
26
28
raise RuntimeError ("LAUNCHPAD_RPC_SHARED_SECRET must be provided or set as environment variable" )
27
29
@@ -47,7 +49,7 @@ def _make_request(
47
49
"Authorization" : f"rpcsignature { self ._generate_signature (body )} " ,
48
50
}
49
51
50
- logger .debug (f"{ method } { url } " )
52
+ logger .debug (f"{ method } { endpoint } " )
51
53
response = requests .request (
52
54
method = method ,
53
55
url = url ,
@@ -58,7 +60,7 @@ def _make_request(
58
60
)
59
61
60
62
if response .status_code != 200 :
61
- logger .warning (f"{ method } { url } -> { response .status_code } " )
63
+ logger .warning (f"{ method } { endpoint } -> { response .status_code } " )
62
64
63
65
return response
64
66
@@ -69,7 +71,6 @@ def _handle_response(self, response: requests.Response) -> Dict[str, Any]:
69
71
else :
70
72
return {
71
73
"error" : f"HTTP { response .status_code } " ,
72
- "message" : response .text ,
73
74
"status_code" : response .status_code ,
74
75
}
75
76
@@ -82,6 +83,13 @@ def assemble_size_analysis(
82
83
chunks : list [str ],
83
84
) -> Dict [str , Any ]:
84
85
"""Call the assemble size analysis endpoint."""
86
+ # Validate hex strings
87
+ if not re .match (r"^[a-fA-F0-9]+$" , checksum ):
88
+ raise ValueError ("Invalid checksum format" )
89
+ for chunk in chunks :
90
+ if not re .match (r"^[a-fA-F0-9]+$" , chunk ):
91
+ raise ValueError ("Invalid chunk format" )
92
+
85
93
data = {
86
94
"checksum" : checksum ,
87
95
"chunks" : chunks ,
@@ -136,10 +144,14 @@ def upload_size_analysis_file(
136
144
max_retries : int = 3 ,
137
145
) -> Dict [str , Any ]:
138
146
"""Upload size analysis file with chunking following Rust sentry-cli pattern."""
139
- if not os .path .exists (file_path ):
147
+ # Basic path validation
148
+ path = Path (file_path ).resolve ()
149
+ if not path .exists ():
140
150
raise FileNotFoundError (f"File not found: { file_path } " )
151
+ if ".." in str (path ):
152
+ raise ValueError ("Invalid file path" )
141
153
142
- with open (file_path , "rb" ) as f :
154
+ with open (path , "rb" ) as f :
143
155
content = f .read ()
144
156
145
157
logger .info (f"Uploading { file_path } ({ len (content )} bytes, { len (content ) / 1024 / 1024 :.2f} MB)" )
@@ -250,7 +262,7 @@ def _upload_chunks(self, org: str, chunks: list[Dict[str, Any]], target_checksum
250
262
def _upload_chunk (self , org : str , chunk : Dict [str , Any ]) -> bool :
251
263
"""Upload single chunk."""
252
264
endpoint = f"/api/0/organizations/{ org } /chunk-upload/"
253
- boundary = f"----FormBoundary{ chunk [ 'checksum' ][: 16 ] } "
265
+ boundary = f"----FormBoundary{ secrets . token_hex ( 16 ) } "
254
266
255
267
# Create multipart body
256
268
body = self ._create_multipart_body (boundary , chunk ["checksum" ], chunk ["data" ])
0 commit comments