Skip to content
Merged
8 changes: 4 additions & 4 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -58,11 +58,11 @@ DOCKER_NETWORK_MODE=none
DOCKER_READ_ONLY=true

# Resource Limits - Execution
MAX_EXECUTION_TIME=30
MAX_EXECUTION_TIME=120
MAX_MEMORY_MB=512
MAX_CPUS=1
MAX_CPU_QUOTA=50000 #Deprecated
MAX_PROCESSES=32
MAX_PIDS=512
MAX_OPEN_FILES=1024

# Resource Limits - Files
Expand All @@ -84,7 +84,7 @@ SESSION_ID_LENGTH=32

# MinIO Orphan Cleanup (optional)
# Enable periodic pruning of MinIO objects older than TTL with missing Redis sessions
ENABLE_ORPHAN_MINIO_CLEANUP=false
ENABLE_ORPHAN_MINIO_CLEANUP=true

# Container Pool Configuration
CONTAINER_POOL_ENABLED=true
Expand Down Expand Up @@ -174,4 +174,4 @@ HEALTH_CHECK_TIMEOUT=5
# Development Configuration
ENABLE_CORS=false
# CORS_ORIGINS=http://localhost:3000,http://localhost:8080
ENABLE_DOCS=true
ENABLE_DOCS=false
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ coverage.xml
.hypothesis/
.pytest_cache/
cover/

baseline_complexity.json
# Translations
*.mo
*.pot
Expand Down
1 change: 1 addition & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ services:
- ${SSL_CERTS_PATH:-./ssl}:/app/ssl
- ./dashboard:/app/dashboard
- ./src:/app/src
- ./docker:/app/docker:ro # Seccomp profile for container security
depends_on:
- redis
- minio
Expand Down
176 changes: 176 additions & 0 deletions docker/seccomp-sandbox.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,176 @@
{
"defaultAction": "SCMP_ACT_ALLOW",
"defaultErrnoRet": 1,
"architectures": [
"SCMP_ARCH_X86_64",
"SCMP_ARCH_X86",
"SCMP_ARCH_X32",
"SCMP_ARCH_AARCH64",
"SCMP_ARCH_ARM"
],
"syscalls": [
{
"names": [
"ptrace"
],
"action": "SCMP_ACT_ERRNO",
"errnoRet": 1,
"comment": "Block process tracing - caused container hang with PTRACE_TRACEME"
},
{
"names": [
"process_vm_readv",
"process_vm_writev"
],
"action": "SCMP_ACT_ERRNO",
"errnoRet": 1,
"comment": "Block cross-process memory access"
},
{
"names": [
"personality"
],
"action": "SCMP_ACT_ERRNO",
"errnoRet": 1,
"comment": "Block ASLR disabling"
},
{
"names": [
"mount",
"umount",
"umount2"
],
"action": "SCMP_ACT_ERRNO",
"errnoRet": 1,
"comment": "Block filesystem manipulation (defense-in-depth, also blocked by CAP_SYS_ADMIN)"
},
{
"names": [
"pivot_root",
"chroot"
],
"action": "SCMP_ACT_ERRNO",
"errnoRet": 1,
"comment": "Block container escape vectors (defense-in-depth, also blocked by CAP_SYS_CHROOT)"
},
{
"names": [
"reboot",
"kexec_load",
"kexec_file_load"
],
"action": "SCMP_ACT_ERRNO",
"errnoRet": 1,
"comment": "Block system disruption (defense-in-depth, also blocked by CAP_SYS_BOOT)"
},
{
"names": [
"init_module",
"finit_module",
"delete_module"
],
"action": "SCMP_ACT_ERRNO",
"errnoRet": 1,
"comment": "Block kernel module manipulation (defense-in-depth, also blocked by CAP_SYS_MODULE)"
},
{
"names": [
"acct"
],
"action": "SCMP_ACT_ERRNO",
"errnoRet": 1,
"comment": "Block process accounting manipulation (defense-in-depth, also blocked by CAP_SYS_PACCT)"
},
{
"names": [
"swapon",
"swapoff"
],
"action": "SCMP_ACT_ERRNO",
"errnoRet": 1,
"comment": "Block swap manipulation (defense-in-depth, also blocked by CAP_SYS_ADMIN)"
},
{
"names": [
"sethostname",
"setdomainname"
],
"action": "SCMP_ACT_ERRNO",
"errnoRet": 1,
"comment": "Block host identity changes (defense-in-depth, also blocked by CAP_SYS_ADMIN)"
},
{
"names": [
"clock_settime",
"clock_adjtime",
"settimeofday",
"adjtimex"
],
"action": "SCMP_ACT_ERRNO",
"errnoRet": 1,
"comment": "Block time manipulation (defense-in-depth, also blocked by CAP_SYS_TIME)"
},
{
"names": [
"iopl",
"ioperm"
],
"action": "SCMP_ACT_ERRNO",
"errnoRet": 1,
"comment": "Block direct I/O port access (defense-in-depth, also blocked by CAP_SYS_RAWIO)"
},
{
"names": [
"create_module",
"get_kernel_syms",
"query_module"
],
"action": "SCMP_ACT_ERRNO",
"errnoRet": 1,
"comment": "Block legacy kernel module syscalls"
},
{
"names": [
"unshare",
"setns"
],
"action": "SCMP_ACT_ERRNO",
"errnoRet": 1,
"comment": "Block namespace manipulation"
},
{
"names": [
"userfaultfd"
],
"action": "SCMP_ACT_ERRNO",
"errnoRet": 1,
"comment": "Block userfaultfd - can be used in exploits"
},
{
"names": [
"bpf"
],
"action": "SCMP_ACT_ERRNO",
"errnoRet": 1,
"comment": "Block BPF - powerful and often exploited"
},
{
"names": [
"perf_event_open"
],
"action": "SCMP_ACT_ERRNO",
"errnoRet": 1,
"comment": "Block perf events - can leak kernel info"
},
{
"names": [
"add_key",
"keyctl",
"request_key"
],
"action": "SCMP_ACT_ERRNO",
"errnoRet": 1,
"comment": "Block kernel keyring manipulation"
}
]
}
41 changes: 6 additions & 35 deletions docker/security-profiles.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,8 @@
"tmpfs": {
"/tmp": "rw,noexec,nosuid,size=100m"
},
"pidsLimit": 512,
"ulimits": [
{
"name": "nproc",
"soft": 32,
"hard": 32
},
{
"name": "nofile",
"soft": 1024,
Expand All @@ -34,20 +30,16 @@
"restrictedProfile": {
"name": "code-interpreter-restricted",
"description": "Highly restricted security profile for untrusted code",
"securityOpt": ["no-new-privileges:true", "seccomp:unconfined"],
"securityOpt": ["no-new-privileges:true"],
"capDrop": ["ALL"],
"capAdd": [],
"readOnly": true,
"networkMode": "none",
"tmpfs": {
"/tmp": "rw,noexec,nosuid,size=50m"
},
"pidsLimit": 256,
"ulimits": [
{
"name": "nproc",
"soft": 16,
"hard": 16
},
{
"name": "nofile",
"soft": 512,
Expand All @@ -67,36 +59,15 @@
"languageProfiles": {
"python": {
"inherits": "defaultProfile",
"description": "Security profile optimized for Python execution",
"ulimits": [
{
"name": "nproc",
"soft": 64,
"hard": 64
}
]
"description": "Security profile optimized for Python execution"
},
"nodejs": {
"inherits": "defaultProfile",
"description": "Security profile optimized for Node.js execution",
"ulimits": [
{
"name": "nproc",
"soft": 48,
"hard": 48
}
]
"description": "Security profile optimized for Node.js execution"
},
"java": {
"inherits": "defaultProfile",
"description": "Security profile optimized for Java execution",
"ulimits": [
{
"name": "nproc",
"soft": 128,
"hard": 128
}
]
"description": "Security profile optimized for Java execution"
},
"go": {
"inherits": "defaultProfile",
Expand Down
14 changes: 7 additions & 7 deletions docs/CONFIGURATION.md
Original file line number Diff line number Diff line change
Expand Up @@ -184,13 +184,13 @@ Docker is used for secure code execution in containers.

#### Execution Limits

| Variable | Default | Description |
| -------------------- | ------- | ------------------------------------- |
| `MAX_EXECUTION_TIME` | `30` | Maximum code execution time (seconds) |
| `MAX_MEMORY_MB` | `512` | Maximum memory per execution (MB) |
| `MAX_CPU_QUOTA` | `50000` | CPU quota (100000 = 1 CPU) |
| `MAX_PROCESSES` | `32` | Maximum processes per container |
| `MAX_OPEN_FILES` | `1024` | Maximum open files per container |
| Variable | Default | Description |
| -------------------- | ------- | ---------------------------------------------------------------- |
| `MAX_EXECUTION_TIME` | `30` | Maximum code execution time (seconds) |
| `MAX_MEMORY_MB` | `512` | Maximum memory per execution (MB) |
| `MAX_CPU_QUOTA` | `50000` | CPU quota (100000 = 1 CPU) |
| `MAX_PIDS` | `512` | Per-container process limit (cgroup pids_limit, prevents fork bombs) |
| `MAX_OPEN_FILES` | `1024` | Maximum open files per container |

#### File Limits

Expand Down
15 changes: 10 additions & 5 deletions src/api/files.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
# Local application imports
from ..config import settings
from ..dependencies import FileServiceDep
from ..services.execution.output import OutputProcessor
from ..utils.id_generator import generate_session_id


Expand Down Expand Up @@ -119,13 +120,13 @@ async def upload_file(
content_type=file.content_type,
)

# Get file info for complete details
file_info = await file_service.get_file_info(session_id, file_id)
# Sanitize filename to match what will be used in container
sanitized_name = OutputProcessor.sanitize_filename(file.filename)

uploaded_files.append(
{
"id": file_id,
"name": file.filename,
"name": sanitized_name,
"session_id": session_id,
"content": None, # LibreChat doesn't return content in upload response
"size": len(content),
Expand Down Expand Up @@ -207,10 +208,12 @@ async def list_files(
# Return simple file information
simple_files = []
for file_info in files:
# Return sanitized filename to match container
sanitized_name = OutputProcessor.sanitize_filename(file_info.filename)
simple_files.append(
{
"id": file_info.file_id,
"name": file_info.filename,
"name": sanitized_name,
"path": file_info.path,
}
)
Expand All @@ -219,9 +222,11 @@ async def list_files(
# Return full file details - LibreChat format
detailed_files = []
for file_info in files:
# Return sanitized filename to match container
sanitized_name = OutputProcessor.sanitize_filename(file_info.filename)
detailed_files.append(
{
"name": file_info.filename,
"name": sanitized_name,
"id": file_info.file_id,
"session_id": session_id,
"content": None, # Not returned in list
Expand Down
Loading
Loading