
AI를 자신의 프로덕트에 탑재할 때, 피할 수 없는 3가지: 열쇠, 지갑, 출력 설계
요약
AI 기능을 프로덕트에 탑재할 때 고려해야 할 보안(API 키 관리), 비용(과금 방지), 신뢰성(출력 설계)의 3가지 핵심 설계 요소를 다룹니다. 개발자가 겪은 실무적 고민과 타협점을 공유합니다.
핵심 포인트
- API 키는 DB 유출에 대비해 암호화하여 저장해야 함
- BYOK 방식 채택 시 사용자의 과도한 과금을 막는 설계가 필수적임
- 비용 방어를 위해 호출 전 서버 측에서 사용 상한을 판정해야 함
- 동시성 문제를 방지하기 위해 원자적(Atomic) 카운팅이 필요함
먼저 답을 적겠습니다. AI 기능을 자신의 프로덕트에 탑재하여 배포할 때, 피할 수 없는 것은 3가지였습니다. 이용자의 열쇠를 어떻게 보관할 것인가. 이용자의 지갑에 어떻게 천장을 만들 것인가. AI의 출력을 어디까지 신뢰하지 않을 것인가.
저는 WordPress용 AI 챗봇 플러그인을 만들어 WordPress.org에서 배포하고 있습니다. 만들면서 깨달은 것은, 이 3가지가 '나중에 추가할 기능'이 아니라 '처음에 결정해야 할 설계'였다는 점입니다. 이번에는 그 3가지에 대해 제가 무엇을 선택했고, 어디에서 타협했는지를 쓰겠습니다. 구현에 관한 세부 사항은 따로 작성했으므로, 여기서는 판단에 대해서만 다룹니다.
사용하는 쪽과, 탑재하는 쪽은 다르다
먼저 전제부터 말씀드리겠습니다. 스스로 AI를 사용하는 경우라면, 실패해도 본인만 손해를 봅니다. 토큰을 낭비하든, 이상한 답변이 돌아오든, 피해자는 본인 한 명뿐입니다.
하지만 탑재하는 쪽이 되면 이야기가 달라집니다. 내가 작성한 코드 때문에 타인의 열쇠가 유출됩니다. 타인의 청구 금액이 폭등합니다. 타인의 사이트가 거짓말을 하게 됩니다. 손해를 보는 것은 이 서비스를 신뢰하고 설치해 준 사람들입니다. 이 비대칭성이 설계의 무게감을 바꾸었습니다.
1. 열쇠: 보관한다면, 평문으로 두지 않는다
AI를 구동하려면 API 키가 필요합니다. 여기서 첫 번째 분기점이 있었습니다. 내가 일괄적으로 결제할 것인가, 아니면 이용자가 자신의 키를 입력하게 할 것인가.
저는 BYOK(이용자가 자신의 키를 입력하는 방식)를 선택했습니다. 이유는 단순합니다. 제가 대리 결제하면 사용량이 늘어날수록 제가 적자가 나기 때문입니다. 개인 개발자가 타인의 사용량을 자신의 지갑으로 감당하는 것은 무리가 있습니다.
대신, 키를 보관하는 책임이 생겼습니다. update_option에 평문으로 넣는 것은 그만두었습니다. DB만 유출되는 경로는 현실적으로 매우 많습니다. 다른 플러그인의 SQL 인젝션(SQL Injection), 백업 방치, 공유 서버에서의 노출 등입니다. 이때 평문 키가 있다면 그대로 사용됩니다.
그래서 wp-config의 salt로부터 키를 유도하여, AES-256-GCM으로 암호화하여 저장하고 있습니다. 키를 코드에 직접 작성하면 누구나 복호화할 수 있고, DB에 두면 함께 유출됩니다. 파일 쪽에 있는 salt로부터 유도하는 것이 타협점이었습니다.
타협한 부분: 서버 파일까지 읽힌다면 salt도 읽을 수 있으므로 복호화됩니다. 즉, 완전히 침입당했을 경우에는 효과가 없습니다. 그럼에도 불구하고 이 방식을 택한 이유는, 실제 사고가 'DB만' 유출되는 형태로 발생하는 경우가 많기 때문입니다. 전부를 지킬 수는 없습니다. 일어나기 쉬운 형태만 막는 것입니다.
2. 지갑: 최악의 경우 얼마인지 말할 수 있게 한다
이 부분이 가장 무서운 부분이었습니다.
BYOK는 월정액이 없어 저렴한 대신, 사용한 만큼 이용자에게 청구됩니다. 즉, 누군가 악의를 품고 자동화된 요청을 밤새도록 계속 보낸다면, 그 청구서는 사이트 운영자에게 전달됩니다. 내 플러그인을 설치했다는 이유로 타인이 모르는 사이에 과금되는 것입니다. 상상만 해도 끔찍했습니다.
처음에는 속도 제한(Rate Limit)을 생각했습니다. 하지만 이것만으로는 부족합니다. 연타는 막을 수 있지만, 30초에 한 번씩 하루 종일 예의 바르게 요청을 보낸다면 그냥 통과하게 됩니다. 제가 지키고 싶은 것은 '연타를 막는 것'이 아니라 '청구 금액이 상정 범위를 넘지 않게 하는 것'이었습니다. 봐야 할 것은 빈도가 아니라 총량입니다.
그래서 하루 이용 상한을 서버 측에서 관리하고, 상한을 넘으면 AI를 호출하지 않고 정해진 메시지를 반환하도록 했습니다. 호출한 뒤에 차단하면 이미 비용이 발생하므로, 호출하기 전에 판정하는 것이 핵심입니다. 카운트는 DB 측에서 원자적(Atomic)으로 가산합니다. 읽고 더해서 다시 쓰는 구현 방식이라면, 동시 접속 시 숫자를 놓칠 수 있습니다. 공격을 받고 있을 때일수록 요청이 겹치므로, 가장 정확히 세어야 할 순간에 실패하게 됩니다. 이 부분은 제가 실제로 겪어보고 수정했습니다.
타협한 부분: 익명의 방문자는 IP로 셀 수밖에 없으며, 이는 완벽하지 않습니다. 같은 직장에서라면 여러 명이 한 명으로 보일 수 있고, IP를 바꾸면 회피할 수 있습니다. 다만 목적은 개인 식별이 아니라, 최악의 청구 금액에 천장을 만드는 것이었습니다. 상한에 도달하면 AI를 호출하지 않는다. 그 한 가지만 지켜진다면, 어떤 공격을 받더라도 청구 금액은 정해진 선에서 멈춥니다. '최악의 경우 얼마'라고 말할 수 있게 되자, AI 챗봇을 훨씬 마음 편히 배치할 수 있게 되었습니다.
3. 출력: 똑똑해 보이는 것을 포기한다
세 번째는 AI의 답변 그 자체입니다.
챗봇은 모르는 질문을 받으면 그럴듯한 거짓말을 만들어냅니다. 존재하지 않는 반품 조건을 안내하거나, 제공하지 않는 서비스를 '있습니다'라고 대답합니다. 방문자는 그것을 사이트의 공식 답변으로 받아들입니다. 즉, AI의 거짓말이 곧바로 사이트 운영자의 거짓말이 됩니다.
그래서 「근거가 있는 답변만」 하는 모드를 만들었습니다. 질문에 대해 사이트 내의 콘텐츠를 검색하고, 충분한 근거를 찾지 못하면 AI가 대답하게 하지 않고 「사이트 내에 해당 정보를 찾을 수 없습니다」라고 반환합니다. 그뿐입니다.
이것은 똑똑해 보이게 만드는 기능이 아닙니다. 똑똑해 보이는 것을 포기하는 기능입니다. 만들면서 조금 이상한 기분이 들었습니다. 어렵게 AI를 탑재했는데, 대답하지 않게 하는 방향으로 구현을 하고 있으니까요. 하지만 고객 지원 창구에 필요한 것은 박학다식한 거짓말쟁이가 아니라, 정직한 안내원이라고 생각합니다.
아울러, LLM의 응답은 외부 입력 (External Input)으로 취급하고 있습니다. 사용자 입력은 의심하면서, 그것을 LLM에 통과시킨 출력은 믿어버립니다. 저는 이것을 이중 신뢰 (Double Trust) 문제라고 부르는데, 실제로 이것 때문에 한 번 당한 적이 있습니다. 응답을 그대로 화면에 출력하고 있어서, 구문상으로는 아무런 문제가 없는데 위험한 상황이었습니다.
타협한 부분: 대답할 수 있는 범위가 좁아집니다. 「모르겠습니다」라고 말하는 횟수가 늘어납니다. 화려함은 떨어집니다. 그럼에도 불구하고, 틀린 것을 자신만만하게 말하는 것보다는 낫다고 판단했습니다. 「모른다」라고 말할 수 있는 봇은, 대답했을 때의 신뢰도가 올라간다는 부수적인 효과도 있었습니다.
3가지에 공통된 것
글을 쓰면서 깨달은 것인데, 이 세 가지 판단은 모두 같은 형태를 띠고 있었습니다.
최악의 케이스를 이용자가 가늠할 수 있도록 하는 것. 열쇠는 최악의 경우 DB가 유출되어도 평문 (Plaintext)으로는 나오지 않는다. 지갑은 최악의 경우 공격을 당해도 상한선에서 멈춘다. 출력은 최악의 경우 모르더라도 거짓말은 하지 않는다. 모두 「잘 되었을 때 멋진 것」이 아니라 「문제가 생겼을 때 어디까지 멈출 것인가」를 설계하고 있습니다.
흥미롭게도, 이 세 가지는 제품 소개 페이지에서는 잘 다뤄지지 않습니다. 나열되는 것은 고정밀, 다기능, 간편 도입입니다. 두려움에 대한 이야기는 셀링 포인트 (Selling Point)가 되지 않기 때문이라고 생각합니다. 하지만 도입을 망설이는 사람의 머릿속에 있는 것은 기능의 수가 아니라, 바로 이런 종류의 불안함일 것입니다.
다음의 나에게 남기는 메모
- 사용하는 쪽과 탑재하는 쪽은 다르다. 탑재하는 쪽의 실패는, 신뢰해 준 타인이 손해를 본다.
- 열쇠를 맡는다면 평문으로 두지 않는다. 단, 전부를 지킬 수는 없다. 일어나기 쉬운 사고의 형태만 막는다.
- 지갑은 빈도가 아니라 총량에 천장을 둔다. 호출하기 전에 판정한다. 카운트는 원자적 (Atomic)으로.
- 출력은 외부 입력 (External Input)으로 취급한다. 근거가 없다면 대답하게 하지 않는다. 똑똑해 보이는 것을 포기하는 용기.
- 3가지에 공통된 것은 「최악의 케이스를 이용자가 가늠할 수 있게 하는 것」
AI를 탑재하는 것은 기능을 추가하는 것보다, 타인의 손실에 책임을 지는 것이었습니다. 화려함 없는 설계뿐이지만, 이 세 가지를 처음에 결정해 둔 덕분에 밤에 잠을 잘 잘 수 있습니다.
구현의 세부 사항 (열쇠의 암호화, 하루 이용 상한 카운트)은 별도의 기사에 썼습니다.
평소에는 raplsworks.com에서 WordPress 플러그인 개발이나 Claude Code 주변의 이야기를 쓰고 있습니다.
Discussion

AI 자동 생성 콘텐츠
본 콘텐츠는 Zenn AI의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기