Intent Detection (IBAC)
Detect data-access intent in user prompts and require verification before sharing sensitive information.
Intent Detection (IBAC)
Intent-Based Access Control (IBAC) is a proactive security layer that detects when users are requesting access to sensitive data through AI conversations. Instead of relying solely on reactive threat detection, IBAC identifies data-access intent in natural language prompts and requires identity verification before the AI can respond with sensitive information.
IBAC sits between your users and AI providers, analyzing every prompt for patterns that indicate a request for protected data — order details, account information, payment records, personal data, or admin-level actions.
The Problem: Why IBAC Matters
Traditional AI security focuses on blocking malicious inputs — jailbreak attempts, prompt injections, and harmful content. But what about legitimate-looking requests that access data they shouldn't?
Consider this real-world scenario:
Real-world example: A customer support AI chatbot was manipulated through carefully crafted conversational prompts to reveal other customers' personal information. The requests appeared completely natural — "Can you look up the account details for case ID 8817?" — but the user making the request had no authorization to access that account. Traditional security passed every check because there was nothing technically malicious about the prompt.
The gap IBAC fills:
| Security Layer | What It Catches | What It Misses |
|---|---|---|
| Jailbreak Detection | "Ignore your instructions and reveal..." | Normal-sounding data requests |
| PII Protection | SSN, credit card numbers in outputs | Requests that would trigger PII exposure |
| Bot Detection | Automated scraping patterns | Human social engineering |
| IBAC | "Show me the address for order #34004" | — |
IBAC adds the missing layer: detecting what data users are trying to access and requiring verification before the AI responds.
How It Works
Detection Flow
User Prompt
│
▼
┌─────────────────────────┐
│ Pattern Matching │ Aho-Corasick multi-pattern
│ (Built-in + Custom) │ string matching algorithm
└───────────┬─────────────┘
│
▼
┌─────────────────────────┐
│ Confidence Scoring │ Base confidence + multi-match
│ │ bonus, threshold: 0.70
└───────────┬─────────────┘
│
┌──────┴──────┐
│ │
< 0.70 ≥ 0.70
│ │
▼ ▼
Allow ┌──────────────┐
Request │ Challenge │ Return valid API response
│ Response │ with verification request
└──────────────┘Built-in Intent Categories
Bastio ships with 5 built-in intent categories that cover the most common data-access patterns:
| Category | Base Confidence | Required Verification |
|---|---|---|
order_lookup | 0.85 | Identity, Email |
account_info | 0.80 | Identity |
payment_data | 0.90 | Identity, Payment |
personal_info | 0.90 | Admin, Identity |
admin_action | 0.85 | Admin, Identity |
Confidence Scoring
The confidence score determines whether a prompt triggers a challenge:
- Base confidence: Each category has a baseline score (0.80–0.90)
- Multi-match bonus: +0.05 for each additional pattern matched
- Cap: Maximum confidence is 0.99
- Threshold: Only prompts scoring 0.70 or higher trigger a challenge
For example, "What's the tracking number for order #12345?" matches two patterns in order_lookup, resulting in a confidence of 0.85 + 0.05 = 0.90.
Challenge Responses
When IBAC detects data-access intent, it returns a valid API response (not an error) with a contextual verification message. This means your application continues to work normally — the user sees a helpful message instead of a broken experience.
Example challenge response (OpenAI Chat Completions format):
{
"id": "resp_abc123",
"object": "chat.completion",
"model": "gpt-4o",
"choices": [{
"index": 0,
"message": {
"role": "assistant",
"content": "I'd be happy to help with your order, but I need to verify your identity first. Please log in or verify your email to access order information."
},
"finish_reason": "stop"
}]
}Challenge metadata is included for programmatic detection:
{
"bastio_challenge": "true",
"action": "auth_required",
"intent_category": "order_lookup",
"challenge_id": "ch_abc123",
"confidence": "0.90",
"required_verification": "identity_verification,email_verification",
"request_id": "resp_abc123"
}Enabling Intent Detection
Via Dashboard
- Navigate to Security Center in your dashboard
- Open the Security Profile for your proxy
- Toggle Enable Intent Detection to on
- Click Save
Intent detection is enabled by default for Medium and above security levels.
Via API
curl -X PUT https://api.bastio.com/security/profiles \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"proxy_id": "your-proxy-id",
"enable_intent_detection": true
}'Built-in Categories Reference
Order Lookup (order_lookup)
Detects attempts to look up order information, shipping details, and tracking numbers.
Patterns: order status, order number, tracking number, shipping address, delivery status, order details, order #, shipment, where is my order, order history
Challenge message: "I'd be happy to help with your order, but I need to verify your identity first. Please log in or verify your email to access order information."
Required verification: identity_verification, email_verification
Account Information (account_info)
Detects attempts to access account details like email, phone, and profile data.
Patterns: account details, my account, account info, email on file, phone number, account settings, profile information, my profile, account balance
Challenge message: "For your security, I need to verify your identity before sharing account information. Please complete the verification process to continue."
Required verification: identity_verification
Payment Data (payment_data)
Detects attempts to access payment methods, billing, and financial information.
Patterns: credit card, payment method, billing address, payment history, bank account, card on file, payment info, billing info, invoice, transaction history
Challenge message: "Payment information requires identity verification. Please verify your identity to access payment details."
Required verification: identity_verification, payment_verification
Personal Information (personal_info)
Detects attempts to access other users' personal data — the highest-risk category.
Patterns: personal information, social security, date of birth, home address, personal data, SSN, driver's license, passport number, other user, customer data
Challenge message: "I can't share personal information about other users without proper verification. Please verify your identity and authorization level."
Required verification: admin_verification, identity_verification
Administrative Action (admin_action)
Detects attempts to perform admin-level operations like deleting accounts or changing permissions.
Patterns: delete account, admin access, change permissions, reset password, modify user, admin panel, system settings, grant access, revoke access, bulk export
Challenge message: "This action requires administrator verification. Please verify your identity and admin privileges to proceed."
Required verification: admin_verification, identity_verification
Custom Intent Rules
Built-in categories cover common patterns, but your application likely has domain-specific data that needs protection. Custom intent rules let you define exactly what to detect and how to respond.
Creating Rules via API
curl -X POST https://api.bastio.com/security/intent-rules \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "Medical Records Access",
"category": "medical_records",
"description": "Detects attempts to access patient medical records",
"patterns": [
"medical record",
"patient history",
"lab results",
"diagnosis",
"prescription history",
"health records"
],
"required_verification": ["identity_verification", "hipaa_verification"],
"verification_message": "Access to medical records requires HIPAA-compliant identity verification. Please complete the verification process.",
"severity": "critical",
"priority": 10
}'Rule Properties
| Field | Type | Required | Description |
|---|---|---|---|
name | string | Yes | Human-readable rule name |
category | string | Yes | Category identifier (lowercase, underscores) |
description | string | No | What this rule detects |
patterns | string[] | Yes | Phrases to match in user prompts |
required_verification | string[] | Yes | Verification types needed |
verification_message | string | No | Custom challenge message |
severity | string | No | low, medium, high, critical (default: medium) |
priority | int | No | Higher priority rules take precedence (default: 0) |
proxy_id | string | No | Limit rule to specific proxy |
bypass_with_verified_user | bool | No | Skip challenge if user is already verified |
Updating a Rule
curl -X PUT https://api.bastio.com/security/intent-rules/RULE_ID \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "Medical Records Access (Updated)",
"patterns": [
"medical record",
"patient history",
"lab results",
"diagnosis",
"prescription history",
"health records",
"treatment plan",
"medication list"
],
"severity": "critical"
}'Deleting a Rule
curl -X DELETE https://api.bastio.com/security/intent-rules/RULE_ID \
-H "Authorization: Bearer YOUR_TOKEN"Real-World Example: Customer Support Chatbot
Let's walk through implementing IBAC for an e-commerce company "ShopCo" that has an AI-powered customer support chatbot.
Step 1: Enable Intent Detection
curl -X PUT https://api.bastio.com/security/profiles \
-H "Authorization: Bearer $BASTIO_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"proxy_id": "shopco-support-proxy",
"enable_intent_detection": true,
"security_level": "medium"
}'Step 2: Create Custom Rules
Create domain-specific rules for ShopCo's data:
Rule 1: Refund Requests
curl -X POST https://api.bastio.com/security/intent-rules \
-H "Authorization: Bearer $BASTIO_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "Refund Request",
"category": "refund_request",
"patterns": ["refund", "return order", "money back", "cancel order", "dispute charge"],
"required_verification": ["identity_verification", "email_verification"],
"verification_message": "I can help with your refund! To protect your account, please verify your email address first.",
"severity": "high",
"proxy_id": "shopco-support-proxy"
}'Rule 2: Loyalty Points
curl -X POST https://api.bastio.com/security/intent-rules \
-H "Authorization: Bearer $BASTIO_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "Loyalty Points Access",
"category": "loyalty_points",
"patterns": ["loyalty points", "rewards balance", "redeem points", "points history"],
"required_verification": ["identity_verification"],
"verification_message": "Let me look up your rewards balance. Please verify your identity first.",
"severity": "medium",
"proxy_id": "shopco-support-proxy"
}'Step 3: Handle Challenge Responses in Your App
import openai
client = openai.OpenAI(
base_url="https://api.bastio.com/v1/guard/shopco-support-proxy",
api_key="sk-your-bastio-api-key",
)
def handle_user_message(user_input: str):
response = client.chat.completions.create(
model="gpt-4o",
messages=[
{"role": "system", "content": "You are ShopCo's support assistant."},
{"role": "user", "content": user_input},
],
)
message = response.choices[0].message.content
# Check for IBAC challenge in metadata
# Metadata is available in response headers or streaming events
if hasattr(response, 'metadata') and response.metadata:
if response.metadata.get("bastio_challenge") == "true":
challenge_id = response.metadata["challenge_id"]
required = response.metadata["required_verification"]
# Show verification UI to user
return {
"type": "challenge",
"message": message,
"challenge_id": challenge_id,
"required_verification": required.split(","),
}
return {"type": "response", "message": message}
def retry_after_verification(
user_input: str,
challenge_id: str,
verification_token: str,
):
"""Retry the request after user completes verification."""
response = client.chat.completions.create(
model="gpt-4o",
messages=[
{"role": "system", "content": "You are ShopCo's support assistant."},
{"role": "user", "content": user_input},
],
extra_headers={
"X-Bastio-Challenge-ID": challenge_id,
"X-Bastio-Verification-Token": verification_token,
},
)
return response.choices[0].message.contentimport OpenAI from "openai";
const client = new OpenAI({
baseURL: "https://api.bastio.com/v1/guard/shopco-support-proxy",
apiKey: "sk-your-bastio-api-key",
});
interface ChallengeResponse {
type: "challenge";
message: string;
challengeId: string;
requiredVerification: string[];
}
interface NormalResponse {
type: "response";
message: string;
}
async function handleUserMessage(
userInput: string
): Promise<ChallengeResponse | NormalResponse> {
const response = await client.chat.completions.create({
model: "gpt-4o",
messages: [
{ role: "system", content: "You are ShopCo's support assistant." },
{ role: "user", content: userInput },
],
});
const message = response.choices[0].message.content ?? "";
// For streaming responses, check metadata in the response.completed event
const metadata = (response as any).metadata;
if (metadata?.bastio_challenge === "true") {
return {
type: "challenge",
message,
challengeId: metadata.challenge_id,
requiredVerification: metadata.required_verification.split(","),
};
}
return { type: "response", message };
}
async function retryAfterVerification(
userInput: string,
challengeId: string,
verificationToken: string
): Promise<string> {
const response = await client.chat.completions.create({
model: "gpt-4o",
messages: [
{ role: "system", content: "You are ShopCo's support assistant." },
{ role: "user", content: userInput },
],
// @ts-ignore - custom Bastio headers
headers: {
"X-Bastio-Challenge-ID": challengeId,
"X-Bastio-Verification-Token": verificationToken,
},
});
return response.choices[0].message.content ?? "";
}How This Works in Practice
Let's walk through the complete flow so you can see how the pieces fit together.
1. User sends a message
A customer types something like "Can I get a refund for order #7291?" into your chat UI. Your app sends it to Bastio the same way it would send any chat completion request — no special handling needed:
response = client.chat.completions.create(
model="gpt-4o",
messages=[{"role": "user", "content": "Can I get a refund for order #7291?"}],
)2. Bastio intercepts and detects intent
This happens transparently — your code doesn't change. Behind the scenes, Bastio's pattern matching identifies refund combined with order # and scores the request above the 0.70 threshold you configured in Step 1. Because the refund_request rule requires identity_verification, Bastio triggers a challenge instead of forwarding the request to the LLM.
3. Your app receives what looks like a normal response
This is the key insight: the chat completion returns normally. There's no error, no special status code. The response
message.contentcontains the challenge text you configured — something like "I can help with your refund! To protect your account, please verify your identity first."
The difference is in the metadata. The response includes bastio_challenge: "true" along with a challenge_id and the required_verification method.
4. Check for the challenge flag
Your app inspects the metadata to decide what to do next:
if response.metadata.get("bastio_challenge") == "true":
challenge_id = response.metadata["challenge_id"]
# Show verification UI instead of a normal chat replyIf there's no challenge flag, the response is a normal LLM reply and you display it as usual.
5. Show verification UI
This is your app's responsibility. Bastio doesn't prescribe how you verify the user — pick whatever fits your security model:
- Email link — send a one-time verification link
- SMS OTP — text a 6-digit code
- OAuth re-auth — prompt the user to re-authenticate via your identity provider
- In-app identity check — ask for the last 4 digits of their payment method, a security question, etc.
The point is that the user proves they are who they claim to be before accessing sensitive data.
6. User completes verification
Once the user finishes the verification step, your auth system issues a verification_token. How this token is generated is entirely up to you — Bastio just needs to receive it on the retry.
7. Retry with headers
Send the original request again, this time attaching the challenge ID and verification token as headers:
response = client.chat.completions.create(
model="gpt-4o",
messages=[{"role": "user", "content": "Can I get a refund for order #7291?"}],
extra_headers={
"X-Bastio-Challenge-ID": challenge_id,
"X-Bastio-Verification-Token": verification_token,
},
)8. Bastio validates and allows
What happens next depends on your verification mode:
- Trust mode (default): Bastio checks the
challenge_idis valid, not expired (10-minute TTL), and the token is non-empty. If all checks pass, the request goes through to the LLM. Works out of the box — no backend integration needed. - Webhook mode (recommended for production): Bastio POSTs to your webhook URL with an HMAC-SHA256 signed payload containing the
challenge_idandverification_token. Your server validates the token against your auth system and responds{"verified": true}. If your webhook confirms, the request goes through.
If verification fails (invalid token, expired challenge, webhook rejects), Bastio re-issues a new challenge so the user can try again.
Step 4: Verify and Retry
After the user completes verification, your app retries the original request with the verification credentials.
Supported verification methods — use whichever fits your security model:
- Email link — user clicks a one-time link, your backend generates a token
- SMS OTP — user enters a code sent to their phone
- OAuth re-auth — user re-authenticates through your identity provider
- In-app identity check — user answers a security question or confirms account details
Retry the request with verification headers:
response = client.chat.completions.create(
model="gpt-4o",
messages=[{"role": "user", "content": original_user_message}],
extra_headers={
"X-Bastio-Challenge-ID": challenge_id,
"X-Bastio-Verification-Token": verification_token,
},
)
# This time the request goes through to the LLM
print(response.choices[0].message.content)What happens next:
- Your app receives a
verification_tokenfrom your auth system - The retry includes both
X-Bastio-Challenge-IDandX-Bastio-Verification-Tokenheaders - Bastio validates the token against the original challenge — if valid, the request is forwarded to the LLM
- If the token is invalid or expired, Bastio re-issues the challenge so the user can verify again
Verification Modes
IBAC supports two verification modes, configured per-proxy on the security profile:
| Trust Mode (Default) | Webhook Mode (Recommended) | |
|---|---|---|
| How it works | Bastio checks challenge_id is valid + token is non-empty | Bastio POSTs to your webhook; your server validates the token |
| Setup required | None | Webhook URL + signing secret |
| Security level | Basic — trusts any non-empty token | Full — your backend validates the actual token |
| Best for | Development, low-risk data | Production, sensitive data |
| Fail behavior | N/A | Fail-closed (timeout/error = rejected) |
Configuring Verification Mode
Via Dashboard: Navigate to Security Center > Intent Detection and select the verification mode in the settings panel.
Via API:
curl -X PUT https://api.bastio.com/security/profiles \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"ibac_verification_mode": "webhook",
"ibac_webhook_url": "https://your-api.com/bastio/verify",
"ibac_webhook_secret": "whsec_your_signing_secret_here"
}'Webhook Payload Format
When a user retries with verification headers, Bastio sends a POST request to your webhook URL:
{
"challenge_id": "ch_abc123",
"verification_token": "tok_user_provided_token",
"proxy_id": "your-proxy-id",
"timestamp": 1711036800
}Headers included:
X-Bastio-Signature: sha256=<hmac_hex>— HMAC-SHA256 of the request body using your signing secretX-Bastio-Timestamp: <unix_seconds>— Request timestampContent-Type: application/json
Expected response:
{"verified": true}Or to reject:
{"verified": false, "reason": "Token expired"}Verifying the Webhook Signature
import hmac
import hashlib
import json
from flask import Flask, request, jsonify
WEBHOOK_SECRET = "whsec_your_signing_secret_here"
app = Flask(__name__)
@app.route("/bastio/verify", methods=["POST"])
def verify_challenge():
# 1. Verify HMAC signature
signature = request.headers.get("X-Bastio-Signature", "")
expected = "sha256=" + hmac.new(
WEBHOOK_SECRET.encode(),
request.data,
hashlib.sha256,
).hexdigest()
if not hmac.compare_digest(signature, expected):
return jsonify({"verified": False, "reason": "Invalid signature"}), 401
# 2. Parse payload
payload = request.json
challenge_id = payload["challenge_id"]
token = payload["verification_token"]
# 3. Validate token against your auth system
is_valid = your_auth_system.validate_token(token, challenge_id)
return jsonify({"verified": is_valid})import crypto from "crypto";
import express from "express";
const WEBHOOK_SECRET = "whsec_your_signing_secret_here";
const app = express();
app.use(express.json());
app.post("/bastio/verify", (req, res) => {
// 1. Verify HMAC signature
const signature = req.headers["x-bastio-signature"] as string;
const body = JSON.stringify(req.body);
const expected =
"sha256=" +
crypto.createHmac("sha256", WEBHOOK_SECRET).update(body).digest("hex");
if (!crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expected))) {
return res.status(401).json({ verified: false, reason: "Invalid signature" });
}
// 2. Parse payload
const { challenge_id, verification_token } = req.body;
// 3. Validate token against your auth system
const isValid = yourAuthSystem.validateToken(verification_token, challenge_id);
res.json({ verified: isValid });
});Challenge TTL
Challenges expire after 10 minutes. If a user takes longer to verify, they'll receive a new challenge when they retry. This prevents replay attacks with stale challenge IDs.
Testing Intent Detection
Via API
Test a prompt against your rules without making a real request:
curl -X POST https://api.bastio.com/security/intent-rules/test \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"prompt": "What is the shipping address for order #34004?"
}'Example response:
{
"detected": true,
"category": "order_lookup",
"confidence": 0.90,
"matched_patterns": ["shipping address", "order #"],
"required_verification": ["identity_verification", "email_verification"],
"challenge_message": "I'd be happy to help with your order, but I need to verify your identity first. Please log in or verify your email to access order information."
}Testing a safe prompt:
curl -X POST https://api.bastio.com/security/intent-rules/test \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"prompt": "What are your store hours?"
}'{
"detected": false,
"confidence": 0.0,
"matched_patterns": [],
"required_verification": []
}Analytics & Monitoring
Event Stats API
Track IBAC activity over time:
curl -X GET "https://api.bastio.com/security/intent-events/stats?days=7" \
-H "Authorization: Bearer YOUR_TOKEN"Example response:
{
"total_events": 1247,
"challenges_issued": 89,
"by_category": [
{ "category": "order_lookup", "count": 52 },
{ "category": "account_info", "count": 21 },
{ "category": "payment_data", "count": 9 },
{ "category": "personal_info", "count": 5 },
{ "category": "admin_action", "count": 2 }
],
"recent_events": [
{
"timestamp": "2026-03-19T14:32:00Z",
"category": "order_lookup",
"confidence": 0.90,
"challenged": true
}
]
}Dashboard Analytics
Navigate to Security Center > Intent Detection to view:
- Total detections over the selected time period
- Challenge rate — percentage of detected intents that triggered challenges
- Category breakdown — which categories are triggered most frequently
- Recent events — timeline of recent intent detections with details
Use these metrics to tune your rules — a high false-positive rate on a category suggests patterns that are too broad.
API Reference
| Method | Path | Description |
|---|---|---|
GET | /security/intent-rules | List all intent rules |
POST | /security/intent-rules | Create a new intent rule |
PUT | /security/intent-rules/:id | Update an existing rule |
DELETE | /security/intent-rules/:id | Delete a rule |
GET | /security/intent-rules/categories | List available categories |
POST | /security/intent-rules/test | Test a prompt for intent detection |
GET | /security/intent-events/stats | Get event statistics |
List Rules
GET /security/intent-rules
Authorization: Bearer YOUR_TOKENReturns all intent rules for your account, including both built-in and custom rules.
Create Rule
POST /security/intent-rules
Authorization: Bearer YOUR_TOKEN
Content-Type: application/json
{
"name": "Rule Name",
"category": "category_id",
"patterns": ["pattern1", "pattern2"],
"required_verification": ["identity_verification"],
"severity": "medium"
}Update Rule
PUT /security/intent-rules/:id
Authorization: Bearer YOUR_TOKEN
Content-Type: application/json
{
"name": "Updated Name",
"patterns": ["pattern1", "pattern2", "pattern3"]
}Delete Rule
DELETE /security/intent-rules/:id
Authorization: Bearer YOUR_TOKENList Categories
GET /security/intent-rules/categories
Authorization: Bearer YOUR_TOKENReturns all available intent categories with their metadata, including built-in and custom categories.
Test Detection
POST /security/intent-rules/test
Authorization: Bearer YOUR_TOKEN
Content-Type: application/json
{
"prompt": "Text to test for intent detection"
}Event Stats
GET /security/intent-events/stats?days=7
Authorization: Bearer YOUR_TOKENQuery parameter days controls the lookback period (default: 7, max: 90).
Best Practices
-
Start with built-in categories — They cover the most common data-access patterns out of the box. Monitor for a week before adding custom rules.
-
Add custom rules for domain-specific data — If your application handles medical records, legal documents, financial portfolios, or other specialized data, create rules with patterns specific to your domain.
-
Set appropriate verification levels — Not all data is equally sensitive. Use
identity_verificationfor basic data, addadmin_verificationfor truly sensitive operations. -
Use
bypass_with_verified_user— For returning, authenticated users, set this flag to skip challenges for lower-sensitivity categories. This reduces friction for legitimate users. -
Monitor analytics for false positives — Review the Intent Detection analytics dashboard weekly. If a category triggers too often on innocent requests, narrow its patterns.
-
Use proxy-specific rules — Different proxies serve different use cases. A customer support proxy needs different rules than an internal analytics proxy.
-
Test before deploying — Use the
/security/intent-rules/testendpoint to validate your patterns against real user prompts before activating rules.
Next Steps
- Security Features Overview — Learn about all security layers
- PII Protection — Detect and mask sensitive data in AI responses
- Jailbreak Prevention — Block prompt injection attacks
- API Authentication — Set up API keys and authentication