DEV Community

Cover image for Secure Generative AI on AWS: Amazon Bedrock, Guardrails & IAM (A Practical Guide)
Abdullateef OGUNDIPE
Abdullateef OGUNDIPE

Posted on

Secure Generative AI on AWS: Amazon Bedrock, Guardrails & IAM (A Practical Guide)

TL;DR — Secure generative AI on AWS is mostly secure cloud, applied to a new kind of workload. Three building blocks do most of the heavy lifting: Amazon Bedrock (the managed model service), Guardrails for Amazon Bedrock (the configurable safety net), and IAM (the identity boundary). This post walksthrough each, assembles them into a reference architecture, and gives you a reproducible demo. Slides, demo guide, and code are linked at the end.
Adapted from my AWS Cloud Security User Group - West Africa Study Series on "Secure Generative AI Applications withAmazon Bedrock, Guardrails, and IAM."
Keywords: Amazon Bedrock, Generative AI Security, AWS Guardrails, IAM BestPractices, Secure AI Applications, Responsible AI, Least-Privilege Access,Prompt Injection, Foundation Models, AWS Well-Architected.


1. Introduction

Every team I talk to is shipping something with a large language model in it, a chatbot, a summarizer, a "talk to your docs" feature. The model is the easy part. Bedrock hands you a state-of-the-art foundation model behind one API call. The hard part is everything around the model: who is allowed to call it, what it is allowed to say, what data it can touch, and how you prove, after the fact, what actually happened.

This post is about that surrounding 90%. It is deliberately practical. By the end you should be able to understand the topic, follow the talk, reproduce the demo, and deploy a small version of this yourself.

Secure GenAI on AWS — the three building blocks (Amazon Bedrock, Guardrails, IAM) surrounded by supporting cloud controls.

Figure 1: The secure GenAI blueprint. Three core controls (Bedrock, Guardrails, IAM)


2. Problem Statement: the chatbot that wasn't ready for production

A team built an internal LLM chatbot, call it PolicyBot. It was wired to a knowledge base of policies, claim notes, and customer correspondence. It worked beautifully in the demo… and then, within a week of going live, a misconfigured prompt returned another customer's record to a user.
The root cause was not the model. It was everything around the model:

  • The Lambda calling the model ran with a wide IAM role (bedrock:*, and more).
  • There was no guardrail — no PII filter, no denied-topics policy, no prompt-injection screen.
  • There was no audit trail — no easy way to ask "which prompt produced which response, for which user?"
  • Sensitive and public data were mixed in one knowledge base.
  • The team had only tested the happy path.

Sound familiar? None of these are exotic AI problems. They are ordinary cloud security problems wearing an AI costume.


3. Why Secure GenAI Matters

Generative AI changes the security conversation in three concrete ways:

  1. The inputs and outputs are the data plane: With S3 you secure objects. With an LLM, the prompt and the completion are the sensitive payload and they are generated dynamically, so you cannot enumerate them in advance.
  2. Natural language is an attack surface: Prompt injection and jailbreaks let an attacker reprogram behaviour using nothing but text, no CVE required.
  3. Agency amplifies blast radius: The moment a model can call tools, query a knowledge base, or trigger an action, its permissions become its permissions.

The community has converged on shared threat models for exactly this. The
OWASP Top 10 for LLM Applications and the NIST AI Risk Management Framework are both worth reading. The good news: nearly every item maps to an AWS control you can configure today.

GenAI threat landscape mapped to AWS controls: prompt injection → guardrails, PII disclosure → sensitive-info filter, excessive agency → IAM, insecure output → output filters, unbounded cost → IAM + caps, no audit trail → invocation logging.

Figure 2: OWASP-style GenAI risks (left) mapped to the AWS control that addresses each one (right). No single layer is sufficient; the controls compose.

4. Amazon Bedrock Overview

Amazon Bedrock is a fully managed service that gives you access to a curated catalogue of foundation models, Claude (Anthropic), Nova/Titan (Amazon), Llama (Meta), Mistral, Cohere, AI21, through one consistent API. You don't host any model yourself. You call InvokeModel, or their streaming variants. Around that, Bedrock offers Knowledge Bases (managed RAG), Agents (tool-using assistants), Prompt Management, and Guardrails.

Three security-relevant facts that shape every design decision below:

  • Access is governed by IAM, not a console toggle: AWS retired the old Model access page in 2025 — serverless models now auto-enable on first invocation, and who can call which model is controlled entirely through IAM policies and SCPs (First-time Anthropic use may require a short use case form.)
  • All requests stay on AWS: Use VPC interface endpoints (PrivateLink) for Bedrock to keep traffic off the public internet entirely.
  • Every call is auditable: Model invocation logging captures prompt, response, and metadata to CloudWatch Logs and/or S3.

That last point matters in a way it didn't for, say, S3: because the inputs and outputs are the data plane, you can't reason about safety without seeing them.


5. Guardrails — moving safety out of the prompt

You can write a beautiful system prompt that says "never reveal PII, never give legal advice, never produce harmful content." It will work most of the time. The problem is that a prompt isn't:

  • Testable — there's no clean way to assert it's enforced for every variation.
  • Versionable — prompt drift across PR reviews is invisible.
  • Auditable — a prompt instruction leaves no record of which request triggered which clause.

Guardrails for Amazon Bedrock solve this by moving safety policy out of the prompt and into a versioned, named resource that lives next to your IAM resources. You attach a guardrail to a model invocation, or call it standalone via the ApplyGuardrail API. A guardrail evaluates policy on input (before themodel sees it) and output (before the caller sees it).

The six policy types:

Policy What it does
Content filters Categories — Hate, Insults, Sexual, Violence, Misconduct — with low/medium/high thresholds.
Denied topics Domain-specific bans ("legal advice", "medical diagnosis") defined by name, definition, and example phrases.
Word filters Profanity list plus your custom words.
Sensitive information PII patterns (email, phone, name, address, SSN, credit card) with block or mask/anonymize actions.
Prompt attacks Detector for prompt injection / jailbreak attempts in user input.
Contextual grounding For RAG — blocks answers not grounded in retrieved sources.

Guardrail evaluation flow: user input passes through the input policy, then the model, then the output policy, before reaching the caller; blocked requests never reach the model and a trace is logged.
Figure 3 — A guardrail applies policy on the way in and on the way out.


6. IAM Controls — half the talk

Here's the line that belongs on a sticker:

The blast radius of an LLM is the IAM role attached to it.

Everything else — guardrails, content filters, prompt techniques — is a
defence-in-depth layer inside that blast radius. Get the role wrong and the layers don't matter.

The good news: this is regular AWS. The least-privilege patterns you already know apply directly. The Bedrock-specific actions you'll touch most are:

  • bedrock:InvokeModel / bedrock:InvokeModelWithResponseStream
  • bedrock:Converse / bedrock:ConverseStream
  • bedrock:ApplyGuardrail, bedrock:GetGuardrail, bedrock:ListGuardrails
  • bedrock:Retrieve / bedrock:RetrieveAndGenerate (Knowledge Bases)

IAM blast radius: an over-permissioned role with bedrock:* on Resource * reaches every model, versus a least-privilege role scoped to one model ARN and one guardrail ARN with short-lived STS credentials.
Figure 4 — Two roles, two blast radii. The left role can reach everything; the right role can call exactly one model and one guardrail, and nothing else.

Two practices that pay off:

  1. Scope to model and guardrail ARNs: Never write Resource: "*" in a Bedrock policy. Pin the foundation-model ARN you use and the guardrail ARN you've configured.
  2. Use short-lived credentials: Code calling Bedrock should run as an IAM role assumed via STS, not as a long-lived user with static access keys.

A minimal least-privilege policy:

json
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "AllowConverseOnSpecificModel",
      "Effect": "Allow",
      "Action": [
        "bedrock:Converse",
        "bedrock:ConverseStream"
      ],
      "Resource": [
        "arn:aws:bedrock:us-east-1::foundation-model/anthropic.claude-3-5-sonnet-20240620-v1:0"
      ]
    },
    {
      "Sid": "AllowSpecificGuardrail",
      "Effect": "Allow",
      "Action": [
        "bedrock:ApplyGuardrail",
        "bedrock:GetGuardrail"
      ],
      "Resource": [
        "arn:aws:bedrock:us-east-1:111122223333:guardrail/abcd1234efgh"
      ]
    }
  ]
}
Enter fullscreen mode Exit fullscreen mode

No bedrock:*, no iam:*, and definitely no *:*. If the application later
needs Knowledge Base APIs or a specific S3 prefix, add those statements
explicitly
.


7. Putting It Together — a reference architecture

None of these controls is interesting alone. The value is in how they compose.
Reference architecture: client to API Gateway to Lambda, which assumes a scoped role to call Bedrock Converse with an attached guardrail; a Knowledge Base provides scoped RAG and CloudWatch captures logs and metrics.

Figure 5 — Reference architecture: The Lambda assumes a role scoped to one model and one guardrail

Key design decisions on this diagram:

  • The Lambda assumes a role scoped to one model + one guardrail. Nothing else.
  • Every call carries a guardrailConfig — guardrails are always on.
  • Knowledge-Base reads are similarly scoped (one KB, one tenant prefix).
  • Logs go to CloudWatch with request-id and user/tenant tags.
  • Secrets — API keys for any non-AWS service the agent might call, live in Secrets Manager, never baked into Lambda environment variables.
  • Data at rest is encrypted with KMS, ideally per-tenant keys.

8. Demo Walkthrough

In the talk we ran a small Python client against this setup. The full,
reproducible steps are in the demo guide; here's the shape of it.

Demo architecture: a local Python CLI assumes a least-privilege role via STS, calls Bedrock Converse with an attached guardrail, and CloudWatch captures the invocation logs; three sample prompts produce pass, anonymized, and blocked outcomes.
Figure 6 — The demo: a local CLI → STS AssumeRole → Bedrock Converse + Guardrail→ CloudWatch.

Three prompts, three outcomes:

Prompt What the guardrail does What you see
"Summarise the AWS Well-Architected pillars in 5 bullets." Nothing — clean Normal model output
"I'm Jane Doe (jane.doe@acme.com), please confirm." Anonymises email/name before the model sees them Model answers using the masked version
"Should I sue my landlord over a leaky roof?" Blocks — denied topic Legal advice Model never invoked; configured refusal returned

The client is intentionally tiny — it sets guardrailConfig with trace: "enabled"
and prints both the model output and the guardrail trace:

python
resp = client.converse(
    modelId=MODEL_ID,
    messages=[{"role": "user", "content": [{"text": prompt}]}],
    guardrailConfig={
        "guardrailIdentifier": GUARDRAIL_ID,
        "guardrailVersion": GUARDRAIL_VERSION,
        "trace": "enabled",
    },
    inferenceConfig={"maxTokens": 500, "temperature": 0.2},
)
Enter fullscreen mode Exit fullscreen mode

9. Lessons Learned & Best Practices

Going back to PolicyBot, here is the fix list, generalised.

If you only do five things:

  1. Pin the model id, guardrail id, and guardrail version in code: Treat them like database schema versions.
  2. Always attach a guardrail: Even a permissive one is a hook for future tightening.
  3. Scope the IAM role: Resource ARNs, not wildcards. Short-lived STS creds.
  4. Log every invocation: request id, principal, model id, guardrail id, and the guardrail trace.
  5. Red-team before you ship: Prompt injection, jailbreaks, PII probes, denied-topic probes. make it a checklist that runs in CI.

Three pitfalls often seen:

  • A bedrock:* policy attached to a Lambda to make it work.
  • Logging full prompts that contain PII before the guardrail has masked them.
  • Forgetting that the guardrail is also a resource, so it needs IAM, change-control, and an audit trail of its own.

Responsible AI controls worth layering in as you mature: human-in-the-loop for high-impact actions, evaluation/regression suites for quality and safety, and clear user-facing disclosure that they're talking to an AI.


10. Conclusion

Secure generative AI on AWS is not a new discipline. It is the cloud security you already practice, pointed at a workload where the prompt and the completion are the sensitive data. Bedrock gives you the managed model and the audit hooks. Guardrails give you testable, versionable, auditable safety policy. IAM draws the blast-radius boundary around the whole thing.

If you remember one line: secure GenAI is mostly secure cloud, applied to a new kind of workload.


Try it yourself

The demo guide and code from the talk are open-sourced:

  • Demo guide (markdown) and client (Python): — full step-by-step with validation checks: demo guide

  • Presentation Video Rewatch: Presentation Video

You'll need an AWS account with Amazon Bedrock available in your Region of choice (serverless models auto-enable on first invocation; first-time Anthropic use may need a short use-case form).

References

All links point to official AWS documentation, AWS blogs, AWS workshops, or recognised standards bodies.

AWS documentation

AWS guidance, blogs & workshops

Standards


If this was useful, leave a ❤ and tell me in the comments what you'd like covered next

Abdullateef Ogundipe (Limitless_AO)

Top comments (0)