Skip to content

security: fix CodeQL SSRF and path injection alerts#854

Merged
KooshaPari merged 1 commit intomainfrom
fix/codeql-security
Mar 5, 2026
Merged

security: fix CodeQL SSRF and path injection alerts#854
KooshaPari merged 1 commit intomainfrom
fix/codeql-security

Conversation

@KooshaPari
Copy link
Owner

@KooshaPari KooshaPari commented Mar 4, 2026

Summary

  • Fix 2 critical SSRF alerts (go/request-forgery) by reconstructing URLs from validated components and using literal allowlisted hostnames instead of string interpolation with tainted variables
  • Fix 5 high path injection alerts (go/path-injection) by applying filepath.Clean at call sites so CodeQL can verify sanitization in the same scope as filesystem operations
  • All existing validation logic (URL allowlisting, IP resolution checks, path traversal rejection) is preserved; these changes only restructure code to make sanitization visible to static analysis

Test plan

  • go build ./... passes
  • go vet ./... passes
  • CodeQL re-scan confirms alerts are resolved
  • Existing management API and auth tests pass in CI

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Bug Fixes
    • Improved URL validation and sanitization for API endpoints
    • Enhanced DNS IP validation with stricter error handling
    • Strengthened file path sanitization across authentication and credential storage to prevent directory traversal vulnerabilities

Break taint propagation chains so CodeQL can verify sanitization:
- SSRF (go/request-forgery): reconstruct URL from validated components
  instead of reusing parsed URL string; use literal allowlisted hostnames
  in copilotQuotaURLFromTokenURL instead of fmt.Sprintf with variable
- Path injection (go/path-injection): apply filepath.Clean at call sites
  in token_storage.go and vertex_credentials.go so static analysis sees
  sanitization in the same scope as the filesystem operations

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@gemini-code-assist
Copy link

Warning

You have reached your daily quota limit. Please wait up to 24 hours and I will start processing your requests again!

@coderabbitai
Copy link

coderabbitai bot commented Mar 4, 2026

📝 Walkthrough

Walkthrough

Security hardening across three modules: API URL handling receives DNS resolution and URL reconstruction improvements, while file path operations in authentication modules incorporate explicit filepath cleaning steps after validation for enhanced sanitization.

Changes

Cohort / File(s) Summary
API URL Validation & Resolution
pkg/llmproxy/api/handlers/management/api_call_url.go
sanitizeAPICallURL reconstructs URLs from validated components instead of clearing fragments; validateResolvedHostIPs now performs DNS resolution and validates resolved IPs; copilotQuotaURLFromTokenURL splits combined host case into explicit host-specific cases.
File Path Sanitization
pkg/llmproxy/auth/base/token_storage.go, pkg/llmproxy/auth/vertex/vertex_credentials.go
Both files add explicit filepath.Clean step after path validation; token_storage applies cleaning to all Save/Load/Clear operations; vertex_credentials renames variable from cleanPath to validatedPath and applies additional cleaning step.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~12 minutes

Poem

🐰 Paths are now squeaky-clean, URLs validated with DNS flair,
Each hop through the network, we sanitize with care,
Fragment by fragment, a rabbit's secure affair,
Cleaned and resolved, our credentials find repair!

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'security: fix CodeQL SSRF and path injection alerts' is fully related to the main change. It accurately describes the primary objective: addressing CodeQL security alerts for SSRF (Server-Side Request Forgery) and path injection vulnerabilities across three files.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch fix/codeql-security

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
pkg/llmproxy/api/handlers/management/api_call_url.go (1)

66-69: ⚠️ Potential issue | 🟠 Major

Add timeout to DNS resolution and address TOCTOU rebinding window.

Line 66 calls net.LookupIP() without timeout or context, risking request blocking. More critically, validating DNS upfront offers no protection against rebinding—the actual HTTP dial path (via h.apiCallTransport()) does not pin or verify that the connection uses one of the resolved IPs, leaving a TOCTOU window for an attacker to rebind the hostname between validation and dial.

Add bounded DNS lookup with context timeout (suggest 2 seconds). To close the rebinding window, the transport's DialContext must validate that the resolved address matches one of the IPs returned from this check, or implement connection-time re-validation.

DNS lookup fix (addressing timeout only)
 import (
+	"context"
 	"fmt"
 	"net"
 	"net/url"
 	"strings"
+	"time"
 )

 func validateResolvedHostIPs(host string) error {
 	trimmed := strings.TrimSpace(host)
 	if trimmed == "" {
 		return fmt.Errorf("invalid url host")
 	}
-	resolved, errLookup := net.LookupIP(trimmed)
+	ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
+	defer cancel()
+	resolved, errLookup := net.DefaultResolver.LookupIP(ctx, "ip", trimmed)
 	if errLookup != nil {
 		return fmt.Errorf("target host resolution failed")
 	}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@pkg/llmproxy/api/handlers/management/api_call_url.go` around lines 66 - 69,
Replace the blind net.LookupIP call with a context-bounded lookup (e.g.,
context.WithTimeout(ctx, 2*time.Second)) and use a resolver API that accepts
context to produce the resolved IP set (the current variables are
resolved/errLookup/trimmed). Then modify the transport returned by
h.apiCallTransport() so its DialContext consults that IP set: after dialing,
extract the remote IP from the connection's RemoteAddr and verify it is one of
the previously resolved IPs (close and return an error if not) to eliminate the
TOCTOU rebind window; alternatively perform the verification inside DialContext
before returning the connection. Ensure the lookup result (set of IPs) is passed
into apiCallTransport/DialContext so it can perform this validation.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In `@pkg/llmproxy/api/handlers/management/api_call_url.go`:
- Around line 66-69: Replace the blind net.LookupIP call with a context-bounded
lookup (e.g., context.WithTimeout(ctx, 2*time.Second)) and use a resolver API
that accepts context to produce the resolved IP set (the current variables are
resolved/errLookup/trimmed). Then modify the transport returned by
h.apiCallTransport() so its DialContext consults that IP set: after dialing,
extract the remote IP from the connection's RemoteAddr and verify it is one of
the previously resolved IPs (close and return an error if not) to eliminate the
TOCTOU rebind window; alternatively perform the verification inside DialContext
before returning the connection. Ensure the lookup result (set of IPs) is passed
into apiCallTransport/DialContext so it can perform this validation.

ℹ️ Review info

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 67070422-e219-4d5d-b164-54b3e8efdb0d

📥 Commits

Reviewing files that changed from the base of the PR and between c4cdd50 and a325b9e.

📒 Files selected for processing (3)
  • pkg/llmproxy/api/handlers/management/api_call_url.go
  • pkg/llmproxy/auth/base/token_storage.go
  • pkg/llmproxy/auth/vertex/vertex_credentials.go
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (5)
  • GitHub Check: go-ci
  • GitHub Check: quality-ci
  • GitHub Check: golangci-lint
  • GitHub Check: lint-test
  • GitHub Check: Analyze (Go) (go)
🧰 Additional context used
📓 Path-based instructions (1)
**/*.go

📄 CodeRabbit inference engine (AGENTS.md)

**/*.go: NEVER create a v2 file - refactor the original instead
NEVER create a new class if an existing one can be made generic
NEVER create custom implementations when an OSS library exists - search pkg.go.dev for existing libraries before writing code
Build generic building blocks (provider interface + registry) before application logic
Use chi for HTTP routing (NOT custom routers)
Use zerolog for logging (NOT fmt.Print)
Use viper for configuration (NOT manual env parsing)
Use go-playground/validator for validation (NOT manual if/else validation)
Use golang.org/x/time/rate for rate limiting (NOT custom limiters)
Use template strings for messages instead of hardcoded messages and config-driven logic instead of code-driven
Zero new lint suppressions without inline justification
All new code must pass: go fmt, go vet, golint
Maximum function length: 40 lines
No placeholder TODOs in committed code

Files:

  • pkg/llmproxy/auth/base/token_storage.go
  • pkg/llmproxy/api/handlers/management/api_call_url.go
  • pkg/llmproxy/auth/vertex/vertex_credentials.go
🔇 Additional comments (4)
pkg/llmproxy/auth/base/token_storage.go (1)

60-66: Good hardening: validated path is consistently used at filesystem call sites.

Line 60/103/123 now validates first, and Line 65/107/127 uses the cleaned path for all file operations. This is a solid security/readability improvement and matches the PR’s static-analysis goal.

Also applies to: 103-107, 123-127

pkg/llmproxy/auth/vertex/vertex_credentials.go (1)

48-53: Path sanitization flow is clearer and consistent.

Using validatedPath and then cleanPath makes the sanitize-then-use flow explicit at the call site, which helps security tooling and code readability.

pkg/llmproxy/api/handlers/management/api_call_url.go (2)

48-58: URL reconstruction from parsed components is a strong SSRF hardening step.

Line 48-58 cleanly breaks taint propagation while preserving validated scheme/host/path/query semantics.


123-126: Nice improvement: explicit host-to-quota URL mapping.

Line 123-126 removes ambiguity and keeps the quota endpoint derivation deterministic for the two allowed hosts.

@KooshaPari KooshaPari merged commit 15b7dc1 into main Mar 5, 2026
31 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant