create_default_code_executor downgrades from container to host execution on any Docker failure with only a Python warning, the local executor's docstring advertises a dangerous-command sanitizer that exists nowhere in the tree, and the docker_jupyter server publishes a stateful arbitrary-code kernel gateway on every host interface. Around that core sits a consistent pattern of safe primitives left unwired — a workspace containment check that the Docker backend bypasses for its own generated filenames, a language allowlist enforced in four backends but not the fifth, and an approval gate that exists only in the calling agent and defaults to off. Nothing in any backend records what was executed, so every one of these gaps is also invisible after the fact.autogen_ext/code_executors/ (local, docker, docker_jupyter, jupyter, azure) behind the abstract CodeExecutor contract in autogen_core/code_executor/_base.py. Each writes a code block to a file in a working directory and runs it — asyncio.create_subprocess_exec on the host for the local backend, container.exec_run for the Docker backend, an nbclient kernel for the Jupyter backend, a websocket to a containerized Jupyter kernel gateway for docker_jupyter, and an HTTPS POST to the ACA dynamic-sessions endpoint for azure. External surface spans the Docker daemon, a container registry, a published kernel-gateway port, and the ACA pool endpoint. The abstract contract carries no approval hook: execute_code_blocks runs whatever it is handed, and the create_default_code_executor factory in code_executors/__init__.py silently substitutes host execution when Docker is unavailable.Every actionable rule in the Worker Remit, checked against the running code. Gap = declared but unenforced; Partial = enforced but incomplete or bypassable; Vague Policy = too imprecise to verify.
| Rule ID | Section | Rule (quoted) | Status | Finding |
|---|---|---|---|---|
| R-01 | Prohibited Behaviors | "The executor runs only code blocks delivered through the authorized agent workflow, and MUST NOT act on execution instructions arriving outside that delivery path." | Enforcement Not Possible | — |
| R-02 | Prohibited Behaviors | "The executor MUST NOT autonomously perform irreversible real-world actions on the basis of generated output (for example publishing or posting content, or sending external communications) — a human decides how execution outputs are used." | Verified | — |
| R-03 | Prohibited Behaviors | "The executor MUST NOT expand or redefine its own role beyond executing supplied code blocks and returning their results; it does not set its own goals or acquire capabilities on its own initiative." | Verified | — |
| R-04 | Prohibited Behaviors | "The executor MUST NOT be operated as an unattended, production-facing service without the additional safety evaluation, hardening, and human oversight its deployment requires." | Enforcement Not Possible | — |
| R-05 | Approved Communication Channels | "Approved Communication Channels" | Partial | PRAX-2026-08-12-003 |
| R-06 | Approved Communication Channels | "Only from agents within the same authorized AutoGen runtime" | Enforcement Not Possible | — |
| R-07 | Authorized Counterparties — Trusted People / Accounts | "Trusted People / Accounts" | Enforcement Not Possible | — |
| R-08 | Authorized Counterparties — Trusted Services / Integrations | "Trusted Services / Integrations" | Partial | PRAX-2026-08-12-014 |
| R-09 | Authorized Counterparties — Trusted Services / Integrations | "Only the operator-configured ACA pool-management endpoint / credential provider and the approved Docker daemon target are authorized backend endpoints; any other backend endpoint, daemon target, or credential/token provider is a trust-expansion finding." | Partial | PRAX-2026-08-12-013 |
| R-10 | Authorized Counterparties — Explicitly Forbidden | "Arbitrary external network endpoints, third-party services, or MCP servers that are not required to execute the supplied code within the authorized backend." | Verified | — |
| R-11 | Tools and Capabilities — Allowed Tools (Known Good Baseline) | "Allowed Tools (Known Good Baseline)" | Gap | PRAX-2026-08-12-005 |
| R-12 | Tools and Capabilities — Restricted Tools (Require Approval Before Use) | "A sandboxed execution backend (Docker or Azure Container Apps dynamic sessions) is required for all code execution." | Gap | PRAX-2026-08-12-001 |
| R-13 | Tools and Capabilities — Restricted Tools (Require Approval Before Use) | "The local host executor (running code directly on the machine hosting the application) MUST NOT be used unless the operator has explicitly authorized it, and then only in a trusted, non-production context." | Partial | PRAX-2026-08-12-001 |
| R-14 | Tools and Capabilities — Forbidden Tools | "The executor MUST NOT carry any capability that grants inbound network access to, or a persistent interactive shell into, the execution environment from outside the authorized runtime." | Gap | PRAX-2026-08-12-003 |
| R-15 | Data Boundaries — Allowed Data Sources | "Allowed Data Sources" | Verified | — |
| R-16 | Data Boundaries — Forbidden Data Movement | "The executor MUST NOT transmit the contents of the working directory or uploaded files to any destination outside the configured execution environment." | Verified | — |
| R-17 | Data Boundaries — Forbidden Data Movement | "Credentials and tokens used to authenticate to a backend MUST NOT be written into generated code files, into execution output, or into logs." | Verified | — |
| R-18 | Data Boundaries — Forbidden Data Movement | "Code executed in the sandbox MAY initiate outbound network connections only to operator-configured outbound destinations; any other outbound network destination is a trust-expansion finding." | Gap | PRAX-2026-08-12-006 |
| R-19 | Action Boundaries — Allowed Without Approval | "Allowed Without Approval" | Partial | PRAX-2026-08-12-004 |
| R-20 | Action Boundaries — Requires Human Approval Before Execution | "Human confirmation by the overseer is required before every code-block execution; there is no risk-threshold carve-out (no category of block is exempt), and a denied confirmation blocks execution." | Partial | PRAX-2026-08-12-004 |
| R-21 | Action Boundaries — Requires Human Approval Before Execution | "Installing packages into the host interpreter, or otherwise modifying state outside the working directory and outside the configured sandbox boundary, MUST require human confirmation." | Gap | PRAX-2026-08-12-010 |
| R-22 | Action Boundaries — Never Allowed | "The executor MUST NOT execute a code block that the human overseer has denied." | Verified | — |
| R-23 | Action Boundaries — Never Allowed | "The executor MUST NOT execute agent- or LLM-generated code outside an isolation boundary (container or remote sandbox), except via the explicitly operator-approved local executor." | Gap | PRAX-2026-08-12-005 |
| R-24 | Action Boundaries — Never Allowed | "The executor MUST NOT expose the code-execution environment, or the Docker daemon socket it relies on, to untrusted or public networks." | Gap | PRAX-2026-08-12-003 |
| R-25 | Escalation Rules — Halt Agent and Alert Operator | "If the configured sandbox backend (container or remote session) is unavailable, the executor MUST halt and alert the operator rather than falling back to unsandboxed host execution." | Gap | PRAX-2026-08-12-001 |
| R-26 | Escalation Rules — Halt Agent and Alert Operator | "If executed code attempts to reach resources outside the sandbox and working directory (host filesystem, unauthorized network destinations, privilege escalation), the executor MUST halt and alert the operator." | Gap | PRAX-2026-08-12-006 |
| R-27 | Escalation Rules — Alert Operator (Do Not Halt) | "Alert the operator when a code block is denied at the human-confirmation gate." | Verified | — |
| R-28 | Escalation Rules — Log Only | "Log every executed code block together with its exit code and output." | Gap | PRAX-2026-08-12-011 |
Findings, ordered by severity — each linked to its remit rule, evidence, and a recommended action. Tag chips jump to the relevant entry in the RAISE framework, the OWASP LLM Top 10, or the OWASP Agentic Top 10.
LLM06 primary — the finding's main OWASP categoryASI10 secondary — a category it also touches
CRITICAL PRAX-2026-08-12-001 The default executor factory silently downgrades from container isolation to host execution when Docker is unavailable or fails to initialize.
"A sandboxed execution backend (Docker or Azure Container Apps dynamic sessions) is required for all code execution. / The local host executor (running code directly on the machine hosting the application) MUST NOT be used unless the operator has explicitly authorized it, and then only in a trusted, non-production context. / If the configured sandbox backend (container or remote session) is unavailable, the executor MUST halt and alert the operator rather than falling back to unsandboxed host execution."
- In
code_executors/__init__.py, raise instead of returning a host executor when Docker is unavailable, and require an explicit opt-in argument (e.g.allow_host_fallback=True) beforeLocalCommandLineCodeExecutorcan ever be returned by this factory. - Remove the bare
except Exception: passat line 64 so a Docker initialization failure surfaces to the caller rather than being converted into a silent change of isolation model.
CRITICAL PRAX-2026-08-12-002 The local executor's documented dangerous-command sanitizer does not exist anywhere in the codebase; host shell blocks run unscreened.
- Either implement the screening the docstring promises in
_execute_code_dont_check_setupbefore the file is written, or delete the sentence atlocal/__init__.py:57-58so operators do not credit a control that does not exist. - Replace
os.environ.copy()at line 397 with an explicit minimal environment (PATH plus operator-listed variables) so host credentials in the parent environment are not inherited by executed blocks.
CRITICAL PRAX-2026-08-12-003 DockerJupyterServer publishes a stateful arbitrary-code kernel gateway on every host interface, not loopback.
"Approved Communication Channels / The executor MUST NOT carry any capability that grants inbound network access to, or a persistent interactive shell into, the execution environment from outside the authorized runtime. / The executor MUST NOT expose the code-execution environment, or the Docker daemon socket it relies on, to untrusted or public networks."
- Replace
publish_all_ports=Trueat_jupyter_server.py:363with an explicit loopback port map (ports={f"{expose_port}/tcp": ("127.0.0.1", 0)}) so the gateway is reachable only from the host running the runtime. - Bind the gateway to 127.0.0.1 inside the container in
DEFAULT_DOCKERFILEunless the operator explicitly opts into a wider bind.
HIGH PRAX-2026-08-12-004 The executor contract has no approval interposition point; every backend runs a supplied block immediately on receipt.
"Allowed Without Approval / Human confirmation by the overseer is required before every code-block execution; there is no risk-threshold carve-out (no category of block is exempt), and a denied confirmation blocks execution."
- Add an optional approval callback to the
CodeExecutorcontract in_base.pyand invoke it in each backend before the block is written, so the gate travels with the executor rather than with one particular caller. - Until that exists, document at each executor's entry point that confirmation is the caller's responsibility and that
approval_funcdefaults to None.
HIGH PRAX-2026-08-12-005 JupyterCodeExecutor runs LLM-generated code in a host kernel with no isolation, no authorization signal, and no warning.
"Allowed Tools (Known Good Baseline) / The executor MUST NOT execute agent- or LLM-generated code outside an isolation boundary (container or remote sandbox), except via the explicitly operator-approved local executor."
- Emit the same construction-time
UserWarningthe local executor emits, and require an explicit operator flag beforeJupyterCodeExecutorcan be constructed, matching the treatment of the other host-executing backend. - Add this backend to the remit's Restricted Tools list (or remove it) so the tool inventory and the code agree on which executors run on the host.
HIGH PRAX-2026-08-12-006 The execution container is created with default networking, root user, full capabilities and no resource ceilings.
"Code executed in the sandbox MAY initiate outbound network connections only to operator-configured outbound destinations; any other outbound network destination is a trust-expansion finding. / If executed code attempts to reach resources outside the sandbox and working directory (host filesystem, unauthorized network destinations, privilege escalation), the executor MUST halt and alert the operator."
- Pass
network_mode="none"by default incontainers.createand require an explicit operator argument to enable networking, so the remit's operator-configured-destinations rule has an enforcement point. - Add
user,cap_drop=["ALL"],mem_limitandpids_limitto the same call so a runaway or hostile block cannot exhaust the host or escalate inside the container.
HIGH PRAX-2026-08-12-007 DockerJupyterServer chmods the host bind directory to 0777 and mounts it read-write into the execution container.
os.chmod(bind_dir, 0o777) with 0o700 (or 0o770 plus a matching container user via the user argument) so the execution directory is not writable by every account on the host.HIGH PRAX-2026-08-12-008 The Docker backend builds the host file path from the model-supplied language string and writes it before validating that language.
- Move a
SUPPORTED_LANGUAGESmembership check to the top of_execute_code_dont_check_setup, before the filename is built, mirroringlocal/__init__.py:364. - Resolve the constructed path and assert containment with
is_relative_to(self.work_dir.resolve())before opening it for write, so the generated-name branch gets the same protection the filename-comment branch already has.
HIGH PRAX-2026-08-12-009 Azure download_files writes to a host path built from filenames supplied by the remote session's own file listing, with no containment check.
- In
download_files, reduce each name to its basename and then verifylocal_path.resolve().is_relative_to(self.work_dir.resolve())before opening it for write. - Apply the same normalization when constructing the download URL at line 367, so a name with separators cannot alter the request path either.
HIGH PRAX-2026-08-12-010 Function setup installs packages into the host interpreter and compiles function source in the host process, with no confirmation on either path.
"Installing packages into the host interpreter, or otherwise modifying state outside the working directory and outside the configured sandbox boundary, MUST require human confirmation."
- Require an explicit confirmation callback (or refuse and raise) before
_setup_functionsinstalls intosys.executable, and reserve the unprompted path for a suppliedvirtual_env_context. - Document at
FunctionWithRequirementsStrthat function source is compiled and executed in the host process, so operators do not assume the chosen sandbox covers it.
HIGH PRAX-2026-08-12-011 No backend records what it executed — neither the code block, nor its exit code, nor its output is logged anywhere.
"Log every executed code block together with its exit code and output."
- Emit one structured record per executed block from each backend — timestamp, backend, language, a hash of the code, exit code and output size — at minimum through a named module logger rather than the root logger.
- Route that logger to a durable sink in the executors' documentation so the record survives the process, and state whether output content is captured or only its digest.
MEDIUM PRAX-2026-08-12-012 Execution output returns to the model raw — no size cap, no provenance label, no control-character neutralization.
- Cap the returned output in each backend at an operator-configurable byte limit and mark truncation explicitly, so a single block cannot flood the model context.
- Strip or escape terminal control characters on all output branches, not just Jupyter tracebacks, and wrap the returned text in an explicit untrusted-output delimiter for callers assembling model context.
MEDIUM PRAX-2026-08-12-013 The Jupyter kernel websocket URL is hardcoded to ws:// even when the connection is configured for HTTPS.
"Only the operator-configured ACA pool-management endpoint / credential provider and the approved Docker daemon target are authorized backend endpoints; any other backend endpoint, daemon target, or credential/token provider is a trust-expansion finding."
use_https in _get_ws_base_url (wss:// when set), matching the REST base-URL helper directly above it.MEDIUM PRAX-2026-08-12-014 Execution environments are resolved by mutable image tag and pulled with no digest pin or signature verification.
"Trusted Services / Integrations"
- Pin both images by digest (
python:3-slim@sha256:...and a digest-pinned base inDEFAULT_DOCKERFILE) and document the refresh process, so the execution environment is reproducible and reviewable. - Add the container registry to the remit's trusted-services list, or make the image a required operator-supplied argument so no default registry pull happens implicitly.
MEDIUM PRAX-2026-08-12-015 The Azure backend caches its access token for the life of the executor with no expiry or refresh handling.
AccessToken.expires_on value alongside the token in _ensure_access_token and re-request when it is within a refresh margin of expiry.Controls and behaviors that are correctly implemented and verified during this scan. These represent areas where the agent's implementation aligns with its stated policy and security best practices.
Workspace containment check on caller-specified filenames
get_file_name_from_content resolves any # filename: comment against the working directory and raises if the result escapes it, so a code block cannot name a write target outside the workspace. It carries a regression test.
Per-block timeout and cancellation on every backend
All five executors bound a single block's runtime — asyncio.wait_for locally, a timeout command in the container, an aiohttp client timeout for ACA — and the Docker backend kills the running command in-container when the cancellation token fires.
Random kernel-gateway auth token by default
DockerJupyterServer generates a 32-byte random token with secrets.token_hex when no token is supplied, so the kernel gateway is authenticated by default rather than open.
Backend-selection warnings at construction
The local executor warns at construction that it runs code on the host and recommends the Docker backend, and the default factory warns when Docker is unavailable, so an operator who reads warnings is told which path they are on.
Approval gate with blocking denial in the calling agent (context)
Read as context, not scored as the subject's: CodeExecutorAgent offers an approval_func whose denial returns before execute_code_blocks is called, and warns when none is set. It is opt-in and defaults to None.
Log files found in the agent's workspace during this scan. Reviewing these files provides runtime evidence to complement the static analysis above.
Each card represents one category and shows the top 3 findings. All items in the Findings section.
Each card represents one category and shows the top 3 findings. All items in the Findings section.
Overall maturity assessment across the six categories of the RAISE framework. This is a maturity model, not a school grade: a score of 3 / 5 means Established, not 60 percent. Most production AI agents today score between Ad hoc (1) and Established (3). See the full RAISE framework reference for the complete scale and scoring.
local/__init__.py:364) and azure (azure/_azure_container_code_executor.py:444) backends, but the docker backend applies no such check before writing the file, and JupyterCodeExecutor adds a host-kernel capability that appears in neither the remit's Allowed Tools nor its Restricted Tools list._docker_code_executor.py:303, local/__init__.py:458-459), with ANSI stripping applied only to Jupyter tracebacks and the azure output suppressor opt-in and defaulted off._common.py:96-111 with a regression test, per-block timeouts and cancellation in all five backends, and actual container and remote-session isolation — but coverage is incomplete in ways that defeat them: no approval interposition in the CodeExecutor contract, a silent container-to-host downgrade, a sanitizer that is documented but absent, and containers created with no network, user, capability or memory limits.python/uv.lock and exact internal pins are real hygiene, but the execution environments themselves are resolved by mutable tag with no digest or signature verification (python:3-slim, quay.io/jupyter/docker-stacks-foundation), and M10 found no SBOM or component inventory of any kind while M11 found no dependency or container scanning — only repo-wide CodeQL static analysis.test_commandline_code_executor.py:217) plus the weekly CodeQL scan recorded at M7, which is its own material with no evidence that any finding changed the design.code_executors/.Maturity Scoring Rubric
Every score above is based on this scale. A score is a snapshot of observable posture — not a verdict on the people or team behind the system.
| Score | Label | Meaning |
|---|---|---|
| 5 | Exemplary | Best-in-class; automated, continuously tested, reference quality. Rarely achieved in shipping systems. |
| 4 | Strong | Comprehensive controls, active management, minor gaps. Production-ready. |
| 3 | Established | Documented controls consistently applied; known gaps accepted. A respectable baseline. |
| 2 | Partial | Some controls exist but coverage is incomplete; key gaps remain. |
| 1 | Ad hoc | Informal or inconsistent measures; relies on individual judgment. |
| 0 | Absent | No evidence this category is addressed at all. |