Lambda Authorizers: Bedrock Policy Explanations With Hard IAM Denies

Lambda Authorizers: Bedrock Policy Explanations With Hard IAM Denies

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

// 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

# 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
// ✅ 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


Discover more from CheatCoders

Subscribe to get the latest posts sent to your email.

1 Comment

Leave a Reply