Skip to content

Secure Your AI Agents on AWS (Part 3): State, Communication, and Detection

21 minute read
Content level: Advanced
0

Part 3 of a three-part series mapping the OWASP Top 10 for Agentic Applications 2026 to AWS security controls. This final part covers the system's dynamics once agents persist state and act autonomously: ASI06 (memory and context poisoning), ASI07 (insecure inter-agent communication), ASI08 (cascading failures), and ASI10 (rogue agents), using AgentCore Memory, AWS KMS, PrivateLink, EventBridge, Step Functions, GuardDuty, and Security Hub. Includes the full series summary.

This is Part 3 of a three-part series mapping the OWASP Top 10 for Agentic Applications 2026 to AWS security controls. Part 1 covered inputs, identity, and human oversight. Part 2 covered tool execution and runtime safety. This part covers the system's dynamics: memory, inter-agent communication, failure propagation, and detection.

This is a personal post. The views are my own and do not represent AWS. The code samples are illustrative — validate every API shape against the current documentation and Regional availability before you deploy.


In Part 1 I established the framework and secured the agent's boundary with users. In Part 2 I secured what the agent runs. This final part covers the system-level dynamics that emerge once agents persist state, talk to each other, and operate autonomously: ASI06 (memory and context poisoning), ASI07 (insecure inter-agent communication), ASI08 (cascading failures), and ASI10 (rogue agents). These map to the "then expand" tier of the roadmap.

Prerequisites

This part assumes the setup from Part 1. Beyond that you need:

  • Permissions to create Amazon Bedrock AgentCore Memory resources, AWS KMS keys, Amazon EventBridge event buses, AWS Step Functions state machines, and Amazon CloudWatch alarms
  • Amazon GuardDuty and AWS Security Hub CSPM enabled, or permission to enable them
  • Familiarity with Retrieval Augmented Generation (RAG) and event-driven architectures

Skill level: Advanced | Time to complete: 2–3 hours for this part | Estimated cost: $10–30 USD for testing

Three cost notes. AWS KMS customer-managed keys carry a monthly charge and cannot be deleted immediately — deletion is scheduled with a waiting period, so the key continues to bill until it is removed. GuardDuty pricing scales with analysed volume, and Extended Threat Detection is included at no additional charge on top of the protection plans you already have enabled. AWS Private CA has a significant monthly cost, so treat the mutual TLS layer in ASI07 as a design pattern to understand rather than something to stand up casually for a walkthrough.

A note on running these examples. Unlike Parts 1 and 2, several controls here touch account-wide security infrastructure. If GuardDuty or Security Hub CSPM is already enabled in your account, do not reconfigure the existing detector or standards to follow along — read the examples and apply them deliberately instead. The cleanup section at the end of this part is explicit about what to remove and what to leave in place.


ASI06: Memory and context poisoning

Memory stores, conversation history, or Retrieval Augmented Generation (RAG) knowledge bases can be corrupted with unintended data, causing persistent reasoning corruption that affects your agent's future interactions.

A travel agent might store fabricated flight prices in its memory, leading to incorrect recommendations in future sessions. Separately, a security AI trained on poisoned data could learn to classify harmful activity as normal. Even indirect prompt injection can corrupt an assistant's memory without the user's knowledge.

Mitigation: Amazon Bedrock AgentCore Memory and AWS KMS

Amazon Bedrock AgentCore Memory provides encrypted, access-controlled storage for your agent's persistent state.

To create a secure memory store for your agents:

  1. Initialize the AgentCore control plane client.
  2. Create a memory configuration with extraction strategies and expiry policies.
  3. Configure namespace-based isolation to separate data by actor.

The following code creates a memory store with long-term extraction strategies:

# Create an AgentCore memory store with namespace isolation by actor
import boto3

control_client = boto3.client('bedrock-agentcore-control', region_name='us-west-2')
data_client = boto3.client('bedrock-agentcore', region_name='us-west-2')

memory_response = control_client.create_memory(
    name='agent-memory-secure',
    description='Encrypted memory with long-term extraction',
    eventExpiryDuration=90,
    memoryStrategies=[{
        'semanticMemoryStrategy': {
            'name': 'FactExtractor',
            'namespaceTemplates': ['/facts/{actorId}/']
        }
    }]
)

Alternatively, you can use the AgentCore Python SDK for a simplified interface:

# Same memory store using the AgentCore Python SDK
from bedrock_agentcore.memory import MemoryClient

client = MemoryClient(region_name="us-west-2")

memory = client.create_memory(
    name="agent-memory-secure",
    description="Encrypted memory with long-term extraction",
    strategies=[{
        'semanticMemoryStrategy': {
            'name': 'FactExtractor',
            'namespaceTemplates': ['/facts/{actorId}/']
        }
    }]
)

For RAG authorization, use metadata filtering to enforce access control:

# Enforce role-based access on RAG retrieval with a metadata filter
response = bedrock_agent_runtime.retrieve(
    knowledgeBaseId='kb-id',
    retrievalQuery={'text': query},
    retrievalConfiguration={
        'vectorSearchConfiguration': {
            # authorized_roles is a list attribute on each document, so use
            # listContains to match when the user's role is one of the allowed
            # roles. Use `equals` only for single-valued scalar attributes.
            'filter': {
                'listContains': {
                    'key': 'authorized_roles',
                    'value': user_role
                }
            }
        }
    }
)

Keeping isolation labels out of the model's hands

Namespace templates separate data by actor, which handles the multi-user case. The harder problem is a label that must survive extraction and consolidation unchanged — a compliance classification, a tenant identifier, a routing attribute. If a model infers that value, poisoned or ambiguous input can shift it, and a record ends up in the wrong bucket.

AgentCore Memory supports marking long-term memory metadata keys as strictly consistent. Setting extractionType: "STRICTLY_CONSISTENT" on a metadata schema key means the value you supply on the event is the value that lands on the resulting long-term memory record. The model is never consulted for that key.

The isolation property is what makes this a poisoning control rather than a data-quality one: events sharing the same strictly consistent values are extracted together and isolated from events with different values, and consolidation respects the same boundary. Records tagged department: "billing" are never merged with records tagged department: "engineering", even when they are semantically similar. Applied to a tenant or classification key, that prevents one tenant's corrupted memory from being consolidated into another's.

Constraints worth knowing before you design around it: a maximum of three deterministic keys per strategy, STRING type only, and the key must also be declared in the memory's indexedKeys. Supported on semantic, user preference, and episodic strategies — not on summary strategies.

Two caveats matter if you are treating this as an isolation control rather than a data-quality one:

  • The direct-write path bypasses it. BatchCreateMemoryRecords and BatchUpdateMemoryRecords skip extraction entirely, so STRICTLY_CONSISTENT has no effect on them and the caller supplies metadata directly. If a knowledge-base import or a self-managed strategy writes through the Batch APIs, your isolation guarantee does not cover that path — you have to enforce the labels in the code that calls it.
  • Reconfiguring the keys re-partitions history. Changing which keys are deterministic changes the grouping used for extraction and consolidation, and records written under the old configuration become isolated from records written under the new one. Decide the deterministic key set before you start ingesting events rather than after.

Multi-account memory boundaries

Where memory resources and the agents that consume them live in different accounts, cross-account access is configured by attaching a resource-based policy to the memory resource. That gives you an explicit allow-list of which principals in which accounts may create events, write records, retrieve records, or run semantic search — an account boundary around the memory store rather than trust inherited through role chaining.

Validation note: I validated the AWS KMS encryption key, the namespace-isolation design (/facts/{actorId}/ separates data by actor), and the RAG metadata-filter API contract in a test account. Amazon Bedrock AgentCore Memory has been generally available since October 2025; the create_memory control-plane call uses the bedrock-agentcore-control client. Strictly consistent metadata and cross-account access are described from the published documentation and were not part of my validation run. Validate the exact request shape (for example, namespaceTemplates) against the current API version and confirm Regional availability before you deploy.


Protecting an individual agent's memory is necessary, but if you're running multiple agents, you also need to secure how they communicate.

ASI07: Insecure inter-agent communication

If your system uses multiple agents, your agents depend on continuous communication. Insecure communication allows interception, spoofing, or manipulation of agent messages and intents.

In practice, an unauthorized party intercepting traffic could inject hidden instructions, replayed messages could trick agents into granting access, or an unintended agent could impersonate a legitimate peer.

Mitigation: AWS PrivateLink, AWS Private CA, and Amazon EventBridge

Secure inter-agent communication relies on encrypted messaging with identity validation. The code below creates an event bus that restricts PutEvents to authorized agents using a principal-tag condition.

A note on the API: put_permission's simplified Condition parameter only supports a single organization check (Key='aws:PrincipalOrgID', Type='StringEquals'). To gate on a principal tag, pass a full IAM resource policy through the Policy parameter instead:

# Create an event bus and allow PutEvents only from tag-authorized agents
import boto3
import json

events = boto3.client('events')
events.create_event_bus(Name='agent-communication-bus')

policy = {
    "Version": "2012-10-17",
    "Statement": [{
        "Sid": "AllowAuthorizedAgents",
        "Effect": "Allow",
        "Principal": {"AWS": "arn:aws:iam::111122223333:root"},
        "Action": "events:PutEvents",
        "Resource": "arn:aws:events:us-west-2:111122223333:event-bus/agent-communication-bus",
        "Condition": {
            "StringEquals": {"aws:PrincipalTag/AgentType": "authorized"}
        }
    }]
}

events.put_permission(
    EventBusName='agent-communication-bus',
    Policy=json.dumps(policy)
)

Secure communication controls:

ControlAWS servicePurpose
Private connectivityAWS PrivateLinkNo public internet exposure
Mutual Transport Layer Security (mTLS)AWS Private Certificate AuthorityAgent-to-agent authentication
Message encryptionAWS KMSEnd-to-end confidentiality
Message signingAWS KMSIntegrity verification
Audit loggingAWS CloudTrailCommunication trail

Validation note: I validated this end to end in a test account: a custom EventBridge bus with a tag-based resource policy allowed PutEvents from an authorized agent and denied it from an unauthorized one. The PrivateLink VPC endpoint and AWS Private CA mTLS layers are described here for completeness; validate those separately, as they carry additional cost and setup.


Even with secure communication, failures in one agent can ripple across your system. The next risk addresses how to contain those failures.

ASI08: Cascading failures

A single fault in one of your agents can propagate across your system, compounding into system-wide harm. Because agents plan, persist, and delegate autonomously, errors can bypass human checks and persist in saved state.

Poisoned market data could cause portfolio agents to recommend the same risky trade, a false positive could trigger automated responses across an entire security operations center, or a remediation agent could suppress alerts and create a feedback loop.

Mitigation: AWS Step Functions and Amazon CloudWatch

AWS Step Functions provides built-in support for circuit breakers and error isolation. The task definition below uses retry logic with exponential backoff and a catch block that routes failures to an isolation state:

Step Functions has an optimized integration with the managed harness in AgentCore, so agent reasoning steps are first-class states rather than raw SDK calls. In Workflow Studio the state appears as AgentCore InvokeHarness. Execution history records agent input, output, token usage, and duration, with links to turn-level detail in CloudWatch — which matters here, because diagnosing a cascade after the fact depends on being able to see which agent step began misbehaving.

A note on what not to build on: Amazon Bedrock Agents, invoked through aws-sdk:bedrockagentruntime:invokeAgent with an AgentId and AgentAliasId, is now Amazon Bedrock Agents Classic and has been closed to new customers since July 30, 2026. An account that has never called CreateAgent receives AccessDeniedException. New work should target AgentCore Runtime or the AgentCore harness.

The task definition below wraps an agent invocation in retry logic with exponential backoff and a catch block that routes failures to an isolation state:

{
  "Type": "Task",
  "Resource": "arn:aws:states:::aws-sdk:bedrockagentcore:invokeAgentRuntime",
  "Parameters": {
    "AgentRuntimeArn": "<agent-runtime-arn>",
    "RuntimeSessionId.$": "$.sessionId",
    "Payload.$": "$.inputText"
  },
  "Retry": [{
    "ErrorEquals": ["States.TaskFailed"],
    "MaxAttempts": 3,
    "BackoffRate": 2.0
  }],
  "Catch": [{
    "ErrorEquals": ["States.ALL"],
    "ResultPath": "$.error",
    "Next": "IsolateFailure"
  }]
}

Amazon CloudWatch composite alarms help detect cascading patterns across your agents:

# Composite alarm that fires only when error rate AND latency are both high
cloudwatch = boto3.client('cloudwatch')

cloudwatch.put_composite_alarm(
    AlarmName='Agent-CascadingFailure',
    AlarmRule='ALARM(Agent-ErrorRate-High) AND ALARM(Agent-Latency-High)',
    ActionsEnabled=True,
    AlarmActions=['arn:aws:sns:<region>:111122223333:critical-alerts']
)

Additional controls:

  • Apply AgentCore Gateway rate limits to cap requests, tokens per minute, and concurrent connections. For cascade containment the cap follows the caller across every target rather than protecting one path at a time, and the token cap bounds a runaway reasoning loop that would otherwise stay well inside a request-rate limit. Note that rate limits fail open if the service is unavailable or a dimension cannot be resolved (see Part 2), so pair them with the circuit breaker rather than relying on them to stop a cascade on their own
  • Alarm on the ActiveSessionCount metric, which AgentCore Runtime and built-in tools publish to CloudWatch per minute with a Service dimension (AgentCore.Runtime, AgentCore.CodeInterpreter, AgentCore.Browser). A sharp rise in concurrent sessions is often the earliest visible sign of a cascade, ahead of any error-rate change
  • Use Amazon API Gateway throttling where tools are fronted by your own APIs outside the Gateway path
  • Enable AWS Shield Advanced for distributed denial of service (DDoS) protection on agent-facing endpoints
  • Implement dead-letter queues for failed agent tasks to prevent retry storms

Validation note: I validated the circuit-breaker behaviour end to end in a test account: Step Functions retried three times with exponential backoff, then routed the exhausted execution to an IsolateFailure state via the catch block, and the CloudWatch composite alarm fired only when both the error-rate and latency alarms were in ALARM. That run used the Bedrock Agents Classic runtime, which is no longer the right target; the retry, catch, and alarm structure is unchanged, but re-validate the task resource against AgentCore Runtime or the optimized AgentCore harness integration before you rely on it. The Step Functions AgentCore integration is available in us-east-1, us-west-2, eu-central-1, and ap-southeast-2.


Finally, even with controls for each of the previous risks, you need to detect and respond when an agent itself becomes the threat.

ASI10: Rogue agents

Unintended or tampered-with agents can deviate from their intended function, acting harmfully or deceptively within multi-agent environments. An agent's emergent behavior can become harmful even if individual actions appear legitimate.

An agent might continue exfiltrating data after a harmful source is removed, a fake review agent injected into a workflow might approve fraudulent transactions, or a tampered-with agent might spawn unauthorized replicas.

Mitigation: Amazon GuardDuty and AWS Security Hub

Amazon GuardDuty provides threat detection across your agent infrastructure, including runtime monitoring for containers and Lambda functions:

# Enable GuardDuty with runtime monitoring and malware protection.
# The nested DataSources parameter is deprecated in favor of the flat
# Features list, so configure everything through Features.
import boto3

guardduty = boto3.client('guardduty')

detector = guardduty.create_detector(
    Enable=True,
    FindingPublishingFrequency='FIFTEEN_MINUTES',
    Features=[
        {'Name': 'EKS_AUDIT_LOGS', 'Status': 'ENABLED'},
        {'Name': 'EBS_MALWARE_PROTECTION', 'Status': 'ENABLED'},
        {'Name': 'RUNTIME_MONITORING', 'Status': 'ENABLED'},
        {'Name': 'LAMBDA_NETWORK_LOGS', 'Status': 'ENABLED'}
    ]
)

Detecting the sequence rather than the event

Individual GuardDuty findings describe single suspicious events. ASI10 is characteristically the opposite: an agent whose individual actions each look legitimate, and whose harm is visible only in the pattern.

GuardDuty Extended Threat Detection targets exactly that shape. It evaluates findings across protection plans and other signal sources to identify attack patterns, and emits attack sequence findings — a finding type introduced specifically to carry critical severity, which GuardDuty had not previously used. Each attack sequence includes the impacted resources, a timeline of events, the actors involved, and the indicators used to detect it. Coverage has since been extended to Amazon EKS clusters.

For a rogue-agent response, the timeline and actor detail are the operationally useful part: containment is a decision about which identity to revoke, and an attack sequence finding names it rather than leaving you to correlate across findings under time pressure.

To automate containment, pair GuardDuty with an AWS Lambda function triggered by findings from AWS Security Hub CSPM. The function below revokes an agent's credentials and notifies your security team:

# Lambda triggered by a Security Hub finding: deny-all the agent role, then notify
import json
import boto3

def contain_rogue_agent(event, context):
    finding = event['detail']['findings'][0]
    # A Security Hub finding resource Id is a full ARN
    # (e.g. arn:aws:iam::111122223333:role/AgentRole-42). IAM APIs take the
    # bare role name, so extract it from the ARN. Splitting on '/' and taking
    # the last segment also handles roles created under a path.
    role_arn = finding['Resources'][0]['Id']
    role_name = role_arn.split('/')[-1]

    # Revoke the agent's credentials immediately with a deny-all policy
    iam = boto3.client('iam')
    iam.put_role_policy(
        RoleName=role_name,
        PolicyName='EmergencyDenyAll',
        PolicyDocument=json.dumps({
            "Version": "2012-10-17",
            "Statement": [{"Effect": "Deny", "Action": "*", "Resource": "*"}]
        })
    )

    # Notify the security team
    sns = boto3.client('sns')
    sns.publish(
        TopicArn='arn:aws:sns:<region>:111122223333:security-critical',
        Subject=f'Rogue Agent Contained: {role_name}',
        Message=f'Agent role {role_name} was automatically contained.'
    )

Validation note: I validated the containment chain end to end in a test account: an EventBridge rule matching high-severity Security Hub findings triggered the containment Lambda, which applied the EmergencyDenyAll inline policy and locked the target agent role out of all actions. Match on the severity representation of the source you route from — Security Hub findings (ASFF) expose Severity.Label (for example, HIGH/CRITICAL) and a normalized 0-100 Severity.Normalized, whereas raw GuardDuty findings use a numeric 1.0-10.0 scale (Low 1.0-3.9 through Critical 9.0-10.0). When adopting this, be careful enabling new GuardDuty features on an existing detector in a production account — a live detector is core security infrastructure, so change its configuration deliberately.

Series summary: AWS services mapped to the OWASP Agentic AI Top 10

This table is the quick reference for the full series — each issue, the part that covers it, and the primary AWS services for mitigation:

IssuePartPrimary AWS servicesKey controls
ASI01: Goal hijack1Amazon Bedrock Guardrails, AgentCore Policy, Amazon S3 Object LockPrompt injection detection, Cedar default-deny authorization, temporal sequencing, immutable prompts
ASI02: Tool misuse2AgentCore Gateway, AgentCore Policy, AWS WAFInput validation, per-identity rate limits, Gateway-only invocation, argument-to-prior-output matching
ASI03: Identity abuse1AgentCore Identity, AWS STS, IAM, AWS Secrets ManagerPrivate Key JWT in place of client secrets, temporary credentials, resource-based policies
ASI04: Supply chain2Amazon Inspector, Amazon ECR managed signing, AWS CodeArtifactEnhanced scanning for OS and language packages, automatic signing on push
ASI05: Code execution2AgentCore Code Interpreter, AWS Lambda, FirecrackerManaged sandbox, network isolation, least-privilege execution role
ASI06: Memory poisoning3AgentCore Memory, AWS KMS, Amazon OpenSearch ServiceEncryption, namespace isolation, strictly consistent metadata, metadata filtering
ASI07: Inter-agent comms3AWS PrivateLink, AWS Private CA, AWS KMSmTLS, message signing
ASI08: Cascading failures3AWS Step Functions, Amazon CloudWatch, AgentCore GatewayCircuit breakers, token and connection caps, ActiveSessionCount alarms
ASI09: Trust abuse1AWS Step Functions, AgentCore Policy, Bedrock Guardrails, Automated ReasoningHuman approval in workflow or in policy, contextual grounding, factual verification (detect-only)
ASI10: Rogue agents3Amazon GuardDuty, AWS Security Hub CSPM, AWS LambdaAttack-sequence detection, automated containment

Conclusion

Across this three-part series, I showed you how to secure your agentic AI systems against each of the 10 OWASP risks using AWS services. You now have a defense-in-depth strategy that protects your agents at every layer — from input validation with Amazon Bedrock Guardrails to threat detection with Amazon GuardDuty — while maintaining their autonomous capabilities.

The key is a layered approach that combines:

  • Identity and access management with distinct agent identities and least-privilege permissions (Part 1)
  • Input validation through Amazon Bedrock Guardrails (Part 1)
  • Human oversight for high-risk actions through Step Functions approval workflows (Part 1)
  • Secure tool orchestration through Amazon Bedrock AgentCore Gateway (Part 2)
  • Runtime isolation for agent-generated code and a verified supply chain (Part 2)
  • Protected memory and knowledge stores using encryption and metadata-based access control (Part 3)
  • Resilient architectures with circuit breakers and error handling (Part 3)
  • Continuous monitoring and threat detection through AWS Security Hub CSPM and Amazon GuardDuty, including attack-sequence findings for behaviour that only looks wrong in aggregate (Part 3)

As agentic AI continues to evolve, security must remain a foundational consideration — not an afterthought.

Next steps

Not every organization needs to implement all ten mitigations at once. The priority order below — reflected in the "week 1 / weeks 2–3 / then expand" guidance across the series — helps you focus on the controls that provide the broadest protection first.

Start here (Part 1 — foundational): Amazon Bedrock Guardrails (ASI01, ASI09), AgentCore Identity (ASI03), logging and observability, and the AWS Security Hub CSPM AI Security Best Practices standard.

Add next (Part 2 — tool and infrastructure): AgentCore Gateway with rate limits and Gateway-only invocation (ASI02), AgentCore Code Interpreter or Lambda isolation (ASI05), Amazon Inspector enhanced scanning and ECR managed signing (ASI04).

Then expand (Part 3 — system resilience): Step Functions circuit breakers (ASI08), AgentCore Memory with strictly consistent metadata (ASI06), AWS PrivateLink and Private CA (ASI07), GuardDuty Extended Threat Detection and Security Hub CSPM (ASI10).

A note on sequencing. AgentCore Policy appears in the "start here" tier for ASI01 and in "add next" for ASI02, which can read as duplicated effort. It isn't: the same policy attaches to the Gateway you stand up in Part 2. If you expect to adopt Gateway at all, standing it up earlier gives you one enforcement point for filtering, authorization, sequencing, and rate limiting rather than four separate controls to assemble.

To begin putting these controls in place:

  1. Review the OWASP Top 10 for Agentic Applications to understand your specific risk profile
  2. For threat modeling guidance, see the MAESTRO framework from the Cloud Security Alliance
  3. Assess your current agent security posture using the AWS Well-Architected Framework
  4. Deploy the Fullstack AgentCore Solution Template (FAST) as a reference implementation
  5. Explore the AWS Samples repository for additional reference implementations

Clean up resources

To avoid ongoing charges from resources created while following this series, delete the following:

  1. Delete the Amazon Bedrock Guardrails you created using the Amazon Bedrock console or the delete_guardrail API.
  2. Remove AgentCore Gateway instances and their targets using the delete_gateway_target and delete_gateway APIs.
  3. Delete AgentCore Memory stores using the delete_memory API.
  4. Remove the AWS Lambda functions created for code execution or containment using the AWS Lambda console or the delete_function API.
  5. Delete Amazon EventBridge event buses using the delete_event_bus API.
  6. Disable Amazon GuardDuty detectors only if they were created solely for this walkthrough. Do not disable or reconfigure a pre-existing detector - it is core security infrastructure for your account.
  7. Remove the IAM roles and policies created specifically for these examples.
  8. Delete Amazon CloudWatch alarms using the delete_alarms API. As a tidiness step, delete composite alarms before the metric alarms they reference.

Additional resources

AWS guidance on agentic security

Service documentation

Frameworks

About the author

Bharadwaz Kari is a Senior Technical Account Manager at AWS specialized in security and AI competencies, where he helps enterprise customers operate and secure their workloads on AWS. This post reflects his personal views, not those of his employer.


Tags: Amazon Bedrock, Amazon Bedrock AgentCore, Amazon GuardDuty, AWS Step Functions, AWS KMS, Generative AI, Security, OWASP, Agentic AI, DevSecOps