This repository has been archived on 2026-03-13. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
greywall/internal/sandbox/utils_test.go
Jose B 6be1cf5620
Some checks failed
Build and test / Lint (pull_request) Failing after 1m3s
Build and test / Test (Linux) (pull_request) Failing after 39s
Build and test / Build (pull_request) Successful in 19s
feat: add domain-based outbound filtering with allowedDomains/deniedDomains
Add NetworkConfig.AllowedDomains and DeniedDomains fields for controlling
outbound connections by hostname. Deny rules are checked first (deny wins).
When AllowedDomains is set, only matching domains are permitted. When only
DeniedDomains is set, all domains except denied ones are allowed.

Implement FilteringProxy that wraps gost HTTP proxy with domain enforcement
via AllowConnect callback. Skip GreyHaven proxy/DNS defaults
2026-02-17 11:52:43 -05:00

321 lines
7.3 KiB
Go

package sandbox
import (
"os"
"path/filepath"
"strings"
"testing"
)
func TestContainsGlobChars(t *testing.T) {
tests := []struct {
pattern string
want bool
}{
{"/path/to/file", false},
{"/path/to/dir/", false},
{"relative/path", false},
{"/path/with/asterisk/*", true},
{"/path/with/question?", true},
{"/path/with/brackets[a-z]", true},
{"/path/**/*.go", true},
{"*.txt", true},
{"file[0-9].txt", true},
}
for _, tt := range tests {
t.Run(tt.pattern, func(t *testing.T) {
got := ContainsGlobChars(tt.pattern)
if got != tt.want {
t.Errorf("ContainsGlobChars(%q) = %v, want %v", tt.pattern, got, tt.want)
}
})
}
}
func TestRemoveTrailingGlobSuffix(t *testing.T) {
tests := []struct {
input string
want string
}{
{"/path/to/dir/**", "/path/to/dir"},
{"/path/to/dir", "/path/to/dir"},
{"/path/**/**", "/path/**"},
{"/**", ""},
{"", ""},
}
for _, tt := range tests {
t.Run(tt.input, func(t *testing.T) {
got := RemoveTrailingGlobSuffix(tt.input)
if got != tt.want {
t.Errorf("RemoveTrailingGlobSuffix(%q) = %q, want %q", tt.input, got, tt.want)
}
})
}
}
func TestNormalizePath(t *testing.T) {
home, _ := os.UserHomeDir()
cwd, _ := os.Getwd()
tests := []struct {
name string
input string
want string
wantErr bool
}{
{
name: "tilde alone",
input: "~",
want: home,
},
{
name: "tilde with path",
input: "~/Documents",
want: filepath.Join(home, "Documents"),
},
{
name: "absolute path",
input: "/usr/bin",
want: "/usr/bin",
},
{
name: "relative dot path",
input: "./subdir",
want: filepath.Join(cwd, "subdir"),
},
{
name: "relative parent path",
input: "../sibling",
want: filepath.Join(filepath.Dir(cwd), "sibling"),
},
{
name: "glob pattern preserved",
input: "/path/**/*.go",
want: "/path/**/*.go",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := NormalizePath(tt.input)
// For paths that involve symlink resolution, we just check the result is reasonable
if strings.Contains(tt.input, "**") || strings.Contains(tt.input, "*") {
if got != tt.want {
t.Errorf("NormalizePath(%q) = %q, want %q", tt.input, got, tt.want)
}
return
}
// For tilde and relative paths, we check prefixes since symlinks may resolve differently
if tt.input == "~" {
if got != home && !strings.HasPrefix(got, "/") {
t.Errorf("NormalizePath(%q) = %q, expected home directory", tt.input, got)
}
} else if strings.HasPrefix(tt.input, "~/") {
if !strings.HasPrefix(got, home) && !strings.HasPrefix(got, "/") {
t.Errorf("NormalizePath(%q) = %q, expected path under home", tt.input, got)
}
}
})
}
}
func TestGenerateProxyEnvVars(t *testing.T) {
tests := []struct {
name string
proxyURL string
wantEnvs []string
dontWant []string
}{
{
name: "no proxy",
proxyURL: "",
wantEnvs: []string{
"GREYWALL_SANDBOX=1",
"TMPDIR=/tmp/greywall",
},
dontWant: []string{
"HTTP_PROXY=",
"HTTPS_PROXY=",
"ALL_PROXY=",
},
},
{
name: "socks5 proxy",
proxyURL: "socks5://localhost:1080",
wantEnvs: []string{
"GREYWALL_SANDBOX=1",
"ALL_PROXY=socks5://localhost:1080",
"all_proxy=socks5://localhost:1080",
"HTTP_PROXY=socks5://localhost:1080",
"HTTPS_PROXY=socks5://localhost:1080",
"http_proxy=socks5://localhost:1080",
"https_proxy=socks5://localhost:1080",
"NO_PROXY=",
"no_proxy=",
},
},
{
name: "socks5h proxy",
proxyURL: "socks5h://proxy.example.com:1080",
wantEnvs: []string{
"GREYWALL_SANDBOX=1",
"ALL_PROXY=socks5h://proxy.example.com:1080",
"HTTP_PROXY=socks5h://proxy.example.com:1080",
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := GenerateProxyEnvVars(tt.proxyURL)
// Check expected env vars are present
for _, want := range tt.wantEnvs {
found := false
for _, env := range got {
if strings.HasPrefix(env, want) || env == want {
found = true
break
}
}
if !found {
t.Errorf("GenerateProxyEnvVars(%q) missing %q", tt.proxyURL, want)
}
}
// Check unwanted env vars are not present
for _, dontWant := range tt.dontWant {
for _, env := range got {
if strings.HasPrefix(env, dontWant) {
t.Errorf("GenerateProxyEnvVars(%q) should not contain %q, got %q", tt.proxyURL, dontWant, env)
}
}
}
})
}
}
func TestGenerateHTTPProxyEnvVars(t *testing.T) {
tests := []struct {
name string
httpProxyURL string
wantEnvs []string
dontWant []string
}{
{
name: "no proxy",
httpProxyURL: "",
wantEnvs: []string{
"GREYWALL_SANDBOX=1",
"TMPDIR=/tmp/greywall",
},
dontWant: []string{
"HTTP_PROXY=",
"HTTPS_PROXY=",
},
},
{
name: "http proxy",
httpProxyURL: "http://127.0.0.1:12345",
wantEnvs: []string{
"GREYWALL_SANDBOX=1",
"HTTP_PROXY=http://127.0.0.1:12345",
"HTTPS_PROXY=http://127.0.0.1:12345",
"http_proxy=http://127.0.0.1:12345",
"https_proxy=http://127.0.0.1:12345",
"NO_PROXY=",
"no_proxy=",
},
dontWant: []string{
"ALL_PROXY=",
"all_proxy=",
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := GenerateHTTPProxyEnvVars(tt.httpProxyURL)
for _, want := range tt.wantEnvs {
found := false
for _, env := range got {
if strings.HasPrefix(env, want) || env == want {
found = true
break
}
}
if !found {
t.Errorf("GenerateHTTPProxyEnvVars(%q) missing %q", tt.httpProxyURL, want)
}
}
for _, dontWant := range tt.dontWant {
for _, env := range got {
if strings.HasPrefix(env, dontWant) {
t.Errorf("GenerateHTTPProxyEnvVars(%q) should not contain %q, got %q", tt.httpProxyURL, dontWant, env)
}
}
}
})
}
}
func TestEncodeSandboxedCommand(t *testing.T) {
tests := []struct {
name string
command string
}{
{"simple command", "ls -la"},
{"command with spaces", "grep -r 'pattern' /path/to/dir"},
{"empty command", ""},
{"special chars", "echo $HOME && ls | grep foo"},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
encoded := EncodeSandboxedCommand(tt.command)
if encoded == "" && tt.command != "" {
t.Error("EncodeSandboxedCommand returned empty string")
}
// Roundtrip test
decoded, err := DecodeSandboxedCommand(encoded)
if err != nil {
t.Errorf("DecodeSandboxedCommand failed: %v", err)
}
// Commands are truncated to 100 chars
expected := tt.command
if len(expected) > 100 {
expected = expected[:100]
}
if decoded != expected {
t.Errorf("Roundtrip failed: got %q, want %q", decoded, expected)
}
})
}
}
func TestEncodeSandboxedCommandTruncation(t *testing.T) {
// Test that long commands are truncated
longCommand := strings.Repeat("a", 200)
encoded := EncodeSandboxedCommand(longCommand)
decoded, _ := DecodeSandboxedCommand(encoded)
if len(decoded) != 100 {
t.Errorf("Expected truncated command of 100 chars, got %d", len(decoded))
}
}
func TestDecodeSandboxedCommandInvalid(t *testing.T) {
_, err := DecodeSandboxedCommand("not-valid-base64!!!")
if err == nil {
t.Error("DecodeSandboxedCommand should fail on invalid base64")
}
}