Documentsdify
Sandbox Seccomp Security
Sandbox Seccomp Security
Type
Topic
Status
Published
Created
Jul 7, 2026
Updated
Jul 7, 2026
Created by
Dosu Bot
Updated by
Dosu Bot

Sandbox Seccomp Security#

langgenius/dify-sandbox is a standalone Go service that executes user-submitted Python 3 and Node.js code on behalf of Dify's Code-execution nodes. Every code run passes through two independent security layers:

  1. Docker: container-level network and filesystem isolation (configured in docker-compose.yaml via the sandbox service)
  2. seccomp BPF: a kernel-level syscall filter installed inside the container on each child process before user code runs

The seccomp filter defaults to ActKillProcess for any syscall not on the whitelist — a blocked call delivers SIGSYS to the process, which the sandbox surfaces as error: operation not permitted.

Dify API ──► dify-sandbox (HTTP :8194)
                  ├── parent process (healthcheck, routing)
                  └── fork child per request
                        ├── chroot(".") + chdir("/")
                        ├── SetNoNewPrivs()
                        ├── seccomp BPF (ActKillProcess default)
                        └── execve(python3 / node, user_code)

The parent process's /health endpoint is always reachable; only the seccomp'd child is affected by filter failures — so seccomp-related outages are silent at the infrastructure layer (container stays "healthy").

Default Syscall Whitelist#

The per-language whitelist for Python/amd64 lives in internal/static/python_syscall/syscalls_amd64.go and is split into three lists:

VariableActionIncluded when
ALLOW_SYSCALLSActAllowAlways
ALLOW_ERROR_SYSCALLSActErrno (no kill)Always — covers CLONE, CLONE3, MKDIRAT, MKDIR
ALLOW_NETWORK_SYSCALLSActAllowenable_network: true only

ALLOW_SYSCALLS covers file I/O (read, write, openat, pread64, readv, writev, readahead), thread operations (futex, sched_getaffinity, sched_setaffinity, set_tid_address), memory (mmap, brk, mprotect, madvise, mbind, set_mempolicy, get_mempolicy), process lifecycle (exit, exit_group, prlimit64), time/event (clock_gettime, epoll_create, epoll_create1, epoll_pwait, pipe2), and modern extensions (statx nr 332, getrandom nr 318, rseq nr 334).

The filter is loaded via SECCOMP_SET_MODE_FILTER with SECCOMP_FILTER_FLAG_TSYNC so it applies to all threads of the child process.

ALLOWED_SYSCALLS — Append Semantics#

Operators can extend the whitelist via two equivalent mechanisms:

  • ALLOWED_SYSCALLS env var: comma-separated syscall numbers (e.g. ALLOWED_SYSCALLS=204,332)
  • allowed_syscalls in config.yaml: list of integers under that key

The AllowedSyscalls []int field is declared in internal/types/config.go and populated from the env var in internal/static/config.go.

In internal/core/lib/python/add_seccomp.go, InitSeccomp() builds the final profile as:

allowed_syscalls = MergeSyscalls(ALLOW_SYSCALLS, [ALLOW_NETWORK_SYSCALLS], SyscallsFromEnv("ALLOWED_SYSCALLS"))

MergeSyscalls() takes a variadic list of slices and returns their deduplicated union — operator-supplied numbers are merged on top of the defaults, not substituted for them.

⚠️ Pre-fix behavior (dify-sandbox ≤ 0.2.15)#

Before PR #275, the ALLOWED_SYSCALLS env var used replace semantics: setting it dropped ALLOW_SYSCALLS and ALLOW_NETWORK_SYSCALLS entirely. Any list that omitted write/exit/mmap etc. caused a 100% sandbox outage while the container's healthcheck continued to report healthy.

Known Issues#

dify-sandbox 0.2.15 regression — panic: could not create filter#

On kernels ≤ 3.10 (e.g. CentOS/RHEL 7), DifySeccomp() fails at filter-creation time with signal: aborted (core dumped). Workaround: pin the sandbox image to langgenius/dify-sandbox:0.2.14.

Incomplete default whitelist — intermittent operation not permitted#

Modern glibc (2.34+) and CPython increasingly call syscalls that were absent from the pre-fix ALLOW_SYSCALLS list. The failure rate is ~0.1% under normal load (same code succeeds 99.9% of the time because the blocked syscall is only hit on certain glibc code paths or timing).

Empirically blocked on amd64 with dify-sandbox 0.2.15 defaults:

nrnamenrname
17pread64204sched_getaffinity
19readv213epoll_create
20writev218set_tid_address
28madvise237mbind
63uname238set_mempolicy
79getcwd239get_mempolicy
99sysinfo281epoll_pwait
187readahead293pipe2
203sched_setaffinity302prlimit64
332statx435clone3

All of these are now present in the current ALLOW_SYSCALLS list in main.

Operator Configuration Reference#

config.yaml (example):

  • enable_network: true/false — gates ALLOW_NETWORK_SYSCALLS
  • allowed_syscalls: [nr, ...] — extra syscalls merged into the whitelist

Environment variables (internal/static/config.go):

  • ALLOWED_SYSCALLS=204,332,... — comma-separated numbers, merged (not replaced) on top of defaults
  • ENABLE_NETWORK=true/false — same as yaml field

Key source files:

FilePurpose
internal/static/python_syscall/syscalls_amd64.goBuilt-in Python/amd64 whitelist (ALLOW_SYSCALLS, ALLOW_ERROR_SYSCALLS, ALLOW_NETWORK_SYSCALLS)
internal/core/lib/python/add_seccomp.goInitSeccomp() — assembles final profile, applies chroot + setuid
internal/core/lib/seccomp.goBPF filter compilation and SECCOMP_SET_MODE_FILTER load
internal/core/lib/syscalls.goMergeSyscalls(), SyscallsFromEnv(), ParseSyscallNumbers()
internal/types/config.goAllowedSyscalls []int config struct field
docker/volumes/sandbox/conf/config.yaml.exampleCanonical operator config template

To change the built-in default whitelist (not just extend it), edit syscalls_amd64.go and rebuild the image — the env var and yaml field can only add syscalls, not remove them.