Python Lambda Executor Security Architecture
Understanding how Krill securely executes user-provided Python scripts with multi-level sandboxing and isolation
Overview
Krill Server includes a powerful Lambda execution system that allows users to run custom Python scripts as part of their automation workflows. Since these scripts run on your server with access to your data, security is paramount. This document explains Krill’s multi-layered security approach to safely execute user-provided Python code.
What Are Krill Lambdas?
Lambdas in Krill are Python scripts that act as data transformation functions in your automation pipeline. They:
- Read input from a source DataPoint (sensor, API, or other data source)
- Process data using your custom Python logic
- Write output to a target DataSource for storage or further processing
Common use cases include:
- Data transformation (e.g., temperature unit conversion)
- Sensor data parsing and averaging
- Custom calculations and algorithms
- Integration with external services
Lambda Execution Flow
sequenceDiagram
participant Source as Source DataPoint
participant Lambda as Lambda Node
participant Executor as Python Executor
participant Sandbox as Sandbox (Firejail/Docker)
participant Script as Python Script
participant Target as Target DataSource
Source->>Lambda: New data arrives
Lambda->>Executor: Trigger execution
Executor->>Executor: Validate script path
Note over Executor: Path traversal check<br/>Must be in /opt/krill/lambdas
Executor->>Executor: Select sandbox type
Note over Executor: Firejail > Docker > None
Executor->>Sandbox: Start sandboxed process
Note over Sandbox: Memory limit: 256MB<br/>Timeout: 30s<br/>Restricted filesystem
Sandbox->>Script: Execute with input
Script-->>Sandbox: Return output
Sandbox-->>Executor: Capture stdout
Executor->>Target: Store result
Target-->>Source: Complete
Security Architecture
Defense in Depth Strategy
Krill implements multiple security layers to protect your system:
flowchart TD
A[User Script] --> B[Layer 1: Path Validation]
B --> C[Layer 2: Sandbox Detection]
C --> D{Sandbox Available?}
D -->|Yes| E[Layer 3: Isolated Execution]
D -->|No| F[Unsandboxed Execution]
E --> G[Layer 4: Resource Limits]
F --> G
G --> H[Layer 5: Timeout Protection]
H --> I[Layer 6: Output Capture]
I --> J[Safe Result Storage]
style B fill:#90EE90
style E fill:#90EE90
style G fill:#90EE90
style H fill:#90EE90
style I fill:#90EE90
style F fill:#FFB6C1
Layer 1: File System Isolation
Path Traversal Prevention
All lambda scripts must reside in /opt/krill/lambdas. The executor validates paths using canonical path resolution:
1
2
3
4
5
6
7
8
9
10
internal fun isPathWithinAllowedDirectory(file: File): Boolean {
return try {
val canonicalRoot = File(sandboxConfig.allowedPath).canonicalPath
val canonicalFile = file.canonicalPath
canonicalFile.startsWith(canonicalRoot)
} catch (e: IOException) {
logger.e("Error validating path: ${e.message}", e)
false
}
}
Why this matters:
- Prevents
../path traversal attacks - Blocks access to system directories
- Ensures scripts only run from trusted location
Layer 2: Sandbox Detection
Krill automatically detects available sandboxing mechanisms at startup:
- Firejail (Preferred) - Lightweight Linux namespace sandboxing
- Docker (Fallback) - Container-based isolation
- None (Last resort) - Direct execution with logging warning
1
2
3
4
5
6
7
8
9
10
11
12
private fun detectSandboxType(): SandboxType {
if (isCommandAvailable("firejail")) {
logger.i { "Sandbox: firejail detected and will be used" }
return SandboxType.FIREJAIL
}
if (isCommandAvailable("docker")) {
logger.i { "Sandbox: docker detected and will be used" }
return SandboxType.DOCKER
}
logger.w { "Sandbox: No mechanism available. Scripts will run without isolation." }
return SandboxType.NONE
}
Layer 3: Firejail Sandboxing (Recommended)
When Firejail is available, Krill uses it for lightweight, effective sandboxing:
Filesystem Restrictions:
1
2
3
4
5
firejail \
--whitelist=/opt/krill/lambdas \
--read-only=/opt/krill/lambdas \
--private \
--private-tmp
- Scripts can only read files in
/opt/krill/lambdas - Lambda directory is mounted read-only
- Scripts get isolated
/tmpand home directories - No access to
/home,/root, or other user directories
Resource Limits:
1
2
--rlimit-as=268435456 # 256MB memory limit
--timeout=30:30 # 30 second execution timeout
Optional Network Isolation:
1
--net=none # When restrictNetwork = true
Installing Firejail on Debian/Ubuntu:
1
2
sudo apt-get update
sudo apt-get install firejail
Layer 4: Docker Sandboxing (Alternative)
When Docker is available but Firejail isn’t, Krill uses containerization:
1
2
3
4
5
6
7
8
9
10
11
docker run \
--rm \
--memory=256m \
--memory-swap=256m \
--cpu-period=100000 \
--cpu-quota=30000000 \
--read-only \
-v /opt/krill/lambdas:/opt/krill/lambdas:ro \
-w /opt/krill/lambdas \
python:3-slim \
python3 /opt/krill/lambdas/script.py input
Security features:
- Memory hard limit (256MB RAM, no swap)
- CPU quota enforcement
- Read-only root filesystem
- Lambda directory mounted read-only
- Minimal Python image (no unnecessary tools)
- Optional network isolation (
--network=none)
Layer 5: Resource Limits
All script executions are protected by hard limits:
| Resource | Limit | Purpose |
|---|---|---|
| Memory | 256MB | Prevent memory exhaustion |
| Execution Time | 30 seconds | Prevent infinite loops |
| Filesystem | Read-only /opt/krill/lambdas | Prevent file system tampering |
| Process Count | 1 (implicit) | Prevent fork bombs |
Layer 6: User and Permission Isolation
System User Setup (from postinst):
1
2
3
4
5
6
# Create krill system user
adduser --system --ingroup krill --no-create-home krill
# Set lambda directory ownership
chown -R krill:krill /opt/krill/lambdas
find /opt/krill/lambdas -type f -name "*.py" -exec chmod 755 {} \;
Key security properties:
- Scripts run as
krilluser (not root) krilluser has no home directorykrilluser has no login shell- Limited group memberships (only hardware access groups when needed)
Installation and Setup
For Debian Package Users
When you install Krill via .deb package:
- System user created automatically:
1 2
# Handled by postinst adduser --system --ingroup krill --no-create-home krill
- Lambda directory initialized:
1 2 3 4
/opt/krill/lambdas/ ├── KrillPythonLambdaBasic.py (example: echo input) ├── TemperatureConverter.py (example: C to F conversion) └── sht30.py (example: sensor data parser)
- Permissions set securely:
- Directory:
0750(rwxr-x—) - Scripts:
0755(rwxr-xr-x) - Owner:
krill:krill
- Directory:
Installing Firejail (Recommended)
For maximum security, install Firejail:
1
2
3
4
5
6
7
8
9
10
# Debian/Ubuntu/Raspberry Pi OS
sudo apt-get update
sudo apt-get install firejail
# Verify installation
which firejail
firejail --version
# Restart Krill to detect firejail
sudo systemctl restart krill
Check logs to confirm:
1
2
sudo journalctl -u krill -f | grep Sandbox
# Should see: "Sandbox: firejail detected and will be used"
Installing Docker (Alternative)
If you prefer Docker-based sandboxing:
1
2
3
4
5
6
7
8
9
# Install Docker
curl -fsSL https://get.docker.com -o get-docker.sh
sudo sh get-docker.sh
# Add krill user to docker group
sudo usermod -aG docker krill
# Restart Krill
sudo systemctl restart krill
Adding Your Own Lambda Scripts
Safe Script Development
1. Create your script in the lambda directory:
1
sudo nano /opt/krill/lambdas/my_script.py
2. Follow the template pattern:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
#!/usr/bin/env python3
"""
My Custom Lambda
Description of what this script does
"""
import sys
import json
def main():
# Validate input exists
if len(sys.argv) < 2:
print("ERROR: No input provided", file=sys.stderr)
sys.exit(1)
try:
# Parse input
input_value = sys.argv[1]
# Your processing logic here
result = process_data(input_value)
# Output result to stdout
print(result)
except Exception as e:
print(f"ERROR: {e}", file=sys.stderr)
sys.exit(1)
def process_data(input_val):
# Your custom logic
return input_val.upper()
if __name__ == "__main__":
main()
3. Set correct permissions:
1
2
sudo chown krill:krill /opt/krill/lambdas/my_script.py
sudo chmod 755 /opt/krill/lambdas/my_script.py
4. Test your script manually:
1
2
3
4
5
6
7
8
9
10
11
# Test as krill user
sudo -u krill python3 /opt/krill/lambdas/my_script.py "test input"
# Test in sandbox (if firejail installed)
sudo -u krill firejail \
--noprofile \
--whitelist=/opt/krill/lambdas \
--read-only=/opt/krill/lambdas \
--private \
--private-tmp \
python3 /opt/krill/lambdas/my_script.py "test input"
Security Best Practices for Lambda Scripts
✅ DO:
- Keep scripts simple and focused
- Validate all inputs
- Use try-catch for error handling
- Write errors to stderr, results to stdout
- Test scripts manually before deploying
- Use standard library when possible
❌ DON’T:
- Import unnecessary external libraries
- Attempt to modify files
- Spawn subprocesses
- Make network requests (unless explicitly needed)
- Store sensitive credentials in scripts
- Assume unbounded execution time
Example Scripts
Basic Echo (Data Pass-through):
1
2
3
4
5
6
7
8
9
10
11
12
#!/usr/bin/env python3
import sys
def main():
if len(sys.argv) < 2:
print("ERROR: No input provided", file=sys.stderr)
sys.exit(1)
print(sys.argv[1])
if __name__ == "__main__":
main()
Temperature Conversion:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#!/usr/bin/env python3
import sys
def celsius_to_fahrenheit(celsius):
return (celsius * 9/5) + 32
def main():
if len(sys.argv) < 2:
print("ERROR: No input provided", file=sys.stderr)
sys.exit(1)
try:
celsius = float(sys.argv[1])
fahrenheit = celsius_to_fahrenheit(celsius)
print(f"{fahrenheit:.2f}")
except ValueError:
print(f"ERROR: Invalid number: {sys.argv[1]}", file=sys.stderr)
sys.exit(1)
if __name__ == "__main__":
main()
Sensor Data Averaging:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#!/usr/bin/env python3
import sys
import json
def main():
if len(sys.argv) < 2:
print("ERROR: No input provided", file=sys.stderr)
sys.exit(1)
try:
# Parse JSON array of values
data = json.loads(sys.argv[1])
values = [float(x) for x in data]
average = sum(values) / len(values)
print(f"{average:.2f}")
except (json.JSONDecodeError, ValueError, ZeroDivisionError) as e:
print(f"ERROR: {e}", file=sys.stderr)
sys.exit(1)
if __name__ == "__main__":
main()
Configuration in Krill UI
Once your script is in /opt/krill/lambdas/, configure it in the Krill interface:
- Create a Lambda Node
- Configure:
- Filename:
my_script.py(just the filename, not full path) - Source: Select the DataPoint that triggers execution
- Target: Select the DataSource to store results
- Filename:
- Test: Update the source DataPoint and verify target receives output
Current Security Limitations
While Krill’s sandboxing is robust for typical use cases, be aware:
⚠️ Not production-grade isolation:
- Firejail uses Linux namespaces, not full virtualization
- Docker containers share the kernel with host
- Scripts run as
krilluser (not fully unprivileged)
⚠️ Resource limits are soft in unsandboxed mode:
- Without Firejail/Docker, only timeout is enforced
- Memory limits require sandbox support
⚠️ Network access:
- Network isolation is optional (
restrictNetwork = falseby default) - Scripts can make outbound connections unless restricted
- This is intentional for IoT/API integration use cases
Security Roadmap
Future improvements planned for enhanced security:
Short Term
- Script signature verification - Optional code signing for trusted environments
- Execution audit logging - Log all script executions with inputs/outputs
- Per-lambda rate limiting - Prevent execution flooding
- Enhanced Docker security - Add
--security-opt=no-new-privileges,--cap-drop=ALL
Medium Term
- CPU limit configuration - Per-script CPU quota controls
- Disk I/O rate limiting - Prevent I/O abuse
- Output size limits - Cap maximum stdout/stderr size
- Environment variable restrictions - Whitelist approach for env vars
Long Term
- Seccomp filtering - System call whitelisting at kernel level
- gVisor runtime option - User-space kernel for stronger isolation
- WebAssembly lambda support - True sandboxing via WASM runtime
- Built-in lambda library - Curated, pre-audited transformation functions
Threat Model
What Krill Protects Against
✅ Path traversal attacks - Scripts cannot escape /opt/krill/lambdas
✅ Memory exhaustion - 256MB hard limit (with sandbox)
✅ Infinite loops - 30-second timeout
✅ Filesystem tampering - Read-only lambda directory
✅ Privilege escalation - Scripts run as unprivileged krill user
✅ Fork bombs - Single process limit (sandboxed)
What Users Are Responsible For
🔒 Script content trust - Only add scripts you understand and trust
🔒 Dependency auditing - Review any pip install requirements
🔒 Access control - Secure SSH/physical access to server
🔒 Network security - Firewall and network segmentation
🔒 System updates - Keep Debian, Python, and Krill updated
Known Attack Vectors (Mitigated)
| Attack | Mitigation | Status |
|---|---|---|
| Path traversal | Canonical path validation | ✅ Blocked |
| Resource exhaustion | Memory/time limits | ✅ Limited (sandbox required) |
| Filesystem writes | Read-only mounts | ✅ Blocked (sandboxed) |
| Network abuse | Optional --net=none | ⚠️ User configurable |
| Privilege escalation | Unprivileged user | ✅ Mitigated |
| Code injection | No eval/exec in framework | ✅ Not possible |
Monitoring and Troubleshooting
Check Sandbox Status
1
2
3
4
5
6
7
8
# View Krill logs
sudo journalctl -u krill -f
# Look for sandbox detection messages
sudo journalctl -u krill | grep Sandbox
# Expected output (with firejail):
# "Sandbox: firejail detected and will be used for script isolation"
Debug Script Execution
1
2
3
4
5
6
7
8
# View all lambda execution attempts
sudo journalctl -u krill | grep "Executing Python script"
# View script failures
sudo journalctl -u krill | grep "Python script failed"
# View script timeouts
sudo journalctl -u krill | grep "timed out"
Manual Testing
1
2
3
4
5
6
7
8
9
10
11
12
# Test script as krill user
sudo -u krill python3 /opt/krill/lambdas/my_script.py "test"
# Test with firejail sandbox
sudo -u krill firejail \
--noprofile \
--whitelist=/opt/krill/lambdas \
--read-only=/opt/krill/lambdas \
--private \
--private-tmp \
--rlimit-as=268435456 \
python3 /opt/krill/lambdas/my_script.py "test"
Common Issues
Script not found:
1
ERROR: File not found: /opt/krill/lambdas/missing.py
→ Verify script exists and has correct permissions
Path validation failed:
1
ERROR: Security violation: Script path is outside allowed directory
→ Script must be in /opt/krill/lambdas/, check for symlinks
Timeout:
1
ERROR: Python script timed out after 30s
→ Optimize script or break into smaller operations
Memory limit (sandboxed):
1
Process terminated (OOM)
→ Reduce memory usage or consider chunking data
Comparison with Other Lambda Systems
| Feature | Krill | AWS Lambda | Azure Functions |
|---|---|---|---|
| Deployment | Local server | Cloud | Cloud |
| Isolation | Firejail/Docker | Firecracker | Hyper-V |
| Cold start | ~100ms | ~1s | ~1-3s |
| Memory limit | 256MB | 128MB-10GB | 128MB-4GB |
| Timeout | 30s | 15min | 5-10min |
| Cost | Free | Pay per execution | Pay per execution |
| Use case | IoT/Edge | Web/API | Enterprise |
Krill is designed for edge computing and IoT automation, prioritizing:
- Low latency (no network round-trip)
- Local data processing
- Offline operation
- Simple deployment
Conclusion
Krill’s Python Lambda executor provides a secure, flexible way to extend your automation capabilities while protecting your system. The multi-layered security approach ensures that user scripts run in a controlled environment, preventing common attack vectors.
Key takeaways:
- ✅ Install Firejail for best security (
apt-get install firejail) - ✅ Only add scripts you trust and understand
- ✅ Test scripts manually before deploying
- ✅ Monitor logs for execution failures
- ✅ Keep your system and dependencies updated
For questions or security concerns, please open an issue on GitHub or contact the development team.
Additional Resources
- Firejail Documentation
- Docker Security Best Practices
- Linux Namespaces Overview
- Krill Server Installation Guide
- TLS Certificate Management
Last updated: December 28, 2025
Krill version: 1.0+
Platform: Debian/Ubuntu/Raspberry Pi OS