Skip to main content

maf-sandbox-otel

OpenTelemetry records of what a sandbox did — which conversation was served what posture, which host tools a guest called and under whose authority, what crossed the boundary and with what integrity label, and how each sandbox disposed by key was disposed of. A conversation's sandboxes can also go away in a scope purge, which core emits no event for and which is therefore not audited here — see the limits below.

maf-sandbox reports these as events on an observer seam and records nothing itself. This package is one observer: it turns each event into a log record and a span, and the countable ones into a metric. A store read has no duration of its own, so its span is a single instant. Every signal goes through the providers you give the constructor, so records you route to a security pipeline do not also land on the application's trace. It depends on maf-sandbox and the OpenTelemetry API, and on nothing else — no backend, no agent framework, no SDK.

pip install maf-sandbox-otel

Wiring

There are two registration points because there are two host-policy objects, and a host that wires one records only that half.

from maf_sandbox import HostToolRegistry, SandboxRouter
from maf_sandbox_otel import OpenTelemetrySandboxObserver

observer = OpenTelemetrySandboxObserver()

router = SandboxRouter([backend], observer=observer)
registry = HostToolRegistry(observer=observer)

collect_outputs is neither — it is a function a kind calls per collection, so a kind that reports its file landings passes the observer and the key as arguments.

Each provider argument defaults to the global one, so with nothing else configured these records land beside the application's own traces:

observer = OpenTelemetrySandboxObserver(
    logger_provider=security_logs,   # a SIEM pipeline, its own exporter and retention
    tracer_provider=None,            # spans stay with the application's traces
)

Splitting them is usually what a security record wants. A SIEM does not want the application's trace sampling applied to it, and an application's trace store does not want a year of egress records. The three providers are independent, so a deployment can move the logs and leave the spans where they were.

What is recorded

Event Span Metric
A sandbox was served, or refused sandbox.acquire maf_sandbox.sandbox.acquires
A guest called back into the host sandbox.host_tool_call maf_sandbox.host_tool.calls, .response_bytes
A call read a file out of the host's store sandbox.files_in (one instant, no duration) maf_sandbox.store.file_reads
A collection landed artifacts in a sink sandbox.files_out maf_sandbox.outputs.landed_files, .landed_bytes
One backend answered one disposal sandbox.dispose maf_sandbox.sandbox.disposals
A sandboxed tool call ended sandbox.call maf_sandbox.call.duration

Every event also emits a log record, and that is the one a security pipeline should keep: it does not depend on anything else being instrumented, and it survives a trace sampler that discarded the span. Attributes are under maf_sandbox.*, so they select cleanly out of a pipeline carrying everyone else's.

What crosses, and what does not

Shape and policy always; content only when asked. A sandbox's posture — the egress mode and its allowlist, the isolation rung, the capabilities, the integrity label, the counts, the sizes, the outcome — is what a security question is asked in, and a guest chooses none of it. Names and sentences are the other half: an artifact name is written by the model, a host-tool refusal quotes a bounded copy of what the guest asked for, and a store file name is the host's own vocabulary about its own data. Those cross only under record_sensitive_data=True, which mirrors the agent framework's switch of the same name and is off by default.

A SandboxKey is the column every other record joins on, so it cannot simply be dropped. It is hashed by default — stable across processes, so grouping still works, and not reversible by reading. It is not a secret: an id drawn from a small space can be recovered by hashing the candidates, and a deployment that needs the key withheld from a pipeline should not send it rather than trust this.

The call id is the one part of a key recorded in the clear. The framework generates it per call, it is drawn from nobody's vocabulary, and it is what names the folder a per_call sink lands that call's artifacts in — so hashing it would cost the correlation a landing record exists for and protect nothing.

Three limits worth knowing before you rely on it

The egress posture recorded is the one that was served, not the traffic that was reached. sandbox.acquire carries the mode and the allowlist the sandbox ran under. It does not carry which hosts the guest actually opened a tunnel to: the docker and wslc proxies print their ALLOW/DENY lines inside their own container, which the backend reads once at acquire and never again, and ACAS enforces egress in the service where the guest is the only party that sees the refusal. Reaching those lines is a change in each backend rather than here, and until one lands, "which conversations were allowed to reach host X" is answerable and "which ones did" is not.

A scope purge is not recorded, so one of the two ways a sandbox goes away is invisible. SandboxDisposed is emitted per key. dispose_scope — what a thread deletion runs, and what router.scope(...) runs when its block ends — asks every backend and emits nothing, so a conversation's sandboxes can be removed with no record here. The gap is core's rather than this package's, and it is not an oversight that a recorder could paper over: a backend answers a purge with a count rather than the keys it removed, so there is no key to put in an event. Until core grows an event keyed on (scope, thread_id) (#917), "was every sandbox for this conversation cleaned up" is not answerable from these records.

The events of one call are siblings, not children of sandbox.call. Every event arrives after the work it describes, and the call's own event arrives last, so there is no moment at which this package could open a parent for the others to nest under. Each is parented to whatever span is current where it arrives instead — the agent framework's execute_tool span — and sandbox.call carries the total the caller waited for. Buffering them into a real tree would need per-call state that a cancellation could leak. That holds for a tool body that awaits nothing too, even though its record arrives on a worker thread: the framework dispatches with asyncio.to_thread from inside the span, and that copies the context the current span lives in, so the span crosses with the body. A test reproduces that dispatch rather than describing it.

Cost

An observer is called synchronously inside the call it records — on the event loop's task, or on the worker thread a synchronous tool body runs on — so this package does no I/O: it hands each record to the OpenTelemetry API and returns. It keeps no per-call state, so two calls reaching one observer at once share nothing to race over; the OpenTelemetry tracer, meter and logger it holds are thread-safe by that API's own contract. With no SDK installed at all, the API's no-op implementations answer and the cost is a few attribute dictionaries per call.

Whether export blocks the call is your SDK configuration, not this package. A BatchSpanProcessor and a BatchLogRecordProcessor hand off to their own thread, which is what keeps a slow collector away from a sandbox call. The Simple* processors call the exporter synchronously, inside span.end() and logger.emit() — so configured that way, a network exporter blocks the call for as long as the export takes. Use the batch processors where call latency matters; the simple ones are for tests, which is what this package's own suite uses them for.

A failure here never reaches the call — maf-sandbox contains whatever an observer does and logs it. With two deliberate exceptions: SystemExit and KeyboardInterrupt are the host's own control flow rather than a recorder failing, so core lets them through, including when one arrives as a leaf of an exception group. Nothing in this package raises them, but an exporter or a provider you configure can, and if it does the call goes down with it.

Licence

MIT — see LICENSE.

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

maf_sandbox_otel-0.1.0.tar.gz (15.7 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

maf_sandbox_otel-0.1.0-py3-none-any.whl (17.8 kB view details)

Uploaded Python 3

File details

Details for the file maf_sandbox_otel-0.1.0.tar.gz.

File metadata

  • Download URL: maf_sandbox_otel-0.1.0.tar.gz
  • Upload date:
  • Size: 15.7 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for maf_sandbox_otel-0.1.0.tar.gz
Algorithm Hash digest
SHA256 446d046d089c2c8235573631ba0ed951e0aaf7e477746d59590d9edeabb05a9d
MD5 f1c036bc6d091de978a16392bd2a65da
BLAKE2b-256 5ca14a4f411c396222a624632fd970d9e552987a098dd9ec52f92aabbb7bcd0d

See more details on using hashes here.

Provenance

The following attestation bundles were made for maf_sandbox_otel-0.1.0.tar.gz:

Publisher: publish-packages.yml on sokolaidev/maf-extensions

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file maf_sandbox_otel-0.1.0-py3-none-any.whl.

File metadata

File hashes

Hashes for maf_sandbox_otel-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 bc072806f4e1c7d405b3f168a0ab2e5040fd1c18544799515c96416fbbde1ec9
MD5 12b36aa6875399153d2ab84a2020cd4a
BLAKE2b-256 89cb7bfb417485148e93aefed56b9fedab7e54776627d58e0838dfe0a4425167

See more details on using hashes here.

Provenance

The following attestation bundles were made for maf_sandbox_otel-0.1.0-py3-none-any.whl:

Publisher: publish-packages.yml on sokolaidev/maf-extensions

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Release history Release notifications | RSS feed

This release

0.1.0 This release

2 files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page