feat: add domain-based outbound filtering with allowedDomains/deniedDomains
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

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
This commit is contained in:
Jose B
2026-02-17 11:52:43 -05:00
parent 5aeb9c86c0
commit 6be1cf5620
12 changed files with 1299 additions and 20 deletions

View File

@@ -14,6 +14,7 @@ type Manager struct {
proxyBridge *ProxyBridge
dnsBridge *DnsBridge
reverseBridge *ReverseBridge
filterProxy *FilteringProxy
tun2socksPath string // path to extracted tun2socks binary on host
exposedPorts []int
debug bool
@@ -118,6 +119,36 @@ func (m *Manager) Initialize() error {
}
}
// Start domain filtering proxy if allowedDomains/deniedDomains are configured
if m.config.Network.HasDomainFiltering() {
fp, err := NewFilteringProxy(&m.config.Network, m.debug)
if err != nil {
// Clean up any bridges that were already started
if m.reverseBridge != nil {
m.reverseBridge.Cleanup()
}
if m.dnsBridge != nil {
m.dnsBridge.Cleanup()
}
if m.proxyBridge != nil {
m.proxyBridge.Cleanup()
}
if m.tun2socksPath != "" {
_ = os.Remove(m.tun2socksPath)
}
return fmt.Errorf("failed to start filtering proxy: %w", err)
}
m.filterProxy = fp
m.logDebug("Domain filtering proxy started on %s", fp.Addr())
// Write Node.js proxy bootstrap script so fetch() honors HTTP_PROXY
if bootstrapPath, err := WriteNodeProxyBootstrap(); err != nil {
m.logDebug("Warning: failed to write Node.js proxy bootstrap: %v", err)
} else {
m.logDebug("Node.js proxy bootstrap written to %s", bootstrapPath)
}
}
m.initialized = true
if m.config.Network.ProxyURL != "" {
dnsInfo := "none"
@@ -148,12 +179,12 @@ func (m *Manager) WrapCommand(command string) (string, error) {
plat := platform.Detect()
switch plat {
case platform.MacOS:
return WrapCommandMacOS(m.config, command, m.exposedPorts, m.debug)
return WrapCommandMacOS(m.config, command, m.exposedPorts, m.filterProxy, m.debug)
case platform.Linux:
if m.learning {
return m.wrapCommandLearning(command)
}
return WrapCommandLinux(m.config, command, m.proxyBridge, m.dnsBridge, m.reverseBridge, m.tun2socksPath, m.debug)
return WrapCommandLinux(m.config, command, m.proxyBridge, m.dnsBridge, m.reverseBridge, m.tun2socksPath, m.filterProxy, m.debug)
default:
return "", fmt.Errorf("unsupported platform: %s", plat)
}
@@ -171,7 +202,7 @@ func (m *Manager) wrapCommandLearning(command string) (string, error) {
m.logDebug("Strace log file: %s", m.straceLogPath)
return WrapCommandLinuxWithOptions(m.config, command, m.proxyBridge, m.dnsBridge, m.reverseBridge, m.tun2socksPath, LinuxSandboxOptions{
return WrapCommandLinuxWithOptions(m.config, command, m.proxyBridge, m.dnsBridge, m.reverseBridge, m.tun2socksPath, m.filterProxy, LinuxSandboxOptions{
UseLandlock: false, // Disabled: seccomp blocks ptrace which strace needs
UseSeccomp: false, // Disabled: conflicts with strace
UseEBPF: false,
@@ -201,6 +232,9 @@ func (m *Manager) GenerateLearnedTemplate(cmdName string) (string, error) {
// Cleanup stops the proxies and cleans up resources.
func (m *Manager) Cleanup() {
if m.filterProxy != nil {
m.filterProxy.Shutdown()
}
if m.reverseBridge != nil {
m.reverseBridge.Cleanup()
}