81 lines
2.6 KiB
Go
81 lines
2.6 KiB
Go
package main
|
|
|
|
import "testing"
|
|
|
|
// Which Origin headers count as "this machine".
|
|
//
|
|
// This exists because Flutter Web's dev server binds a random high port on every
|
|
// launch, so no fixed allowlist can name it — the app hit
|
|
// PreflightMissingAllowOriginHeader from http://localhost:65256 and would have
|
|
// hit it again from a different port tomorrow.
|
|
//
|
|
// The reason it is worth a test rather than a one-line helper: the obvious
|
|
// implementation is a prefix or substring match on "localhost", and that quietly
|
|
// admits http://localhost.attacker.com — a completely different machine that
|
|
// merely starts with the right word. Combined with AllowCredentials, that would
|
|
// let an attacker-controlled page make authenticated calls as the signed-in user.
|
|
// A parsed-host comparison is the only version that is actually safe, and this
|
|
// pins it.
|
|
|
|
func TestLoopbackOriginsAreAllowed(t *testing.T) {
|
|
for _, origin := range []string{
|
|
"http://localhost:65256", // the port Flutter Web picked; it changes every run
|
|
"http://localhost:5173",
|
|
"http://localhost",
|
|
"https://localhost:8443",
|
|
"http://127.0.0.1:3000",
|
|
"http://127.0.0.1",
|
|
"http://[::1]:8080",
|
|
} {
|
|
if !isLoopbackOrigin(origin) {
|
|
t.Errorf("%q is this machine and should be allowed in development", origin)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestLookalikeOriginsAreRefused(t *testing.T) {
|
|
// Every one of these contains "localhost" or "127.0.0.1" as a substring and
|
|
// is a different host. A prefix or Contains check would admit all of them.
|
|
for _, origin := range []string{
|
|
"http://localhost.attacker.com",
|
|
"https://localhost.evil.io:443",
|
|
"http://notlocalhost",
|
|
"http://mylocalhost:3000",
|
|
"http://127.0.0.1.attacker.com",
|
|
"http://evil.com/?x=http://localhost:3000",
|
|
"http://evil.com#localhost",
|
|
} {
|
|
if isLoopbackOrigin(origin) {
|
|
t.Errorf("%q is NOT this machine and must be refused", origin)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestNonHTTPSchemesAreRefused(t *testing.T) {
|
|
// An Origin is a scheme/host/port triple. Anything else is either a browser
|
|
// that will not send it or something forged, and neither should be trusted.
|
|
for _, origin := range []string{
|
|
"file://localhost",
|
|
"ftp://localhost:21",
|
|
"javascript:alert(1)",
|
|
"chrome-extension://abcdefghijklmnop",
|
|
} {
|
|
if isLoopbackOrigin(origin) {
|
|
t.Errorf("%q is not an http(s) origin and must be refused", origin)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestMalformedOriginsAreRefused(t *testing.T) {
|
|
for _, origin := range []string{
|
|
"",
|
|
"null", // what a sandboxed iframe sends
|
|
"not a url at all",
|
|
"://missing-scheme",
|
|
} {
|
|
if isLoopbackOrigin(origin) {
|
|
t.Errorf("%q is not a usable origin and must be refused", origin)
|
|
}
|
|
}
|
|
}
|