Alasan Mengapa Anda Tidak Boleh Memberikan Master Key ke Agen Node.js dan Cara Mengimplementasikan Token Sementara 60 Detik
Saat membangun agen otonom menggunakan LangChain atau LlamaIndex, Anda akhirnya akan sampai pada titik di mana Anda menghubungkan database dan API eksternal. Di sinilah kecelakaan biasanya terjadi. Hanya dengan satu serangan prompt injection, master key OpenAI atau kata sandi admin DB yang tertulis di dalam file .env bisa bocor. Betapa pun ketatnya Anda merancang prompt guardrail, hal itu tidak akan berguna. Selama area penalaran LLM dan area eksekusinya terikat bersama, keamanan dapat dengan mudah ditembus hanya dengan satu kalimat.
Menurut laporan SAIF (Secure AI Framework) Google tahun 2025, 88% perusahaan yang mengadopsi agen AI mengalami percobaan serangan prompt injection. Tingkat pemblokiran dari teknik deteksi pola teks konvensional hanya mencapai 23%. Lebih tenang rasanya jika Anda tidak memercayai proses agen itu sendiri sejak awal. Jangan berikan izin sama sekali kepada agen, dan ubah strukturnya dengan menyuntikkan token sementara yang hanya aktif selama 60 detik di lapisan middleware.
Mengimplementasikan Injeksi Token Dynamic Vault 60 Detik di Express
Menggunakan otentikasi AppRole dari HashiCorp Vault memungkinkan Anda menerbitkan token berdurasi 60 detik tepat pada saat tool dipanggil. Ini adalah metode di mana interceptor mengintervensi di tengah jalan saat agen memanggil API eksternal dan menyisipkan token short-lived ke dalam header.
`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;
}
};
}
}
`
Tugas belum selesai hanya dengan menulis kode. Karena heap memory pada mesin V8 Node.js dijalankan secara acak oleh garbage collector, string token akan tetap berada di dalam heap untuk beberapa saat meskipun Anda telah mengosongkan variabelnya. Jika terkena serangan heap dump, ada risiko kebocoran.
- Segera simpan nilai sensitif ke dalam objek
Buffer begitu menerima respons API.
- Setelah eksekusi selesai, jalankan
Buffer.fill(0) untuk memaksa pembersihan byte menjadi 0.
- Tetapkan
null pada variabel referensi agar diproses oleh GC.
Hanya dengan memperhatikan tiga langkah ini, kemungkinan kebocoran token melalui profiling memori akan berkurang drastis.
| Metode Pengelolaan Kredensial |
Masa Pakai Rata-rata (TTL) |
Cakupan Kerusakan Saat Kebocoran |
Jejak Audit |
| Master API Key yang Dihardcode |
Tanpa batas |
Pengambilalihan seluruh izin infrastruktur |
Tidak dapat mengidentifikasi subjek karena pemakaian key secara bersama |
| Static Secret Variable Lingkungan (.env) |
Masa pakai proses |
Penyalahgunaan seluruh izin proses Node terkait |
Tidak dapat melacak workflow per pengguna |
| Vault AppRole Ephemeral Token |
60 detik |
Terbatas pada 1 kali eksekusi Tool terkait |
Pelacakan hingga tingkat detik dengan Vault Audit Log |
Meneruskan Claim OIDC Auth0 ke Kebijakan PostgreSQL RLS
Bahkan jika agen menjalankan perintah "tampilkan semua informasi anggota" akibat jailbreak prompt, cukup cegah hal tersebut di tingkat engine database. Jika Anda berada di lingkungan SaaS multi-tenant, Anda harus mengonfigurasi Row Level Security (RLS) pada PostgreSQL.
Ambil userId dan tenantId dari JWT yang diterbitkan oleh Auth0 dan teruskan ke konteks sesi RunnableConfig milik LangChain. Injeksikan konteks ini sebagai variabel sesi di dalam transaksi 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);
});
}
`
Anda harus meneruskan true sebagai argumen terakhir set_config agar variabel hanya diterapkan pada cakupan transaksi saat ini (SET LOCAL). Ini adalah pengaturan krusial untuk mencegah kebocoran izin pengguna sebelumnya ke permintaan berikutnya dalam lingkungan DB connection pooling.
Sekarang, buat kebijakan yang membaca variabel sesi ini di dalam file 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
);
`
Dengan kombinasi ini, seaneh apa pun query yang dibuat oleh agen, data di luar area tenant-nya sendiri bahkan tidak akan bisa diakses. Meskipun terjadi insiden kebocoran, Waktu Pemulihan Data (MTTR) dapat dipangkas dari beberapa hari menjadi hitungan menit saja.
Pipeline Verifikasi CI/CD Menggunakan Promptfoo dan PyRIT
Pengembang tidak mungkin menguji secara manual setiap kali mengubah kode tool agen untuk memastikan apakah isolasi izin berjalan dengan benar. Hubungkan alat open-source Promptfoo dan PyRIT dari Microsoft ke GitHub Actions untuk memverifikasinya pada setiap unit 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
`
Pada saat server runtime, pasang middleware circuit breaker yang akan langsung menghentikan operasi ketika mendeteksi pola ancaman.
`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,
/concats*(\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(() => {});
}
}
`
Langkah-langkah untuk membangun pipeline pengujian keamanan sangatlah sederhana:
- Letakkan
promptfooconfig.yaml di root proyek dan definisikan item pemeriksaan untuk jailbreak dan excessive-agency.
- Pasang workflow deteksi PR di GitHub Actions untuk menjalankan skenario Promptfoo dan PyRIT.
- Tempatkan middleware
AgentCircuitBreaker di bagian depan endpoint Express untuk menghentikan eksekusi agen jika mendeteksi pola injeksi 3 kali berturut-turut.
Dengan arsitektur ini, Anda dapat menghemat lebih dari 5 jam waktu yang biasanya dihabiskan untuk peninjauan prompt secara manual setiap minggu.
| Metrik Evaluasi |
Metode Verifikasi Manual |
Penerapan Otomatisasi Zero-Trust |
| Waktu Persiapan Audit |
5-8 jam per minggu |
Kurang dari 1 jam per minggu |
| MTTR Saat Kebocoran Izin |
Beberapa hari (investigasi menyeluruh pada seluruh log/DB) |
Beberapa menit (terbatas pada cakupan RLS) |
| Tingkat Pemblokiran Prompt Injection |
Sekitar 23% |
99,9% |
| Risiko Tagihan Saat Key Bocor |
Tagihan cloud tanpa batas |
Pemblokiran tagihan dengan TTL 60 detik |
Kunci utama keamanan agen bukanlah berdoa agar model selalu patuh pada perintah. Kuncinya adalah memborgol model di tingkat infrastruktur sehingga meskipun model mengucapkan hal yang tidak relevan atau berhasil diserang, ia tetap tidak dapat membahayakan sistem.