> ## Documentation Index
> Fetch the complete documentation index at: https://docs.blobhub.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Sandbox

> What the logic.code sandbox pre-binds, what it refuses, and how the blob import allowlist extends it

A `logic.code` component's code is executed with its builtins replaced: the sandbox's `__builtins__` holds only
`__build_class__` and `__name__`, and everything else it offers is bound as an ordinary global before the code
runs. There is no `__import__`, so any `import` statement that survives into deployed code raises
`ImportError: __import__ not found` at execution time.

That is the whole reason the [compiler](/cli/workflow/compiler) exists, and this page is its ground truth: what
is already bound decides which imports it may drop, which names your modules must not redefine, and which
imports it has to refuse.

## What is pre-bound

**16 standard-library modules**, bound as module objects:

```text theme={null}
abc, base64, copy, dataclasses, datetime, decimal, functools, io, json, math, re, textwrap, time,
traceback, uuid, zoneinfo
```

**Typing:** `typing`, `Union`, `Literal`.

**Exceptions:** `Exception`, `ValueError`, `IndexError`, plus the six session exceptions —
`GraphMutationConflict`, `GraphTraversalLimitExceeded`, `InvalidGraphQuery`, `InvalidSessionObjectAlias`,
`InvalidThreadEnvelope`, `InvalidGraphEnvelope`.

**59 built-in functions**, re-exposed by name since `__builtins__` no longer carries them:

```text theme={null}
abs, all, any, ascii, bin, bool, breakpoint, bytearray, bytes, callable, chr, classmethod, complex,
delattr, dict, dir, divmod, enumerate, filter, float, format, frozenset, getattr, hasattr, hash, hex,
id, int, isinstance, issubclass, iter, len, list, map, max, min, next, object, oct, ord, pow, print,
property, range, repr, reversed, round, set, setattr, slice, sorted, staticmethod, str, sum, super,
tuple, type, vars, zip
```

**Platform names**, in six groups:

| Group                               | Names                                                                                                                                                                                                                                                                                                                                                                     |
| ----------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Component data model                | `data_inputs`, `data_outputs`, `flow_outputs`, `get_data_input`, `set_data_output`                                                                                                                                                                                                                                                                                        |
| Message, value and provider helpers | `create_message`, `create_value`, `provider_inputs`, `get_provider_credential`                                                                                                                                                                                                                                                                                            |
| Result and exit                     | `result`, `set_result`, `Exit`, `exit`                                                                                                                                                                                                                                                                                                                                    |
| Event posting                       | `post_output_event`, `post_warning_event`, `post_error_event`                                                                                                                                                                                                                                                                                                             |
| Session data (13)                   | `download_session_object`, `upload_session_object`, `delete_session_object`, `list_session_objects`, `post_session_thread_item`, `list_session_thread_items`, `get_session_thread_item`, `apply_session_graph_mutations`, `query_session_graph`, `list_session_graph_elements`, `get_session_graph_element`, `get_session_graph_elements`, `list_session_graph_neighbors` |
| Metadata dicts and time             | `blob`, `session`, `definition`, `execution`, `attributes`, `current_time_utc_iso`                                                                                                                                                                                                                                                                                        |

Plus three deprecated aliases still bound for backward compatibility, and still a silent-collision hazard:
`post_event`, `get_input_value`, `set_output_value`.

[Code Component](/blob-types/workflow/workflows/component-code) documents what each of these does. This page is
concerned only with the fact that they are bound.

## What is not bound

`sys`, `os`, `subprocess`, `requests`, `open`, `eval`, `exec`.

None of them can be reached by importing, either — there is no `__import__`. Code needing any of them belongs in
an [external worker](/worker/introduction) rather than in a `logic.code` component.

## Names your code must not redefine

Everything above is an ordinary global in one namespace, so a top-level definition in your code simply
**replaces** it for everything that runs afterwards. That never raises. The platform does not read your globals
back — it reads the objects it bound — so redefining `set_result` means your function is called instead of the
platform's, and redefining `result` means the platform still collects its own dict while your writes go nowhere.
Either way the component completes and produces nothing, with no plausible cause anywhere in the trace.

The compiler refuses this up front as `SANDBOX_NAME_SHADOWED`, over 109 reserved names: the 59 builtins, the
platform names above, the typing names and the exception names.

One gap worth knowing: **the 16 stdlib module names are not in that reserved set**. A module defining a top-level
`json` — a function, a class, an assignment — compiles without complaint and shadows the pre-bound `json` module
for every other inlined module in the same namespace. The related case that *is* caught is a local module file
shadowing one, which the compiler resolves local-first and reports as the `LOCAL_SHADOWS_SANDBOX_MODULE`
advisory.

## Two gates before any of this runs

The platform checks both before executing a component:

* **`content[]` must be non-empty and `content[0]["type"]` must be `"text"`.**
* **The `language` port must resolve to `python`.** An absent or empty port defaults to `python` platform-side;
  the CLI is stricter and refuses a `code` binding whose component has no `language` port at all, as
  `LANGUAGE_NOT_PYTHON`. Writing code into a component that cannot execute it is precisely the failure worth
  catching at build time.

The parts of `content[]` are then concatenated, each joined with a **leading newline**, and any part that is not
`type: "text"` is skipped. So every part must be whole statements and never a partial line — which is why the
compiler emits exactly one part per module.

## Extending the set: `workflow_component_code_imports`

A blob limit listing the third-party modules a revision's code may import. Each name is imported and merged into
the execution namespace under its literal name from the list, and the compiler treats those names as droppable
exactly like the pre-bound ones.

Reading it is the CLI's business as much as the platform's, and it has three sharp edges.

**Reading it requires an `admin` key.** The `read` and `write` keys the platform recommends for automation get a
`403`. So the compiler falls back to a local cache, and when it has none, an unresolved import degrades from the
hard failure `IMPORT_UNAVAILABLE` to the advisory `IMPORT_UNVERIFIED` — the build proceeds and says it could not
verify. `--strict-imports` on `build` and `deploy` restores the refusal.

**A dotted entry binds a key that is not an identifier.** `numpy.linalg` binds under the literal key
`"numpy.linalg"`, which no Python expression can name, so it is unreachable from sandboxed code. The CLI reports
this as the `ALLOWLIST_ENTRY_UNUSABLE` advisory. It still has a use: importing it is what makes the submodule
reachable as an attribute of an also-allowlisted parent.

**An entry colliding with a pre-bound name shadows it.** The merge puts the allowlist last, so an entry named
`json` or `re` replaces the stdlib binding for every component on the blob. The CLI reports this as
`ALLOWLIST_SHADOWS_SANDBOX`.

There is a fourth consequence the CLI cannot advise on, because it depends on the deployed environment rather
than the list: the platform imports the whole list inside one `try`, so **a name that fails to import aborts the
loop** and every module after it is missing too, with nothing said at execution time. Keeping the list short and
verified is the only defence.

The list is resolved when an execution is created and shipped with the queued message, so editing the limit does
not affect executions already queued.

### The local cache

`build` never talks to the platform, so it classifies against `~/.blobhub/cache/allowlist/`, keyed on the
manifest's own `blob:` reference — lower-cased, and org-qualified exactly when the manifest itself qualifies it.
A cache entry older than seven days raises the `ALLOWLIST_STALE` advisory; no entry at all raises
`ALLOWLIST_UNREADABLE`.

Two commands write that cache, both needing an `admin` key:
[`deploy`](/cli/workflow/commands/deploy), which refreshes it live on every run, and
[`blobhub blob limits --refresh`](/cli/blob/limits), which is how an operator seeds it for builds running under a
key that cannot read limits.

Because the key is the reference as written, a manifest saying `blob: checkout` and a refresh run against
`acme-corp/checkout` land on different cache entries. Qualifying the manifest's `blob:` with its org avoids that
— and avoids the collision between identically-named blobs in different orgs, which share one entry under a bare
name.

Every advisory named here, with its remediation, is in [Error codes](/cli/error-codes).

## See also

* [Compiler](/cli/workflow/compiler) — how these bindings decide what may be dropped, rewritten or refused.
* [`blobhub blob limits`](/cli/blob/limits) — reading the allowlist, and the cache `--refresh` writes.
* [Code Component](/blob-types/workflow/workflows/component-code) — what each bound helper does.
* [Concepts](/cli/concepts) — the allowlist among the four platform behaviours that shape the binary.
