python.go 3.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166
  1. package python
  2. import (
  3. "crypto/rand"
  4. _ "embed"
  5. "encoding/base64"
  6. "fmt"
  7. "os"
  8. "os/exec"
  9. "path"
  10. "strconv"
  11. "strings"
  12. "time"
  13. "github.com/google/uuid"
  14. "github.com/langgenius/dify-sandbox/internal/core/runner"
  15. "github.com/langgenius/dify-sandbox/internal/core/runner/types"
  16. "github.com/langgenius/dify-sandbox/internal/static"
  17. )
  18. type PythonRunner struct {
  19. runner.TempDirRunner
  20. }
  21. //go:embed prescript.py
  22. var sandbox_fs []byte
  23. func (p *PythonRunner) Run(
  24. code string,
  25. timeout time.Duration,
  26. stdin []byte,
  27. preload string,
  28. options *types.RunnerOptions,
  29. ) (chan []byte, chan []byte, chan bool, error) {
  30. configuration := static.GetDifySandboxGlobalConfigurations()
  31. // initialize the environment
  32. untrusted_code_path, key, err := p.InitializeEnvironment(code, preload, options)
  33. if err != nil {
  34. return nil, nil, nil, err
  35. }
  36. // capture the output
  37. output_handler := runner.NewOutputCaptureRunner()
  38. output_handler.SetTimeout(timeout)
  39. output_handler.SetAfterExitHook(func() {
  40. // remove untrusted code
  41. os.Remove(untrusted_code_path)
  42. })
  43. // create a new process
  44. cmd := exec.Command(
  45. configuration.PythonPath,
  46. untrusted_code_path,
  47. LIB_PATH,
  48. key,
  49. )
  50. cmd.Env = []string{}
  51. cmd.Dir = LIB_PATH
  52. if configuration.Proxy.Socks5 != "" {
  53. cmd.Env = append(cmd.Env, fmt.Sprintf("HTTPS_PROXY=%s", configuration.Proxy.Socks5))
  54. cmd.Env = append(cmd.Env, fmt.Sprintf("HTTP_PROXY=%s", configuration.Proxy.Socks5))
  55. } else if configuration.Proxy.Https != "" || configuration.Proxy.Http != "" {
  56. if configuration.Proxy.Https != "" {
  57. cmd.Env = append(cmd.Env, fmt.Sprintf("HTTPS_PROXY=%s", configuration.Proxy.Https))
  58. }
  59. if configuration.Proxy.Http != "" {
  60. cmd.Env = append(cmd.Env, fmt.Sprintf("HTTP_PROXY=%s", configuration.Proxy.Http))
  61. }
  62. }
  63. if len(configuration.AllowedSyscalls) > 0 {
  64. cmd.Env = append(cmd.Env,
  65. fmt.Sprintf("ALLOWED_SYSCALLS=%s",
  66. strings.Trim(strings.Join(strings.Fields(fmt.Sprint(configuration.AllowedSyscalls)), ","), "[]"),
  67. ),
  68. )
  69. }
  70. err = output_handler.CaptureOutput(cmd)
  71. if err != nil {
  72. return nil, nil, nil, err
  73. }
  74. return output_handler.GetStdout(), output_handler.GetStderr(), output_handler.GetDone(), nil
  75. }
  76. func (p *PythonRunner) InitializeEnvironment(code string, preload string, options *types.RunnerOptions) (string, string, error) {
  77. if !checkLibAvaliable() {
  78. // ensure environment is reversed
  79. releaseLibBinary(false)
  80. }
  81. // create a tmp dir and copy the python script
  82. temp_code_name := strings.ReplaceAll(uuid.New().String(), "-", "_")
  83. temp_code_name = strings.ReplaceAll(temp_code_name, "/", ".")
  84. script := strings.Replace(
  85. string(sandbox_fs),
  86. "{{uid}}", strconv.Itoa(static.SANDBOX_USER_UID), 1,
  87. )
  88. script = strings.Replace(
  89. script,
  90. "{{gid}}", strconv.Itoa(static.SANDBOX_GROUP_ID), 1,
  91. )
  92. if options.EnableNetwork {
  93. script = strings.Replace(
  94. script,
  95. "{{enable_network}}", "1", 1,
  96. )
  97. } else {
  98. script = strings.Replace(
  99. script,
  100. "{{enable_network}}", "0", 1,
  101. )
  102. }
  103. script = strings.Replace(
  104. script,
  105. "{{preload}}",
  106. fmt.Sprintf("%s\n", preload),
  107. 1,
  108. )
  109. // generate a random 512 bit key
  110. key_len := 64
  111. key := make([]byte, key_len)
  112. _, err := rand.Read(key)
  113. if err != nil {
  114. return "", "", err
  115. }
  116. // encrypt the code
  117. encrypted_code := make([]byte, len(code))
  118. for i := 0; i < len(code); i++ {
  119. encrypted_code[i] = code[i] ^ key[i%key_len]
  120. }
  121. // encode code using base64
  122. code = base64.StdEncoding.EncodeToString(encrypted_code)
  123. // encode key using base64
  124. encoded_key := base64.StdEncoding.EncodeToString(key)
  125. code = strings.Replace(
  126. script,
  127. "{{code}}",
  128. code,
  129. 1,
  130. )
  131. untrusted_code_path := fmt.Sprintf("%s/tmp/%s.py", LIB_PATH, temp_code_name)
  132. err = os.MkdirAll(path.Dir(untrusted_code_path), 0755)
  133. if err != nil {
  134. return "", "", err
  135. }
  136. err = os.WriteFile(untrusted_code_path, []byte(code), 0755)
  137. if err != nil {
  138. return "", "", err
  139. }
  140. return untrusted_code_path, encoded_key, nil
  141. }