Node.js एजेंटों को मास्टर कुंजी क्यों नहीं देनी चाहिए और 60-सेकंड का अस्थायी टोकन कैसे लागू करें
LangChain या LlamaIndex के साथ स्वायत्त (autonomous) एजेंट बनाते समय, अंततः एक ऐसा क्षण आता है जब आप उन्हें DB और बाहरी API से जोड़ते हैं। आमतौर पर दुर्घटना यहीं होती है। केवल एक प्रॉम्प्ट इंजेक्शन से ही .env में लिखी OpenAI मास्टर कुंजी या DB व्यवस्थापक (Admin) पासवर्ड लीक हो जाता है। आप चाहे कितना भी सख्त प्रॉम्प्ट गार्डरेल क्यों न बनाएं, इसका कोई फायदा नहीं होता। जब तक LLM का तर्क (reasoning) क्षेत्र और निष्पादन (execution) क्षेत्र एक साथ जुड़े रहेंगे, तब तक सिर्फ एक वाक्य सुरक्षा को आसानी से तोड़ सकता है।
गूगल की 2025 SAIF (Secure AI Framework) रिपोर्ट के अनुसार, एजेंटों को अपनाने वाली 88% कंपनियों को प्रॉम्प्ट इंजेक्शन के प्रयासों का सामना करना पड़ा। पारंपरिक टेक्स्ट पैटर्न पहचान तकनीकों की ब्लॉक दर केवल 23% थी। इसलिए, स्वयं एजेंट प्रक्रिया पर भरोसा न करना ही बेहतर है। एजेंट को कोई अधिकार न देकर, मिडलवेयर लेयर में 60 सेकंड के लिए सक्रिय रहने वाले अस्थायी टोकन को इंजेक्ट करने वाली संरचना में स्विच करना चाहिए।
Express में 60-सेकंड का Dynamic Vault टोकन इंजेक्शन लागू करना
HashiCorp Vault के AppRole प्रमाणीकरण (Authentication) का उपयोग करके, आप ठीक उसी क्षण 60-सेकंड का टोकन जारी कर सकते हैं जब कोई टूल कॉल किया जाता है। जब एजेंट किसी बाहरी API को हिट करता है, तो एक इंटरसेप्टर बीच में हस्तक्षेप करता है और हेडर में Short-lived टोकन डाल देता है।
`typescript
import { Request, Response, NextFunction } from 'express';
import vault from 'node-vault';
interface VaultAppRoleAuth {
roleId: string;
secretId: string;
}
export class EphemeralTokenInjector {
private vaultClient: any;
private roleId: string;
private secretId: string;
constructor(endpoint: string, auth: VaultAppRoleAuth) {
this.vaultClient = vault({ endpoint });
this.roleId = auth.roleId;
this.secretId = auth.secretId;
}
private async getAppRoleToken(): Promise {
const result = await this.vaultClient.approleLogin({
role_id: this.roleId,
secret_id: this.secretId,
});
return result.auth.client_token;
}
public createToolInterceptor(targetServiceRole: string) {
return async (req: Request, res: Response, next: NextFunction) => {
let clientToken: string | null = null;
try {
clientToken = await this.getAppRoleToken();
const dynamicSecret = await this.vaultClient.write(
`sys/leases/generate/${targetServiceRole}`,
{ ttl: '60s' }
);
req.headers['authorization'] = `Bearer ${dynamicSecret.data.token}`;
req.body.ephemeralContext = {
leaseId: dynamicSecret.lease_id,
expiresAt: Date.now() + 60000,
};
next();
} catch (error) {
res.status(500).json({ error: 'Failed to inject ephemeral dynamic secret' });
} finally {
clientToken = null;
}
};
}
}
`
बात सिर्फ कोड लिखने तक ही सीमित नहीं है। Node.js V8 इंजन की हीप मेमोरी में गार्बेज कलेक्टर अपनी मर्जी से चलता है, इसलिए वेरिएबल्स को खाली करने के बाद भी टोकन स्ट्रिंग कुछ समय के लिए हीप में बनी रहती है। यदि हीप डंप हमला होता है, तो लीक होने का जोखिम रहता है।
- API प्रतिक्रिया प्राप्त होते ही संवेदनशील मानों को
Buffer ऑब्जेक्ट में रखें।
- निष्पादन पूरा होने के बाद, बाइट्स को ज़बरदस्ती 0 पर रीसेट करने के लिए
Buffer.fill(0) चलाएं।
- इसे GC (Garbage Collector) द्वारा हटाने के लिए संदर्भ चर (reference variable) में
null असाइन करें।
इन तीन चरणों का पालन करने से मेमोरी प्रोफाइलिंग के माध्यम से टोकन लीक होने की संभावना काफी कम हो जाती है।
| क्रेडेंशियल प्रबंधन विधि |
औसत जीवन काल (TTL) |
चोरी होने पर क्षति का दायरा |
ऑडिट ट्रेल |
| हार्डकोडेड Master API Key |
असीमित |
संपूर्ण इंफ्रास्ट्रक्चर विशेषाधिकारों का अधिग्रहण |
एकल कुंजी साझाकरण के कारण इकाई की पहचान असंभव |
| पर्यावरण चर (.env) Static Secret |
प्रक्रिया जीवनकाल |
संबंधित Node प्रक्रिया विशेषाधिकारों का पूर्ण दुरुपयोग |
उपयोगकर्ता-विशिष्ट वर्कफ़्लो ट्रैकिंग असंभव |
| Vault AppRole Ephemeral Token |
60 सेकंड |
संबंधित Tool के 1 बार निष्पादन तक सीमित |
Vault Audit Log द्वारा प्रति सेकंड ट्रैकिंग |
Auth0 OIDC Claim को PostgreSQL RLS नीति में स्थानांतरित करना
यदि एजेंट प्रॉम्प्ट जेलब्रेक के कारण "सभी सदस्यों की जानकारी खोजें" जैसा आदेश निष्पादित करता है, तो भी यदि DB इंजन स्तर पर इसे अस्वीकार कर दिया जाता है, तो बात वहीं खत्म हो जाती है। Multi-tenant SaaS वातावरण में, आपको PostgreSQL की Row Level Security (RLS) सेट करनी होगी।
Auth0 द्वारा जारी JWT से userId और tenantId प्राप्त करें और इसे LangChain के RunnableConfig सत्र संदर्भ (session context) में पास करें। इस संदर्भ को Prisma लेनदेन के भीतर एक सत्र चर के रूप में इंजेक्ट किया जाता है।
`typescript
import { RunnableConfig } from '@langchain/core/runnables';
import { PrismaClient } from '@prisma/client';
export interface AgentUserClaims {
userId: string;
tenantId: string;
}
export async function executeAgentToolWithRLS(
prisma: PrismaClient,
config: RunnableConfig,
dbOperation: (tx: any) => Promise
): Promise {
const claims = config.configurable?.userClaims as AgentUserClaims;
if (!claims || !claims.tenantId || !claims.userId) {
throw new Error('Unauthorized: Missing OIDC Claims in Agent Execution Context');
}
return await prisma.transaction(async (tx) => {
await tx.executeRawSELECT set_config('app.current_tenant_id', ${claims.tenantId}, true);
await tx.$executeRawSELECT set_config('app.current_user_id', ${claims.userId}, true);
return await dbOperation(tx);
});
}
`
चर को केवल वर्तमान लेनदेन दायरा (SET LOCAL) पर लागू करने के लिए set_config के अंतिम तर्क के रूप में true पास किया जाना चाहिए। यह DB कनेक्शन पूल वातावरण में पिछले उपयोगकर्ता के विशेषाधिकारों को अगले अनुरोध में लीक होने से रोकने के लिए एक महत्वपूर्ण सेटिंग है।
अब SQL फ़ाइल में इस सत्र चर को पढ़ने के लिए एक नीति बनाएं।
`sql}
ALTER TABLE tenant_documents ENABLE ROW LEVEL SECURITY;
ALTER TABLE tenant_documents FORCE ROW LEVEL SECURITY;
CREATE POLICY agent_tenant_isolation_policy ON tenant_documents
FOR ALL
TO authenticated_agent_role
USING (
tenant_id = current_setting('app.current_tenant_id', true)::uuid
)
WITH CHECK (
tenant_id = current_setting('app.current_tenant_id', true)::uuid
);
`
इस संयोजन के साथ, भले ही एजेंट कोई भी असामान्य क्वेरी उत्पन्न करे, वह अपने किरायेदार (tenant) क्षेत्र के बाहर का डेटा देख भी नहीं सकता है। भले ही कोई डेटा लीक हो जाए, फिर भी डेटा पुनर्प्राप्ति समय (MTTR) को दिनों से घटाकर केवल कुछ मिनटों में लाया जा सकता है।
Promptfoo और PyRIT का उपयोग करके CI/CD सत्यापन पाइपलाइन
हर बार जब कोई डेवलपर एजेंट टूल कोड बदलता है, तो आप मैन्युअल रूप से यह परीक्षण नहीं कर सकते कि विशेषाधिकार पृथक्करण ठीक से काम कर रहा है या नहीं। खुले स्रोत टूल Promptfoo और Microsoft के PyRIT को GitHub Actions से बांधकर PR स्तर पर सत्यापित करें।
`yaml
name: Agent Red Teaming Security Gate
on:
pull_request:
paths:
- 'src/agents/'
- 'src/tools/'
- 'prompts/**'
jobs:
security-eval:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: 20
cache: 'npm'
- name: Install Dependencies
run: npm ci
- name: Run Promptfoo Scan
uses: promptfoo/promptfoo-action@v1
with:
config: 'promptfooconfig.yaml'
openai-api-key: ${{ secrets.OPENAI_API_KEY }}
github-token: ${{ secrets.GITHUB_TOKEN }}
- name: Run PyRIT Multi-Turn Test
env:
AGENT_ENDPOINT: 'http://localhost:3000/api/agent'
run: |
python -m pip install pyrit
python scripts/run_pyrit_eval.py --endpoint $AGENT_ENDPOINT --pass-threshold 0.98
`
सर्वर रनटाइम पर, एक सर्किट ब्रेकर मिडलवेयर संलग्न करें जो खतरे के पैटर्न का पता चलने पर तुरंत निष्पादन को रोक देता है।
`typescript
import { Request, Response, NextFunction } from 'express';
export class AgentCircuitBreaker {
private failureCount: number = 0;
private readonly threshold: number = 3;
private state: 'CLOSED' | 'OPEN' | 'HALF_OPEN' = 'CLOSED';
private forbiddenSignatures: RegExp[] = [
/ignore\s+all\s+previous\s+instructions/i,
/system::override_privileges/i,
/grant\s+role\s+admin/i,
/concat\s*(\s*select/i
];
public middleware() {
return (req: Request, res: Response, next: NextFunction) => {
if (this.state === 'OPEN') {
return res.status(503).json({
error: 'CircuitBreaker:Open - Agent execution halted'
});
}
const promptInput = JSON.stringify(req.body);
const isPatternViolated = this.forbiddenSignatures.some(sig => sig.test(promptInput));
if (isPatternViolated) {
this.failureCount++;
this.dispatchSecurityAlert(req.body);
if (this.failureCount >= this.threshold) {
this.state = 'OPEN';
}
return res.status(403).json({
error: 'Security Policy Violation: Malicious prompt pattern'
});
}
next();
};
}
private dispatchSecurityAlert(payload: any): void {
const webhookUrl = process.env.SECURITY_WEBHOOK_URL;
if (!webhookUrl) return;
fetch(webhookUrl, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
event: 'AGENT_PRIVILEGE_ESCALATION_DETECTED',
timestamp: new Date().toISOString(),
payload
})
}).catch(() => {});
}
}
`
सुरक्षा परीक्षण पाइपलाइन बनाने के चरण सरल हैं:
- प्रोजेक्ट रूट में
promptfooconfig.yaml रखें और jailbreak व excessive-agency निरीक्षण आइटम परिभाषित करें।
- Promptfoo और PyRIT परिदृश्यों को चलाने के लिए GitHub Actions पर एक PR डिटेक्शन वर्कफ़्लो सेट करें।
- Express एंडपॉइंट के सामने एक
AgentCircuitBreaker मिडलवेयर तैनात करें ताकि लगातार 3 बार इंजेक्शन पैटर्न का पता चलने पर एजेंट निष्पादन को रोक दिया जाए।
इस कॉन्फ़िगरेशन के साथ, आप हर हफ्ते मैन्युअल प्रॉम्प्ट समीक्षा पर खर्च किए जाने वाले 5 घंटे से अधिक समय की बचत कर सकते हैं।
| मूल्यांकन मीट्रिक |
मैन्युअल सत्यापन विधि |
Zero-Trust स्वचालन का परिचय |
| ऑडिट तैयारी का समय |
प्रति सप्ताह 5-8 घंटे |
प्रति सप्ताह 1 घंटे से कम |
| अनुमति लीक पर MTTR |
कई दिन (सभी लॉग/DB का पूर्ण निरीक्षण) |
कुछ मिनट (RLS दायरे तक सीमित) |
| प्रॉम्प्ट इंजेक्शन रोकथाम दर |
लगभग 23% |
99.9% |
| कुंजी लीक पर बिलिंग जोखिम |
असीमित क्लाउड बिलिंग |
60-सेकंड TTL के साथ बिलिंग ब्लॉक |
एजेंट सुरक्षा की कुंजी यह प्रार्थना करना नहीं है कि मॉडल निर्देशों का पालन करे। इसका उद्देश्य इंफ्रास्ट्रक्चर स्तर पर हथकड़ी लगाना है ताकि अगर मॉडल गलत बातें करता है या हमले का शिकार हो जाता है, तब भी वह सिस्टम को नुकसान न पहुँचा सके।