Babelagent runs code and calls services you bring, and its REST/MCP interfaces accept untrusted input, so security is treated as part of "done." The current surface has been through a full audit and four adversarial red-team rounds (the last found nothing new). This page states what is hardened, how to deploy safely, and the residual risks no audit removes.
Network input (REST service, babelagent[serve]).
- Bearer-token auth compared in constant time (
hmac.compare_digest). Zero-config on loopback; fail-closed on any non-loopback bind without a token (serve()refuses to start). - Anti-DNS-rebind Host-header pinning on loopback binds (a browser cannot rebind
attacker.comto127.0.0.1and drive/run). Fails closed on a missing Host. - Request-body size cap enforced before buffering; a body-phase idle/slowloris timeout; a
concurrency semaphore;
limit_concurrency+timeout_keep_aliveon uvicorn. - Caller-supplied
deadline_sis validated (finite, positive) and clamped to the server ceiling (it can only lower the run budget, never raise it), backed by a hard wall-clock guillotine on the run. The REST service always passes a deadline, so the guillotine is always active there. Note the guillotine is opt-in for direct library calls:graph.run(x)with nodeadline_sruns unbounded (trusted local use); passdeadline_s=to bound an untrusted run. Cancellation cleanup (in-flight nodes are cancelled and awaited when a run is cancelled or the client disconnects) is unconditional. - Errors are sanitized: internal exception messages are redacted from the returned trace.
Outbound requests (HTTP / OpenAPI / A2A adapters).
- SSRF allowlist: only public global-unicast targets. Blocks loopback/private/link-local/reserved/ multicast/unspecified, and normalizes IPv4-mapped, IPv4-compatible, NAT64, 6to4, and Teredo IPv6 so an internal IPv4 cannot be smuggled through an IPv6 literal.
- Redirects are never followed; the guard is re-run at connect time; upstream responses are streamed with a size cap so a hostile agent cannot exhaust memory.
- Secrets: API keys and tokens come only from the environment; never hard-coded, never serialized into a topology or returned in a response.
Execution / resources.
- No
eval/exec/pickle/shell=Trueon untrusted input. The OpenAPI path only reads a schema. - The scheduler bounds concurrency; response serialization is bounded by depth and a total-node budget (so a wide or shared-reference structure cannot fan out to exponential work).
- Untrusted upstream output cannot inject keyword/positional "flag" arguments into a callable node (payload spreads into a callable only when it exactly matches the callable's required parameters).
- Entry-point adapter discovery can be disabled with
BABELAGENT_NO_PLUGINS=1.
- Front a routable deployment with a reverse proxy (nginx
client_header_timeout, an ALB idle timeout, etc.). uvicorn has no header-receive timeout, so a slow-headers Slowloris is only fully mitigated by a fronting proxy. Always set a token (BABELAGENT_API_TOKEN) for non-loopback binds. - Front untrusted outbound targets with an egress allowlist / proxy. The SSRF guard blocks internal targets, but a host under attacker DNS control can still rebind between validation and connect; an egress allowlist closes that residual.
- Do not run untrusted, blocking, or CPU-bound sync callables on a shared server. A blocked worker thread cannot be force-killed (a Python limitation); use an async agent or an out-of-process agent for such work.
Dependencies are declared as bounded ranges (>= for the features we use, <next-major so a
breaking or compromised major cannot be pulled in silently). Babelagent intentionally does not
exact-pin (==) its dependencies: a library that pins exact versions forces conflicts on everyone who
installs it next to another package, and it blocks security patches. Reproducibility and integrity come
from a lockfile instead:
- A hash-pinned
uv.lockis committed for reproducible, verified dev/CI installs (uv sync --locked). - If you deploy Babelagent in an application and want to freeze exact versions, pin them in your
application's lockfile with hashes (
uv.lock,pip-compile --generate-hashes, Poetry) — that is the correct layer to pin, and it protects you against a compromised upstream release without constraining other consumers. - Publishing uses OIDC Trusted Publishing (no long-lived PyPI token in the repo or CI).
- A blocked/CPU-bound sync callable's worker thread cannot be interrupted; the run still reports the timeout, but the thread lingers.
- DNS-rebinding of an outbound target is not fully closed without an egress allowlist/proxy.
- Header-phase Slowloris requires a fronting reverse proxy.
- Brought agents and entry-point plugins run code by design; trust your dependency set and the agents you plug in. LLM provider SDKs dial trusted first-party endpoints.
- The usual unknowns: third-party dependencies and the kernel/OS.
This project is in local development. Once public, report vulnerabilities privately to the maintainer.