Skip to content

chore(deps): strands-agents-tools 0.8.6 → 0.8.8 (calculator sandbox escape) - #1011

Merged
philmerrell merged 1 commit into
developfrom
feature/strands-tools-0-8-8
Sep 9, 2026
Merged

chore(deps): strands-agents-tools 0.8.6 → 0.8.8 (calculator sandbox escape)#1011
philmerrell merged 1 commit into
developfrom
feature/strands-tools-0-8-8

Conversation

@philmerrell

Copy link
Copy Markdown
Contributor

Bumps strands-agents-tools from 0.8.6 to 0.8.8 (released 2026-09-04). One-line pin change plus the lockfile and a regression test.

Why

0.8.8 closes a sandbox escape in strands_tools/calculator.py.

The calculator validates model-supplied expressions against an AST allowlist before parsing. That allowlist trusts a string literal only as a positional argument to constructors that parse it as a plain name or numeric literal — Symbol, symbols, Rational, Integer, Float — which is what blocks the sympify-backed re-parse escape (N("..."), simplify("..."), solve("...")).

Through 0.8.6 that check ignored the call's keyword arguments. symbols('...', cls=N) reroutes symbols to apply an arbitrary constructor — here N — to the string, which re-parses it through sympify and escapes the restricted namespace.

0.8.8 adds _has_only_assumption_keywords(): a string positional is trusted only when every keyword on the call is a boolean assumption flag (Symbol('x', positive=True)). **kwargs unpacking (kw.arg is None) is treated as untrusted too, since it can smuggle in cls.

This is reachable by every user. calculator is registered in create_default_registry() (backend/src/agents/main_agent/tools/tool_registry.py:93) and seeded with enabledByDefault=True (backend/scripts/seed_bootstrap_data.py), so it is on by default and evaluates model-supplied input.

Verification

Release notes for this package are monorepo-wide and unreliable, so this was verified by diffing the actual 0.8.6 and 0.8.8 wheels.

Six files differ: calculator.py, http_request.py, mem0_memory.py, mongodb_memory.py, think.py, use_aws.py. We import only strands_tools.calculator — confirmed by grep, the sole import in the repo is from strands_tools.calculator import calculator, plus one mention in USAGE_EXAMPLES.md. The other five are unreachable from our import graph, and calculator.py itself imports none of them. (strands_tools.browser exists but is unused and would need Playwright, which is deliberately not installed.) strands_tools/__init__.py is byte-identical between the two versions.

The calculator.py diff is exactly this fix — the new helper plus a continue at the call site. Nothing else.

Behavioural check against both versions:

Input 0.8.6 0.8.8
symbols('x', cls=N) allowed rejected
symbols('__import__("os").system("id")', cls=N) allowed rejected
symbols('x', **kw) allowed rejected
2 + 2 * 10, Symbol('x'), Rational('1/3') allowed allowed

Through the real agent path (create_default_registry()calculator): 2 + 2 * 10Result: 22, sin(pi/2) + log(E)Result: 2, and symbols('x', cls=N){'status': 'error', ...'string literals are not supported'}.

Full backend suite green: 7796 passed, 3 skipped, 0 failed.

Lockfile churn is minimal — only the strands-agents-tools entry moves; no transitive resolution changes. (0.8.8's metadata loosens rich<15rich<16 and the agent-core-* extras' bedrock-agentcore bound, but we install neither extra and rich did not move.)

Regression test

backend/tests/security/test_calculator_sandbox.py — a sibling to the existing test_python_ast_policy.py, pinning the boundary to the installed wheel so a downgrade or resolver drift back below 0.8.8 fails the suite instead of silently reopening the escape. It fails on 0.8.6 (all 5 escape cases) and passes on 0.8.8.

One test asserts the assumption-keyword carve-out against the validator rather than parse_expression, because Symbol('x', positive=True) fails further downstream for an unrelated pre-existing reason: sympy's implicit_multiplication_application transform rewrites positive=True into a multiplication before parse_expr sees it. Verified identical on 0.8.6 — not introduced by this bump.

Scope

Deliberately kept to this one bump:

  • strands-agents==1.51.0 untouched — a separate PR is taking that to 1.55.0 on feature/strands-1-55-upgrade. Both touch adjacent lines in backend/pyproject.toml, so expect a trivial conflict on whichever merges second; resolve by keeping both bumps.
  • bedrock-agentcore==1.21.0 untouched — 1.22.0 is entirely the unused payments/ module.

Noted, not addressed here

  • calculator is marked deprecated upstream ("becomes an error log in v0.9.0", suggesting the vended bash tool instead). This is pre-existing in 0.8.6, not new in 0.8.8, but it is worth a follow-up given we ship the tool on by default — the suggested replacement has a materially wider security boundary.
  • backend/README.md:440 still lists strands-agents-tools 0.2.3, stale since well before this PR. Left alone to keep the diff to one logical change.

Deploy

Merging to develop auto-deploys dev (backend.yml, frontend-deploy.yml, platform.yml are all push-triggered).

🤖 Generated with Claude Code

…scape)

0.8.8 closes a sandbox escape in `strands_tools/calculator.py`. The AST
allowlist trusts string literals as positional arguments to a few
constructors that parse them as a plain name or numeric literal (Symbol,
symbols, Rational, Integer, Float). Through 0.8.6 that check ignored the
call's keywords, so `symbols('...', cls=N)` rerouted `symbols` to apply N —
and therefore sympify — to the string, re-parsing it outside the restricted
namespace. 0.8.8 adds `_has_only_assumption_keywords()`: a string positional
is trusted only when every keyword is a boolean assumption flag, and
`**kwargs` unpacking is untrusted because it can smuggle in `cls`.

This matters here because `calculator` is registered in
`create_default_registry()` and seeded `enabledByDefault=True`, so it is on
for every user and evaluates model-supplied expressions.

Verified by diffing the 0.8.6 and 0.8.8 wheels rather than trusting the
monorepo-wide release notes. Six files differ; we import only
`strands_tools.calculator`, whose diff is exactly this fix. The other five
(http_request, mem0_memory, mongodb_memory, think, use_aws) are unreachable
from our import graph.

Adds a regression test pinning the boundary to the installed wheel — it
fails on 0.8.6 and passes on 0.8.8, so a downgrade or resolver drift cannot
silently reopen the escape.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@philmerrell
philmerrell merged commit a15c787 into develop Sep 9, 2026
4 checks passed
@philmerrell
philmerrell deleted the feature/strands-tools-0-8-8 branch September 9, 2026 03:31
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant