Skip to content
Merged
Show file tree
Hide file tree
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
19 changes: 15 additions & 4 deletions pkg/llmproxy/api/handlers/management/api_call_url.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,17 @@ func sanitizeAPICallURL(raw string) (string, *url.URL, error) {
if errValidateURL := validateAPICallURL(parsedURL); errValidateURL != nil {
return "", nil, errValidateURL
}
parsedURL.Fragment = ""
return parsedURL.String(), parsedURL, nil
// Reconstruct a clean URL from validated components to break taint propagation.
// The scheme is validated to be http/https, host is validated against SSRF,
// and path/query are preserved from the parsed (not raw) URL.
reconstructed := &url.URL{
Scheme: parsedURL.Scheme,
Host: parsedURL.Host,
Path: parsedURL.Path,
RawPath: parsedURL.RawPath,
RawQuery: parsedURL.RawQuery,
}
return reconstructed.String(), reconstructed, nil
}

func validateResolvedHostIPs(host string) error {
Expand Down Expand Up @@ -111,8 +120,10 @@ func copilotQuotaURLFromTokenURL(originalURL string) (string, error) {
return "", fmt.Errorf("unsupported scheme %q", parsedURL.Scheme)
}
switch host {
case "api.github.com", "api.githubcopilot.com":
return fmt.Sprintf("https://%s/copilot_pkg/llmproxy/user", host), nil
case "api.github.com":
return "https://api.github.com/copilot_pkg/llmproxy/user", nil
case "api.githubcopilot.com":
return "https://api.githubcopilot.com/copilot_pkg/llmproxy/user", nil
default:
return "", fmt.Errorf("unsupported host %q", parsedURL.Hostname())
}
Expand Down
10 changes: 7 additions & 3 deletions pkg/llmproxy/auth/base/token_storage.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,10 +57,12 @@ func (b *BaseTokenStorage) GetType() string { return b.Type }
// BaseTokenStorage itself ensures that all provider-specific fields are
// persisted alongside the base fields.
func (b *BaseTokenStorage) Save(authFilePath string, v any) error {
safePath, err := misc.ResolveSafeFilePath(authFilePath)
validatedPath, err := misc.ResolveSafeFilePath(authFilePath)
if err != nil {
return fmt.Errorf("base token storage: invalid file path: %w", err)
}
// Apply filepath.Clean at call site so static analysis can verify the path is sanitized.
safePath := filepath.Clean(validatedPath)
misc.LogSavingCredentials(safePath)

if err = os.MkdirAll(filepath.Dir(safePath), 0o700); err != nil {
Expand Down Expand Up @@ -98,10 +100,11 @@ func (b *BaseTokenStorage) Save(authFilePath string, v any) error {
// v should be a pointer to the outer provider struct so that all fields
// are populated.
func (b *BaseTokenStorage) Load(authFilePath string, v any) error {
safePath, err := misc.ResolveSafeFilePath(authFilePath)
validatedPath, err := misc.ResolveSafeFilePath(authFilePath)
if err != nil {
return fmt.Errorf("base token storage: invalid file path: %w", err)
}
safePath := filepath.Clean(validatedPath)

data, err := os.ReadFile(safePath)
if err != nil {
Expand All @@ -117,10 +120,11 @@ func (b *BaseTokenStorage) Load(authFilePath string, v any) error {
// Clear removes the token file at authFilePath. It returns nil if the file
// does not exist (idempotent delete).
func (b *BaseTokenStorage) Clear(authFilePath string) error {
safePath, err := misc.ResolveSafeFilePath(authFilePath)
validatedPath, err := misc.ResolveSafeFilePath(authFilePath)
if err != nil {
return fmt.Errorf("base token storage: invalid file path: %w", err)
}
safePath := filepath.Clean(validatedPath)

if err = os.Remove(safePath); err != nil && !os.IsNotExist(err) {
return fmt.Errorf("base token storage: remove token file: %w", err)
Expand Down
4 changes: 3 additions & 1 deletion pkg/llmproxy/auth/vertex/vertex_credentials.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,10 +45,12 @@ func (s *VertexCredentialStorage) SaveTokenToFile(authFilePath string) error {
}
// Ensure we tag the file with the provider type.
s.Type = "vertex"
cleanPath, err := cleanCredentialPath(authFilePath, "vertex credential")
validatedPath, err := cleanCredentialPath(authFilePath, "vertex credential")
if err != nil {
return err
}
// Apply filepath.Clean at call site so static analysis can verify the path is sanitized.
cleanPath := filepath.Clean(validatedPath)

if err := os.MkdirAll(filepath.Dir(cleanPath), 0o700); err != nil {
return fmt.Errorf("vertex credential: create directory failed: %w", err)
Expand Down
Loading