@@ -3,6 +3,7 @@ package main
33
44import (
55 "context"
6+ "crypto/subtle"
67 "embed"
78 "encoding/json"
89 "flag"
@@ -158,11 +159,12 @@ func main() {
158159 mux .HandleFunc ("/health" , server .handleHealth )
159160
160161 srv := & http.Server {
161- Addr : ":" + * port ,
162- Handler : loggingMiddleware (mux ),
163- ReadTimeout : readTimeout ,
164- WriteTimeout : writeTimeout ,
165- IdleTimeout : idleTimeout ,
162+ Addr : ":" + * port ,
163+ Handler : loggingMiddleware (mux ),
164+ ReadTimeout : readTimeout ,
165+ WriteTimeout : writeTimeout ,
166+ IdleTimeout : idleTimeout ,
167+ MaxHeaderBytes : 1 << 16 , // 64KB max header size
166168 }
167169
168170 go func () {
@@ -317,10 +319,8 @@ func (s *Server) handleReport(writer http.ResponseWriter, request *http.Request)
317319 // Security: Check API key if configured
318320 if * apiKey != "" {
319321 providedKey := request .Header .Get ("X-API-Key" )
320- if providedKey == "" {
321- providedKey = request .URL .Query ().Get ("api_key" )
322- }
323- if providedKey != * apiKey {
322+ // Security: Don't accept API key from query params (exposes in logs)
323+ if ! constantTimeCompare (providedKey , * apiKey ) {
324324 s .incrementErrorCount ()
325325 log .Printf ("[WARN] Unauthorized request from %s" , request .RemoteAddr )
326326 http .Error (writer , "Unauthorized" , http .StatusUnauthorized )
@@ -343,10 +343,17 @@ func (s *Server) handleReport(writer http.ResponseWriter, request *http.Request)
343343 // Security: Validate input fields
344344 if report .HardwareID == "" || len (report .HardwareID ) > maxFieldLength {
345345 s .incrementErrorCount ()
346- log .Printf ("[WARN] Invalid Hardware ID from %s: %s " , request .RemoteAddr , report .HardwareID )
346+ log .Printf ("[WARN] Invalid Hardware ID from %s: length %d " , request .RemoteAddr , len ( report .HardwareID ) )
347347 http .Error (writer , "Invalid Hardware ID" , http .StatusBadRequest )
348348 return
349349 }
350+ // Additional validation: only allow safe characters in hardware ID
351+ if ! isValidHardwareID (report .HardwareID ) {
352+ s .incrementErrorCount ()
353+ log .Printf ("[WARN] Invalid Hardware ID format from %s" , request .RemoteAddr )
354+ http .Error (writer , "Invalid Hardware ID format" , http .StatusBadRequest )
355+ return
356+ }
350357 if len (report .Hostname ) > maxFieldLength {
351358 s .incrementErrorCount ()
352359 log .Printf ("[WARN] Hostname too long from %s: %d bytes" , request .RemoteAddr , len (report .Hostname ))
@@ -364,10 +371,17 @@ func (s *Server) handleReport(writer http.ResponseWriter, request *http.Request)
364371 for name , check := range report .Checks {
365372 if len (name ) > maxCheckName {
366373 s .incrementErrorCount ()
367- log .Printf ("[WARN] Check name too long from %s: %s (% d bytes) " , request .RemoteAddr , name , len (name ))
374+ log .Printf ("[WARN] Check name too long from %s: %d bytes" , request .RemoteAddr , len (name ))
368375 http .Error (writer , "Check name too long" , http .StatusBadRequest )
369376 return
370377 }
378+ // Additional validation: only allow safe characters in check names
379+ if ! isValidCheckName (name ) {
380+ s .incrementErrorCount ()
381+ log .Printf ("[WARN] Invalid check name format from %s" , request .RemoteAddr )
382+ http .Error (writer , "Invalid check name format" , http .StatusBadRequest )
383+ return
384+ }
371385 // Check combined output size (stdout + stderr)
372386 totalOutput := len (check .Stdout ) + len (check .Stderr )
373387 if totalOutput > maxCheckOutput {
@@ -497,9 +511,11 @@ func (s *Server) handleHealth(writer http.ResponseWriter, _ *http.Request) {
497511
498512func loggingMiddleware (next http.Handler ) http.Handler {
499513 return http .HandlerFunc (func (w http.ResponseWriter , r * http.Request ) {
500- // Security: Add security headers
514+ // Security: Add comprehensive security headers
501515 w .Header ().Set ("X-Content-Type-Options" , "nosniff" )
502516 w .Header ().Set ("X-Frame-Options" , "DENY" )
517+ w .Header ().Set ("X-XSS-Protection" , "1; mode=block" )
518+ w .Header ().Set ("Referrer-Policy" , "strict-origin-when-cross-origin" )
503519 w .Header ().Set ("Content-Security-Policy" , "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'" )
504520
505521 start := time .Now ()
@@ -539,3 +555,36 @@ func (s *Server) setHealthy(healthy bool) {
539555 log .Printf ("[INFO] Server health status changed to healthy" )
540556 }
541557}
558+
559+ // constantTimeCompare performs constant-time string comparison to prevent timing attacks.
560+ func constantTimeCompare (a , b string ) bool {
561+ return len (a ) == len (b ) && subtle .ConstantTimeCompare ([]byte (a ), []byte (b )) == 1
562+ }
563+
564+ // isValidHardwareID validates that a hardware ID contains only safe characters.
565+ func isValidHardwareID (id string ) bool {
566+ // Allow alphanumeric, hyphens, underscores, and dots (common in UUIDs and machine IDs)
567+ for _ , r := range id {
568+ if ! ((r >= 'a' && r <= 'z' ) ||
569+ (r >= 'A' && r <= 'Z' ) ||
570+ (r >= '0' && r <= '9' ) ||
571+ r == '-' || r == '_' || r == '.' ) {
572+ return false
573+ }
574+ }
575+ return len (id ) > 0
576+ }
577+
578+ // isValidCheckName validates that a check name contains only safe characters.
579+ func isValidCheckName (name string ) bool {
580+ // Allow alphanumeric, hyphens, and underscores only
581+ for _ , r := range name {
582+ if ! ((r >= 'a' && r <= 'z' ) ||
583+ (r >= 'A' && r <= 'Z' ) ||
584+ (r >= '0' && r <= '9' ) ||
585+ r == '-' || r == '_' ) {
586+ return false
587+ }
588+ }
589+ return len (name ) > 0
590+ }
0 commit comments