@@ -185,7 +185,7 @@ func New() *Server {
185185
186186 // Load GitHub token at startup and cache in memory for performance and billing.
187187 // This avoids repeated GSM API calls which cost money.
188- token := server .getFallbackToken (ctx )
188+ token := server .token (ctx )
189189 if token != "" {
190190 logger .InfoContext (ctx , "GitHub fallback token loaded at startup" )
191191 } else {
@@ -256,8 +256,8 @@ func (s *Server) SetDataSource(source string) {
256256 s .logger .InfoContext (ctx , "Data source configured" , "source" , source )
257257}
258258
259- // getLimiter returns a rate limiter for the given IP address.
260- func (s * Server ) getLimiter (ctx context.Context , ip string ) * rate.Limiter {
259+ // limiter returns a rate limiter for the given IP address.
260+ func (s * Server ) limiter (ctx context.Context , ip string ) * rate.Limiter {
261261 s .ipLimitersMu .RLock ()
262262 limiter , exists := s .ipLimiters [ip ]
263263 s .ipLimitersMu .RUnlock ()
@@ -325,8 +325,8 @@ func (s *Server) clearCache(mu *sync.RWMutex, cache map[string]*cacheEntry, name
325325 }
326326}
327327
328- // getCachedPRQuery retrieves cached PR query results.
329- func (s * Server ) getCachedPRQuery (key string ) ([]github.PRSummary , bool ) {
328+ // cachedPRQuery retrieves cached PR query results.
329+ func (s * Server ) cachedPRQuery (key string ) ([]github.PRSummary , bool ) {
330330 s .prQueryCacheMu .RLock ()
331331 defer s .prQueryCacheMu .RUnlock ()
332332
@@ -349,8 +349,8 @@ func (s *Server) cachePRQuery(key string, prs []github.PRSummary) {
349349 }
350350}
351351
352- // getCachedPRData retrieves cached PR data.
353- func (s * Server ) getCachedPRData (key string ) (cost.PRData , bool ) {
352+ // cachedPRData retrieves cached PR data.
353+ func (s * Server ) cachedPRData (key string ) (cost.PRData , bool ) {
354354 s .prDataCacheMu .RLock ()
355355 defer s .prDataCacheMu .RUnlock ()
356356
@@ -496,7 +496,9 @@ func (s *Server) handleCalculate(writer http.ResponseWriter, request *http.Reque
496496 ctx := request .Context ()
497497
498498 // Extract client IP for rate limiting and logging.
499- // Check X-Forwarded-For (only trust if behind known proxy).
499+ // SECURITY: X-Forwarded-For is trusted because Cloud Run (GCP) sanitizes it.
500+ // Cloud Run strips client-provided XFF headers and replaces with actual client IP.
501+ // For non-Cloud Run deployments, consider validating source or using RemoteAddr only.
500502 clientIP := request .RemoteAddr
501503 if xff := request .Header .Get ("X-Forwarded-For" ); xff != "" {
502504 if idx := strings .Index (xff , "," ); idx > 0 {
@@ -512,7 +514,7 @@ func (s *Server) handleCalculate(writer http.ResponseWriter, request *http.Reque
512514 s .logger .InfoContext (ctx , "[handleCalculate] Incoming request" , "client_ip" , clientIP , "method" , request .Method , "path" , request .URL .Path )
513515
514516 // Per-IP rate limiting (SECURITY: Prevents single client from DoS-ing all users).
515- limiter := s .getLimiter (ctx , clientIP )
517+ limiter := s .limiter (ctx , clientIP )
516518 if ! limiter .Allow () {
517519 s .logger .WarnContext (ctx , "[handleCalculate] Rate limit exceeded" , "client_ip" , clientIP , "path" , request .URL .Path )
518520 http .Error (writer , "Rate limit exceeded" , http .StatusTooManyRequests )
@@ -531,7 +533,7 @@ func (s *Server) handleCalculate(writer http.ResponseWriter, request *http.Reque
531533 token := s .extractToken (request )
532534 if token == "" {
533535 // Try fallback token (GITHUB_TOKEN env var or GITHUB_SECRET from GSM)
534- token = s .getFallbackToken (ctx )
536+ token = s .token (ctx )
535537 if token == "" {
536538 s .logger .WarnContext (ctx , "[handleCalculate] No GitHub token available" , "remote_addr" , request .RemoteAddr )
537539 http .Error (writer , "GitHub token required (set GITHUB_TOKEN env var or provide Authorization header)" , http .StatusUnauthorized )
@@ -647,10 +649,10 @@ func (*Server) extractToken(r *http.Request) string {
647649 return auth
648650}
649651
650- // getFallbackToken retrieves a GitHub token from environment or Google Secret Manager.
652+ // token retrieves a GitHub token from environment or Google Secret Manager.
651653// Results are cached in memory to avoid repeated API calls (performance and billing).
652654// Priority: GITHUB_TOKEN env var, then GITHUB_TOKEN from GSM.
653- func (s * Server ) getFallbackToken (ctx context.Context ) string {
655+ func (s * Server ) token (ctx context.Context ) string {
654656 // Check cache first (read lock)
655657 s .fallbackTokenMu .RLock ()
656658 if s .fallbackToken != "" {
@@ -703,7 +705,7 @@ func (s *Server) processRequest(ctx context.Context, req *CalculateRequest, toke
703705
704706 // Try cache first
705707 cacheKey := fmt .Sprintf ("pr:%s" , req .URL )
706- prData , cached := s .getCachedPRData (cacheKey )
708+ prData , cached := s .cachedPRData (cacheKey )
707709 if cached {
708710 s .logger .InfoContext (ctx , "[processRequest] Using cached PR data" , "url" , req .URL )
709711 } else {
@@ -923,6 +925,9 @@ func (s *Server) handleRepoSample(writer http.ResponseWriter, request *http.Requ
923925 ctx := request .Context ()
924926
925927 // Extract client IP for rate limiting and logging.
928+ // SECURITY: X-Forwarded-For is trusted because Cloud Run (GCP) sanitizes it.
929+ // Cloud Run strips client-provided XFF headers and replaces with actual client IP.
930+ // For non-Cloud Run deployments, consider validating source or using RemoteAddr only.
926931 clientIP := request .RemoteAddr
927932 if xff := request .Header .Get ("X-Forwarded-For" ); xff != "" {
928933 if idx := strings .Index (xff , "," ); idx > 0 {
@@ -938,7 +943,7 @@ func (s *Server) handleRepoSample(writer http.ResponseWriter, request *http.Requ
938943 s .logger .InfoContext (ctx , "[handleRepoSample] Incoming request" , "client_ip" , clientIP )
939944
940945 // Per-IP rate limiting.
941- limiter := s .getLimiter (ctx , clientIP )
946+ limiter := s .limiter (ctx , clientIP )
942947 if ! limiter .Allow () {
943948 s .logger .WarnContext (ctx , "[handleRepoSample] Rate limit exceeded" , "client_ip" , clientIP )
944949 http .Error (writer , "Rate limit exceeded" , http .StatusTooManyRequests )
@@ -956,7 +961,7 @@ func (s *Server) handleRepoSample(writer http.ResponseWriter, request *http.Requ
956961 // Get auth token - try Authorization header first, then fallback.
957962 token := s .extractToken (request )
958963 if token == "" {
959- token = s .getFallbackToken (ctx )
964+ token = s .token (ctx )
960965 if token == "" {
961966 s .logger .WarnContext (ctx , "[handleRepoSample] No GitHub token available" , "remote_addr" , request .RemoteAddr )
962967 http .Error (writer , "GitHub token required (set GITHUB_TOKEN env var or provide Authorization header)" , http .StatusUnauthorized )
@@ -999,6 +1004,9 @@ func (s *Server) handleOrgSample(writer http.ResponseWriter, request *http.Reque
9991004 ctx := request .Context ()
10001005
10011006 // Extract client IP for rate limiting and logging.
1007+ // SECURITY: X-Forwarded-For is trusted because Cloud Run (GCP) sanitizes it.
1008+ // Cloud Run strips client-provided XFF headers and replaces with actual client IP.
1009+ // For non-Cloud Run deployments, consider validating source or using RemoteAddr only.
10021010 clientIP := request .RemoteAddr
10031011 if xff := request .Header .Get ("X-Forwarded-For" ); xff != "" {
10041012 if idx := strings .Index (xff , "," ); idx > 0 {
@@ -1014,7 +1022,7 @@ func (s *Server) handleOrgSample(writer http.ResponseWriter, request *http.Reque
10141022 s .logger .InfoContext (ctx , "[handleOrgSample] Incoming request" , "client_ip" , clientIP )
10151023
10161024 // Per-IP rate limiting.
1017- limiter := s .getLimiter (ctx , clientIP )
1025+ limiter := s .limiter (ctx , clientIP )
10181026 if ! limiter .Allow () {
10191027 s .logger .WarnContext (ctx , "[handleOrgSample] Rate limit exceeded" , "client_ip" , clientIP )
10201028 http .Error (writer , "Rate limit exceeded" , http .StatusTooManyRequests )
@@ -1032,7 +1040,7 @@ func (s *Server) handleOrgSample(writer http.ResponseWriter, request *http.Reque
10321040 // Get auth token - try Authorization header first, then fallback.
10331041 token := s .extractToken (request )
10341042 if token == "" {
1035- token = s .getFallbackToken (ctx )
1043+ token = s .token (ctx )
10361044 if token == "" {
10371045 s .logger .WarnContext (ctx , "[handleOrgSample] No GitHub token available" , "remote_addr" , request .RemoteAddr )
10381046 http .Error (writer , "GitHub token required (set GITHUB_TOKEN env var or provide Authorization header)" , http .StatusUnauthorized )
@@ -1154,7 +1162,7 @@ func (s *Server) processRepoSample(ctx context.Context, req *RepoSampleRequest,
11541162
11551163 // Try cache first
11561164 cacheKey := fmt .Sprintf ("repo:%s/%s:days=%d" , req .Owner , req .Repo , req .Days )
1157- prs , cached := s .getCachedPRQuery (cacheKey )
1165+ prs , cached := s .cachedPRQuery (cacheKey )
11581166 if cached {
11591167 s .logger .InfoContext (ctx , "Using cached PR query results" ,
11601168 "owner" , req .Owner , "repo" , req .Repo , "total_prs" , len (prs ))
@@ -1192,7 +1200,7 @@ func (s *Server) processRepoSample(ctx context.Context, req *RepoSampleRequest,
11921200
11931201 // Try cache first
11941202 prCacheKey := fmt .Sprintf ("pr:%s" , prURL )
1195- prData , prCached := s .getCachedPRData (prCacheKey )
1203+ prData , prCached := s .cachedPRData (prCacheKey )
11961204 if ! prCached {
11971205 var err error
11981206 // Use configured data source with updatedAt for effective caching
@@ -1241,7 +1249,7 @@ func (s *Server) processOrgSample(ctx context.Context, req *OrgSampleRequest, to
12411249
12421250 // Try cache first
12431251 cacheKey := fmt .Sprintf ("org:%s:days=%d" , req .Org , req .Days )
1244- prs , cached := s .getCachedPRQuery (cacheKey )
1252+ prs , cached := s .cachedPRQuery (cacheKey )
12451253 if cached {
12461254 s .logger .InfoContext (ctx , "Using cached PR query results" ,
12471255 "org" , req .Org , "total_prs" , len (prs ))
@@ -1278,7 +1286,7 @@ func (s *Server) processOrgSample(ctx context.Context, req *OrgSampleRequest, to
12781286
12791287 // Try cache first
12801288 prCacheKey := fmt .Sprintf ("pr:%s" , prURL )
1281- prData , prCached := s .getCachedPRData (prCacheKey )
1289+ prData , prCached := s .cachedPRData (prCacheKey )
12821290 if ! prCached {
12831291 var err error
12841292 // Use configured data source with updatedAt for effective caching
@@ -1368,6 +1376,9 @@ func (s *Server) handleRepoSampleStream(writer http.ResponseWriter, request *htt
13681376 ctx := request .Context ()
13691377
13701378 // Extract client IP for rate limiting and logging.
1379+ // SECURITY: X-Forwarded-For is trusted because Cloud Run (GCP) sanitizes it.
1380+ // Cloud Run strips client-provided XFF headers and replaces with actual client IP.
1381+ // For non-Cloud Run deployments, consider validating source or using RemoteAddr only.
13711382 clientIP := request .RemoteAddr
13721383 if xff := request .Header .Get ("X-Forwarded-For" ); xff != "" {
13731384 if idx := strings .Index (xff , "," ); idx > 0 {
@@ -1382,7 +1393,7 @@ func (s *Server) handleRepoSampleStream(writer http.ResponseWriter, request *htt
13821393 s .logger .InfoContext (ctx , "[handleRepoSampleStream] Incoming request" , "client_ip" , clientIP )
13831394
13841395 // Per-IP rate limiting.
1385- limiter := s .getLimiter (ctx , clientIP )
1396+ limiter := s .limiter (ctx , clientIP )
13861397 if ! limiter .Allow () {
13871398 s .logger .WarnContext (ctx , "[handleRepoSampleStream] Rate limit exceeded" , "client_ip" , clientIP )
13881399 http .Error (writer , "Rate limit exceeded" , http .StatusTooManyRequests )
@@ -1401,7 +1412,7 @@ func (s *Server) handleRepoSampleStream(writer http.ResponseWriter, request *htt
14011412 // Get auth token - try Authorization header first, then fallback.
14021413 token := s .extractToken (request )
14031414 if token == "" {
1404- token = s .getFallbackToken (ctx )
1415+ token = s .token (ctx )
14051416 if token == "" {
14061417 s .logger .WarnContext (ctx , "[handleRepoSampleStream] No GitHub token available" , "remote_addr" , request .RemoteAddr )
14071418 http .Error (writer , "GitHub token required (set GITHUB_TOKEN env var or provide Authorization header)" , http .StatusUnauthorized )
@@ -1441,6 +1452,9 @@ func (s *Server) handleOrgSampleStream(writer http.ResponseWriter, request *http
14411452 ctx := request .Context ()
14421453
14431454 // Extract client IP for rate limiting and logging.
1455+ // SECURITY: X-Forwarded-For is trusted because Cloud Run (GCP) sanitizes it.
1456+ // Cloud Run strips client-provided XFF headers and replaces with actual client IP.
1457+ // For non-Cloud Run deployments, consider validating source or using RemoteAddr only.
14441458 clientIP := request .RemoteAddr
14451459 if xff := request .Header .Get ("X-Forwarded-For" ); xff != "" {
14461460 if idx := strings .Index (xff , "," ); idx > 0 {
@@ -1455,7 +1469,7 @@ func (s *Server) handleOrgSampleStream(writer http.ResponseWriter, request *http
14551469 s .logger .InfoContext (ctx , "[handleOrgSampleStream] Incoming request" , "client_ip" , clientIP )
14561470
14571471 // Per-IP rate limiting.
1458- limiter := s .getLimiter (ctx , clientIP )
1472+ limiter := s .limiter (ctx , clientIP )
14591473 if ! limiter .Allow () {
14601474 s .logger .WarnContext (ctx , "[handleOrgSampleStream] Rate limit exceeded" , "client_ip" , clientIP )
14611475 http .Error (writer , "Rate limit exceeded" , http .StatusTooManyRequests )
@@ -1473,7 +1487,7 @@ func (s *Server) handleOrgSampleStream(writer http.ResponseWriter, request *http
14731487 // Get auth token - try Authorization header first, then fallback.
14741488 token := s .extractToken (request )
14751489 if token == "" {
1476- token = s .getFallbackToken (ctx )
1490+ token = s .token (ctx )
14771491 if token == "" {
14781492 s .logger .WarnContext (ctx , "[handleOrgSampleStream] No GitHub token available" , "remote_addr" , request .RemoteAddr )
14791493 http .Error (writer , "GitHub token required (set GITHUB_TOKEN env var or provide Authorization header)" , http .StatusUnauthorized )
@@ -1592,7 +1606,7 @@ func (s *Server) processRepoSampleWithProgress(ctx context.Context, req *RepoSam
15921606
15931607 // Try cache first
15941608 cacheKey := fmt .Sprintf ("repo:%s/%s:days=%d" , req .Owner , req .Repo , req .Days )
1595- prs , cached := s .getCachedPRQuery (cacheKey )
1609+ prs , cached := s .cachedPRQuery (cacheKey )
15961610 if ! cached {
15971611 // Send progress update before GraphQL query
15981612 logSSEError (ctx , s .logger , sendSSE (writer , ProgressUpdate {
@@ -1696,7 +1710,7 @@ func (s *Server) processOrgSampleWithProgress(ctx context.Context, req *OrgSampl
16961710
16971711 // Try cache first
16981712 cacheKey := fmt .Sprintf ("org:%s:days=%d" , req .Org , req .Days )
1699- prs , cached := s .getCachedPRQuery (cacheKey )
1713+ prs , cached := s .cachedPRQuery (cacheKey )
17001714 if ! cached {
17011715 // Send progress update before GraphQL query
17021716 logSSEError (ctx , s .logger , sendSSE (writer , ProgressUpdate {
@@ -1826,7 +1840,7 @@ func (s *Server) processPRsInParallel(workCtx, reqCtx context.Context, samples [
18261840
18271841 // Try cache first
18281842 prCacheKey := fmt .Sprintf ("pr:%s" , prURL )
1829- prData , prCached := s .getCachedPRData (prCacheKey )
1843+ prData , prCached := s .cachedPRData (prCacheKey )
18301844 if ! prCached {
18311845 var err error
18321846 // Use work context for actual API calls (not tied to client connection)
0 commit comments