Skip to content
Open
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
4 changes: 4 additions & 0 deletions build/build_amd64.sh
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
rm -f internal/core/runner/python/python.so
rm -f internal/core/runner/nodejs/nodejs.so
rm -f internal/core/runner/uv/uv.so
rm -f /tmp/sandbox-python/python.so
rm -f /tmp/sandbox-nodejs/nodejs.so
rm -f /tmp/sandbox-uv/uv.so
echo "Building Python lib"
CGO_ENABLED=1 GOOS=linux GOARCH=amd64 go build -o internal/core/runner/python/python.so -buildmode=c-shared -ldflags="-s -w" cmd/lib/python/main.go &&
echo "Building Nodejs lib" &&
CGO_ENABLED=1 GOOS=linux GOARCH=amd64 go build -o internal/core/runner/nodejs/nodejs.so -buildmode=c-shared -ldflags="-s -w" cmd/lib/nodejs/main.go &&
echo "Building uv lib" &&
CGO_ENABLED=1 GOOS=linux GOARCH=amd64 go build -o internal/core/runner/uv/uv.so -buildmode=c-shared -ldflags="-s -w" cmd/lib/uv/main.go &&
echo "Building main" &&
GOOS=linux GOARCH=amd64 go build -o main -ldflags="-s -w" cmd/server/main.go
echo "Building env"
Expand Down
4 changes: 4 additions & 0 deletions build/build_arm64.sh
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
rm -f internal/core/runner/python/python.so
rm -f internal/core/runner/nodejs/nodejs.so
rm -f internal/core/runner/uv/uv.so
rm -f /tmp/sandbox-python/python.so
rm -f /tmp/sandbox-nodejs/nodejs.so
rm -f /tmp/sandbox-uv/uv.so
echo "Building Python lib"
CGO_ENABLED=1 GOOS=linux GOARCH=arm64 go build -o internal/core/runner/python/python.so -buildmode=c-shared -ldflags="-s -w" cmd/lib/python/main.go &&
echo "Building Nodejs lib" &&
CGO_ENABLED=1 GOOS=linux GOARCH=arm64 go build -o internal/core/runner/nodejs/nodejs.so -buildmode=c-shared -ldflags="-s -w" cmd/lib/nodejs/main.go &&
echo "Building uv lib" &&
CGO_ENABLED=1 GOOS=linux GOARCH=arm64 go build -o internal/core/runner/uv/uv.so -buildmode=c-shared -ldflags="-s -w" cmd/lib/uv/main.go &&
echo "Building main" &&
GOOS=linux GOARCH=arm64 go build -o main -ldflags="-s -w" cmd/server/main.go
echo "Building env"
Expand Down
8 changes: 8 additions & 0 deletions cmd/dependencies/init.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package main

import (
"github.com/langgenius/dify-sandbox/internal/core/runner/python"
"github.com/langgenius/dify-sandbox/internal/core/runner/uv"
"github.com/langgenius/dify-sandbox/internal/static"
"github.com/langgenius/dify-sandbox/internal/utils/log"
)
Expand All @@ -15,4 +16,11 @@ func main() {
}

log.Info("Python dependencies initialized successfully")

err = uv.PrepareUvDependenciesEnv()
if err != nil {
log.Panic("failed to initialize uv dependencies sandbox: %v", err)
}

log.Info("UV dependencies initialized successfully")
}
13 changes: 13 additions & 0 deletions cmd/lib/uv/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package main

import (
"github.com/langgenius/dify-sandbox/internal/core/lib/uv"
)
import "C"

//export DifySeccomp
func DifySeccomp(uid int, gid int, enable_network bool) {
uv.InitSeccomp(uid, gid, enable_network)
}

func main() {}
1 change: 1 addition & 0 deletions conf/config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ max_workers: 4
max_requests: 50
worker_timeout: 5
python_path: /usr/local/bin/python3
uv_path: /usr/local/bin/uv
enable_network: True # please make sure there is no network risk in your environment
enable_preload: False # please keep it as False for security purposes
allowed_syscalls: # please leave it empty if you have no idea how seccomp works
Expand Down
2 changes: 2 additions & 0 deletions docker/generate.sh
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ echo "Reading version configuration..."
PYTHON_VERSION=$(yq eval '.versions.python' "$VERSIONS_FILE")
GOLANG_VERSION=$(yq eval '.versions.golang' "$VERSIONS_FILE")
NODEJS_VERSION=$(yq eval '.versions.nodejs' "$VERSIONS_FILE")
UV_VERSION=$(yq eval '.versions.uv' "$VERSIONS_FILE")
PYTHON_PACKAGES=$(yq eval '.versions.python_packages' "$VERSIONS_FILE")
DEBIAN_MIRROR=$(yq eval '.mirrors.debian' "$VERSIONS_FILE")
NODEJS_MIRROR=$(yq eval '.mirrors.nodejs' "$VERSIONS_FILE")
Expand Down Expand Up @@ -79,6 +80,7 @@ esac
sed -e "s/\${PYTHON_VERSION}/${PYTHON_VERSION}/g" \
-e "s/\${GOLANG_VERSION}/${GOLANG_VERSION}/g" \
-e "s/\${NODEJS_VERSION}/${NODEJS_VERSION}/g" \
-e "s|\${UV_VERSION}|${UV_VERSION}|g" \
-e "s|\${PYTHON_PACKAGES}|${PYTHON_PACKAGES}|g" \
-e "s|\${DEBIAN_MIRROR}|${DEBIAN_MIRROR}|g" \
-e "s|\${NODEJS_MIRROR}|${NODEJS_MIRROR}|g" \
Expand Down
2 changes: 2 additions & 0 deletions docker/templates/production.dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ ARG DEBIAN_MIRROR="http://deb.debian.org/debian testing main"
ARG PYTHON_PACKAGES="httpx==0.27.2 requests==2.32.3 jinja2==3.1.6 PySocks httpx[socks]"
ARG NODEJS_VERSION=v20.11.1
ARG NODEJS_MIRROR="https://npmmirror.com/mirrors/node"
ARG UV_VERSION=0.8.3
ARG TARGETARCH

FROM python:${PYTHON_VERSION}
Expand All @@ -27,6 +28,7 @@ RUN echo "deb ${DEBIAN_MIRROR}" > /etc/apt/sources.list \
# Copy binary files
COPY main /main
COPY env /env
COPY --from=ghcr.io/astral-sh/uv:${UV_VERSION} /uv /uvx /usr/local/bin/

# Copy configuration files
COPY conf/config.yaml /conf/config.yaml
Expand Down
7 changes: 7 additions & 0 deletions docker/templates/test.dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ ARG PYTHON_PACKAGES="httpx==0.27.2 requests==2.32.3 jinja2==3.1.6 PySocks httpx[
ARG NODEJS_VERSION=v20.11.1
ARG NODEJS_MIRROR="https://npmmirror.com/mirrors/node"
ARG GOLANG_MIRROR="https://golang.org/dl"
ARG UV_VERSION=0.8.3
ARG TARGETARCH

# Build stage
Expand Down Expand Up @@ -55,9 +56,13 @@ WORKDIR /app
# Copy source code
COPY . /app

# Copy uv binary files
COPY --from=ghcr.io/astral-sh/uv:${UV_VERSION} /uv /uvx /usr/local/bin/

# Copy binary files from build stage
COPY --from=builder /app/internal/core/runner/python/python.so /app/internal/core/runner/python/python.so
COPY --from=builder /app/internal/core/runner/nodejs/nodejs.so /app/internal/core/runner/nodejs/nodejs.so
COPY --from=builder /app/internal/core/runner/uv/uv.so /app/internal/core/runner/uv/uv.so

# Copy configuration files
COPY conf/config.yaml /conf/config.yaml
Expand All @@ -66,6 +71,8 @@ COPY dependencies/python-requirements.txt /dependencies/python-requirements.txt
# Install Python dependencies
RUN pip3 install --no-cache-dir ${PYTHON_PACKAGES}

RUN uv python install 3.10.18

# Install Node.js based on architecture
RUN case "${TARGETARCH}" in \
"amd64") \
Expand Down
1 change: 1 addition & 0 deletions docker/versions.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ versions:
python: "3.10-slim-bookworm"
golang: "1.23.9"
nodejs: "v20.11.1"
uv: "0.8.3"

# Python packages (unified configuration)
python_packages: "httpx==0.27.2 requests==2.32.3 jinja2==3.1.6 PySocks httpx[socks]"
Expand Down
14 changes: 10 additions & 4 deletions internal/controller/run.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,11 @@ import (

func RunSandboxController(c *gin.Context) {
BindRequest(c, func(req struct {
Language string `json:"language" form:"language" binding:"required"`
Code string `json:"code" form:"code" binding:"required"`
Preload string `json:"preload" form:"preload"`
EnableNetwork bool `json:"enable_network" form:"enable_network"`
Language string `json:"language" form:"language" binding:"required"`
Code string `json:"code" form:"code" binding:"required"`
Preload string `json:"preload" form:"preload"`
EnableNetwork bool `json:"enable_network" form:"enable_network"`
Dependenchies []runner_types.Dependency `json:"dependencies" form:"dependencies"`
}) {
switch req.Language {
case "python3":
Expand All @@ -23,6 +24,11 @@ func RunSandboxController(c *gin.Context) {
c.JSON(200, service.RunNodeJsCode(req.Code, req.Preload, &runner_types.RunnerOptions{
EnableNetwork: req.EnableNetwork,
}))
case "uv":
c.JSON(200, service.RunUvCode(req.Code, req.Preload, &runner_types.RunnerOptions{
EnableNetwork: req.EnableNetwork,
Dependenchies: req.Dependenchies,
}))
default:
c.JSON(400, types.ErrorResponse(-400, "unsupported language"))
}
Expand Down
66 changes: 66 additions & 0 deletions internal/core/lib/uv/add_seccomp.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
package uv

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

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

//var allow_syscalls = []int{}

func InitSeccomp(uid int, gid int, enable_network bool) error {
err := syscall.Chroot(".")
if err != nil {
return err
}
err = syscall.Chdir("/")
if err != nil {
return err
}

lib.SetNoNewPrivs()

allowed_syscalls := []int{}
allowed_not_kill_syscalls := []int{}
allowed_not_kill_syscalls = append(allowed_not_kill_syscalls, uv_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)
}
} else {
allowed_syscalls = append(allowed_syscalls, uv_syscall.ALLOW_SYSCALLS...)
if enable_network {
allowed_syscalls = append(allowed_syscalls, uv_syscall.ALLOW_NETWORK_SYSCALLS...)
}
}

err = lib.Seccomp(allowed_syscalls, allowed_not_kill_syscalls)
if err != nil {
return err
}

// setuid
err = syscall.Setuid(uid)
if err != nil {
return err
}

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

return nil
}
3 changes: 2 additions & 1 deletion internal/core/runner/types/runner_options.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ type Dependency struct {
}

type RunnerOptions struct {
EnableNetwork bool `json:"enable_network"`
EnableNetwork bool `json:"enable_network"`
Dependenchies []Dependency `json:"dependencies"`
}

func (r *RunnerOptions) Json() string {
Expand Down
1 change: 1 addition & 0 deletions internal/core/runner/uv/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
uv.so
51 changes: 51 additions & 0 deletions internal/core/runner/uv/env.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package uv

import (
_ "embed"
"os"
"os/exec"
"path"

"github.com/langgenius/dify-sandbox/internal/core/runner"
"github.com/langgenius/dify-sandbox/internal/static"
"github.com/langgenius/dify-sandbox/internal/utils/log"
)

//go:embed env.sh
var env_script string

func PrepareUvDependenciesEnv() error {
config := static.GetDifySandboxGlobalConfigurations()

runner := runner.TempDirRunner{}
err := runner.WithTempDir("/", []string{}, func(root_path string) error {
err := os.WriteFile(path.Join(root_path, "env.sh"), []byte(env_script), 0755)
if err != nil {
return err
}

for _, lib_path := range config.UvLibPaths {
// check if the lib path is available
if _, err := os.Stat(lib_path); err != nil {
log.Warn("uv lib path %s is not available", lib_path)
continue
}
exec_cmd := exec.Command(
"bash",
path.Join(root_path, "env.sh"),
lib_path,
LIB_PATH,
)
exec_cmd.Stderr = os.Stderr

if err := exec_cmd.Run(); err != nil {
return err
}
}

os.RemoveAll(root_path)
return nil
})

return err
}
61 changes: 61 additions & 0 deletions internal/core/runner/uv/env.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
#!/bin/bash

# Check if the correct number of arguments are provided
if [ "$#" -ne 2 ]; then
echo "Usage: $0 <src> <dest>"
exit 1
fi

src="$1"
dest="$2"

# Function to copy and link files
copy_and_link() {
local src_file="$1"
local dest_file="$2"

if [ -L "$src_file" ]; then
# If src_file is a symbolic link, copy it without changing permissions
cp -P "$src_file" "$dest_file"
elif [ -b "$src_file" ] || [ -c "$src_file" ]; then
# If src_file is a device file, copy it and change permissions
cp "$src_file" "$dest_file"
chmod 444 "$dest_file"
else
# Otherwise, create a hard link and change the permissions to read-only
ln -f "$src_file" "$dest_file" 2>/dev/null || { cp "$src_file" "$dest_file" && chmod 444 "$dest_file"; }
fi
}

# Check if src is a file or directory
if [ -f "$src" ]; then
# src is a file, create hard link directly in dest
mkdir -p "$(dirname "$dest/$src")"
copy_and_link "$src" "$dest/$src"
elif [ -d "$src" ]; then
# src is a directory, process as before
mkdir -p "$dest/$src"

# Find all files in the source directory
find "$src" -type f,l | while read -r file; do
# Get the relative path of the file
rel_path="${file#$src/}"
# Get the directory of the relative path
rel_dir=$(dirname "$rel_path")
# Create the same directory structure in the destination
mkdir -p "$dest/$src/$rel_dir"
# Copy and link the file
copy_and_link "$file" "$dest/$src/$rel_path"
done
else
echo "Error: $src is neither a file nor a directory"
exit 1
fi

# copy dev/urandom if it exists
if [ ! -d /var/sandbox/sandbox-uv/dev ]; then
mkdir -p /var/sandbox/sandbox-uv/dev
fi
if [ ! -e /var/sandbox/sandbox-uv/dev/urandom ]; then
cp -a /dev/urandom /var/sandbox/sandbox-uv/dev/
fi
Loading