Root cause
Authentication & access control
The Copilot-coauthored file created both execution entry points with an automatic SubprocessSandbox fallback whenever the Landlock backend reported unavailable.
How AI contributed: Flawed AI-written codePublished Jun 18, 2026 in
mervinpraison/praisonaiAll 19 findings in this repository
SandlockSandbox.execute() and run_command() treated unavailable Landlock as a cue to instantiate SubprocessSandbox with the same configuration. That backend did not enforce the requested Landlock filesystem allowlist or network=False boundary, so execution continued with the host user's filesystem and network access.
Root cause
The Copilot-coauthored file created both execution entry points with an automatic SubprocessSandbox fallback whenever the Landlock backend reported unavailable.
Fix
The fix makes Sandlock fail at construction when the required Landlock ABI is unavailable and removes the execute/run-command subprocess fallbacks, so weaker isolation requires an explicit caller choice.
Case facts
@@ -0,0 +258,18 @@+ async def execute(+ self,+ code: str,+ language: str = "python",+ limits: Optional[ResourceLimits] = None,+ env: Optional[Dict[str, str]] = None,+ working_dir: Optional[str] = None,+ ) -> SandboxResult:+ """Execute code in the sandlock-isolated sandbox."""+ if not self._is_running:+ await self.start()+ + if not self.is_available:+ # Fallback to subprocess sandbox if sandlock not available+ logger.warning("Sandlock not available, falling back to subprocess")+ from .subprocess import SubprocessSandbox+ fallback = SubprocessSandbox(self.config)+ return await fallback.execute(code, language, limits, env, working_dir)@@ -0,0 +317,16 @@+ async def run_command(+ self,+ command: Union[str, List[str]],+ limits: Optional[ResourceLimits] = None,+ env: Optional[Dict[str, str]] = None,+ working_dir: Optional[str] = None,+ ) -> SandboxResult:+ """Run a shell command in the sandbox."""+ if not self._is_running:+ await self.start()+ + if not self.is_available:+ logger.warning("Sandlock not available, falling back to subprocess")+ from .subprocess import SubprocessSandbox+ fallback = SubprocessSandbox(self.config)+ return await fallback.run_command(command, limits, env, working_dir)@@ -75,12 +75,32 @@ class SandlockSandbox: "sandlock package required for SandlockSandbox. " "Install with: pip install 'praisonai[sandbox]' or pip install sandlock" )- ++ # Fail loud if Landlock isn't supported on this kernel. sandlock+ # requires a minimum Landlock ABI (currently v6, which shipped in+ # Linux 6.12); query it rather than hard-coding so we track the SDK's+ # own requirement.+ try:+ abi = self._sandlock.landlock_abi_version()+ min_abi = self._sandlock.min_landlock_abi()+ except Exception as e:+ raise RuntimeError(+ f"failed to query Landlock ABI version: {e}"+ ) from e+ if abi < min_abi:+ raise RuntimeError(+ "SandlockSandbox requires Landlock ABI >= "+ f"{min_abi} (Linux kernel >= 6.12 with "+ "CONFIG_SECURITY_LANDLOCK=y). This kernel reports Landlock "+ f"ABI version {abi}. Use SubprocessSandbox explicitly if "+ "weaker isolation is acceptable."+ )+ @property def is_available(self) -> bool: """Whether sandlock backend is available.""" try:- return self._sandlock.landlock_abi_version() >= 1+ return self._sandlock.landlock_abi_version() >= self._sandlock.min_landlock_abi() except (AttributeError, ImportError): return False @@ -306,24 +365,10 @@ class SandlockSandbox: """Execute code in the sandlock-isolated sandbox.""" if not self._is_running: await self.start()- - if not self.is_available:- from .subprocess import SubprocessSandbox- fallback = SubprocessSandbox(self.config)- if self.config.metadata.get("require_landlock"):- return SandboxResult(- status=SandboxStatus.FAILED,- error="Landlock is required but unavailable on this system",- metadata={"isolation_level": "none", "sandlock_downgrade": True},- )- logger.warning("Sandlock not available, falling back to subprocess (no kernel isolation)")- self._used_subprocess_fallback = True- result = await fallback.execute(code, language, limits, env, working_dir)- return self._tag_subprocess_fallback(result)- + limits = limits or self.config.resource_limits execution_id = str(uuid.uuid4())- + if language == "bash": cmd = ["bash", "-c", code] else:@@ -371,24 +433,10 @@ class SandlockSandbox: """Run a shell command in the sandbox.""" if not self._is_running: await self.start()- - if not self.is_available:- from .subprocess import SubprocessSandbox- fallback = SubprocessSandbox(self.config)- if self.config.metadata.get("require_landlock"):- return SandboxResult(- status=SandboxStatus.FAILED,- error="Landlock is required but unavailable on this system",- metadata={"isolation_level": "none", "sandlock_downgrade": True},- )- logger.warning("Sandlock not available, falling back to subprocess (no kernel isolation)")- self._used_subprocess_fallback = True- result = await fallback.run_command(command, limits, env, working_dir)- return self._tag_subprocess_fallback(result)- + limits = limits or self.config.resource_limits execution_id = str(uuid.uuid4())- + if isinstance(command, str): cmd = ["sh", "-c", command] else:AI-assisted change 18ec450a78d41347bdc8078fb2c9311658485cb704021a7058638fbb878f66eb · Fix 3aa5f05d3adc91b6d79934cca5b982e41ec8b2b7f26d744b75e3ee4d7160e8b3