adjust code to prevent security scan false positives (#5963)

... about string escaping.
This commit is contained in:
Alessandro Ros
2026-07-19 10:20:37 +02:00
committed by GitHub
parent e14ada6f4b
commit 0e6426c648
2 changed files with 135 additions and 23 deletions
+94 -23
View File
@@ -1,23 +1,58 @@
package whip
import (
"encoding/json"
"fmt"
"regexp"
"strings"
"github.com/pion/webrtc/v4"
)
var linkHeaderCredentialReplacer = strings.NewReplacer(
`\`, `\\`,
`"`, `\"`,
)
func quoteCredential(v string) string {
b, _ := json.Marshal(v)
s := string(b)
return s[1 : len(s)-1]
return linkHeaderCredentialReplacer.Replace(v)
}
func unquoteCredential(v string) string {
var s string
json.Unmarshal([]byte("\""+v+"\""), &s) //nolint:errcheck
return s
func readQuotedCredential(v string) (string, string, bool) {
if len(v) == 0 || v[0] != '"' {
return "", "", false
}
var ret strings.Builder
ret.Grow(len(v) - 1)
escaped := false
for i := 1; i < len(v); i++ {
switch v[i] {
case '\\':
if escaped {
ret.WriteByte('\\')
escaped = false
} else {
escaped = true
}
case '"':
if escaped {
ret.WriteByte('"')
escaped = false
} else {
return ret.String(), v[i+1:], true
}
default:
if escaped {
return "", "", false
}
ret.WriteByte(v[i])
}
}
return "", "", false
}
// LinkHeaderMarshal encodes a link header.
@@ -25,37 +60,73 @@ func LinkHeaderMarshal(iceServers []webrtc.ICEServer) []string {
ret := make([]string, len(iceServers))
for i, server := range iceServers {
link := "<" + server.URLs[0] + ">; rel=\"ice-server\""
var link strings.Builder
link.WriteByte('<')
link.WriteString(server.URLs[0])
link.WriteString(`>; rel="ice-server"`)
if server.Username != "" {
link += "; username=\"" + quoteCredential(server.Username) + "\"" +
"; credential=\"" + quoteCredential(server.Credential.(string)) + "\"; credential-type=\"password\""
link.WriteString(`; username="`)
link.WriteString(quoteCredential(server.Username))
link.WriteString(`"; credential="`)
link.WriteString(quoteCredential(server.Credential.(string)))
link.WriteString(`"; credential-type="password"`)
}
ret[i] = link
ret[i] = link.String()
}
return ret
}
var reLink = regexp.MustCompile(`^<(.+?)>; rel="ice-server"(; username="(.+?)"` +
`; credential="(.+?)"; credential-type="password")?`)
// LinkHeaderUnmarshal decodes a link header.
func LinkHeaderUnmarshal(link []string) ([]webrtc.ICEServer, error) {
ret := make([]webrtc.ICEServer, len(link))
for i, li := range link {
m := reLink.FindStringSubmatch(li)
if m == nil {
var ok bool
li, ok = strings.CutPrefix(li, "<")
if !ok {
return nil, fmt.Errorf("invalid link header: '%s'", li)
}
s := webrtc.ICEServer{
URLs: []string{m[1]},
var url string
url, li, ok = strings.Cut(li, `>; rel="ice-server"`)
if !ok {
return nil, fmt.Errorf("invalid link header: '<%s'", li)
}
if m[3] != "" {
s.Username = unquoteCredential(m[3])
s.Credential = unquoteCredential(m[4])
s := webrtc.ICEServer{
URLs: []string{url},
}
if li != "" {
li, ok = strings.CutPrefix(li, `; username=`)
if !ok {
return nil, fmt.Errorf("invalid link header: '<%s'", li)
}
s.Username, li, ok = readQuotedCredential(li)
if !ok || s.Username == "" {
return nil, fmt.Errorf("invalid link header: '<%s'", li)
}
li, ok = strings.CutPrefix(li, `; credential=`)
if !ok {
return nil, fmt.Errorf("invalid link header: '<%s'", li)
}
s.Credential, li, ok = readQuotedCredential(li)
if !ok {
return nil, fmt.Errorf("invalid link header: '<%s'", li)
}
li, ok = strings.CutPrefix(li, `; credential-type="password"`)
if !ok || li != "" {
return nil, fmt.Errorf("invalid link header: '<%s'", li)
}
s.CredentialType = webrtc.ICECredentialTypePassword
}
@@ -30,6 +30,20 @@ var linkHeaderCases = []struct {
},
},
},
{
"slashes-and-quotes",
[]string{
`<turns:turn.example.com>; rel="ice-server"; username="my\\user\"a"; ` +
`credential="my\\pwd\"b"; credential-type="password"`,
},
[]webrtc.ICEServer{
{
URLs: []string{"turns:turn.example.com"},
Username: "my\\user\"a",
Credential: "my\\pwd\"b",
},
},
},
}
func TestLinkHeaderUnmarshal(t *testing.T) {
@@ -50,3 +64,30 @@ func TestLinkHeaderMarshal(t *testing.T) {
})
}
}
func TestLinkHeaderUnmarshalInvalid(t *testing.T) {
for _, ca := range []struct {
name string
enc []string
}{
{
"invalid escape in username",
[]string{
`<turns:turn.example.com>; rel="ice-server"; username="my\nuser"; ` +
`credential="mypwd"; credential-type="password"`,
},
},
{
"truncated escape in credential",
[]string{
`<turns:turn.example.com>; rel="ice-server"; username="myuser"; ` +
`credential="mypwd\"; credential-type="password"`,
},
},
} {
t.Run(ca.name, func(t *testing.T) {
_, err := LinkHeaderUnmarshal(ca.enc)
require.Error(t, err)
})
}
}