Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
129 changes: 62 additions & 67 deletions httpclient/multipartrequest.go
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
package httpclient

import (
"bytes"
"context"
"encoding/base64"
"fmt"
"io"
"mime/multipart"
"net/http"
"net/textproto"
"net/url"
"os"
"path/filepath"
"strings"
Expand Down Expand Up @@ -432,109 +434,102 @@
zap.String("method", method),
zap.String("endpoint", endpoint),
zap.String("fileName", fileName),
zap.String("originalBoundary", customBoundary))
zap.String("boundary", customBoundary))

// Remove any leading hyphens from the boundary when setting in header
cleanBoundary := strings.TrimPrefix(customBoundary, "-----")
c.Sugar.Debugw("Processed boundary",
zap.String("cleanBoundary", cleanBoundary))
// URL encode the filename for both the Content-Disposition and data prefix
encodedFileName := url.QueryEscape(fileName)
c.Sugar.Debugw("URL encoded filename", zap.String("encodedFileName", encodedFileName))

// Format the multipart payload with the specified boundary
// Construct payload exactly like the example
payload := fmt.Sprintf("%s\r\n"+
"Content-Disposition: form-data; name=\"file\"; filename=\"%s\"\r\n"+
"Content-Type: image/png\r\n\r\n"+
"data:image/png;name=%s;base64,%s\r\n"+
"%s--",
"%s-",
customBoundary,
fileName,
fileName,
encodedFileName,
encodedFileName,
base64Data,
customBoundary)

// Log the payload structure (not the full base64 data)
payloadPreview := fmt.Sprintf("%s\r\n"+
"Content-Disposition: form-data; name=\"file\"; filename=\"%s\"\r\n"+
"Content-Type: image/png\r\n\r\n"+
"data:image/png;name=%s;base64,[BASE64_DATA_LENGTH: %d]\r\n"+
"%s--",
customBoundary,
fileName,
fileName,
len(base64Data),
customBoundary)
// Create truncated version of payload for logging
truncatedPayload := payload
if len(base64Data) > 100 {
// Find the position of base64 data in the payload
base64Start := strings.Index(payload, ";base64,") + 8
if base64Start > 0 {
truncatedPayload = payload[:base64Start] + "[BASE64_DATA_LENGTH: " +
fmt.Sprintf("%d", len(base64Data)) + "]\r\n" +
customBoundary + "-"
}
}

c.Sugar.Debugw("Constructed payload",
zap.String("payloadStructure", payloadPreview),
zap.Int("totalPayloadLength", len(payload)))
c.Sugar.Debugw("Constructed request payload",
zap.String("payload", truncatedPayload))

url := (*c.Integration).GetFQDN() + endpoint
c.Sugar.Debugw("Constructed URL", zap.String("fullURL", url))
c.Sugar.Debugw("Full request URL", zap.String("url", url))

// Create the request with the formatted payload
// Create request with string payload
req, err := http.NewRequest(method, url, strings.NewReader(payload))
if err != nil {
c.Sugar.Errorw("Failed to create request",
zap.Error(err),
zap.String("method", method),
zap.String("url", url))
c.Sugar.Errorw("Failed to create request", zap.Error(err))
return nil, fmt.Errorf("failed to create request: %v", err)
}

// Clear existing headers and set only what we need
req.Header = http.Header{}
contentTypeHeader := fmt.Sprintf("multipart/form-data; boundary=---%s", cleanBoundary)
req.Header.Set("Accept", "application/json")
req.Header.Set("Content-Type", contentTypeHeader)
// Set headers exactly as in example
req.Header.Set("accept", "application/json")
req.Header.Set("content-type", fmt.Sprintf("multipart/form-data; boundary=%s", strings.TrimPrefix(customBoundary, "---")))

c.Sugar.Debugw("Request headers before auth",
c.Sugar.Debugw("Initial headers",
zap.Any("headers", req.Header),
zap.String("contentType", contentTypeHeader))
zap.String("accept", req.Header.Get("accept")),
zap.String("content-type", req.Header.Get("content-type")))

preservedAccept := req.Header.Get("Accept")
preservedContentType := req.Header.Get("Content-Type")
// Store initial headers
contentType := req.Header.Get("content-type")
accept := req.Header.Get("accept")

// Apply auth
(*c.Integration).PrepRequestParamsAndAuth(req)

// Restore our specific headers
req.Header.Set("Accept", preservedAccept)
req.Header.Set("Content-Type", preservedContentType)

c.Sugar.Debugw("Request headers after auth and restoration",
zap.Any("headers", req.Header))
// Restore and log final headers
req.Header.Set("accept", accept)
req.Header.Set("content-type", contentType)

c.Sugar.Infow("Sending custom multipart request",
zap.String("method", method),
zap.String("url", url),
zap.String("filename", fileName),
zap.String("contentType", req.Header.Get("Content-Type")),
zap.String("accept", req.Header.Get("Accept")))
c.Sugar.Infow("Final request headers",
zap.Any("headers", req.Header),

Check failure

Code scanning / CodeQL

Clear-text logging of sensitive information High

Sensitive data returned by HTTP request headers
flows to a logging call.

Copilot Autofix

AI about 1 year ago

To fix the problem, we should avoid logging sensitive information contained in the headers. Instead of logging all headers, we can selectively log non-sensitive headers or obfuscate sensitive values before logging.

The best way to fix this without changing existing functionality is to:

  1. Identify and exclude sensitive headers from being logged.
  2. Optionally, obfuscate the values of sensitive headers if they need to be logged for debugging purposes.

We will modify the logging statements to exclude or obfuscate sensitive headers.

Suggested changeset 1
httpclient/multipartrequest.go

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/httpclient/multipartrequest.go b/httpclient/multipartrequest.go
--- a/httpclient/multipartrequest.go
+++ b/httpclient/multipartrequest.go
@@ -2,2 +2,3 @@
 
+
 import (
@@ -484,5 +485,5 @@
 	c.Sugar.Debugw("Initial headers",
-		zap.Any("headers", req.Header),
 		zap.String("accept", req.Header.Get("accept")),
-		zap.String("content-type", req.Header.Get("content-type")))
+		zap.String("content-type", req.Header.Get("content-type")),
+		zap.String("authorization", obfuscateHeader(req.Header.Get("authorization"))))
 
@@ -500,5 +501,5 @@
 	c.Sugar.Infow("Final request headers",
-		zap.Any("headers", req.Header),
 		zap.String("accept", req.Header.Get("accept")),
-		zap.String("content-type", req.Header.Get("content-type")))
+		zap.String("content-type", req.Header.Get("content-type")),
+		zap.String("authorization", obfuscateHeader(req.Header.Get("authorization"))))
 
EOF
@@ -2,2 +2,3 @@


import (
@@ -484,5 +485,5 @@
c.Sugar.Debugw("Initial headers",
zap.Any("headers", req.Header),
zap.String("accept", req.Header.Get("accept")),
zap.String("content-type", req.Header.Get("content-type")))
zap.String("content-type", req.Header.Get("content-type")),
zap.String("authorization", obfuscateHeader(req.Header.Get("authorization"))))

@@ -500,5 +501,5 @@
c.Sugar.Infow("Final request headers",
zap.Any("headers", req.Header),
zap.String("accept", req.Header.Get("accept")),
zap.String("content-type", req.Header.Get("content-type")))
zap.String("content-type", req.Header.Get("content-type")),
zap.String("authorization", obfuscateHeader(req.Header.Get("authorization"))))

Copilot is powered by AI and may make mistakes. Always verify output.
zap.String("accept", req.Header.Get("accept")),
zap.String("content-type", req.Header.Get("content-type")))

startTime := time.Now()
// Send the request
resp, err := c.http.Do(req)
duration := time.Since(startTime)

if err != nil {
c.Sugar.Errorw("Failed to send request",
zap.String("method", method),
zap.String("endpoint", endpoint),
zap.Error(err),
zap.Duration("requestDuration", duration))
return nil, fmt.Errorf("failed to send request: %v", err)
}

c.Sugar.Debugw("Request sent successfully",
zap.String("method", method),
zap.String("endpoint", endpoint),
zap.Int("status_code", resp.StatusCode),
zap.Duration("duration", duration))

c.Sugar.Debugw("Response headers",
zap.Any("headers", resp.Header))
c.Sugar.Debugw("Response received",
zap.Int("statusCode", resp.StatusCode),
zap.Any("responseHeaders", resp.Header))

// Handle response
if resp.StatusCode >= 200 && resp.StatusCode < 300 {
c.Sugar.Info("Request succeeded, processing response")
return resp, response.HandleAPISuccessResponse(resp, out, c.Sugar)
}

// For error responses, try to log the response body
if resp.Body != nil {
bodyBytes, err := io.ReadAll(resp.Body)
if err != nil {
c.Sugar.Warnw("Failed to read error response body", zap.Error(err))
} else {
c.Sugar.Errorw("Request failed",
zap.Int("statusCode", resp.StatusCode),
zap.String("responseBody", string(bodyBytes)))
// Create new reader with same data for error handler
resp.Body = io.NopCloser(bytes.NewReader(bodyBytes))
}
}

return resp, response.HandleAPIErrorResponse(resp, c.Sugar)
}
Loading