Skip to content
Merged
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
2 changes: 2 additions & 0 deletions conf/config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,5 @@ proxy:
socks5: ''
http: ''
https: ''
no_proxy: ''
allowed_env_vars: [] # list of environment variable names to propagate from the container to the script execution environment
22 changes: 22 additions & 0 deletions internal/core/runner/nodejs/nodejs.go
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,28 @@ func (p *NodeJsRunner) Run(
}
cmd.ExtraFiles = []*os.File{codeReader}

if configuration.Proxy.Socks5 != "" {
cmd.Env = append(cmd.Env, fmt.Sprintf("HTTPS_PROXY=%s", configuration.Proxy.Socks5))
cmd.Env = append(cmd.Env, fmt.Sprintf("HTTP_PROXY=%s", configuration.Proxy.Socks5))
} else if configuration.Proxy.Https != "" || configuration.Proxy.Http != "" {
if configuration.Proxy.Https != "" {
cmd.Env = append(cmd.Env, fmt.Sprintf("HTTPS_PROXY=%s", configuration.Proxy.Https))
}
if configuration.Proxy.Http != "" {
cmd.Env = append(cmd.Env, fmt.Sprintf("HTTP_PROXY=%s", configuration.Proxy.Http))
}
}

if configuration.Proxy.NoProxy != "" {
cmd.Env = append(cmd.Env, fmt.Sprintf("NO_PROXY=%s", configuration.Proxy.NoProxy))
}

for _, envVar := range configuration.AllowedEnvVars {
if val := os.Getenv(envVar); val != "" {
cmd.Env = append(cmd.Env, fmt.Sprintf("%s=%s", envVar, val))
}
}

if len(configuration.AllowedSyscalls) > 0 {
cmd.Env = append(
cmd.Env,
Expand Down
10 changes: 10 additions & 0 deletions internal/core/runner/python/python.go
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,16 @@ func (p *PythonRunner) Run(
}
}

if configuration.Proxy.NoProxy != "" {
cmd.Env = append(cmd.Env, fmt.Sprintf("NO_PROXY=%s", configuration.Proxy.NoProxy))
}

for _, envVar := range configuration.AllowedEnvVars {
if val := os.Getenv(envVar); val != "" {
cmd.Env = append(cmd.Env, fmt.Sprintf("%s=%s", envVar, val))
}
}

if len(configuration.AllowedSyscalls) > 0 {
cmd.Env = append(cmd.Env,
fmt.Sprintf("ALLOWED_SYSCALLS=%s",
Expand Down
22 changes: 22 additions & 0 deletions internal/static/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -178,7 +178,29 @@ func InitConfig(path string) error {
if difySandboxGlobalConfigurations.Proxy.Http != "" {
slog.Info("using http proxy", "proxy", difySandboxGlobalConfigurations.Proxy.Http)
}

no_proxy := os.Getenv("NO_PROXY")
if no_proxy != "" {
difySandboxGlobalConfigurations.Proxy.NoProxy = no_proxy
}

if difySandboxGlobalConfigurations.Proxy.NoProxy != "" {
slog.Info("using no proxy", "no_proxy", difySandboxGlobalConfigurations.Proxy.NoProxy)
}
}

allowed_env_vars := os.Getenv("ALLOWED_ENV_VARS")
if allowed_env_vars != "" {
parts := strings.Split(allowed_env_vars, ",")
difySandboxGlobalConfigurations.AllowedEnvVars = make([]string, 0, len(parts))
for _, p := range parts {
trimmed := strings.TrimSpace(p)
if trimmed != "" {
difySandboxGlobalConfigurations.AllowedEnvVars = append(difySandboxGlobalConfigurations.AllowedEnvVars, trimmed)
}
}
}

return nil
}

Expand Down
143 changes: 143 additions & 0 deletions internal/static/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -312,3 +312,146 @@ func writeTempConfig(t *testing.T, content string) string {

return configPath
}

func TestInitConfigNoProxyFromEnv(t *testing.T) {
pythonPath := mustFindTestPython(t)
configPath := writeTempConfig(t, "app:\n port: 8194\npython_path: "+pythonPath+"\nenable_network: true\n")

t.Setenv("SOCKS5_PROXY", "")
t.Setenv("HTTPS_PROXY", "")
t.Setenv("HTTP_PROXY", "")
t.Setenv("NO_PROXY", "localhost,127.0.0.1")

if err := InitConfig(configPath); err != nil {
t.Fatalf("InitConfig returned error: %v", err)
}

config := GetDifySandboxGlobalConfigurations()
if config.Proxy.NoProxy != "localhost,127.0.0.1" {
t.Fatalf("expected NO_PROXY to be propagated, got %q", config.Proxy.NoProxy)
}
}

func TestInitConfigNoProxyFromYAML(t *testing.T) {
pythonPath := mustFindTestPython(t)
configPath := writeTempConfig(t, "app:\n port: 8194\npython_path: "+pythonPath+"\nenable_network: true\nproxy:\n no_proxy: 'internal.example.com'\n")

t.Setenv("NO_PROXY", "")

if err := InitConfig(configPath); err != nil {
t.Fatalf("InitConfig returned error: %v", err)
}

config := GetDifySandboxGlobalConfigurations()
if config.Proxy.NoProxy != "internal.example.com" {
t.Fatalf("expected no_proxy from YAML, got %q", config.Proxy.NoProxy)
}
}

func TestInitConfigNoProxyEnvOverridesYAML(t *testing.T) {
pythonPath := mustFindTestPython(t)
configPath := writeTempConfig(t, "app:\n port: 8194\npython_path: "+pythonPath+"\nenable_network: true\nproxy:\n no_proxy: 'from-yaml'\n")

t.Setenv("NO_PROXY", "from-env")

if err := InitConfig(configPath); err != nil {
t.Fatalf("InitConfig returned error: %v", err)
}

config := GetDifySandboxGlobalConfigurations()
if config.Proxy.NoProxy != "from-env" {
t.Fatalf("expected NO_PROXY env to override YAML, got %q", config.Proxy.NoProxy)
}
}

func TestInitConfigNoProxyIgnoredWhenNetworkDisabled(t *testing.T) {
pythonPath := mustFindTestPython(t)
configPath := writeTempConfig(t, "app:\n port: 8194\npython_path: "+pythonPath+"\nenable_network: false\n")

t.Setenv("NO_PROXY", "should-be-ignored")

if err := InitConfig(configPath); err != nil {
t.Fatalf("InitConfig returned error: %v", err)
}

config := GetDifySandboxGlobalConfigurations()
if config.Proxy.NoProxy != "" {
t.Fatalf("expected NO_PROXY to be empty when network disabled, got %q", config.Proxy.NoProxy)
}
}

func TestInitConfigAllowedEnvVarsFromEnv(t *testing.T) {
pythonPath := mustFindTestPython(t)
configPath := writeTempConfig(t, "app:\n port: 8194\npython_path: "+pythonPath+"\n")

t.Setenv("ALLOWED_ENV_VARS", "MY_VAR, ANOTHER_VAR , THIRD_VAR")

if err := InitConfig(configPath); err != nil {
t.Fatalf("InitConfig returned error: %v", err)
}

config := GetDifySandboxGlobalConfigurations()
want := []string{"MY_VAR", "ANOTHER_VAR", "THIRD_VAR"}
if len(config.AllowedEnvVars) != len(want) {
t.Fatalf("expected AllowedEnvVars %v, got %v", want, config.AllowedEnvVars)
}
for i, v := range want {
if config.AllowedEnvVars[i] != v {
t.Fatalf("AllowedEnvVars[%d]: expected %q, got %q", i, v, config.AllowedEnvVars[i])
}
}
}

func TestInitConfigAllowedEnvVarsFromYAML(t *testing.T) {
pythonPath := mustFindTestPython(t)
configPath := writeTempConfig(t, "app:\n port: 8194\npython_path: "+pythonPath+"\nallowed_env_vars:\n - FOO\n - BAR\n")

t.Setenv("ALLOWED_ENV_VARS", "")

if err := InitConfig(configPath); err != nil {
t.Fatalf("InitConfig returned error: %v", err)
}

config := GetDifySandboxGlobalConfigurations()
want := []string{"FOO", "BAR"}
if len(config.AllowedEnvVars) != len(want) {
t.Fatalf("expected AllowedEnvVars %v, got %v", want, config.AllowedEnvVars)
}
for i, v := range want {
if config.AllowedEnvVars[i] != v {
t.Fatalf("AllowedEnvVars[%d]: expected %q, got %q", i, v, config.AllowedEnvVars[i])
}
}
}

func TestInitConfigAllowedEnvVarsEnvOverridesYAML(t *testing.T) {
pythonPath := mustFindTestPython(t)
configPath := writeTempConfig(t, "app:\n port: 8194\npython_path: "+pythonPath+"\nallowed_env_vars:\n - FROM_YAML\n")

t.Setenv("ALLOWED_ENV_VARS", "FROM_ENV")

if err := InitConfig(configPath); err != nil {
t.Fatalf("InitConfig returned error: %v", err)
}

config := GetDifySandboxGlobalConfigurations()
if len(config.AllowedEnvVars) != 1 || config.AllowedEnvVars[0] != "FROM_ENV" {
t.Fatalf("expected ALLOWED_ENV_VARS env to override YAML, got %v", config.AllowedEnvVars)
}
}

func TestInitConfigAllowedEnvVarsEmptyByDefault(t *testing.T) {
pythonPath := mustFindTestPython(t)
configPath := writeTempConfig(t, "app:\n port: 8194\npython_path: "+pythonPath+"\n")

t.Setenv("ALLOWED_ENV_VARS", "")

if err := InitConfig(configPath); err != nil {
t.Fatalf("InitConfig returned error: %v", err)
}

config := GetDifySandboxGlobalConfigurations()
if len(config.AllowedEnvVars) != 0 {
t.Fatalf("expected AllowedEnvVars to be empty by default, got %v", config.AllowedEnvVars)
}
}
8 changes: 5 additions & 3 deletions internal/types/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,10 @@ type DifySandboxGlobalConfigurations struct {
AllowedSyscalls []int `yaml:"allowed_syscalls"`
LogPath string `yaml:"log_path"`
Proxy struct {
Socks5 string `yaml:"socks5"`
Https string `yaml:"https"`
Http string `yaml:"http"`
Socks5 string `yaml:"socks5"`
Https string `yaml:"https"`
Http string `yaml:"http"`
NoProxy string `yaml:"no_proxy"`
} `yaml:"proxy"`
AllowedEnvVars []string `yaml:"allowed_env_vars"`
}
3 changes: 3 additions & 0 deletions tests/integration_tests/conf/config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,10 @@ worker_timeout: 30
python_path: /opt/python/bin/python3
enable_network: True # please make sure there is no network risk in your environment
allowed_syscalls: # please leave it empty if you have no idea how seccomp works
allowed_env_vars:
- TEST_SANDBOX_ENV_VAR
proxy:
socks5: ''
http: ''
https: ''
no_proxy: 'test.no-proxy.internal'
60 changes: 60 additions & 0 deletions tests/integration_tests/nodejs_feature_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -159,3 +159,63 @@ throw new Error("bad input");
t.Fatalf("expected non-zero exit code, got: %d\n", data.ExitCode)
}
}
func TestNodejsNoProxyEnvPropagation(t *testing.T) {
resp := service.RunNodeJsCode(context.TODO(), `
console.log(process.env.NO_PROXY || '');
`, "", &types.RunnerOptions{
EnableNetwork: true,
})
if resp.Code != 0 {
t.Fatal(resp)
}

data := resp.Data.(*service.RunCodeResponse)
if data.Stderr != "" {
t.Fatalf("unexpected stderr: %s\n", data.Stderr)
}
if !strings.Contains(data.Stdout, "test.no-proxy.internal") {
t.Fatalf("expected NO_PROXY to be propagated to subprocess, got: %q\n", data.Stdout)
}
}

func TestNodejsAllowedEnvVarsPropagation(t *testing.T) {
t.Setenv("TEST_SANDBOX_ENV_VAR", "hello_from_allowed_env")

resp := service.RunNodeJsCode(context.TODO(), `
console.log(process.env.TEST_SANDBOX_ENV_VAR || '');
`, "", &types.RunnerOptions{
EnableNetwork: true,
})
if resp.Code != 0 {
t.Fatal(resp)
}

data := resp.Data.(*service.RunCodeResponse)
if data.Stderr != "" {
t.Fatalf("unexpected stderr: %s\n", data.Stderr)
}
if !strings.Contains(data.Stdout, "hello_from_allowed_env") {
t.Fatalf("expected TEST_SANDBOX_ENV_VAR to be propagated to subprocess, got: %q\n", data.Stdout)
}
}

func TestNodejsUnlistedEnvVarNotPropagated(t *testing.T) {
t.Setenv("UNLISTED_ENV_VAR", "should_not_appear")

resp := service.RunNodeJsCode(context.TODO(), `
console.log(process.env.UNLISTED_ENV_VAR || 'not_found');
`, "", &types.RunnerOptions{
EnableNetwork: true,
})
if resp.Code != 0 {
t.Fatal(resp)
}

data := resp.Data.(*service.RunCodeResponse)
if data.Stderr != "" {
t.Fatalf("unexpected stderr: %s\n", data.Stderr)
}
if strings.Contains(data.Stdout, "should_not_appear") {
t.Fatalf("expected UNLISTED_ENV_VAR NOT to be propagated, but it was: %q\n", data.Stdout)
}
}
63 changes: 63 additions & 0 deletions tests/integration_tests/python_feature_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -213,3 +213,66 @@ raise ValueError("bad input")
t.Fatalf("expected non-zero exit code, got: %d\n", data.ExitCode)
}
}
func TestPythonNoProxyEnvPropagation(t *testing.T) {
resp := service.RunPython3Code(context.TODO(), `
import os
print(os.environ.get('NO_PROXY', ''))
`, "", &types.RunnerOptions{
EnableNetwork: true,
})
if resp.Code != 0 {
t.Fatal(resp)
}

data := resp.Data.(*service.RunCodeResponse)
if data.Stderr != "" {
t.Fatalf("unexpected stderr: %s\n", data.Stderr)
}
if !strings.Contains(data.Stdout, "test.no-proxy.internal") {
t.Fatalf("expected NO_PROXY to be propagated to subprocess, got: %q\n", data.Stdout)
}
}

func TestPythonAllowedEnvVarsPropagation(t *testing.T) {
t.Setenv("TEST_SANDBOX_ENV_VAR", "hello_from_allowed_env")

resp := service.RunPython3Code(context.TODO(), `
import os
print(os.environ.get('TEST_SANDBOX_ENV_VAR', ''))
`, "", &types.RunnerOptions{
EnableNetwork: true,
})
if resp.Code != 0 {
t.Fatal(resp)
}

data := resp.Data.(*service.RunCodeResponse)
if data.Stderr != "" {
t.Fatalf("unexpected stderr: %s\n", data.Stderr)
}
if !strings.Contains(data.Stdout, "hello_from_allowed_env") {
t.Fatalf("expected TEST_SANDBOX_ENV_VAR to be propagated to subprocess, got: %q\n", data.Stdout)
}
}

func TestPythonUnlistedEnvVarNotPropagated(t *testing.T) {
t.Setenv("UNLISTED_ENV_VAR", "should_not_appear")

resp := service.RunPython3Code(context.TODO(), `
import os
print(os.environ.get('UNLISTED_ENV_VAR', 'not_found'))
`, "", &types.RunnerOptions{
EnableNetwork: true,
})
if resp.Code != 0 {
t.Fatal(resp)
}

data := resp.Data.(*service.RunCodeResponse)
if data.Stderr != "" {
t.Fatalf("unexpected stderr: %s\n", data.Stderr)
}
if strings.Contains(data.Stdout, "should_not_appear") {
t.Fatalf("expected UNLISTED_ENV_VAR NOT to be propagated, but it was: %q\n", data.Stdout)
}
}
Loading