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
6 changes: 5 additions & 1 deletion FAQ.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,11 @@ If you still see missing shared-library errors, install the package or shared ob

### 2. My Python code returns an "operation not permitted" error?

`dify-sandbox` uses Linux seccomp to restrict system calls. It’s recommended to read the source code ([internal/core/lib/python/add_seccomp.go](https://github.com/langgenius/dify-sandbox/blob/main/internal/core/lib/python/add_seccomp.go)). When you encounter this error, it usually means your code executed a restricted system call. The default allowed system calls are configured in [syscalls_amd64](https://github.com/langgenius/dify-sandbox/blob/main/internal/static/python_syscall/syscalls_amd64.go). You can modify this according to your system’s needs (currently, it cannot be modified through the configuration file).
`dify-sandbox` uses Linux seccomp to restrict system calls. It’s recommended to read the source code ([internal/core/lib/python/add_seccomp.go](https://github.com/langgenius/dify-sandbox/blob/main/internal/core/lib/python/add_seccomp.go)). When you encounter this error, it usually means your code executed a restricted system call. The default allowed system calls are configured in [syscalls_amd64](https://github.com/langgenius/dify-sandbox/blob/main/internal/static/python_syscall/syscalls_amd64.go).

To add extra syscalls without dropping the defaults, set the `ALLOWED_SYSCALLS` environment variable to a comma-separated list of syscall numbers. These numbers are **merged** onto the built-in Python whitelist (and network syscalls when network access is enabled), not substituted for it. For example, `ALLOWED_SYSCALLS=204` keeps `read`/`write`/`exit` and adds `sched_getaffinity`.

If you need to change the default whitelist itself, edit [syscalls_amd64.go](https://github.com/langgenius/dify-sandbox/blob/main/internal/static/python_syscall/syscalls_amd64.go) (and the arm64 file when applicable), then rebuild the image.

To quickly identify the system calls your Python code depends on, here is the recommended method:

Expand Down
35 changes: 7 additions & 28 deletions internal/core/lib/nodejs/add_seccomp.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,12 @@
package nodejs

import (
"os"
"strconv"
"strings"
"syscall"

"github.com/langgenius/dify-sandbox/internal/core/lib"
"github.com/langgenius/dify-sandbox/internal/static/nodejs_syscall"
)

//var allow_syscalls = []int{}

func InitSeccomp(uid int, gid int, enable_network bool) error {
err := syscall.Chroot(".")
if err != nil {
Expand All @@ -26,29 +21,15 @@ func InitSeccomp(uid int, gid int, enable_network bool) error {

lib.SetNoNewPrivs()

allowed_syscalls := []int{}
allowed_not_kill_syscalls := []int{}

allowed_syscall := os.Getenv("ALLOWED_SYSCALLS")
if allowed_syscall != "" {
nums := strings.Split(allowed_syscall, ",")
for num := range nums {
syscall, err := strconv.Atoi(nums[num])
if err != nil {
continue
}
allowed_syscalls = append(allowed_syscalls, syscall)
}
allowed_syscalls = append(allowed_syscalls, syscall.SYS_SETGROUPS)
} else {
allowed_syscalls = append(allowed_syscalls, nodejs_syscall.ALLOW_SYSCALLS...)
allowed_not_kill_syscalls = append(allowed_not_kill_syscalls, nodejs_syscall.ALLOW_ERROR_SYSCALLS...)

if enable_network {
allowed_syscalls = append(allowed_syscalls, nodejs_syscall.ALLOW_NETWORK_SYSCALLS...)
}
allowed_syscalls := append([]int{}, nodejs_syscall.ALLOW_SYSCALLS...)
allowed_not_kill_syscalls := append([]int{}, nodejs_syscall.ALLOW_ERROR_SYSCALLS...)
if enable_network {
allowed_syscalls = append(allowed_syscalls, nodejs_syscall.ALLOW_NETWORK_SYSCALLS...)
}

allowed_syscalls = lib.MergeSyscalls(allowed_syscalls, lib.SyscallsFromEnv("ALLOWED_SYSCALLS"))
allowed_syscalls = lib.MergeSyscalls(allowed_syscalls, []int{syscall.SYS_SETGROUPS})

err = lib.Seccomp(allowed_syscalls, allowed_not_kill_syscalls)
if err != nil {
return err
Expand All @@ -59,13 +40,11 @@ func InitSeccomp(uid int, gid int, enable_network bool) error {
return err
}

// setgid
err = syscall.Setgid(gid)
if err != nil {
return err
}

// setuid
err = syscall.Setuid(uid)
if err != nil {
return err
Expand Down
34 changes: 7 additions & 27 deletions internal/core/lib/python/add_seccomp.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,12 @@
package python

import (
"os"
"strconv"
"strings"
"syscall"

"github.com/langgenius/dify-sandbox/internal/core/lib"
"github.com/langgenius/dify-sandbox/internal/static/python_syscall"
)

//var allow_syscalls = []int{}

func InitSeccomp(uid int, gid int, enable_network bool) error {
err := syscall.Chroot(".")
if err != nil {
Expand All @@ -26,28 +21,15 @@ func InitSeccomp(uid int, gid int, enable_network bool) error {

lib.SetNoNewPrivs()

allowed_syscalls := []int{}
allowed_not_kill_syscalls := []int{}
allowed_not_kill_syscalls = append(allowed_not_kill_syscalls, python_syscall.ALLOW_ERROR_SYSCALLS...)

allowed_syscall := os.Getenv("ALLOWED_SYSCALLS")
if allowed_syscall != "" {
nums := strings.Split(allowed_syscall, ",")
for num := range nums {
syscall, err := strconv.Atoi(nums[num])
if err != nil {
continue
}
allowed_syscalls = append(allowed_syscalls, syscall)
}
allowed_syscalls = append(allowed_syscalls, syscall.SYS_SETGROUPS)
} else {
allowed_syscalls = append(allowed_syscalls, python_syscall.ALLOW_SYSCALLS...)
if enable_network {
allowed_syscalls = append(allowed_syscalls, python_syscall.ALLOW_NETWORK_SYSCALLS...)
}
allowed_syscalls := append([]int{}, python_syscall.ALLOW_SYSCALLS...)
allowed_not_kill_syscalls := append([]int{}, python_syscall.ALLOW_ERROR_SYSCALLS...)
if enable_network {
allowed_syscalls = append(allowed_syscalls, python_syscall.ALLOW_NETWORK_SYSCALLS...)
}

allowed_syscalls = lib.MergeSyscalls(allowed_syscalls, lib.SyscallsFromEnv("ALLOWED_SYSCALLS"))
allowed_syscalls = lib.MergeSyscalls(allowed_syscalls, []int{syscall.SYS_SETGROUPS})

err = lib.Seccomp(allowed_syscalls, allowed_not_kill_syscalls)
if err != nil {
return err
Expand All @@ -58,13 +40,11 @@ func InitSeccomp(uid int, gid int, enable_network bool) error {
return err
}

// setgid
err = syscall.Setgid(gid)
if err != nil {
return err
}

// setuid
err = syscall.Setuid(uid)
if err != nil {
return err
Expand Down
51 changes: 51 additions & 0 deletions internal/core/lib/syscalls.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package lib

import (
"os"
"strconv"
"strings"
)

func ParseSyscallNumbers(value string) []int {
if value == "" {
return nil
}

parts := strings.Split(value, ",")
out := make([]int, 0, len(parts))
for _, part := range parts {
part = strings.TrimSpace(part)
if part == "" {
continue
}

n, err := strconv.Atoi(part)
if err != nil {
continue
}
out = append(out, n)
}

return out
}

func SyscallsFromEnv(key string) []int {
return ParseSyscallNumbers(os.Getenv(key))
}

func MergeSyscalls(lists ...[]int) []int {
seen := make(map[int]struct{})
out := make([]int, 0)

for _, list := range lists {
for _, n := range list {
if _, ok := seen[n]; ok {
continue
}
seen[n] = struct{}{}
out = append(out, n)
}
}

return out
}
58 changes: 58 additions & 0 deletions internal/core/lib/syscalls_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
package lib

import "testing"

func TestMergeSyscallsDedupesAndPreservesOrder(t *testing.T) {
got := MergeSyscalls([]int{1, 2, 3}, []int{2, 4}, []int{3, 5})
want := []int{1, 2, 3, 4, 5}

if len(got) != len(want) {
t.Fatalf("MergeSyscalls() len = %d, want %d (%v)", len(got), len(want), got)
}
for i := range want {
if got[i] != want[i] {
t.Fatalf("MergeSyscalls()[%d] = %d, want %d (full: %v)", i, got[i], want[i], got)
}
}
}

func TestParseSyscallNumbers(t *testing.T) {
got := ParseSyscallNumbers(" 204, 302 ,,435 ")
want := []int{204, 302, 435}

if len(got) != len(want) {
t.Fatalf("ParseSyscallNumbers() = %v, want %v", got, want)
}
for i := range want {
if got[i] != want[i] {
t.Fatalf("ParseSyscallNumbers()[%d] = %d, want %d", i, got[i], want[i])
}
}
}

func TestParseSyscallNumbersIgnoresInvalidEntries(t *testing.T) {
got := ParseSyscallNumbers("204,abc,302")
want := []int{204, 302}

if len(got) != len(want) {
t.Fatalf("ParseSyscallNumbers() = %v, want %v", got, want)
}
}

func TestMergeSyscallsKeepsDefaultsWhenExtending(t *testing.T) {
// read(0), write(1), and exit(60) are required for basic I/O.
defaults := []int{0, 1, 60}
custom := []int{204} // sched_getaffinity on amd64

got := MergeSyscalls(defaults, custom)
want := []int{0, 1, 60, 204}

if len(got) != len(want) {
t.Fatalf("MergeSyscalls() len = %d, want %d (%v)", len(got), len(want), got)
}
for i := range want {
if got[i] != want[i] {
t.Fatalf("MergeSyscalls()[%d] = %d, want %d (full: %v)", i, got[i], want[i], got)
}
}
}
18 changes: 12 additions & 6 deletions internal/static/python_syscall/syscalls_amd64.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,39 +8,45 @@ const (
SYS_GETRANDOM = 318
SYS_RSEQ = 334
SYS_SENDMMSG = 307
SYS_STATX = 332
SYS_CLONE3 = 435
)

var ALLOW_SYSCALLS = []int{
// file io
syscall.SYS_NEWFSTATAT, syscall.SYS_FSTAT, syscall.SYS_FCNTL, syscall.SYS_IOCTL, syscall.SYS_LSEEK, syscall.SYS_GETDENTS64,
syscall.SYS_WRITE, syscall.SYS_CLOSE, syscall.SYS_OPENAT, syscall.SYS_READ,
syscall.SYS_WRITE, syscall.SYS_CLOSE, syscall.SYS_OPENAT, syscall.SYS_READ, syscall.SYS_PREAD64,
syscall.SYS_READV, syscall.SYS_WRITEV, syscall.SYS_READAHEAD,
// thread
syscall.SYS_FUTEX, syscall.SYS_SCHED_GETAFFINITY,
syscall.SYS_FUTEX, syscall.SYS_SCHED_GETAFFINITY, syscall.SYS_SCHED_SETAFFINITY,
syscall.SYS_SET_TID_ADDRESS,
// memory
syscall.SYS_MMAP, syscall.SYS_BRK, syscall.SYS_MPROTECT, syscall.SYS_MUNMAP, syscall.SYS_RT_SIGRETURN,
syscall.SYS_MREMAP, syscall.SYS_MADVISE,
syscall.SYS_MREMAP, syscall.SYS_MADVISE, syscall.SYS_MBIND, syscall.SYS_SET_MEMPOLICY, syscall.SYS_GET_MEMPOLICY,

// user/group
syscall.SYS_SETGROUPS, syscall.SYS_SETGID, syscall.SYS_SETUID, syscall.SYS_GETUID,
// process
syscall.SYS_GETPID, syscall.SYS_GETPPID, syscall.SYS_GETTID,
syscall.SYS_EXIT, syscall.SYS_EXIT_GROUP,
syscall.SYS_TGKILL, syscall.SYS_RT_SIGACTION, syscall.SYS_IOCTL,
syscall.SYS_SCHED_YIELD,
syscall.SYS_SCHED_YIELD, syscall.SYS_PRLIMIT64,
syscall.SYS_SET_ROBUST_LIST, syscall.SYS_GET_ROBUST_LIST, SYS_RSEQ,

// time
syscall.SYS_CLOCK_GETTIME, syscall.SYS_GETTIMEOFDAY, syscall.SYS_NANOSLEEP,
syscall.SYS_EPOLL_CREATE1,
syscall.SYS_EPOLL_CREATE, syscall.SYS_EPOLL_CREATE1,
syscall.SYS_EPOLL_CTL, syscall.SYS_CLOCK_NANOSLEEP, syscall.SYS_PSELECT6,
syscall.SYS_TIME,

syscall.SYS_RT_SIGPROCMASK, syscall.SYS_SIGALTSTACK, SYS_GETRANDOM,
syscall.SYS_EVENTFD2,
syscall.SYS_EVENTFD2, syscall.SYS_PIPE2, syscall.SYS_GETCWD, syscall.SYS_SYSINFO,
syscall.SYS_UNAME, SYS_STATX,
}

var ALLOW_ERROR_SYSCALLS = []int{
syscall.SYS_CLONE,
SYS_CLONE3,
syscall.SYS_MKDIRAT,
syscall.SYS_MKDIR,
}
Expand Down
17 changes: 12 additions & 5 deletions internal/static/python_syscall/syscalls_arm64.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,20 +7,25 @@ import (
)

const (
SYS_RSEQ = 293
SYS_RSEQ = 293
SYS_STATX = 397
SYS_CLONE3 = 435
)

var ALLOW_SYSCALLS = []int{
// file io
syscall.SYS_WRITE, syscall.SYS_CLOSE, syscall.SYS_OPENAT, syscall.SYS_READ, syscall.SYS_LSEEK, syscall.SYS_GETDENTS64,
syscall.SYS_FSTAT, syscall.SYS_FCNTL,
syscall.SYS_FSTAT, syscall.SYS_FCNTL, syscall.SYS_PREAD64,
syscall.SYS_READV, syscall.SYS_WRITEV,

// thread
syscall.SYS_FUTEX, syscall.SYS_SCHED_GETAFFINITY,
syscall.SYS_FUTEX, syscall.SYS_SCHED_GETAFFINITY, syscall.SYS_SCHED_SETAFFINITY,
syscall.SYS_SET_TID_ADDRESS,

// memory
syscall.SYS_MMAP, syscall.SYS_BRK, syscall.SYS_MPROTECT, syscall.SYS_MUNMAP, syscall.SYS_RT_SIGRETURN, syscall.SYS_RT_SIGPROCMASK,
syscall.SYS_SIGALTSTACK, syscall.SYS_MREMAP, syscall.SYS_MADVISE,
syscall.SYS_MBIND, syscall.SYS_SET_MEMPOLICY, syscall.SYS_GET_MEMPOLICY,

// user/group
syscall.SYS_SETGROUPS, syscall.SYS_SETGID, syscall.SYS_SETUID, syscall.SYS_GETUID,
Expand All @@ -29,7 +34,7 @@ var ALLOW_SYSCALLS = []int{
syscall.SYS_GETPID, syscall.SYS_GETPPID, syscall.SYS_GETTID,
syscall.SYS_EXIT, syscall.SYS_EXIT_GROUP,
syscall.SYS_TGKILL, syscall.SYS_RT_SIGACTION,
syscall.SYS_IOCTL, syscall.SYS_SCHED_YIELD,
syscall.SYS_IOCTL, syscall.SYS_SCHED_YIELD, syscall.SYS_PRLIMIT64,
syscall.SYS_GET_ROBUST_LIST, syscall.SYS_SET_ROBUST_LIST,
SYS_RSEQ,

Expand All @@ -41,11 +46,13 @@ var ALLOW_SYSCALLS = []int{

// get random
syscall.SYS_GETRANDOM,
syscall.SYS_EVENTFD2,
syscall.SYS_EVENTFD2, syscall.SYS_PIPE2, syscall.SYS_GETCWD, syscall.SYS_SYSINFO,
syscall.SYS_UNAME, SYS_STATX,
}

var ALLOW_ERROR_SYSCALLS = []int{
syscall.SYS_CLONE,
SYS_CLONE3,
syscall.SYS_MKDIRAT,
}

Expand Down
Loading