Lambda Authorizers: Bedrock Policy Explanations With Hard IAM Denies

Lambda Authorizers: Bedrock Policy Explanations With Hard IAM Denies
9 views

This guide covers Lambda authorizers with production-focused examples and trade-offs.

LLM-flavored authorizers are a trap when the model becomes the gate. The unfair pattern: keep hard allow/deny in IAM, Cedar, or a deterministic policy engine, and use Bedrock only to explain why a decision happened for humans and audit UIs. Prototype explanations inside Lambda authorizers; never let token probabilities decide production access.
⚡ TL;DR: Evaluate Cedar/IAM first; call Bedrock only after a deny (or allow) is final. Stream a grounded explanation citing policy statements and request context. Fail closed if the model times out — the decision already exists. Wire with Bedrock Guardrails and AppSync+Bedrock tool safety.

Split decision from narration — Lambda authorizers

typescript
// authorizer.ts — decision is deterministic; Bedrock is optional narration
import { evaluate } from "./cedar-engine.js";
import { explainDeny } from "./bedrock-explain.js";

export async function handler(event: APIGatewayRequestAuthorizerEvent) {
  const principal = verifyJwt(event.headers?.authorization);
  const decision = evaluate({
    principal,
    action: event.requestContext.routeKey,
    resource: event.path,
    context: { tenantId: principal.tenantId },
  });

  // ✅ Hard gate — model never flips this
  const effect = decision.allowed ? "Allow" : "Deny";

  let explanation: string | undefined;
  if (!decision.allowed && process.env.EXPLAIN_DENIES === "1") {
    try {
      explanation = await explainDeny({
        statements: decision.matchedStatements,
        request: { action: event.requestContext.routeKey, path: event.path },
      });
    } catch {
      // ❌ Never: deny because Bedrock failed — decision already Deny
      explanation = "Denied by policy; explanation unavailable";
    }
  }

  return {
    principalId: principal.sub,
    policyDocument: buildIamPolicy(effect, event.methodArn),
    context: {
      tenantId: principal.tenantId,
      explanation: explanation?.slice(0, 1024) ?? "",
      policyVersion: decision.policyVersion,
    },
  };
}
✅ Effect derived solely from Cedar/IAM evaluation.
❌ if (await bedrockSaysAllow(event)) return Allow.

Ground explanations in matched statements

python
# bedrock_explain.py — illustrative
SYSTEM = """You explain authorization denials.
Only cite the provided policy statements and request fields.
Never invent permissions. Never suggest bypasses.
Output <= 80 words for the API Gateway authorizer context map."""

def explain_deny(statements: list[dict], request: dict) -> str:
    user = {
        "request": request,
        "matchedStatements": statements,  # from Cedar residual
    }
    # Use Converse with guardrails; low temperature
    return converse_text(system=SYSTEM, user=json.dumps(user), temp=0.1)
Prompt injection in a path or header must not rewrite the effect — only the narrative. Same discipline as retrieval tenant filters.

Latency and fail-closed UX

Authorizers sit on the hot path. Budget explanation calls separately from the decision:
Path Budget Behavior
Decision (Cedar) ≤ 20 ms p95 Always runs
Bedrock explain ≤ 800 ms, async optional Skip on timeout
Cache Hash(principal, action, resource, policyVersion) Reuse narratives
typescript
// ✅ Optional async explain after Deny response already returned to edge
void queueExplanationJob({ requestId, statements }).catch(() => {});

// ❌ Await 5s Bedrock call inside every authorizer invoke

Closing checklist

✅ Dos
– ✅ Deterministic policy engine is the only effect source
– ✅ Bedrock cites matched statements only
– ✅ Authorizer context carries policyVersion for audits
– ✅ Timeouts skip explanation, never flip Allow/Deny
– ✅ Guardrails on explanation prompts ❌ Don’ts
– ❌ Don’t let the model be the final authorization gate
– ❌ Don’t put Allow/Deny tokens in free-form model output you parse
– ❌ Don’t block requests waiting on Bedrock health
– ❌ Don’t log raw JWTs into model prompts
– ❌ Don’t skip idempotent agent tool patterns if explanations trigger side effects

Related reading

Last updated on September 11, 2026

Related: Day 31: Lambda + Bedrock: Sync, Stream, and Batch · Lambda Plus Bedrock: Stream Tokens Without API Gateway Caps · Bedrock Custom Model Import: Guardrails and IAM Still Must Wrap

Related: Day 56: Supervisor Patterns on Bedrock Agents · Day 43: Distillation for Edge and Lambda · Bedrock Prompt Management: Versioned System Prompts for Production

Watch: 100 Days of AI on YouTube

Short videos from CheatCoders, one AI engineering topic per day.

Open the playlist on YouTube · CheatCoders on YouTube

Deep-dive PDF

Get the expanded guide for this post — extra diagrams-style checklists, failure modes, and a production walkthrough. Free when you subscribe to CheatCoders.

Already subscribed? or open the subscribe page.


Discover more from CheatCoders

Subscribe to get the latest posts sent to your email.

1 Comment

Leave a comment

No account needed. Name and email are optional.