fix(examples): strengthen example agent code security by adding input validation and safe file extraction, and security notes to agent app docstring and example agent Readme - #95
Conversation
… validation and safe file extraction, and security notes to agent app docstring and example agent Readme
| """Raised when a comparator string contains constructs outside the allowlist.""" | ||
|
|
||
|
|
||
| def _make_safe_comparator(expr: str): |
There was a problem hiding this comment.
It's a fairly high maintenance cost to ensure that this function is correct and does not miss some edge cases. Instead, this could be a good fit for a sandboxed function execution (AWS Lambda, AgentCore Code Interpreter, Firecracker, Docker, etc).
There was a problem hiding this comment.
Thanks for the suggestion! Actually the goal is not to cover every edge case, but primarily block one specific escape. ACR is already a Firecracker sandbox, so arbitrary code in the reward is contained by default. What impacts is one way to reach outside of the sandbox by AWS role attack: untrusted code reads the session's execution-role credentials (via MMDSv2, like EC2 instance metadata) and acts against other AWS services / exfiltrates. We don't need to be perfect on edge cases; we need to make sure malformed code can't grab the role and leave.
Doing sandboxed reward code execution with AWS Lambda or AgentCore Code Interpreter is an alternative. However, this reward function doesn't just run a comparator — it reads the whole post-rollout testbed off disk (Excel, Word, PDF, email, calendar) across 9 functions. A separate sandbox has its own filesystem, so for every rollout we'd marshal that entire binary tree in and port two eval paths whose libs aren't preinstalled. This cause a large time cost to RL training.
Hence, directly inspecting the code itself is the easiest fix to solve the primary vulnerability. Using the implemented AST allowlist (single-arg lambda over comparisons/membership, arithmetic, literals; anything else rejected) sets a strict security gate preventing AWS role attack from happening with little time cost. Other edge cases not related to escaping Bedrock AgentCore do not make real harms.
Summary
This PR hardens the example agents so that untrusted input, such as request payloads and
data fetched from S3 (task configs, repo/testbed tarballs), is validated before it reaches an agent,
the filesystem, or an expression evaluator. All changes are behavior-preserving on valid inputs.
Math agent (
examples/strands_math_agent)models.pywith a pydanticInvocationRequestmodel whosepromptfield istyped
str(plus optionalanswer).basic_app.pyandrl_app.pynow constructInvocationRequest(**payload)and readrequest.promptinstead ofpayload.get("prompt"), so non-string payload values arerejected before the agent runs.
the payload and that the
prompt: strfield should be preserved when adapting theexample.
OfficeBench agent (
examples/strands_officebench_agent)reward.py). Replacedeval(match["comparator"])(value)in
evaluate_excel_cell_comparatorwith_make_safe_comparator(), a small AST-basedinterpreter. It accepts only a single-argument
lambdabuilt from comparisons(including
in/not in), boolean/arithmetic/unary ops, numeric/string literals,list/tuple/set literals, the lambda parameter, and a fixed allowlist of pure numeric
builtins (
int,float,str,len,abs,round,bool). Any other construct(attribute access, arbitrary names/calls,
**, etc.) raisesUnsafeComparatorError,which is logged and scored
0.0.models.py,utils.py). Added pydanticTaskConfigandEvaluationCheckmodels.load_task_from_s3now validates the downloaded JSON againstTaskConfig(requiringtaskto be a string and a well-formedevaluationlist) beforereturning it.
utils.py).setup_testbednow extracts the testbedarchive with
tarfile.extractall(..., filter="data"), rejecting members with absolutepaths or
..traversal that would escape/testbed.task_uri/testbed_uricome from, how they flow into the invocation payload, and thetrust-boundary expectation that they shoud point to user-controlled S3 buckets.
Migration agent (
examples/strands_migration_agent)utils.py).load_repo_from_s3now extracts the repoarchive with
tarfile.extractall(..., filter="data"), preventing a crafted tar fromescaping the work directory.
models.py. Documented thatInvocationRequest.promptis intentionally typedstrand should not be relaxed, since it is passed to an agent with
shell+editortools.repo_uriis a trust boundary and should point touser-controlled S3 buckets.
Toolkit (
src/agentcore_rl_toolkit/app.py)rollout_entrypointdocstring to state that payload validation is thehandler's responsibility (the decorator is framework-agnostic plumbing and performs no
sanitization) and to recommend the pydantic
prompt: strpattern, pointing toexamples/strands_math_agent/models.pyas the reference.