Skip to content

security: core/digital/code_tracer.py traces arbitrary user code via sys.settrace without sandboxing or privilege separation, executing untrusted code with the full privileges of the Execra process.Β #280

Description

@divyanshim27

πŸ”΄ Security Problem

Execra's coding system is described as:

sys.settrace, PyDebug β€” Runtime tracing & execution flow analysis

sys.settrace hooks into the Python interpreter to trace every function call, return, and exception during code execution. To trace code, Execra must execute that code. If a user asks Execra to help debug a script that contains malicious code (intentionally or via a dependency), that code runs with full privileges:

  • File system access: import os; os.system("rm -rf ~/Documents")
  • Network access: Exfiltrate data, make outbound connections
  • Process spawning: Launch subprocesses
  • Keylogging: Access stdin/keyboard events

Since Execra is positioned as a tool for debugging unfamiliar code ("don't learn to do it β€” just do it correctly"), users will routinely ask it to trace code they haven't fully reviewed. This is a textbook code execution attack surface.

Proposed Fix

Execute traced code in an isolated subprocess with restricted OS-level permissions:

# core/digital/code_tracer.py
import subprocess
import sys
import os
import tempfile
import resource
import json

class SandboxedCodeTracer:
    """Executes user code in an isolated subprocess with resource limits."""

    MAX_EXECUTION_SECONDS = 10
    MAX_MEMORY_MB = 256
    MAX_OUTPUT_BYTES = 1_000_000  # 1MB stdout/stderr cap

    def trace(self, code: str, stdin_input: str = "") -> dict:
        # Write code to a temp file (never exec() directly)
        with tempfile.NamedTemporaryFile(mode='w', suffix='.py', delete=False) as f:
            f.write(code)
            code_path = f.name

        try:
            result = subprocess.run(
                [sys.executable, "-c", f"""
import sys, trace, io, json
tracer = trace.Trace(trace=True, count=False)
output_lines = []
class TracingCapture(io.StringIO):
    def write(self, s):
        output_lines.append(s)
        return super().write(s)
sys.stdout = TracingCapture()
try:
    tracer.runpy("{code_path}")
except Exception as e:
    print(f"EXECRA_ERROR: {{type(e).__name__}}: {{e}}")
print(json.dumps({{"trace_output": output_lines}}))
"""],
                stdin=stdin_input.encode() if stdin_input else None,
                capture_output=True,
                timeout=self.MAX_EXECUTION_SECONDS,
                # No shell=True β€” prevents shell injection
                env={
                    "PATH": "/usr/bin:/bin",  # Strip most of PATH
                    "PYTHONPATH": "",
                    # Do NOT inherit HOME, DISPLAY, SSH keys, etc.
                },
                preexec_fn=self._apply_resource_limits,  # Linux only
            )
            return {"stdout": result.stdout.decode()[:self.MAX_OUTPUT_BYTES],
                    "stderr": result.stderr.decode()[:self.MAX_OUTPUT_BYTES],
                    "returncode": result.returncode}

        except subprocess.TimeoutExpired:
            return {"error": f"Code execution timed out after {self.MAX_EXECUTION_SECONDS}s."}
        finally:
            os.unlink(code_path)

    @staticmethod
    def _apply_resource_limits():
        """Called in child process before exec β€” applies OS-level resource limits."""
        import resource
        # Limit CPU time
        resource.setrlimit(resource.RLIMIT_CPU, (10, 10))
        # Limit memory
        mem_bytes = 256 * 1024 * 1024
        resource.setrlimit(resource.RLIMIT_AS, (mem_bytes, mem_bytes))
        # Limit file descriptors (prevent fork bombs via fd exhaustion)
        resource.setrlimit(resource.RLIMIT_NOFILE, (32, 32))

For production, wrap in Docker with --network=none --read-only --cap-drop=ALL.

Add a clear warning in the UI:

⚠️ Execra will execute this code in a sandboxed environment. Review before tracing.

Files to Modify

File Change
core/digital/code_tracer.py Replace direct sys.settrace with SandboxedCodeTracer subprocess approach
core/digital/error_detector.py Use sandboxed tracer output instead of in-process execution
docs/SECURITY.md Document sandboxing approach and known limitations
docker-compose.yml Add --network=none, --cap-drop=ALL to code-execution service

Suggested labels: security, critical, backend

I would like to work on this. Could you please assign it to me?

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions