

The recent statistic that 54% of enterprises have experienced an AI agent security incident highlights a critical architectural failure: Credential Over-Privilege. Most developers implement agents by passing a single "Superuser" API key to the LLM, essentially giving an autonomous agent the keys to the entire kingdom.
This guide provides a production-ready implementation pattern for Scoped Agent Identities, ensuring your agents only have the permissions necessary for their specific task.
Before implementing secure agent patterns, ensure you have the following:
python-dotenv (Python) or dotenv (Node.js).Install the necessary dependencies for both environments.
pip install openai python-dotenv pydantic
npm install openai dotenv typescript ts-node @types/node
# Initialize TS if needed
npx tsc --init
The goal is to move away from "Global Keys" and toward "Scoped Tool Execution." We will implement a pattern where the agent receives a Session Token rather than a Master Key.
import os
from typing import Dict, Any
from openai import OpenAI
from dotenv import load_dotenv
from pydantic import BaseModel, Field
load_dotenv()
# --- SECURITY LAYER: The Scoped Credential Manager ---
class AgentSecurityContext:
"""
Simulates a secure vault that exchanges a generic agent session
for a highly scoped, short-lived credential.
"""
def __init__(self):
# In production, this would call HashiCorp Vault or AWS Secrets Manager
self._vault = {
"user_123_read_only": "sk-scoped-read-only-token-abc",
"user_123_write_limited": "sk-scoped-write-limited-token-xyz"
}
def get_scoped_token(self, user_id: str, permission_level: str) -> str:
key = f"{user_id}_{permission_level}"
token = self._vault.get(key)
if not token:
raise PermissionError(f"No scoped token found for {key}")
return token
# --- TOOL DEFINITION ---
class DatabaseTools:
"""
Tools that enforce permission checks before execution.
"""
def __init__(self, scoped_token: str):
self.token = scoped_token
def query_customer_data(self, customer_id: str) -> str:
# REAL WORLD: The token would be passed in the header to the DB/API
if "read-only" not in self.token:
return "ERROR: Unauthorized. This agent lacks write/access permissions."
return f"Data for {customer_id}: Status=Active, Balance=$500.00"
# --- AGENT LOGIC ---
class SecureAgent:
def __init__(self, user_id: str):
self.user_id = user_id
self.security_context = AgentSecurityContext()
# Get the specific token for this specific user session
self.scoped_token = self.security_context.get_scoped_token(user_id, "read_only")
self.tools = DatabaseTools(self.scoped_token)
self.client = OpenAI(api_key=os.getenv("OPENAI_API_KEY"))
def run(self, user_prompt: str):
try:
# Step 1: LLM decides which tool to use
# In a real app, use OpenAI Function Calling/Tools API
print(f"[*] Agent processing request: {user_prompt}")
# Simulating tool invocation based on LLM decision
if "customer" in user_prompt.lower():
result = self.tools.query_customer_data("CUST-001")
return f"Agent Response: {result}"
return "I'm sorry, I can't help with that."
except Exception as e:
return f"Security/Runtime Error: {str(e)}"
# --- EXECUTION ---
if __name__ == "__main__":
# Scenario: Agent acting on behalf of User 123
agent = SecureAgent(user_id="user_123")
response = agent.run("Please fetch the data for customer CUST-001")
print(response)
import 'dotenv/config';
import OpenAI from 'openai';
// --- TYPES ---
type Permission = 'READ_ONLY' | 'WRITE_ACCESS';
interface ScopedSession {
sessionId: string;
permissions: Permission[];
token: string;
}
// --- SECURITY LAYER ---
class SecurityGatekeeper {
// In production, this verifies a JWT or interacts with an IAM service
static async getSessionForUser(userId: string): Promise<ScopedSession> {
return {
sessionId: `sess_${Math.random().toString(36).substr(2, 9)}`,
permissions: ['READ_ONLY'], // Hardcoded for demo
token: process.env.SCOPED_TEST_TOKEN || 'default_token'
};
}
}
// --- TOOLSET ---
class SecureDatabaseTool {
constructor(private session: ScopedSession) {}
async fetchCustomer(customerId: string): Promise<string> {
// CRITICAL: Validate permissions before executing sensitive logic
if (!this.session.permissions.includes('READ_ONLY')) {
throw new Error("Access Denied: Insufficient Scoped Permissions");
}
console.log(`[LOG] Executing DB Query with Token: ${this.session.token}`);
return `Data for ${customerId}: Verified`;
}
}
// --- AGENT ENGINE ---
class AIAgent {
private tools: SecureDatabaseTool;
constructor(private userId: string) {
// Initialize session and tools in constructor
// Note: In real apps, this would be an async init method
this.tools = new SecureDatabaseTool(SecurityGatekeeper.getSessionForUser(userId));
}
async execute(prompt: string) {
try {
console.log(`[SYSTEM] Agent received: "${prompt}"`);
// Simulate LLM determining tool use
if (prompt.includes("customer")) {
const data = await this.tools.fetchCustomer("ID_99");
return `Agent Output: ${data}`;
}
return "Unknown command.";
} catch (error) {
return `Security Violation: ${error instanceof Error? error.message : 'Unknown error'}`;
}
}
}
// --- MAIN EXECUTION ---
async function main() {
const agent = new AIAgent("user_abc_123");
const response = await agent.execute("Get customer info");
console.log(response);
}
main();
Never hardcode master keys. Use a tiered environment variable approach.
.env File Structure:
# 1. LLM Provider Keys (System Level)
OPENAI_API_KEY=sk-your-actual-llm-key
# 2. Master Service Keys (Internal Use Only - Never expose to Agent)
MASTER_DB_URL=mongodb://admin:password@localhost:27017/
# 3. Scoped Test Tokens (For local development/testing)
SCOPED_TEST_TOKEN=sk-scoped-read-only-token-abc
Instead of giving the agent a delete_user() function, give it a request_deletion(user_id) function. The function returns a "Pending" status, and a human must approve the action in a separate workflow.
| Error | Cause | Fix |
|---|---|---|
PermissionError: No scoped token found | The mapping between User ID and Permission level failed. | Check the AgentSecurityContext logic and ensure the user exists in your IdP. |
OpenAI Error: Invalid API Key | The LLM key is missing or incorrectly loaded from .env. | Ensure load_dotenv() is called at the very top of your entry file. |
Access Denied: Insufficient Permissions | The agent tried to call a "Write" tool with a "Read" token. | This is expected behavior. Log the event to detect "Prompt Injection" attempts. |
ADMIN_API_KEY is passed into the LLM context or tool parameters.agent_id, user_id, and tool_called.customer_id) match expected formats to prevent injection.Source: VentureBeat AI
Follow ICARAX for more AI insights and tutorials.
