AI 에이전트가 API 키, 개인 키 및 PII를 유출하는 것을 방지하세요
Source: Dev.to
AI 에이전트가 API 키, 비공개 키 및 개인 식별 정보(PII)를 유출하지 않도록 차단하는 방법
📌 문제점
AI 에이전트를 프로덕션에 배포할 때 가장 큰 위험 중 하나는 민감한 정보가 실수로 외부에 노출되는 것입니다.
- API 키: 외부 서비스에 대한 무단 접근을 허용합니다.
- 비공개 키: 암호화된 데이터 복호화, 서명 생성 등에 악용될 수 있습니다.
- PII(개인 식별 정보): GDPR, CCPA 등 규제 위반으로 큰 벌금이 부과될 수 있습니다.
🛡️ 해결 방안
아래 단계들을 따라 에이전트가 민감한 정보를 절대 반환하거나 로그에 남기지 않도록 방어막을 구축합니다.
1️⃣ 환경 변수와 비밀 관리 도구 사용
# .env 파일 (절대 버전 관리에 포함되지 않도록 .gitignore에 추가)
OPENAI_API_KEY=sk-****************
AWS_SECRET_ACCESS_KEY=****************
- dotenv 혹은 python‑decouple, AWS Secrets Manager, HashiCorp Vault 등과 같은 비밀 관리 솔루션을 활용합니다.
- 코드 내부에 하드코딩된 키가 절대 없도록 CI/CD 파이프라인에서도 비밀을 주입합니다.
2️⃣ 프롬프트에 “민감한 정보는 절대 반환하지 말라” 명시
SYSTEM_PROMPT = """
You are a helpful assistant.
Never reveal API keys, private keys, passwords, or any personally identifiable information (PII).
If a user asks for such data, respond with: "I’m sorry, I can’t help with that."
"""
- 시스템 프롬프트에 강제 규칙을 넣어 LLM이 기본적으로 차단하도록 합니다.
- 필요 시 재시도 로직을 추가해 LLM이 규칙을 위반했을 경우 자동으로 재생성하도록 합니다.
3️⃣ 출력 후 필터링 레이어 적용
import re
def mask_sensitive(text: str) -> str:
# API 키 패턴 (예: sk-xxxx...)
text = re.sub(r'sk-[A-Za-z0-9]{20,}', '[REDACTED_API_KEY]', text)
# 비공개 키 (PEM 형식) 패턴
text = re.sub(r'-----BEGIN PRIVATE KEY-----[\s\S]+?-----END PRIVATE KEY-----', '[REDACTED_PRIVATE_KEY]', text)
# 이메일, 전화번호 등 PII 패턴
text = re.sub(r'\b[\w.%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}\b', '[REDACTED_EMAIL]', text)
return text
- LLM이 반환한 텍스트를 전처리 단계에서 정규식으로 스캔하고, 민감한 부분을 마스킹합니다.
- 마스킹된 문자열을 로그에 남기면 감사 추적에도 도움이 됩니다.
4️⃣ 토큰 제한 및 컨텍스트 관리
- 컨텍스트 창에 민감한 정보를 포함시키지 않도록 요청 전에 데이터를 정제합니다.
- 필요 시 요청 토큰 수를 제한해 LLM이 긴 텍스트를 그대로 복사해 반환하는 상황을 방지합니다.
5️⃣ 테스트와 모니터링 자동화
| 테스트 항목 | 기대 동작 | 자동화 도구 |
|---|---|---|
| API 키 요청 | “I’m sorry, I can’t help with that.” 반환 | pytest + mock LLM |
| 비공개 키 반환 | 마스킹된 문자열 반환 | unit test |
| PII 포함 질문 | 마스킹 또는 차단 | integration test |
- CI 파이프라인에 보안 테스트를 포함시켜 배포 전 누수가 없는지 검증합니다.
- 운영 중에는 로그 분석(예: Elastic Stack)과 알림(Slack, PagerDuty)으로 의심스러운 반환을 실시간 감시합니다.
📚 참고 자료
- OpenAI 안전 가이드라인 – https://platform.openai.com/docs/guides/safety
- LangChain – Prompt Management – https://python.langchain.com/docs/use_cases/prompt_management
- OWASP Top 10 – Sensitive Data Exposure – https://owasp.org/www-project-top-ten/
✅ 요약
- 비밀은 환경 변수/비밀 관리 서비스에 보관하고 코드에 절대 노출하지 않는다.
- 시스템 프롬프트에 “민감한 정보를 절대 반환하지 말라”는 규칙을 명시한다.
- 출력 후 필터링 레이어로 API 키, 비공개 키, PII 등을 정규식 기반으로 마스킹한다.
- 컨텍스트와 토큰을 제한해 의도치 않은 정보 복사가 일어나지 않게 한다.
- 자동화된 테스트와 모니터링을 구축해 실시간으로 누수를 감지한다.
위 원칙들을 적용하면 AI 에이전트가 실수로라도 중요한 자격 증명이나 개인 정보를 외부에 노출하는 위험을 크게 줄일 수 있습니다. 안전하고 신뢰할 수 있는 AI 서비스를 제공하기 위해서는 코드, 프롬프트, 운영 전 단계에서 보안 방어를 설계하는 것이 핵심입니다.
개요
당신의 AI 에이전트는 텍스트를 생성합니다. 그 텍스트에는 때때로 비밀 정보가 포함될 수 있습니다.
- LLM이 학습 데이터에서 AWS 키를 환각해서 생성했을 수도 있습니다.
- 도구가 출력에 데이터베이스 자격 증명을 반환했을 수도 있습니다.
- 에이전트가 사용자의 주민등록번호, 이메일, 혹은 암호화 지갑 개인 키와 같은 정보를 포함한 문서를 요약하고 있을 수도 있습니다.
그 출력이 최종 사용자에게 전달되거나—더 나아가 제3자 서비스에 로그로 남게 된다면, 이는 데이터 유출이 됩니다.
이 글에서는 Agntor SDK의 redact() 함수를 사용해 시스템을 떠나기 전 모든 텍스트에서 민감 데이터를 자동으로 제거하는 방법을 보여줍니다. 이 SDK는 PII, 클라우드 비밀, 블록체인 전용 키 등을 포괄하는 17개의 내장 패턴을 제공합니다.
설치
npm install @agntor/sdk
기본 사용법
import { redact } from "@agntor/sdk";
const input = `
Here are the credentials:
AWS Key: AKIA1234567890ABCDEF
Email: admin@internal-corp.com
Server: 192.168.1.100
`;
const { redacted, findings } = redact(input, {});
console.log(redacted);
// Here are the credentials:
// AWS Key: [AWS_KEY]
// Email: [EMAIL]
// Server: [IP_ADDRESS]
console.log(findings);
// [
// { type: "aws_access_key", span: [42, 62] },
// { type: "email", span: [72, 95] },
// { type: "ipv4", span: [106,119] }
// ]
설정이 필요 없습니다. 빈 정책 {}은 17개의 내장 패턴을 모두 활성화합니다.
잡히는 항목
표준 개인식별정보
| 유형 | 예시 | 대체된 값 |
|---|---|---|
user@example.com | [EMAIL] | |
| Phone (US) | +1 (555) 123-4567 | [PHONE] |
| SSN | 123-45-6789 | [SSN] |
| Credit card | 4111 1111 1111 1111 | [CREDIT_CARD] |
| Street address | 123 Main Street | [ADDRESS] |
| IPv4 | 192.168.1.1 | [IP_ADDRESS] |
클라우드 비밀
| 유형 | 예시 | 대체된 값 |
|---|---|---|
| AWS access key | AKIA1234567890ABCDEF | [AWS_KEY] |
| Bearer token | Bearer eyJhbGciOiJI... | Bearer [REDACTED] |
| API key/secret | api_key: "sk-abc123..." | api_key: [REDACTED] |
API 키 패턴은 똑똑합니다 — api_key, secret, password, token 뒤에 : 또는 =가 오고 20자 이상인 값을 매칭합니다. 키 이름은 출력에 유지되어 어떤 비밀이 삭제되었는지 알 수 있습니다.
블록체인 / 암호화 키
| 유형 | 예시 | 대체된 값 |
|---|---|---|
| EVM 개인 키 | 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 | [PRIVATE_KEY] |
| Solana 개인 키 | 87‑88‑char base58 string | [SOLANA_PRIVATE_KEY] |
| Bitcoin WIF 키 | Starts with 5, K, or L + 50‑51 base58 chars | [BTC_PRIVATE_KEY] |
| BIP‑39 니모닉 (12) | abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about | [MNEMONIC_12] |
| BIP‑39 니모닉 (24) | 24‑word seed phrase | [MNEMONIC_24] |
| Keystore JSON 암호문 | "ciphertext": "a1b2c3..." | "ciphertext": "[REDACTED_KEYSTORE]" |
| HD 파생 경로 | m/44'/60'/0'/0/0 | [HD_PATH] |
실제 예시: Crypto Agent 출력
import { redact } from "@agntor/sdk";
const agentOutput = `
I've set up your wallet. Here are the details:
Address: 0x742d35Cc6634C0532925a3b844Bc9e7595f2bD18
Private Key: 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80
Recovery Phrase: abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about
Derivation Path: m/44'/60'/0'/0/0
`;
const { redacted } = redact(agentOutput, {});
console.log(redacted);
// I've set up your wallet. Here are the details:
// Address: 0x742d35Cc6634C0532925a3b844Bc9e7595f2bD18
// Private Key: [PRIVATE_KEY]
// Recovery Phrase: [MNEMONIC_12]
// Derivation Path: [HD_PATH]
Note: 공개된 지갑 주소(42개의 16진수 문자)는 마스킹되지 않으며 개인 키(64개의 16진수 문자)만 마스킹됩니다. 정규식은 EVM 개인 키의 길이에 해당하는 64‑16진수 문자열을 정확히 매치하도록 설계되었습니다.
맞춤 패턴
도메인별 비밀에 대한 자체 패턴을 추가하세요:
const { redacted } = redact(agentOutput, {
redactionPatterns: [
{
type: "internal_endpoint",
regex: /https?:\/\/internal\.[a-z]+\.corp\/[^\s]*/gi,
replacement: "[INTERNAL_URL]",
},
{
type: "jwt_token",
regex: /eyJ[A-Za-z0-9_-]+\.eyJ[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+/g,
replacement: "[JWT]",
},
],
});
맞춤 패턴은 기본 패턴과 병합되므로, 기본 제공 17개의 패턴에 여러분이 추가한 패턴이 모두 포함됩니다.
겹치는 매치 처리 방법
두 패턴이 겹치는 텍스트에 매치될 때(예: 개인 키와 API‑키 할당의 일부가 될 수 있는 16진수 문자열) 알고리즘은 다음과 같이 동작합니다:
matchAll()을 사용해 모든 패턴을 실행하고, 각 매치와 그 위치를 수집합니다.- 매치를 시작 위치 순으로, 그 다음 길이 순(긴 것이 먼저)으로 정렬합니다.
- 왼쪽에서 오른쪽으로 스캔하면서, 이미 허용된 매치와 겹치는 경우 해당 매치를 건너뜁니다.
결과: 가장 길고 가장 왼쪽에 있는 매치가 승리합니다. 실제로는 가장 유용한 출력이 나오며—예를 들어 부분적으로 가려진 문자열 대신 [PRIVATE_KEY]가 표시됩니다.
Express 미들웨어 예제
모든 JSON 응답을 마스킹하는 실용적인 미들웨어:
import express from "express";
import { redact } from "@agntor/sdk";
const app = express();
// Redaction middleware — intercepts JSON responses
app.use((req, res, next) => {
const originalJson = res.json.bind(res);
res.json = (body: unknown) => {
const bodyStr = JSON.stringify(body);
const { redacted, findings } = redact(bodyStr, {});
if (findings.length > 0) {
console.warn(
`Redacted ${findings.length} sensitive items:`,
findings.map((f) => f.type)
);
}
// Send the redacted payload
return originalJson(JSON.parse(redacted));
};
next();
});
// ... define routes as usual
app.listen(3000, () => console.log("Server listening on :3000"));
이 미들웨어는 응답을 직렬화하고, 비밀 정보를 마스킹하며, 제거된 항목을 로그에 기록한 뒤, 정제된 JSON을 클라이언트에 다시 전송합니다.
Takeaway
redact()를 AI‑agent 파이프라인(또는 텍스트가 신뢰 경계를 벗어나는 모든 위치)에 삽입하면, API 키, 개인 키, 그리고 개인 식별 정보가 실수로 유출되는 것을 자동으로 방지할 수 있습니다.
.post("/api/agent", async (req, res) => {
const llmOutput = await callYourLLM(req.body.prompt);
// Even if the LLM leaks secrets, they get stripped here
res.json({ result: llmOutput });
});
입력 가드와 결합하기
Redaction은 출력 측면을 처리합니다. 입력 측면에서는 guard()와 결합합니다:
import { guard, redact } from "@agntor/sdk";
async function processAgentRequest(userInput: string) {
// 1. 입력을 가드합니다
const guardResult = await guard(userInput, {});
if (guardResult.classification === "block") {
throw new Error(
"입력 거부: " + guardResult.violation_types.join(", ")
);
}
// 2. LLM으로 처리합니다
const output = await callYourLLM(userInput);
// 3. 출력에 대해 Redact를 수행합니다
const { redacted } = redact(output, {});
return redacted;
}
또는 wrapAgentTool()을 사용하면 가드 + 레드랙트 + SSRF 검사를 한 번에 수행할 수 있습니다:
import { wrapAgentTool } from "@agntor/sdk";
const safeTool = wrapAgentTool(myTool, {
policy: {},
});
// 입력은 레드랙트되고 가드된 뒤, 도구가 실행됩니다
const result = await safeTool("https://api.example.com/data");
성능
Redaction은 정규식으로 전 과정이 인‑프로세스에서 실행됩니다. 네트워크 호출도 없고, LLM 추론도 없으며, 외부 의존성도 없습니다(단, SDK 자체는 제외).
- 일반적인 에이전트 출력(500–2000자)에서는
redact()가 1 ms 이하에 완료됩니다. - 100 KB 이상의 큰 문서에서도 10 ms 이하로 유지됩니다.
측정 가능한 지연 시간 영향을 주지 않으면서 모든 응답에 안전하게 호출할 수 있습니다.
제한 사항
- 헥스 문자열에 대한 오탐 – 64자 헥스 해시(예: SHA‑256 다이제스트)는 개인키 패턴과 일치합니다. 에이전트가 비밀이 아닌 헥스 해시를 자주 출력한다면 패턴을 적절히 조정하세요.
- 니모닉 감지가 과도함 – 12개 또는 24개의 소문자 단어(각 3–8자) 시퀀스는 모두 일치합니다. 이는 드물게 정상적인 영어 텍스트를 표시하게 할 수 있습니다.
- 의미 이해 부족 – 삭제는 순전히 패턴 기반이며 실제 AWS 키와 유사 문자열을 구별할 수 없습니다. 이 트레이드오프는 오탐(안전)보다 누락(위험)을 더 선호합니다.
소스 코드
Everything is open source (MIT):
redact()source- Full SDK → 전체 SDK
- npm package → npm 패키지
If you’re building agents that generate text — especially agents that interact with APIs, databases, or blockchain — add output redaction. It’s a one‑line change that prevents an entire class of data breaches.
(Agntor 은 AI 에이전트를 위한 오픈‑source 신뢰 및 결제 레일입니다. 도움이 되었다면 저장소에 ⭐를 눌러 주세요.)