fix(strands-agent-base): CVE remediation + AccessDenied retry + A2A factory fix - #36
Open
shapirov103 wants to merge 4 commits into
Open
fix(strands-agent-base): CVE remediation + AccessDenied retry + A2A factory fix#36shapirov103 wants to merge 4 commits into
shapirov103 wants to merge 4 commits into
Conversation
ADRs for secretless workload identity (Shape A gateway identity; OAM identity traits; aws-service-identity/XPodIdentity/env-config; per-cluster eks_oidc_provider) and the user-delegated token-exchange roadmap, incl. the pending agentgateway release that ships backend.auth.oauthTokenExchange (v1.1.0 lacks it; PRs #2189/#2458).
…nt resource requests aws-service-identity trait: replace the STS-based init wait with a distroless, digest-pinned Chainguard kubectl 'wait --for=condition=Ready' on the PodIdentity resource, plus a namespaced Role/RoleBinding (get/list/watch podidentities) so the pod ServiceAccount can read it. Control-plane readiness only; the brief IAM data-plane propagation window is handled by app-level retry. example-agent-agentcore-memory: add CPU/memory requests (+ mem limit) so the agent isn't CPU-starved on contended nodes (root cause of the liveness-kill crashloops). Regenerated oam-agent-components templates.
…ctory fix Dependency/base-image hardening: - Pin fastapi==0.139.2, starlette==1.3.1 (fixes CVE-2025-62727, CVE-2026-48818, CVE-2026-54283), pydantic==2.13.4, uvicorn==0.51.0, bedrock-agentcore==1.18.1 (previously unpinned), aws-opentelemetry-distro==0.18.0, strands-agents==1.48.0. - Add tenacity==9.1.4 for retry-with-backoff (replaces hand-rolled loop). - Pin Dockerfile builder (uv:0.9.30-python3.13-bookworm-slim) and final stage (python:3.13-slim-bookworm by digest) for supply-chain immutability. - Remaining OS-layer CVEs (zlib1g, perl, libsqlite3-0, util-linux) are Debian bookworm will_not_fix/fix_deferred/no-dsa - not resolvable via apt upgrade. Resilience: - agent.py: tenacity retry() on _construct_agent, retrying only on botocore AccessDeniedException (first-boot IAM/Pod-Identity propagation race), exponential backoff (multiplier=1, max=16s), capped at 6 attempts. - agent.py: skip AgentCore session-manager attachment for the A2A agent-card placeholder context id (__agent_card__) - that probe agent is never used for real requests, and the placeholder fails AgentCore's sessionId validation regex (must start with alphanumeric). - main.py: switch A2AServer from the deprecated single shared agent param (not multi-tenant-safe, and rejected by strands-agents 1.48's StrandsA2AExecutor when a session_manager is attached) to the agent_factory param, giving each A2A context its own context-scoped agent + session_manager. Verified: rebuilt multi-arch (amd64+arm64), re-scanned with trivy (starlette/ fastapi/tenacity/etc. all 0 vulnerabilities), pushed to public.ecr.aws/z0a4o2j5/strands-agent (:latest, :v1.1.0-security), and confirmed on the hub cluster - all 3 my-agent pods Running 1/1, 0 restarts, Rollout Healthy.
…oken expires
The MCP client opened one persistent connection per server at first use and
reused it indefinitely (_mcp_exit_stack), authenticated with the
gateway-identity workload token read at connect time. That token has a fixed
1h TTL (expirationSeconds=3600 on the projected ServiceAccount token). The
kubelet rotates the token file on disk, but the already-open MCP connection
never re-reads it, so any tool call issued after ~1h of pod uptime failed
with a 401 from agentgateway ("Error(ExpiredSignature)"), surfacing to the
user as "unable to access the time tool due to a connection issue."
Fix: track each MCPClient instance (not just an ExitStack) and, once a
connection exceeds _MCP_CONNECTION_MAX_AGE_SECONDS (45 min, under the 1h
TTL), call stop() then start() on the SAME instance rather than creating a
new one. This re-invokes the transport callable (and therefore
_gateway_headers(), which reads the token file fresh) while preserving
object identity - required because MCPClient.call_tool_async is bound to
self and tool objects returned by list_tools_sync() delegate through the
client instance, not a frozen session. Any already-cached Agent (the
session cache in get_or_create_agent is unbounded and never evicted) keeps
working after a recycle instead of holding a reference to a client that
would otherwise need replacing.
Verified via a mocked unit test: after forcing a near-zero max age, the
second call recycles via stop()+start() on the identical object (checked
with `is`), not a new instance, with the tools list unchanged. Rebuilt and
pushed a multi-arch image (public.ecr.aws/z0a4o2j5/strands-agent :latest,
:v1.2.0-mcp-recycle) and redeployed both default/oap-assistant-a and
agents/my-agent; confirmed a live mcp-time tool call succeeds end-to-end
with no regression.
Contributor
|
@shapirov103 I see merge conflicts fix those. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Hardens the
strands-agent-baseimage: dependency/base-image CVE remediation, retry-with-backoff for the first-boot IAM propagation race, and two A2A SDK compatibility fixes discovered while testing on the hub cluster.Also includes an earlier commit hardening the
aws-service-identityOAM trait's init container (control-plane readiness gate onPodIdentityviakubectl wait, replacing an STS-based check that could be fooled by node-role fallback).Changes
Dependency/base-image hardening (
pyproject.toml,Dockerfile):fastapi==0.139.2,starlette==1.3.1— fixes CVE-2025-62727, CVE-2026-48818, CVE-2026-54283pydantic==2.13.4,uvicorn==0.51.0,bedrock-agentcore==1.18.1(previously unpinned),aws-opentelemetry-distro==0.18.0,strands-agents==1.48.0tenacity==9.1.4for retry-with-backoffuv:0.9.30-python3.13-bookworm-slim) and final stage (python:3.13-slim-bookwormby digest) for supply-chain immutabilitywill_not_fix/fix_deferred/no-dsa— not resolvable viaapt upgradeResilience (
app/agent.py):tenacity.retry(...)on_construct_agent, retrying only onbotocore.exceptions.ClientErrorwith codeAccessDeniedException(the first-boot IAM/Pod-Identity propagation race), exponential backoff (multiplier=1, max=16s), capped at 6 attempts__agent_card__) — that probe agent is never used for real requests, and the placeholder fails AgentCore'ssessionIdvalidation regex (must start with alphanumeric)A2A SDK compatibility (
app/main.py):A2AServerfrom the deprecated single sharedagent=(not multi-tenant-safe; rejected outright bystrands-agents1.48'sStrandsA2AExecutorwhen asession_manageris attached) toagent_factory=create_agent, giving each A2A context its own context-scoped agent + session managerTesting
/healthon both archesstarlette,fastapi,tenacity,bedrock_agentcore,pydantic,uvicornall show 0 vulnerabilities post-fixpublic.ecr.aws/z0a4o2j5/strands-agent(:latest,:v1.1.0-security)peeks-hub): all 3my-agentpodsRunning 1/1,0restarts,RolloutphaseHealthy— noAccessDeniedcrashes, noValueError/ValidationExceptioncrashesNot in scope
resources/startupProbefor theagentOAM component (flagged as a follow-up, not applied here beyond the one example)