diff --git a/internal/router/admin_peers.go b/internal/router/admin_peers.go index f392d71..c7e2d9c 100644 --- a/internal/router/admin_peers.go +++ b/internal/router/admin_peers.go @@ -35,7 +35,7 @@ func FetchNodeInfo(baseURL string) (*nodeInfoResponse, error) { } url := strings.TrimSuffix(baseURL, "/") + "/-/nodeinfo" - client := &http.Client{Timeout: 10 * time.Second} + client := fetch.SafeClient() resp, err := client.Get(url) if err != nil { return nil, fmt.Errorf("failed to connect to peer: %w", err) @@ -140,7 +140,7 @@ func (h *handler) checkPeerHealth(w http.ResponseWriter, r *http.Request) { http.Error(w, "Invalid request", http.StatusBadRequest) return } - client := &http.Client{Timeout: 5 * time.Second} + client := fetch.SafeClient() resp, err := client.Get(strings.TrimSuffix(req.URL, "/") + "/-/nodeinfo") if err != nil || resp.StatusCode != http.StatusOK { w.WriteHeader(http.StatusServiceUnavailable) @@ -157,7 +157,7 @@ func (h *handler) checkPeerHealthHandler(w http.ResponseWriter, r *http.Request) http.Error(w, "missing url parameter", http.StatusBadRequest) return } - client := &http.Client{Timeout: 5 * time.Second} + client := fetch.SafeClient() resp, err := client.Get(strings.TrimSuffix(peerURL, "/") + "/-/nodeinfo") status := "down" if err == nil && resp.StatusCode == http.StatusOK { @@ -297,7 +297,7 @@ func importPeerGossip(peerURL string) { } endpoint := strings.TrimSuffix(base, "/") + "/-/peers" - client := &http.Client{Timeout: 10 * time.Second} + client := fetch.SafeClient() resp, err := client.Get(endpoint) if err != nil { log.Printf("[Gossip] fetch %s: %v", endpoint, err) diff --git a/internal/router/announce.go b/internal/router/announce.go index 797087a..6a8a2a8 100644 --- a/internal/router/announce.go +++ b/internal/router/announce.go @@ -216,7 +216,7 @@ func AnnounceURLToPeer(peer registry.Peer, publicURL string) error { return fmt.Errorf("node identity not initialised") } - client := &http.Client{Timeout: 10 * time.Second} + client := fetch.SafeClient() // Step 1: request a fresh nonce. chalBody, _ := json.Marshal(challengeRequest{PublicKey: pubKey}) diff --git a/internal/router/peer_sync.go b/internal/router/peer_sync.go index c3693a0..8c32c48 100644 --- a/internal/router/peer_sync.go +++ b/internal/router/peer_sync.go @@ -48,7 +48,12 @@ func (h *handler) pullFromPeer(peerURL, remotePath, destDir string) error { // 4. Download every file listed in the manifest for relPath := range manifest.Files { - fileURL := peerURL + "/content/" + remotePath + "/" + relPath + cleanRel := strings.TrimPrefix(filepath.ToSlash(filepath.Clean(relPath)), "/") + if cleanRel == "" || strings.HasPrefix(cleanRel, "..") { + return fmt.Errorf("security violation: manifest contains path traversal attempt: %s", relPath) + } + + fileURL := peerURL + "/content/" + remotePath + "/" + cleanRel fileResp, err := http.Get(fileURL) if err != nil { return fmt.Errorf("failed to download %s: %w", relPath, err) @@ -58,7 +63,7 @@ func (h *handler) pullFromPeer(peerURL, remotePath, destDir string) error { return fmt.Errorf("file %s returned HTTP %d", relPath, fileResp.StatusCode) } - localPath := filepath.Join(destDir, relPath) + localPath := filepath.Join(destDir, filepath.FromSlash(cleanRel)) if err := os.MkdirAll(filepath.Dir(localPath), 0755); err != nil { return err } diff --git a/internal/router/sync.go b/internal/router/sync.go index 0090c9a..93df571 100644 --- a/internal/router/sync.go +++ b/internal/router/sync.go @@ -150,7 +150,7 @@ func (h *handler) importRemote(w http.ResponseWriter, r *http.Request) { return } httpReq.Header.Set("User-Agent", "RED-Engine-Sync/1.0") - client := &http.Client{Timeout: 15 * time.Second} + client := fetch.SafeClient() resp, err := client.Do(httpReq) if err != nil { http.Error(w, "Failed to connect to remote server", http.StatusBadGateway)