

Disclaimer: This guide is for educational and defensive purposes only. The context of the Atlassian Rovo vulnerability highlights how improper access control in AI-driven enterprise search can lead to data leakage. This tutorial demonstrates how to build a "Security Gateway" pattern to prevent such vulnerabilities when integrating AI with enterprise data.
Before implementing secure AI data retrieval patterns, ensure you have the following:
python-dotenv (Python) or dotenv (JS) for environment variable management.Install the necessary dependencies for both ecosystems.
# Create a virtual environment
python -m venv venv
source venv/bin/activate # On Windows: venv\Scripts\activate
# Install dependencies
pip install openai python-dotenv pydantic fastapi uvicorn
# Initialize project
npm init -y
# Install dependencies
npm install openai dotenv express zod
npm install --save-dev typescript ts-node @types/node @types/express
The core issue in the Atlassian Rovo vulnerability was Broken Object Level Authorization (BOLA)—the AI had access to data the user shouldn't see. We will implement a "Security Middleware Layer" that validates user permissions before the AI processes the data.
This implementation uses a "Gatekeeper" pattern to ensure the AI only sees data the user is explicitly authorized to view.
import os
from typing import List, Dict
from fastapi import FastAPI, HTTPException, Depends
from pydantic import BaseModel
from dotenv import load_dotenv
from openai import OpenAI
load_dotenv()
app = FastAPI()
client = OpenAI(api_key=os.getenv("OPENAI_API_KEY"))
# --- MOCK DATABASE ---
# In a real scenario, this would be Jira, Confluence, or a SQL DB
MOCK_ENTERPRISE_DATA = [
{"id": "DOC_001", "content": "Project X roadmap: Secret launch date Dec 2025", "owner_group": "executives"},
{"id": "DOC_002", "content": "Standard office lunch menu: Pizza on Fridays", "owner_group": "all"},
{"id": "DOC_003", "content": "Server credentials: admin/password123", "owner_group": "devops"},
]
# --- MODELS ---
class QueryRequest(BaseModel):
user_id: str
user_groups: List[str]
query: str
# --- SECURITY GATEKEEPER ---
def filter_data_by_permissions(user_groups: List[str], data: List[Dict]) -> List[Dict]:
"""
CRITICAL: This function prevents the 'Rovo Vulnerability' pattern.
It ensures the AI context ONLY contains data the user is authorized to see.
"""
authorized_data = []
for item in data:
# Allow if user is in 'all' group OR user belongs to the document's specific group
if item["owner_group"] == "all" or item["owner_group"] in user_groups:
authorized_data.append(item)
return authorized_data
@app.post("/ask-ai")
async def secure_ai_query(request: QueryRequest):
try:
# 1. Filter sensitive data BEFORE sending to LLM
# This prevents the AI from accidentally leaking 'DOC_001' to a 'guest' user
context_data = filter_data_by_permissions(request.user_groups, MOCK_ENTERPRISE_DATA)
context_string = "\n".join([d["content"] for d in context_data])
# 2. Construct the Prompt with Context
prompt = f"""
You are a secure enterprise assistant. Use the following context to answer the user.
If the answer is not in the context, say you don't know.
CONTEXT:
{context_string}
USER QUERY: {request.query}
"""
# 3. Call LLM
response = client.chat.completions.create(
model="gpt-4-turbo-preview",
messages=[{"role": "system", "content": "You are a helpful assistant."},
{"role": "user", "content": prompt}]
)
return {"answer": response.choices[0].message.content}
except Exception as e:
print(f"Error: {e}")
raise HTTPException(status_code=500, detail="Internal Server Error")
if __name__ == "__main__":
import uvicorn
uvicorn.run(app, host="0.0.0.0", port=8000)
Using Zod for strict schema validation to prevent prompt injection and data leakage.
import express, { Request, Response } from 'express';
import dotenv from 'dotenv';
import { OpenAI } from 'openai';
import { z } from 'zod';
dotenv.config();
const app = express();
app.use(express.json());
const openai = new OpenAI({ apiKey: process.env.OPENAI_API_KEY });
// --- SCHEMAS ---
const QuerySchema = z.object({
userId: z.string(),
userGroups: z.array(z.string()),
query: z.string().min(3),
});
// --- MOCK DATA ---
const MOCK_DATA = [
{ id: '1', text: 'Salary info: John Doe makes $150k', group: 'hr' },
{ id: '2', text: 'Public holiday: New Year Day', group: 'all' }
];
app.post('/api/ai-query', async (req: Request, res: Response) => {
try {
// 1. Validate Input Schema
const validatedQuery = QuerySchema.parse(req.body);
// 2. IMPLEMENTATION OF THE SECURITY PATTERN:
// Filter data based on user permissions BEFORE it hits the LLM context
const safeContext = MOCK_DATA
filter(item => item.group === 'all' || validatedQuery.userGroups.includes(item.group))
map(item => item.text)
join("\n");
// 3. Execute AI Call
const completion = await openai.chat.completions.create({
model: "gpt-4",
messages: [
{ role: "system", content: `Use this context: ${safeContext}` },
{ role: "user", content: validatedQuery.query }
],
});
res.json({ response: completion.choices[0].message.content });
} catch (error) {
if (error instanceof z.ZodError) {
return res.status(400).json({ error: "Invalid request format", details: error.errors });
}
res.status(500).json({ error: "Internal Server Error" });
}
});
app.listen(3000, () => console.log('Secure AI Gateway running on port 3000'));
Never hardcode credentials. Use a .env file.
.env File Structure:
# LLM Provider
OPENAI_API_KEY=sk-your-actual-key-here
# Database Connection (Use Managed Identities in Production)
DATABASE_URL=postgresql://user:password@localhost:5432/enterprise_db
# Security Settings
MAX_CONTEXT_TOKENS=2048
ALLOWED_DOMAINS=myapp.com,internal.enterprise.com
When building Retrieval-Augmented Generation (RAG) systems, always follow this sequence:
❌ Anti-Pattern (Vulnerable to Rovo-style exploits):
User Query $\rightarrow$ Retrieve all matching docs from DB $\rightarrow$ Send all to LLM $\rightarrow$ LLM answers. (This allows users to "ask" for data they shouldn't see).
✅ Correct Pattern (Secure):
User Query $\rightarrow$ Retrieve ONLY docs where doc.group IN user.groups $\rightarrow$ Send to LLM.
| Error | Cause | Fix |
|---|---|---|
401 Unauthorized | Missing/Invalid API Key | Check .env and ensure OPENAI_API_KEY is correct. |
422 Unprocessable Entity | Schema Mismatch | Ensure your JSON body matches the QueryRequest (Python) or QuerySchema (TS) exactly. |
LLM returns wrong info | Context Overflow | The context sent to the AI is too large. Implement a tokenizer to truncate text. |
Data Leakage detected | Logic Error | Ensure the filtering logic happens locally in your backend, not via the LLM's instructions. |
Before deploying your AI integration to a production enterprise environment, verify these points:
Source: Security Week AI
Follow ICARAX for more AI insights and tutorials.
