Skip to content
Open
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
16 changes: 8 additions & 8 deletions lib/agent/go.mod
Original file line number Diff line number Diff line change
@@ -1,21 +1,21 @@
module main

go 1.23.0
go 1.24.0

toolchain go1.23.3
toolchain go1.24.8

require (
github.com/stretchr/testify v1.9.0
google.golang.org/grpc v1.74.2
google.golang.org/grpc v1.76.0
google.golang.org/protobuf v1.36.6
)

require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
golang.org/x/net v0.40.0 // indirect
golang.org/x/sys v0.33.0 // indirect
golang.org/x/text v0.25.0 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20250528174236-200df99c418a // indirect
golang.org/x/net v0.42.0 // indirect
golang.org/x/sys v0.34.0 // indirect
golang.org/x/text v0.27.0 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20250804133106-a7a43d27e69b // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
)
20 changes: 19 additions & 1 deletion lib/agent/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,10 @@ golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8=
golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8=
golang.org/x/net v0.40.0 h1:79Xs7wF06Gbdcg4kdCCIQArK11Z1hr5POQ6+fIYHNuY=
golang.org/x/net v0.40.0/go.mod h1:y0hY0exeL2Pku80/zKK7tpntoX23cqL3Oa6njdgRtds=
golang.org/x/net v0.41.0 h1:vBTly1HeNPEn3wtREYfy4GZ/NECgw2Cnl+nK6Nz3uvw=
golang.org/x/net v0.41.0/go.mod h1:B/K4NNqkfmg07DQYrbwvSluqCJOOXwUjeb/5lOisjbA=
golang.org/x/net v0.42.0 h1:jzkYrhi3YQWD6MLBJcsklgQsoAcw89EcZbJw8Z614hs=
golang.org/x/net v0.42.0/go.mod h1:FF1RA5d3u7nAYA4z2TkclSCKh68eSXtiFwcWQpPXdt8=
golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU=
golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc=
Expand All @@ -32,6 +36,8 @@ golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik=
golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw=
golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/sys v0.34.0 h1:H5Y5sJ2L2JRdyv7ROF1he/lPdvFsd0mJHFw2ThKHxLA=
golang.org/x/sys v0.34.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM=
Expand All @@ -40,6 +46,10 @@ golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY=
golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4=
golang.org/x/text v0.25.0 h1:qVyWApTSYLk/drJRO5mDlNYskwQznZmkpV2c8q9zls4=
golang.org/x/text v0.25.0/go.mod h1:WEdwpYrmk1qmdHvhkSTNPm3app7v4rsT8F2UD6+VHIA=
golang.org/x/text v0.26.0 h1:P42AVeLghgTYr4+xUnTRKDMqpar+PtX7KWuNQL21L8M=
golang.org/x/text v0.26.0/go.mod h1:QK15LZJUUQVJxhz7wXgxSy/CJaTFjd0G+YLonydOVQA=
golang.org/x/text v0.27.0 h1:4fGWRpyh641NLlecmyl4LOe6yDdfaYNrGb2zdfo4JV4=
golang.org/x/text v0.27.0/go.mod h1:1D28KMCvyooCX9hBiosv5Tz/+YLxj0j7XhWjpSUF7CU=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250115164207-1a7da9e5054f h1:OxYkA3wjPsZyBylwymxSHa7ViiW1Sml4ToBrncvFehI=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250115164207-1a7da9e5054f/go.mod h1:+2Yz8+CLJbIfL9z73EW45avw8Lmge3xVElCP9zEKi50=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250218202821-56aae31c358a h1:51aaUVRocpvUOSQKM6Q7VuoaktNIaMCLuhZB6DKksq4=
Expand All @@ -48,6 +58,10 @@ google.golang.org/genproto/googleapis/rpc v0.0.0-20250324211829-b45e905df463 h1:
google.golang.org/genproto/googleapis/rpc v0.0.0-20250324211829-b45e905df463/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250528174236-200df99c418a h1:v2PbRU4K3llS09c7zodFpNePeamkAwG3mPrAery9VeE=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250528174236-200df99c418a/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250707201910-8d1bb00bc6a7 h1:pFyd6EwwL2TqFf8emdthzeX+gZE1ElRq3iM8pui4KBY=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250707201910-8d1bb00bc6a7/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250804133106-a7a43d27e69b h1:zPKJod4w6F1+nRGDI9ubnXYhU9NSWoFAijkHkUXeTK8=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250804133106-a7a43d27e69b/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A=
google.golang.org/grpc v1.71.0 h1:kF77BGdPTQ4/JZWMlb9VpJ5pa25aqvVqogsxNHHdeBg=
google.golang.org/grpc v1.71.0/go.mod h1:H0GRtasmQOh9LkFoCPDu3ZrwUtD1YGE+b2vYBYd/8Ec=
google.golang.org/grpc v1.71.1 h1:ffsFWr7ygTUscGPI0KKK6TLrGz0476KUvvsbqWK0rPI=
Expand All @@ -58,6 +72,10 @@ google.golang.org/grpc v1.73.0 h1:VIWSmpI2MegBtTuFt5/JWy2oXxtjJ/e89Z70ImfD2ok=
google.golang.org/grpc v1.73.0/go.mod h1:50sbHOUqWoCQGI8V2HQLJM0B+LMlIUjNSZmow7EVBQc=
google.golang.org/grpc v1.74.2 h1:WoosgB65DlWVC9FqI82dGsZhWFNBSLjQ84bjROOpMu4=
google.golang.org/grpc v1.74.2/go.mod h1:CtQ+BGjaAIXHs/5YS3i473GqwBBa1zGQNevxdeBEXrM=
google.golang.org/grpc v1.75.1 h1:/ODCNEuf9VghjgO3rqLcfg8fiOP0nSluljWFlDxELLI=
google.golang.org/grpc v1.75.1/go.mod h1:JtPAzKiq4v1xcAB2hydNlWI2RnF85XXcV0mhKXr2ecQ=
google.golang.org/grpc v1.76.0 h1:UnVkv1+uMLYXoIz6o7chp59WfQUYA2ex/BXQ9rHZu7A=
google.golang.org/grpc v1.76.0/go.mod h1:Ju12QI8M6iQJtbcsV+awF5a4hfJMLi4X0JLo94ULZ6c=
google.golang.org/protobuf v1.36.4 h1:6A3ZDJHn/eNqc1i+IdefRzy/9PokBTPvcqMySR7NNIM=
google.golang.org/protobuf v1.36.4/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
google.golang.org/protobuf v1.36.5 h1:tPhr+woSbjfYvY6/GPufUoYizxw1cF/yFoxJ2fmpwlM=
Expand All @@ -67,4 +85,4 @@ google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
3 changes: 3 additions & 0 deletions lib/request-processor/context/event_getters.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,9 @@ func GetModule() string {

func getHostNameAndPort(urlCallbackId int) (string, uint32) { // urlcallbackid is the type of data we request, eg C.OUTGOING_REQUEST_URL
urlStr := Context.Callback(urlCallbackId)
// remove all control characters (< 32) and 0x7f(DEL) also replace \@ with @ and remove all whitespace
Copy link
Contributor

@aikido-pr-checks aikido-pr-checks bot Nov 4, 2025

Choose a reason for hiding this comment

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

Comment describes what the code does (removing control chars and whitespace) instead of explaining why URL normalization is required or what invariants it preserves.

Details

✨ AI Reasoning
​​1) The added comment on line 52-53 describes the exact string transformations performed (removing control characters, replacing @, removing whitespace) rather than explaining why these transformations are necessary or any constraints around them.
​2) This harms maintainability because such 'what' comments can become outdated when the helper implementation changes and do not convey the rationale behind normalizing the URL before parsing.
​3) The violation is introduced by this PR because the comment was added in the changed lines and is not present before.

🔧 How do I fix it?
Write comments that explain the purpose, reasoning, or business logic behind the code using words like 'because', 'so that', or 'in order to'.

More info - Comment @AikidoSec feedback: [FEEDBACK] to get better review comments in the future.

// url.Parse fails if the url contains control characters
urlStr = helpers.NormalizeRawUrl(urlStr)
urlParsed, err := url.Parse(urlStr)
if err != nil {
return "", 0
Expand Down
90 changes: 90 additions & 0 deletions lib/request-processor/helpers/normalizeRequestUrl.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
package helpers

import (
"net/url"
"regexp"
"strings"
)

// remove all control characters (< 32) and 0x7f(DEL) + whitespace
func removeCTLByte(urlStr string) string {
Copy link
Contributor

Choose a reason for hiding this comment

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

Function parameter 'urlStr' is reassigned/modified inside removeCTLByte, harming clarity and risking logic errors when mutating while iterating.

Details

✨ AI Reasoning
​​1) The function removeCTLByte iterates over the input string and modifies the parameter 'urlStr' in-place by slicing it while using the original loop bounds.
​2) Reassigning function parameters can confuse readers and makes it harder to reason about the original value; modifying the parameter while iterating also risks skipping characters due to changing length during iteration.
​3) This was introduced in this diff as a new function and therefore is a regression relative to the prior code.

🔧 How do I fix it?
Create new local variables instead of reassigning parameters. Use different variable names to clearly distinguish between input and modified values.

More info - Comment @AikidoSec feedback: [FEEDBACK] to get better review comments in the future.

for i := 0; i < len(urlStr); i++ {
if urlStr[i] <= ' ' || urlStr[i] == 0x7f {
urlStr = urlStr[:i] + urlStr[i+1:]
}
}
return urlStr
}

func removeUserInfo(raw string) string {
Copy link
Contributor

Choose a reason for hiding this comment

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

Function parameter 'raw' is reused/reassigned within removeUserInfo instead of using distinct local variables, reducing clarity.

Details

✨ AI Reasoning
​​1) The function removeUserInfo treats its input 'raw' as mutable and reassigns it (splitting into scheme/rest and reusing 'raw' slices) instead of using new local variables for transformed values.
​2) Reassigning parameters reduces clarity about original input and can lead to subtle bugs; the function was added in this diff.
​3) This is a maintainability concern introduced by the change.

🔧 How do I fix it?
Create new local variables instead of reassigning parameters. Use different variable names to clearly distinguish between input and modified values.

More info - Comment @AikidoSec feedback: [FEEDBACK] to get better review comments in the future.

schemeEnd := strings.Index(raw, "://")
if schemeEnd == -1 {
// No scheme, can't safely identify authority
return raw
}

scheme := raw[:schemeEnd+3]
rest := raw[schemeEnd+3:]

// Authority is up to first '/', '?', or '#' (https://datatracker.ietf.org/doc/html/rfc3986#section-3.2)
authorityEnd := len(rest)
for _, sep := range []string{"/", "?", "#"} {
if idx := strings.Index(rest, sep); idx != -1 && idx < authorityEnd {
authorityEnd = idx
}
}

authority := rest[:authorityEnd]
path := rest[authorityEnd:]

// Remove userinfo if present
if at := strings.LastIndex(authority, "@"); at != -1 {
authority = authority[at+1:]
}

return scheme + authority + path
}

func UnescapeUrl(urlStr string) string {
unescapedUrl, err := url.QueryUnescape(urlStr)
if err != nil {
return urlStr
}
return unescapedUrl
}

// ConvertIPv6Mapped converts IPv6-mapped IPv4 (only if it contains ::ffff:)
// Example: "http://[::ffff:10.0.0.1]" -> "http://10.0.0.1"
func convertIPv6Mapped(input string) string {
Copy link
Contributor

Choose a reason for hiding this comment

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

Function parameter 'input' is reassigned multiple times within convertIPv6Mapped instead of using separate local variables.

Details

✨ AI Reasoning
​​1) convertIPv6Mapped uses its 'input' parameter as a working variable, reassigning it multiple times (removing scheme, trimming brackets, replacing content).
​2) Reassigning parameters harms readability and can obscure original input semantics; this function was added in the diff.
​3) This is a maintainability concern introduced by the change.

🔧 How do I fix it?
Create new local variables instead of reassigning parameters. Use different variable names to clearly distinguish between input and modified values.

More info - Comment @AikidoSec feedback: [FEEDBACK] to get better review comments in the future.

// Return immediately if not IPv6-mapped form
if !strings.Contains(input, "::ffff:") {
return input
}

// Extract URL scheme if present (http://, https://, etc.)
scheme := ""
if strings.Contains(input, "://") {
parts := strings.SplitN(input, "://", 2)
scheme = parts[0] + "://"
input = parts[1]
}

// Strip brackets
input = strings.TrimPrefix(input, "[")
input = strings.TrimSuffix(input, "]")

// Replace ::ffff:x.x.x.x -> x.x.x.x
re := regexp.MustCompile(`::ffff:(\d+\.\d+\.\d+\.\d+)`)
ip := re.ReplaceAllString(input, "$1")

return scheme + ip
}

func NormalizeRawUrl(urlStr string) string {
Copy link
Contributor

Choose a reason for hiding this comment

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

Function parameter 'urlStr' is repeatedly reassigned in NormalizeRawUrl rather than using a new local variable for the normalized value.

Details

✨ AI Reasoning
​​1) NormalizeRawUrl takes 'urlStr' and repeatedly reassigns it through a series of transformations (UnescapeUrl, removeCTLByte, FixURL, removeUserInfo, convertIPv6Mapped).
​2) Reassigning the parameter across multiple transforms reduces ability to reference the original input and can confuse readers; this top-level function was added in the diff.
​3) This is a clarity/maintainability regression introduced here.

🔧 How do I fix it?
Create new local variables instead of reassigning parameters. Use different variable names to clearly distinguish between input and modified values.

More info - Comment @AikidoSec feedback: [FEEDBACK] to get better review comments in the future.

urlStr = UnescapeUrl(urlStr)
urlStr = removeCTLByte(urlStr)
urlStr = FixURL(urlStr)
urlStr = removeUserInfo(urlStr)
urlStr = convertIPv6Mapped(urlStr)
return urlStr
}
24 changes: 24 additions & 0 deletions lib/request-processor/helpers/normalizeRequestUrl_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package helpers

import "testing"

func TestNormalizeRequestUrl(t *testing.T) {
tests := []struct {
input string
expected string
}{
{"http://localhost:4000", "http://localhost:4000"},
{"http://localhost:4000 ", "http://localhost:4000"},
{"http://localhost:4000" + "\x00", "http://localhost:4000"},
{"http://\\@localhost:4000", "http://localhost:4000"},
{"http://127.1.1.1:4000\\\\\\@127.0.0.1:80/", "http://127.0.0.1:80/"},
{"https:/localhost:4000", "https://localhost:4000"},
{"http://127%2E0%2E0%2E1:4000", "http://127.0.0.1:4000"},
}
for _, test := range tests {
result := NormalizeRawUrl(test.input)
if result != test.expected {
t.Errorf("For input '%s', expected %v but got %v", test.input, test.expected, result)
}
}
}
27 changes: 25 additions & 2 deletions lib/request-processor/helpers/tryParseURL.go
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
package helpers

import (
"net"
"net/url"
"strconv"

"golang.org/x/net/idna"
)

func TryParseURL(input string) *url.URL {
parsedURL, err := url.ParseRequestURI(input)
if err != nil {
parsedURL, err := url.Parse(input)
if err != nil || parsedURL.Host == "" {
return nil
}

Expand All @@ -17,5 +19,26 @@ func TryParseURL(input string) *url.URL {
if err == nil {
parsedURL.Host = parsedHost
}
// If the port is not present, we need to add it based on the scheme
Copy link
Contributor

@aikido-pr-checks aikido-pr-checks bot Nov 4, 2025

Choose a reason for hiding this comment

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

Comment states the mechanical action (adding a port) rather than explaining why default ports are added; replace with a goal/rationale comment.

Details

✨ AI Reasoning
​​1) The added comment at line 22 describes what the following code does (adding a port when absent) rather than why this behavior is necessary; 2) This is a redundant "what" comment that doesn't add design or rationale and may become outdated; 3) It harms maintainability by duplicating obvious behavior instead of documenting the reason for adding default ports (e.g., normalizing for SSRF checks or downstream expectations); 4) Flagging is appropriate because replacing it with a brief rationale would be more valuable and is a small, local improvement; 5) The code context (URL normalization) makes the intent clear from code, so the comment is unnecessary; 6) Fix is a small change within the PR scope (update comment to explain rationale).

🔧 How do I fix it?
Write comments that explain the purpose, reasoning, or business logic behind the code using words like 'because', 'so that', or 'in order to'.

More info - Comment @AikidoSec feedback: [FEEDBACK] to get better review comments in the future.

if parsedURL.Port() == "" {
port := 0
switch parsedURL.Scheme {
case "http":
port = 80
case "https":
port = 443
}
parsedURL.Host = parsedURL.Host + ":" + strconv.Itoa(int(port))
Copy link
Contributor

@aikido-pr-checks aikido-pr-checks bot Nov 4, 2025

Choose a reason for hiding this comment

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

Defaulting to port 0 and appending ":0" for unknown schemes results in invalid host values (e.g., example.com:0).

Details

✨ AI Reasoning
​​1) The new code appends a port to parsedURL.Host when parsedURL.Port() == "" by defaulting port to 0 for unknown schemes and always concatenating ":"; 2) This will produce incorrect hosts like "example.com:0" for non-http/https schemes (and generally a bogus :0) harming correctness and potentially downstream URL handling; 3) It was introduced by the new default-port logic in this PR;

🔧 How do I fix it?
Remove debugging statements like console.log, debugger, dd(), or logic bypasses like || true. Keep legitimate logging for monitoring and error handling.

More info - Comment @AikidoSec feedback: [FEEDBACK] to get better review comments in the future.

}
host, port, err := net.SplitHostPort(parsedURL.Host)
if err == nil {
ip := net.ParseIP(host)
if ip != nil {
parsedURL.Host = ip.String() + ":" + port
} else {
parsedURL.Host = host + ":" + port
}
}

return parsedURL
}
2 changes: 1 addition & 1 deletion lib/request-processor/helpers/tryParseURL_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ func TestTryParseURL_InvalidURL(t *testing.T) {
}

func TestTryParseURL_ValidURL(t *testing.T) {
input := "https://example.com"
input := "https://example.com:443"
expected, _ := url.Parse(input)
result := TryParseURL(input)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -142,8 +142,6 @@ var privateIPs = []string{
"0000:0000:0000:0000:0000:0000:0000:0000",
"::",
"::1",
"::ffff:0.0.0.0",
"::ffff:127.0.0.1",
"fe80::",
"fe80::1",
"fe80::abc:1",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,28 +2,50 @@ package ssrf

import (
"main/helpers"
"main/log"
"net/url"
"strconv"
"strings"
)

func findHostnameInUserInput(userInput string, hostname string, port uint32) bool {
func getVariants(userInput string) []string {
variants := []string{userInput, "http://" + userInput, "https://" + userInput}
decodedUserInput, err := url.QueryUnescape(userInput)
if err == nil && decodedUserInput != userInput {
variants = append(variants, decodedUserInput, "http://"+decodedUserInput, "https://"+decodedUserInput)
}
return variants
}

func findHostnameInUserInput(userInput string, hostname string, port uint32) bool {
log.Debugf("findHostnameInUserInput: userInput: %s, hostname: %s, port: %d", userInput, hostname, port)
if len(userInput) <= 1 {
return false
}
// if hostname contains : we need to add the [ and ] to the hostname (ipv6)
Copy link
Contributor

@aikido-pr-checks aikido-pr-checks bot Nov 4, 2025

Choose a reason for hiding this comment

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

Function parameter 'hostname' is reassigned (modified) in-place; avoid reassigning function arguments.

Details

✨ AI Reasoning
​​1) The code updates findHostnameInUserInput to modify the function parameters 'hostname' and 'userInput' in-place (lines 25-28 and 36): it wraps hostname in brackets for IPv6 and replaces userInput with a normalized value.
​2) Reassigning parameters harms clarity because callers' original values are shadowed and readers can't easily see original inputs; it can confuse debugging and reasoning about the function.
​3) This is a maintainability issue introduced by this change (the old code did not reassign these parameters at these locations). Therefore it's a valid violation of the no-argument-reassignment rule.

🔧 How do I fix it?
Create new local variables instead of reassigning parameters. Use different variable names to clearly distinguish between input and modified values.

More info - Comment @AikidoSec feedback: [FEEDBACK] to get better review comments in the future.

if strings.Contains(hostname, ":") {
hostname = "[" + hostname + "]"
}

hostnameURL := helpers.TryParseURL("http://" + hostname)
hostnameURL := helpers.TryParseURL("http://" + hostname + ":" + strconv.Itoa(int(port)))
if hostnameURL == nil {
return false
}

userInput = helpers.ExtractResourceOrOriginal(userInput)
variants := []string{userInput, "http://" + userInput, "https://" + userInput}
userInput = helpers.NormalizeRawUrl(userInput)

variants := getVariants(userInput)

for _, variant := range variants {
userInputURL := helpers.TryParseURL(variant)
if userInputURL == nil {
continue
}

// https://datatracker.ietf.org/doc/html/rfc3986#section-3.2.2
// "The host subcomponent is case-insensitive."
if userInputURL != nil && strings.EqualFold(userInputURL.Hostname(), hostnameURL.Hostname()) {
if strings.EqualFold(userInputURL.Hostname(), hostnameURL.Hostname()) {
userPort := helpers.GetPortFromURL(userInputURL)

if port == 0 {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,25 @@ func TestFindHostnameInUserInput(t *testing.T) {
port uint32
expected bool
}{
{"aa:@localhost:8080", "localhost", 8080, true},
{"http://127.1.1.1:4000∖@127.0.0.1:80/", "127.0.0.1", 80, true},
{"http://127.1.1.1:4000\\\\@127.0.0.1:8080/", "127.0.0.1", 8080, true},
{"http://[0:0:0:0:0:ffff:127.0.0.1]/thefile", "0:0:0:0:0:ffff:127.0.0.1", 80, true},
{"http://[0000:0000:0000:0000:0000:0000:0000:0001]:4000", "0000:0000:0000:0000:0000:0000:0000:0001", 4000, true},
{"http://127.0.0.1:8080#\\@127.2.2.2:80/ ", "127.0.0.1", 8080, true},
{"http://1.1.1.1 &@127.0.0.1:4000# @3.3.3.3/", "127.0.0.1", 4000, true},
{"http://127.1.1.1:4000:\\@@127.0.0.1:8080/", "127.0.0.1", 8080, true},
{"http://127.1.1.1:4000\\@127.0.0.1:8080/", "127.0.0.1", 8080, true},
{"http://127.0.0.1:8080/@/127.1.1.1:4000", "127.0.0.1", 8080, true},
{"http://%31%32%37.%30.%30.%31:4000", "127.0.0.1", 4000, true},
{"http://%30:4000", "0", 4000, true},
{"http://127%2E0%2E0%2E1:4000", "127.0.0.1", 4000, true},
{"http://[::ffff:127.0.0.1]:4000", "::ffff:127.0.0.1", 4000, true},
{"http://[0:0:0:0:0:0:0:1]:4000", "::1", 4000, true},
{"http://[::]:4000", "::", 4000, true},
{"http://[0000:0000:0000:0000:0000:0000:0000:0001]:4000", "::1", 4000, true},
{"http://[::1]:4000", "::1", 4000, true},
{"http://[0:0::1]:4000", "::1", 4000, true},
{"https://m%C3%BCnchen.de", "münchen.de", 0, true},
{"https://münchen.de", "xn--mnchen-3ya.de", 0, true},
{"https://xn--mnchen-3ya.de", "münchen.de", 0, true},
Expand Down
Loading
Loading