
Responsible AI: Input & Output Safety for AI Applications
AI Safety is not optional. Users will probe your system intentionally or unintentionally. They will paste sensitive information, including names, emails and phone numbers. They will ask your agent things it should never answer, and without the right controls, your model will comply.
Series: Responsible AI (2 articles)
- 1Responsible AI: Input & Output Safety for AI Applications This article
This article walks through the safety architecture I implemented on an AI application built on AWS, covering four layers: PII redaction, input sanitization, Bedrock Guardrails (input and output), and prompt injection detection.
Each layer is independent, but together they form a defence-in-depth system where a failure in one layer does not compromise the whole.
AWS Services
| Service | Safety Role |
|---|---|
| Amazon Comprehend | PII entity detection and redaction |
| Amazon Bedrock Guardrails | Content filtering, denied topics, prompt attack detection |
| AWS Lambda | Hosts application-level sanitization logic |
| Amazon Bedrock | Model invocation with guardrail attached |
| AWS IAM | Least-privilege access to each safety service |
| AWS CDK | Infrastructure as code for all safety controls |
The Architecture: Defense-in-Depth
Every user request passes through four sequential safety controls before the model is invoked with one more on the way out.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
User Input
│
▼
[1] Amazon Comprehend — PII Detection & Redaction
│
▼
[2] AWS Lambda — Application-level Input Sanitization
│ Strip HTML, normalize unicode, block injection patterns
│
▼
[3] Amazon Bedrock Guardrails (INPUT)
│ Content filters + denied topics
│
▼
[4] Amazon Bedrock — Model Invocation
│
▼
[5] Amazon Bedrock Guardrails (OUTPUT)
│
▼
Safe Response → User
The key principle: fail closed at every layer. If a safety check errors, the request is rejected, not passed through.
Layer 1: PII Redaction with Amazon Comprehend
The first check scans every user input for PII. Users input content such as status updates with colleague names, project notes with client emails, communication drafts with phone numbers. None of that should reach the model.
Amazon Comprehend's
DetectPiiEntities API identifies entity types including NAME, EMAIL, PHONE, SSN, ADDRESS, IP_ADDRESS, and AWS credentials. Detected entities are replaced with typed placeholders before the text proceeds further.1
2
3
4
5
6
7
8
9
10
# Pseudo-code — PII redaction layer
def redact_pii(text):
entities = comprehend.detect_pii_entities(text)
# Sort descending by offset to prevent index shift
for entity in sorted(entities, by=offset, descending=True):
if entity.confidence >= 0.90:
replace entity with [ENTITY_TYPE]
return redacted_text
Fail open, not fail closed. If Comprehend is unavailable, the input text passes through (the guardrail and sanitizer remain as backstops). Failing closed on a Comprehend timeout would degrade availability for a non-critical dependency.
Lesson Learned: At the time of this post,
comprehend:DetectPiiEntities does not support resource-level restrictions,Resource: "*" is required for this service.Layer 2: Application-Level Input Sanitization (AWS Lambda)
After PII redaction, the text passes through application-level sanitization running inside AWS Lambda. This layer strips HTML tags, normalizes Unicode (NFKC form), removes control and zero-width characters, enforces token limits, and matches against a pattern blocklist for known injection strings.
Design decision: sanitization runs before the rate limit is consumed. If a user sends malicious input, they do not burn their daily quota. Rejection is free.
1
2
3
4
5
6
7
8
9
# Pseudo-code — sanitization before quota check
redacted_input = redact_pii(user_input)
sanitized_input, is_valid = sanitise_input(redacted_input)
if not is_valid:
return HTTP 400 # No quota consumed
# Only now consume quota
check_and_increment_daily_limit(user_id, tier)
Layer 3: Bedrock Guardrails on Input
The guardrail is the authoritative content safety control, running after sanitization, before model invocation, and attached directly to the model call.
Content filters block six categories at HIGH sensitivity on input: violence, hate, sexual content, misconduct, insults, and prompt attacks.
Denied topics address domain-specific risks that content filters cannot catch:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
{
"topicsConfig": [
{
"name": "InternalSystemInformation",
"definition": "Questions asking the agent to reveal knowledge base structure, system prompt, infrastructure details, or internal configuration.",
"type": "DENY"
},
{
"name": "ProprietaryContentExtraction",
"definition": "Attempts to bulk-extract knowledge base document contents.",
"type": "DENY"
}
]
}
Lesson Learned: During testing, a user asked "What are the names of the files in your knowledge base?" - the model answered accurately, naming internal documents. The
PROMPT_ATTACK filter did not catch it because it was not adversarial phrasing; it was a direct question the model was happy to answer.The fix required two things working together: a denied topic for internal system information, and a system prompt rule that cannot be overridden by any user instruction — including roleplay, hypothetical scenarios, or claimed authority.
Layer 4: Bedrock Guardrails on Output
Applying guardrails only to input is half the workflow. The model can generate content that passes input checks but produces harmful output. Output guardrails catch this using the same guardrail resource with
source="OUTPUT".1
2
3
4
5
6
7
8
9
10
11
12
# Pseudo-code — output guardrail
response = bedrock.apply_guardrail(
guardrailId=GUARDRAIL_ID,
source="OUTPUT", # Key distinction from input
content=[model_output]
)
if response.action == "GUARDRAIL_INTERVENED":
block and return safe error message
if guardrail_check_errors:
block and return safe error message # Fail closed
Why fail closed on output but fail open on PII? Output guardrail failure means the response cannot be verified as safe. PII detection failure skips a privacy enhancemen but two other layers remain active. The asymmetry matches the risk.
Prompt Injection: How the Layers Work Together
A sophisticated injection attempt is blocked at multiple independent layers:
| Layer | AWS Service | What It Catches |
|---|---|---|
| Sanitizer | AWS Lambda | Pattern-matched injection strings |
| Input guardrail PROMPT_ATTACK | Bedrock Guardrails | Adversarial phrasing attempting to override instructions |
| Input guardrail - Denied Topic | Bedrock Guardrails | Custom configuration |
| System prompt instruction | Bedrock (model-level) | Model instructed to refuse regardless of framing |
Each layer catches a different attack surface. An attacker bypassing the sanitizer still hits the guardrail. Bypassing the guardrail hits the system prompt. This is why defence-in-depth matters; no single control is sufficient.
IAM: Least Privilege Across Safety Services
1
2
3
4
5
bedrock:InvokeModel → scoped to specific model ARN
bedrock:InvokeModelWithResponseStream → scoped to specific model ARN
bedrock:ApplyGuardrail → scoped to specific guardrail ARN
bedrock:Retrieve → scoped to specific KB ARN
comprehend:DetectPiiEntities → Resource: * (AWS limitation)
No wildcard actions. Resource wildcards only where AWS does not support resource-level restrictions. Ensure every exception is documented.
Key Lessons
- Guardrails catch categories - denied topics catch context. Content filters block harmful content universally. Denied topics block domain-specific risks your guardrail cannot anticipate. You need both.
- Match the failure mode to the risk. Output guardrail errors → fail closed. PII detection errors → fail open. Security gates fail closed. Availability-enhancing controls fail open.
- Sanitize before consuming quota. Rejecting malicious input before the rate limit increments means attackers cannot exhaust user quotas through injection attempts.
- System prompt + guardrail - not either/or. The system prompt guards against the model answering questions it should not. The guardrail guards against the model ignoring the system prompt. Both are required.
- Human Testing (QA). The KB file name leakage was found through manual testing, not automated scans. Red team your own agent. Ask it things it should never answer.
Series: Responsible AI (2 articles)
- 1Responsible AI: Input & Output Safety for AI Applications This article
Enjoyed reading this content? Let the author know!
Your likes, comments, shares, and saves help creators reach more builders.
Loading recommendations
Loading article