Integrations/Amazon Bedrock

Amazon Bedrock runtime authorization

Wrap Amazon Bedrock action-group invocations with Veto. Each governed action-group invocation is evaluated before dispatch: allow, review, or deny, with an exportable decision record per governed decision.

Bedrock agent guardrails with Veto

Amazon Bedrock Agents use action groups backed by Lambda functions or return-of-control to execute tools. AWS access policies control which AWS resources the Lambda can access, but they cannot inspect tool arguments or enforce business rules on individual calls. Veto adds a per-call guardrail boundary inside your Lambda handlers, evaluating each action-group invocation against your policies before execution.

Amazon Bedrock runtime authorization, Bedrock action group guardrails, AWS agent tool policy, Bedrock Lambda guardrails, Bedrock tool authorization, AWS Bedrock runtime authorization, Bedrock AgentCore policy

AWS access controls are not enough

AWS access controls handle infrastructure access: which Lambda can invoke which service, which DynamoDB table the agent can read. This is necessary but insufficient. They cannot answer questions like: "Should this agent transfer $50,000?" or "Can this agent send data to an external email?" These are runtime authorization decisions that depend on the arguments of each individual tool call.

Access controls: infrastructure access

Access controls decide which AWS services your Lambda can call. They answer "can this function invoke DynamoDB?": a binary yes or no at the service level. They cannot inspect the contents of the request.

Veto: runtime authorization

Veto controls what the agent does with its access. It answers "should this specific transfer of $50,000 to account EXT_789 execute?": inspecting arguments, applying business rules, enforcing rate limits.

Privilege escalation risk

Bedrock agents with broad Lambda execution roles can chain tool calls in unintended ways. An agent with sts:AssumeRole and lambda:InvokeFunction access could escalate privileges through creative tool chaining that access controls alone do not reliably prevent.

Data exfiltration via responses

Agents with database read access can return sensitive data in their text responses, bypassing data loss prevention controls. Veto can restrict which queries execute and what data patterns are allowed in results.

Lambda action group handler

Add Veto guardrails inside your Lambda handler. When Bedrock invokes the Lambda with an action group request, Veto evaluates the action and arguments against your policies before your business logic executes.

Lambda handler with Veto guardrails
import json
from veto_sdk import Veto

veto = None

async def get_veto():
    global veto
    if veto is None:
        veto = await Veto.init()
    return veto

def lambda_handler(event, context):
    """Bedrock Agent action group Lambda handler with Veto guardrails."""
    import asyncio
    return asyncio.get_event_loop().run_until_complete(
        _handle(event, context)
    )

async def _handle(event, context):
    v = await get_veto()

    action_group = event.get("actionGroup", "")
    api_path = event.get("apiPath", "")
    http_method = event.get("httpMethod", "")
    parameters = event.get("parameters", [])
    request_body = event.get("requestBody", {})

    # Convert Bedrock parameters to a dict for policy evaluation
    args = {p["name"]: p["value"] for p in parameters}
    if request_body:
        body_content = request_body.get("content", {})
        for content_type, props in body_content.items():
            for prop in props.get("properties", []):
                args[prop["name"]] = prop["value"]

    tool_name = f"{action_group}/{api_path}"

    # Veto evaluates the action against your policies
    decision = await v.guard(tool_name, args)

    if decision.decision == "deny":
        return {
            "messageVersion": "1.0",
            "response": {
                "actionGroup": action_group,
                "apiPath": api_path,
                "httpMethod": http_method,
                "httpStatusCode": 403,
                "responseBody": {
                    "application/json": {
                        "body": json.dumps({
                            "error": "Action blocked by policy",
                            "reason": decision.reason,
                        })
                    }
                },
            },
        }

    if decision.decision == "require_approval":
        return {
            "messageVersion": "1.0",
            "response": {
                "actionGroup": action_group,
                "apiPath": api_path,
                "httpMethod": http_method,
                "httpStatusCode": 202,
                "responseBody": {
                    "application/json": {
                        "body": json.dumps({
                            "status": "pending_approval",
                            "approval_id": decision.approval_id,
                        })
                    }
                },
            },
        }

    # Execute the actual business logic
    result = execute_action(action_group, api_path, args)

    return {
        "messageVersion": "1.0",
        "response": {
            "actionGroup": action_group,
            "apiPath": api_path,
            "httpMethod": http_method,
            "httpStatusCode": 200,
            "responseBody": {
                "application/json": {
                    "body": json.dumps(result)
                }
            },
        },
    }

Return of control pattern

When using Bedrock's return-of-control mode, the agent returns tool call requests to your application instead of invoking Lambda directly. This gives you a natural interception point for Veto guardrails before you execute the tool and send the result back.

Return of control with Veto
import boto3
import json
from veto_sdk import Veto

bedrock_runtime = boto3.client("bedrock-agent-runtime")
veto = await Veto.init()

def invoke_agent_with_veto(agent_id, agent_alias_id, session_id, prompt):
    """Invoke a Bedrock agent with return-of-control and Veto guardrails."""
    response = bedrock_runtime.invoke_agent(
        agentId=agent_id,
        agentAliasId=agent_alias_id,
        sessionId=session_id,
        inputText=prompt,
    )

    for event in response["completion"]:
        if "returnControl" in event:
            invocation = event["returnControl"]["invocationInputs"][0]
            action = invocation["functionInvocationInput"]

            tool_name = action["function"]
            args = {
                p["name"]: p["value"]
                for p in action.get("parameters", [])
            }

            # Veto authorization check
            import asyncio
            decision = asyncio.run(veto.guard(tool_name, args))

            if decision.decision == "deny":
                # Return error to the agent
                send_result(
                    agent_id, agent_alias_id, session_id,
                    invocation["invocationId"],
                    {"error": decision.reason}
                )
                continue

            # Execute and return result
            result = execute_tool(tool_name, args)
            send_result(
                agent_id, agent_alias_id, session_id,
                invocation["invocationId"],
                result
            )

Powertools for AWS Lambda

If you use Powertools for AWS Lambda, add Veto guard checks inside your tool functions. The BedrockAgentResolver handles request and response formatting while Veto handles enforcement.

Powertools + Veto
from aws_lambda_powertools.event_handler import BedrockAgentResolver
from veto_sdk import Veto

app = BedrockAgentResolver()
veto = None

@app.tool(name="transfer_funds", description="Transfer money between accounts")
async def transfer_funds(
    amount: float,
    from_account: str,
    to_account: str,
) -> dict:
    # Initialize Veto lazily
    global veto
    if veto is None:
        veto = await Veto.init()

    decision = await veto.guard("transfer_funds", {
        "amount": amount,
        "from_account": from_account,
        "to_account": to_account,
    })

    if decision.decision == "deny":
        return {"error": decision.reason}

    if decision.decision == "require_approval":
        return {
            "status": "pending_approval",
            "approval_id": decision.approval_id,
        }

    # Execute transfer
    return perform_transfer(amount, from_account, to_account)

@app.tool(name="query_database", description="Query the analytics database")
async def query_database(sql: str, database: str = "analytics") -> dict:
    global veto
    if veto is None:
        veto = await Veto.init()

    decision = await veto.guard("query_database", {
        "sql": sql,
        "database": database,
    })

    if decision.decision == "deny":
        return {"error": decision.reason}

    return execute_query(sql, database)

def lambda_handler(event, context):
    return app.resolve(event, context)

Policy rules

Define policies that complement your AWS access roles. Veto rules inspect tool arguments, enforce business constraints, and rate-limit agent actions.

veto/policies/bedrock.yaml
version: "1.0"
name: Bedrock agent policies

rules:
  - id: block-production-writes
    action: block
    tools: ["*/POST*", "*/PUT*", "*/DELETE*"]
    conditions:
      - field: arguments.database
        operator: equals
        value: "production"
    message: "Production writes blocked. Use staging."

  - id: approve-large-transfers
    action: require_approval
    tools: [transfer_funds]
    conditions:
      - field: arguments.amount
        operator: greater_than
        value: 5000
    message: "Transfers over $5000 require human approval"

  - id: block-sql-mutations
    action: block
    tools: [query_database]
    conditions:
      - field: arguments.sql
        operator: matches
        value: "(?i)(DROP|DELETE|TRUNCATE|ALTER|UPDATE|INSERT)"
    message: "Only SELECT queries allowed"

  - id: restrict-cross-account
    action: block
    tools: [transfer_funds]
    conditions:
      - field: arguments.to_account
        operator: matches
        value: "^EXT_"
    message: "External account transfers blocked"

  - id: rate-limit-agent
    action: block
    tools: ["*"]
    conditions:
      - field: context.hourly_count
        operator: greater_than
        value: 100
    message: "Agent hourly action limit exceeded"

How Veto protects Bedrock agents

1

Agent decides to act

The Bedrock agent processes user input and decides to invoke an action group. It selects the API path and constructs parameters from the conversation.

2

Lambda receives the request

Bedrock invokes your Lambda function with the action group, API path, HTTP method, and parameters. Or in return-of-control mode, your application receives the invocation request.

3

Veto evaluates the action

Before your business logic runs, Veto evaluates the tool name and arguments against your policies. Deterministic rules check argument values, patterns, rate limits, and business constraints.

4

Enforcement

Allowed actions execute normally. Blocked actions return a 403 with a policy reason. The Bedrock agent receives the response and can adjust its approach. Governed decisions are recorded.

Frequently asked questions

How is Veto different from Bedrock Guardrails?
Bedrock Guardrails filter content with prompt, PII, and topic controls. Veto controls actions: it evaluates tool-call arguments against business policies. Use Bedrock Guardrails for content safety and Veto for tool authorization. They complement each other.
Does Veto replace your AWS access policies?
No. AWS access controls handle infrastructure access (which services Lambda can call). Veto controls business logic (should this specific $50K transfer execute). You need both. Access controls are your security perimeter. Veto is your decision logic inside that perimeter.
How do I deploy Veto in a Lambda function?
Add veto to your Lambda layer or requirements.txt. The SDK initializes lazily on first invocation. Set VETO_API_KEY as a Lambda environment variable (encrypted via KMS). Cold start depends on Lambda package size. Warm invocations stay in-process before dispatch.
Does Veto work with Bedrock's return-of-control mode?
Yes. Return-of-control gives your application the tool-call boundary: the agent returns requests, you run Veto guardrails, execute if allowed, and send results back.
Can I use Veto with Bedrock AgentCore?
Yes. AgentCore provides infrastructure for running agents (compute, networking, observability). Veto operates inside your tool handlers regardless of the hosting infrastructure. Whether your tools run in Lambda, ECS, or AgentCore's managed compute, Veto guardrails work the same way.

Related integrations

Browse integrations

Secure your Bedrock agents beyond access controls.