그 데이터를 모델에 넣어도 됩니까: 데이터가 지나는 네 구간의 답
감탄이 멈추는 순간
생성형 AI 도입 검토 미팅에는 일정한 패턴이 있습니다. 초반 30분은 분위기가 좋습니다. 데모를 보여주면 감탄이 나오고, 이걸 어디에 쓰면 좋겠다는 아이디어가 테이블 위에 쌓입니다. 분위기는 그때까지 조용히 듣고 있던 정보보안 담당자가 첫 질문을 꺼내면서 바뀝니다.
우리 문서를 모델에 보내면, 그 데이터는 어디로 갑니까.
이 질문은 시작일 뿐입니다. 누가 볼 수 있습니까. 어디에 남습니까. 학습에 쓰이지는 않습니까. 지워달라고 하면 지워집니까. 개인정보가 섞여 들어가면 어떻게 됩니까. 개인정보와 내부 규정 문서를 다루는 교육·공공 분야로 갈수록 이 질문들이 사실상 도입 여부를 결정합니다. 데모가 아무리 인상적이어도 여기서 막히면 프로젝트는 시작되지 않습니다.
흔한 답은 관리형 서비스라서 안전하다는 것입니다. 틀린 말은 아닙니다. 다만 그 답만으로는 검토를 통과하지 못합니다. 회의실에 잠깐 침묵이 흐르고, 보안 담당자는 같은 질문을 단어만 바꿔 다시 묻습니다. 여기서 안심시키는 문장을 하나 더 얹기 쉽지만, 통하지 않습니다. 담당자가 원하는 것은 안심이 아니라, 데이터가 지나가는 구간마다 무엇이 어떻게 보호되는지에 대한 확인 가능한 답이기 때문입니다.
'하고 있다'와 '어떻게'의 거리
넥스트클라우드는 최근 AWS AI 컴피턴시를 취득했습니다. AWS 발표에 따르면 AI 컴피턴시는 2025년 11월 기존 생성형 AI 컴피턴시를 확대 개편한 프로그램으로, 기존 생성형 AI에 에이전틱 AI 카테고리(Agentic AI Tools, Agentic AI Applications, Agentic AI Consulting Services)가 더해진 구조입니다. 여기서 취득 소감을 늘어놓을 생각은 없습니다. 다만 심사 과정에서 배운 것 하나가 이 글의 출발점이라 짚고 갑니다.
심사 초반, 우리는 보안 영역 답변에 꽤 자신이 있었습니다. 암호화하고 있고, 권한을 관리하고 있고, 로그를 남기고 있다고 썼습니다. 돌아온 피드백의 요지는 간결했습니다. 하고 있다는 것은 알겠는데, '어떻게' 하는지가 없다는 것입니다. 심사는 무엇을 만들었는지가 아니라 어떤 절차로 만들었는지를 봅니다. '어떻게'를 서면으로 답하지 못하면, 하고 있다는 말은 검증되지 않은 주장으로 취급됩니다.
그때 알아차렸습니다. 심사관의 질문과 고객 미팅에서 받던 질문이 정확히 같은 종류였습니다. 안전하다는 감각과 증명 가능한 절차 사이에는 거리가 있고, 심사 준비는 그 거리를 문서로 메우는 작업이었습니다. 그 과정에서 발견한 함정들은 실수의 문제가 아니라 기본값의 문제였습니다. 필터의 내장 목록에 한국 주민등록번호와 외국인등록번호 전용 유형은 없었고, 권한 모델은 한 층이 아니라 두 층이었고, 학습에 쓰지 않는다는 답과 아무 데도 남지 않는다는 답은 같은 답이 아니었고, 마스킹은 호출 로그에까지 미치지 않았습니다.
이 글은 그 함정들을 정리한 것입니다. 데이터가 모델에 닿기 전, 이동하는 동안, 저장될 때, 기록이 남을 때. 네 구간 각각에서 무엇을 설정해야 하고 어디에 구멍이 있는지 다룹니다. 컴피턴시 이야기는 여기까지만 하고, 이제부터는 기술입니다.
그림 1. 데이터가 지나는 네 구간과 각 구간의 보호 장치. 모델로 향하는 경로(1, 2구간)와 데이터가 남는 경로(3, 4구간)를 나누어 답을 준비해야 합니다.
1구간. 주민등록번호는 왜 필터를 통과했나
가상의 상황을 하나 놓고 시작하겠습니다. 학사 안내 챗봇을 운영하는데, 한 학생이 이렇게 입력합니다. "제 주민등록번호는 001231-4567890인데 장학금 신청 자격이 되나요." (형식만 맞춘 가짜 값입니다.) 이 문장은 어디까지 갈까요.
Amazon Bedrock Guardrails의 민감 정보 필터(sensitive information filters)를 켜두었다면 당연히 잡힐 것이라 기대하기 쉽습니다. 우리 테스트에서는 절반만 맞았습니다. 이름, 이메일, 전화번호는 잡혔고, 주민등록번호 형식의 가짜 값은 통과했습니다.[1] 물론 내장 PII 탐지는 문맥을 보는 확률적 ML 방식이라, 같은 값도 주변 문장과 입력 방식에 따라 결과가 달라질 수 있습니다. 그래서 이런 테스트는 사용한 리전, 가드레일 버전, API, 프롬프트 문맥, 확인 날짜를 결과와 함께 남겨야 합니다. 재현되지 않는 검증은 검증이 아니니까요.
이유는 내장 PII 목록의 구성에 있습니다. 내장 유형은 일반(이름, 이메일, 전화번호, 주소 등), 금융, IT 항목과 국가 특화 항목으로 나뉘는데, 국가 특화 목록은 미국(US_SOCIAL_SECURITY_NUMBER 등), 캐나다, 영국의 식별자로 구성됩니다. 한국 주민등록번호나 외국인등록번호에 해당하는 전용 유형은 없습니다.
여기서 언어 지원과 식별자 지원을 구분해야 합니다. 민감 정보 필터는 한국어를 공식 지원하지만, 한국어를 처리한다는 사실이 주민등록번호 전용 탐지기를 제공한다는 뜻은 아닙니다. 내장 목록의 공백은 리전 설정으로 채워지지 않으므로, 실제 서비스 문맥과 형식만 맞춘 테스트 값으로 탐지 범위를 확인해야 합니다.
적용 범위에도 경계가 있습니다. 민감 정보 필터는 텍스트 입력과 출력을 대상으로 하지만, 모델이 tool_use 함수 호출의 출력 파라미터로 반환한 PII는 탐지하지 않습니다. 도구 호출 인자와 결과를 다루는 애플리케이션이라면 이 경로에 별도의 검증을 두어야 합니다.
이 공백은 커스텀 정규식 필터로 메웁니다.
{
"sensitiveInformationPolicyConfig": {
"piiEntitiesConfig": [
{ "type": "NAME", "action": "ANONYMIZE" },
{ "type": "EMAIL", "action": "ANONYMIZE" },
{ "type": "PHONE", "action": "ANONYMIZE" }
],
"regexesConfig": [
{
"name": "KR-RRN",
"pattern": "\\d{6}[-]?[1-8]\\d{6}",
"action": "BLOCK",
"description": "Korean resident registration number and foreigner registration number"
}
]
}
}
이 패턴에는 함정이 하나 더 숨어 있습니다. 우리가 처음 쓴 패턴은 일곱 번째 자리를 [1-4]로 좁힌 것이었습니다. 주민등록번호의 일곱 번째 자리는 세기와 성별을 나타내는 코드인데, 내국인이 1에서 4를 쓰고 외국인등록번호는 같은 열세 자리 형식에 5에서 8을 씁니다. [1-4]로 좁히면 유학생과 외국인 교직원의 등록번호가 그대로 통과합니다. 내장 필터의 공백을 메우겠다고 만든 필터에 같은 종류의 공백이 숨어 있었습니다. 위 패턴이 [1-8]인 이유입니다.[2] 물론 [1-8]도 완결은 아닙니다. 1800년대 출생을 나타내는 9와 0은 여전히 빠져 있습니다. 학사 챗봇 트래픽에서 만날 일이 사실상 없다고 판단해 그대로 두었지만, 모르고 빠뜨린 것과 알고 남겨둔 것은 다릅니다. 커스텀 필터를 만들 때는 자기 필터의 경계에도, 내장 필터를 의심했던 것과 같은 강도의 질문을 던져야 합니다.
반대 방향의 경계도 함께 봐야 합니다. 이 패턴은 같은 열세 자리 형식을 쓰는 법인등록번호도 잡고, 긴 숫자열 안의 일부에도 걸립니다.[2] 우리는 이 과탐(False Positive)을 감수하는 쪽을 택했습니다. 다만 감수한다는 결정과 모른다는 상태는 다르므로, 어떤 정상 입력이 걸리는지는 미리 확인하고 넘어갔습니다.
동작 방식은 세 가지 중에서 고릅니다. 차단(BLOCK)은 민감 정보가 감지된 요청이나 응답 전체를 막고 설정해 둔 안내 문구를 반환합니다. 마스킹(ANONYMIZE)은 해당 값을 {NAME}, {EMAIL} 같은 유형 표기로 치환해 나머지 대화를 계속 진행시킵니다. 세 번째 값(NONE)은 탐지 전용을 뜻하는데, 여기에 함정이 있습니다. action에만 NONE을 넣으면 문서 설명과 달리 실제로는 차단됩니다. 탐지만 하게 하려면 inputAction과 outputAction까지 NONE으로 명시해야 합니다({"type": "NAME", "action": "NONE", "inputAction": "NONE", "outputAction": "NONE"}).[3] 감지 결과는 사용하는 API의 trace 또는 assessment에서 확인합니다. 다만 그 결과를 직접 찍어보면 match 필드에 마스킹 전 원문이 그대로 들어 있습니다. 마스킹을 켜도 감지 결과까지 가려지지는 않으므로, trace를 저장하는 경로도 민감 데이터로 다뤄야 합니다.
직접 돌려보고서야 안 함정도 있습니다. 위 예시처럼 action만 지정한 마스킹은 입력에 적용되지 않습니다. 이메일을 마스킹으로 설정한 가드레일을 붙이고 모델에게 그 주소의 글자 수를 물었더니 정답이 돌아왔습니다. 모델이 원문을 그대로 받았다는 뜻입니다. 같은 호출의 inputAssessment는 입력 전체를 검사했다고 보고하면서 탐지 항목은 비워서 돌려줬습니다. 반면 차단으로 설정한 유형은 같은 자리에서 원문을 탐지하고 요청을 멈췄습니다. 한 가드레일에 차단과 마스킹을 섞어 두고 확인해도 결과는 같았습니다. 이 구성에서 마스킹이 동작하는 곳은 응답 경로뿐입니다. ApplyGuardrail을 source를 INPUT으로 두고 호출했을 때 조치 없음이 돌아온 것도 같은 이유입니다. 모델에 보내기 전에 입력까지 가리려면 inputAction을 ANONYMIZE로 명시해야 합니다. 그렇게 지정한 가드레일은 같은 INPUT 검사에서 입력을 {EMAIL}로 치환해 돌려줬습니다.[4]
정규식을 작성할 때는 먼저 제약을 확인해야 합니다. 규칙 이름은 1자에서 100자, 패턴은 1자에서 500자까지입니다.[5]
문서가 말해주지 않는 것도 있습니다. Guardrails가 어떤 정규식 엔진을 쓰는지, 단어 경계(\b)가 한글을 어떻게 다루는지는 공개되어 있지 않습니다. (^|[^0-9])패턴([^0-9]|$)처럼 경계 문자를 함께 매칭하면 긴 숫자열 안의 부분 일치를 줄일 수 있지만, 경계 문자를 소비하는 패턴은 인접 문자까지 마스킹 범위에 끌고 들어갑니다. 직접 굴려보니 \b는 한글을 단어 문자로 취급하지 않았습니다. 한글이 숫자열에 바로 붙어 있어도 경계가 성립해 매치됐고, 영문자가 붙은 경우에는 매치되지 않았습니다. 한글 문장에서는 \b가 기대대로 동작하지만, 영숫자가 섞이는 입력에서는 여전히 확인이 필요합니다.[6]
우리는 우선 넓게 차단한 뒤, 앞서 본 대로 inputAction·outputAction을 지정한 탐지 전용 모드로 운영 트래픽을 평가해 정상 문의가 얼마나 차단될 뻔했는지 확인하고 오탐을 줄이는 쪽을 택했습니다. 이 선택은 정규식 엔진에 대한 가정이 아니라, 개인정보 노출을 놓치는 위험을 과도한 차단보다 더 크게 평가한 운영 정책입니다.
과금 구조도 필터 설계에 영향을 줍니다. Guardrails의 요금은 사용하는 필터별로 계산됩니다. 1 텍스트 유닛은 최대 1,000자이며, 2026년 8월 확인 기준 일반 민감 정보 필터는 1,000 텍스트 유닛당 0.10달러이지만 정규식 기반 민감 정보 필터는 무료입니다.[7]
주민등록번호처럼 형식이 고정된 식별자를 정규식으로 분리하면 민감 정보 필터 비용을 줄일 수 있습니다. 무료가 정확도를 보장하지는 않습니다. 정규식의 과탐과 미탐은 별도로 검증해야 하며, 모델 추론과 함께 활성화한 다른 Guardrails 필터의 요금도 따로 발생합니다.
마지막으로 스트리밍 응답의 처리 모드를 확인해야 합니다. 채팅 UI는 대부분 응답을 청크 단위로 흘려보내는데, Guardrails는 스트리밍 응답을 동기 또는 비동기 방식으로 검사할 수 있습니다. 기본값인 동기 모드는 한 개 이상의 응답 청크를 모아 정책 검사를 마친 뒤 사용자에게 보내므로 지연이 붙지만, 해당 내용은 전송 전에 검사됩니다.
비동기 모드는 응답 청크가 준비되는 즉시 사용자에게 보내면서 정책 검사를 뒤에서 진행합니다. 지연을 줄일 수 있는 대신 검사가 끝나기 전의 청크에 부적절한 내용이 포함될 수 있고, 위반이 식별된 뒤에야 이후 청크가 차단됩니다. 그러니 비동기 모드가 가드레일 전체를 무효화하는 것은 아닙니다. 검사가 늦어질 뿐이고, 문제는 그다음입니다.
더 중요한 것은 민감 정보 마스킹입니다. AWS는 비동기 스트리밍 모드에서 민감 정보 마스킹을 지원하지 않는다고 명시합니다. 잃는 것은 정확히 하나, 응답 쪽 마스킹입니다. 입력 프롬프트는 여전히 일반적인 처리 순서에 따라 모델 호출 전에 평가되지만, 앞서 확인한 대로 action만 지정한 구성에서 입력에 실제로 동작하는 것은 차단뿐입니다.
공식 문서는 이때 요청 오류가 나는지 결과에서 조용히 생략되는지까지는 설명하지 않습니다. 직접 확인한 답은 둘 다 아니었습니다. 같은 가드레일로 동기 모드에서는 응답이 {EMAIL}로 치환됐지만, 비동기 모드에서는 이메일 주소가 원문 그대로 스트리밍됐습니다. 오류도 경고도 없었습니다. 더 나쁜 것은 그 호출의 outputAssessments가 여전히 해당 항목을 ANONYMIZED로 보고했다는 점입니다. trace만 보고 마스킹이 걸렸다고 판단하면 원문이 나가는 것을 놓칩니다. 스트리밍 응답의 마스킹이 필요하면 동기 모드를 유지해야 합니다.[8]
요청 형식도 API마다 다릅니다. InvokeModelWithResponseStream에서는 amazon-bedrock-guardrailConfig.streamProcessingMode에 SYNCHRONOUS 또는 ASYNCHRONOUS를 사용합니다. ConverseStream에서는 guardrailConfig.streamProcessingMode에 sync 또는 async를 사용합니다. 필드 이름이 같아 보여도 값과 위치가 다르므로, 사용하는 API를 기준으로 확인해야 합니다.[9]
2구간. 데이터가 인터넷을 지나는가, 국경을 넘는가
보안 검토 문서에서 가장 자주 지적받는 부분이 통신 경로 다이어그램입니다. 그리고 그 운명은 화살표 하나에 갈립니다. 애플리케이션에서 모델로 가는 화살표가 인터넷 게이트웨이를 지나는가, 지나지 않는가.
Amazon Bedrock 호출은 기본적으로 퍼블릭 리전 엔드포인트를 향합니다. 일반적인 IPv4 VPC 구성에서는 퍼블릭 IP를 사용하는 퍼블릭 서브넷의 트래픽은 인터넷 게이트웨이를, 프라이빗 서브넷의 트래픽은 NAT 게이트웨이와 인터넷 게이트웨이를 거칩니다. 다만 Amazon VPC FAQ는 AWS 네트워크에서 출발해 AWS 네트워크를 목적지로 하는 패킷은 AWS 글로벌 네트워크 안에 머문다고 설명합니다(중국 리전과 유럽 소버린 클라우드 리전은 예외). 전송 구간에는 TLS 1.2가 적용됩니다.
bedrock-runtime 인터페이스 VPC 엔드포인트(AWS PrivateLink)를 만들고 프라이빗 DNS를 활성화하면 기본 Bedrock DNS 이름이 엔드포인트의 사설 IP로 해석(resolution)되어, 인터넷 게이트웨이나 NAT 없이 모델을 호출할 수 있습니다. 프라이빗 DNS를 켜지 않았다면 SDK나 CLI에 VPC 엔드포인트 URL을 명시해야 합니다. 검토 문서에서는 퍼블릭 서비스 엔드포인트와 IGW·NAT를 사용하는 경로인지, PrivateLink의 사설 IP 경로인지 구분해 적어야 합니다.
리전 문제는 한 층 더 미묘합니다. 교차 리전 추론(cross-Region inference)을 쓰면 요청과 응답이 원본 리전 밖의 목적지 리전으로 이동하고 그곳에서 처리될 수 있습니다. 이 전송은 Amazon의 보안 네트워크 안에서 암호화되지만, 리전과 국가의 경계를 넘을 수 있다는 사실은 그대로 남습니다. AWS는 기본적으로 데이터를 목적지 리전에 저장하지 않는다고 설명하면서도, 악용 탐지를 위해 데이터를 보관하는 경우에는 입력 프롬프트와 출력 결과가 목적지 리전에 저장될 수 있다고 명시합니다. 트래픽이 몰릴 때 처리량을 확보하는 좋은 수단입니다. 다만 켜는 순간 답해야 할 질문이 바뀝니다. 우리 데이터가 어느 리전에 있는가에서, 어느 리전에 있을 수 있는가로.
여기서 프로파일 종류를 구분해야 합니다. 지리권 프로파일은 해당 지리권(US, EU, APAC) 안에서만 라우팅하지만, 글로벌 프로파일은 지리권 경계를 넘습니다. 그리고 이 선택은 모델 선택과 묶여 있습니다. 2026년 8월 서울 리전에서 ListInferenceProfiles로 확인해 보니 Anthropic 모델의 apac. 프로파일은 Claude Sonnet 4가 가장 최신이었고, 그보다 새 모델은 전부 global. 프로파일로만 있었습니다. 게다가 그 Sonnet 4는 같은 시점 ListFoundationModels에서 LEGACY로 표시됩니다. 서울에서 지리권 경계를 지키려면 공급자가 은퇴 단계로 표시한 모델을 쓰거나 한 세대 더 물러나야 하고, 최신 모델을 쓰겠다는 결정은 곧 지리권 경계를 포기하는 결정이 됩니다.[10]
당연하게도 APAC(Asia Pacific)은 한국 전용 경계가 아닙니다. 같은 시점 GetInferenceProfile로 확인한 APAC Claude 3.5 Sonnet v2 프로파일의 목적지는 도쿄, 서울, 오사카, 뭄바이, 싱가포르, 시드니 여섯 개 리전이었습니다. 서울에서 APAC 프로파일을 호출한다는 것은 요청이 국경을 넘을 수 있다는 뜻입니다. 그리고 이 목록은 모델마다 다릅니다. 같은 명령을 APAC Claude Sonnet 4에 실행하면 여기에 하이데라바드와 멜버른이 더해진 여덟 곳이 나옵니다. 쓰는 모델로 직접 확인하는 수밖에 없습니다.
따라서 국내 처리만을 약속해야 하는 워크로드라면 APAC 지리권 프로파일을 국내 프로파일처럼 적어서는 안 되고, 해당 모델의 단일 리전 호출 지원 여부를 확인해야 합니다. 교차 리전 추론을 쓴다면 모델 ID와 프로파일 ID, 원본 리전, 가능한 목적지 리전, 확인 날짜를 검토 문서에 함께 남기고, GetInferenceProfile 결과와 모델별 리전 표를 근거로 붙이는 것이 안전합니다.
이 구간에서 겪은 실패를 하나 공유합니다. 교차 리전 추론 프로파일을 도입하던 때의 일입니다. IAM 정책에 프로파일 ARN 호출 권한을 부여했습니다. 당연히 되겠거니 했는데, 모델 호출이 거부됐습니다. 정책을 몇 번이나 다시 읽어도 문제가 없어 보였습니다. 한참을 들여다보고서야 구조를 이해했습니다. 프로파일을 호출할 권한과, 프로파일이 라우팅하는 목적지 리전의 기반 모델을 호출할 권한이 별개였던 것입니다. 프로파일은 문이고 모델은 방인데, 우리는 문 열쇠만 주고 방 열쇠를 주지 않았던 셈입니다.
그렇다고 방 열쇠를 조건 없이 주면 이번에는 다른 문제가 생깁니다. 프로파일을 우회해 목적지 모델을 직접 호출하는 경로까지 열릴 수 있습니다. 답은 프로파일과 모델을 서로 다른 문장으로 허용하되, 모델 문장에 정확한 프로파일 ARN 조건을 거는 것입니다. 다음은 서울에서 APAC Claude 3.5 Sonnet v2 프로파일을 호출하는 예입니다. 앞서 본 Sonnet 4가 아니라 이 모델을 쓴 것은, 은퇴 표시가 없는 apac. 프로파일 중 가장 최신이기 때문입니다. 계정 ID만 자리표시자이고, 목적지 리전 여섯 곳은 위 GetInferenceProfile 결과 그대로입니다. 여기서 리전을 몇 개만 골라 적으면 그쪽으로 라우팅된 요청이 거부되므로, 목록은 반드시 쓰는 모델의 확인 결과로 채워야 합니다.
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "InvokeExactApacProfile",
"Effect": "Allow",
"Action": [
"bedrock:InvokeModel",
"bedrock:InvokeModelWithResponseStream"
],
"Resource": "arn:aws:bedrock:ap-northeast-2:111122223333:inference-profile/apac.anthropic.claude-3-5-sonnet-20241022-v2:0"
},
{
"Sid": "InvokeProfileDestinationModels",
"Effect": "Allow",
"Action": [
"bedrock:InvokeModel",
"bedrock:InvokeModelWithResponseStream"
],
"Resource": [
"arn:aws:bedrock:ap-northeast-1::foundation-model/anthropic.claude-3-5-sonnet-20241022-v2:0",
"arn:aws:bedrock:ap-northeast-2::foundation-model/anthropic.claude-3-5-sonnet-20241022-v2:0",
"arn:aws:bedrock:ap-northeast-3::foundation-model/anthropic.claude-3-5-sonnet-20241022-v2:0",
"arn:aws:bedrock:ap-south-1::foundation-model/anthropic.claude-3-5-sonnet-20241022-v2:0",
"arn:aws:bedrock:ap-southeast-1::foundation-model/anthropic.claude-3-5-sonnet-20241022-v2:0",
"arn:aws:bedrock:ap-southeast-2::foundation-model/anthropic.claude-3-5-sonnet-20241022-v2:0"
],
"Condition": {
"StringEquals": {
"bedrock:InferenceProfileArn": "arn:aws:bedrock:ap-northeast-2:111122223333:inference-profile/apac.anthropic.claude-3-5-sonnet-20241022-v2:0"
}
}
}
]
}
첫 번째 문장은 정확히 지정한 프로파일을, 두 번째 문장은 그 프로파일이 라우팅할 수 있는 기반 모델만 허용합니다. 두 번째 문장의 bedrock:InferenceProfileArn 조건은 직접 모델 호출에는 충족되지 않습니다. 따라서 동일한 IAM 주체에 기반 모델을 허용하는 다른 정책이 없다면 직접 호출은 허용되지 않습니다.
다만 이것은 명시적 거부가 아닙니다. AmazonBedrockFullAccess처럼 더 넓은 Allow가 같은 주체에 붙어 있으면 IAM은 허용을 합쳐 평가하므로 직접 호출이 다시 열릴 수 있습니다. 이 제약을 조직 수준에서 보장하려면 권한 경계나 SCP, 또는 영향 범위를 검토한 명시적 Deny를 함께 설계해야 합니다.
목적지 리전 목록은 모델과 프로파일마다 다르므로 배포 시점의 GetInferenceProfile 결과와 모델 문서로 다시 확인해야 합니다. 조직의 SCP가 목록 중 한 리전이라도 막으면 교차 리전 추론이 실패할 수 있습니다. 액션은 와일드카드 대신 비스트리밍용 bedrock:InvokeModel과 스트리밍용 bedrock:InvokeModelWithResponseStream을 명시했습니다.
그림 2. 두 문장으로 나눈 IAM 정책의 구조. 문장 1은 정확히 지정한 프로파일을, 문장 2는 그 프로파일이 사용하는 모델 ARN을 조건부로 허용합니다. 동일 주체에 기반 모델을 허용하는 다른 정책이 없을 때 (프로파일을 거치지 않은) 직접 호출은 이 정책에서 허용되지 않습니다.
3구간. 어디에 얼마나 남는가
먼저 학습 사용 여부부터 정리하겠습니다. AWS는 Bedrock에 입력한 프롬프트와 출력 결과를 기반 모델 학습에 사용하지 않는다고 명시합니다. 다만 학습에 쓰지 않는다는 것과 아무 데도 남지 않는다는 것은 다릅니다. 안전과 악용 방지를 위해 데이터가 보관되는 경우가 있고, 보관이 필요한 모델에서는 최대 30일까지 저장될 수 있습니다. 우리 워크로드에 어느 쪽이 적용되는지는 뒤에서 볼 보관 모드가 결정합니다. 2구간에서 본 목적지 리전 저장 문제와 다시 만나는 지점입니다.
그래서 검토 문서에 적어야 할 것은 학습에 쓰이지 않는다는 문장이 아니라, 우리 워크로드에 실제로 적용되는 보관 모드입니다. Bedrock은 데이터 보관을 켜고 끄는 토글이 아니라 default, provider_data_share, none, inherit 네 가지 모드로 다루고, 신규 계정과 프로젝트의 기본값은 inherit입니다. 유효 모드는 프로젝트, 계정, 모델 기본값 순으로 훑어 inherit이 아닌 첫 값으로 결정됩니다.
함정은 모델마다 허용하는 모드 목록(allowed_modes)이 다르다는 데 있습니다. 유효 모드가 그 목록에 없으면 해당 모델은 unavailable 상태가 되고, 보관을 요구하는 모델을 none으로 설정된 계정에서 호출하면 요청이 차단되며 오류가 돌아옵니다. 반대로 특정 모델을 쓰려고 계정 수준 모드를 넓히면, 유효 모드 결정 규칙상 inherit으로 남아 있던 다른 프로젝트의 정책까지 함께 바뀝니다. 게다가 출시 시점에는 이 설정에 콘솔 UI가 없어 API나 SDK로만 설정하고 확인할 수 있습니다. 유효 보관 모드와 모델별 allowed_modes를 확인 날짜와 함께 문서에 남겨야 하는 이유입니다.
저장 데이터 암호화는 답하기 쉬운 질문처럼 보입니다. 저장소마다 암호화를 켜면 되니까요. 그런데 생성형 AI 워크로드에서는 질문의 범위가 조용히 넓어집니다. 원문 문서만 보호 대상이 아니기 때문입니다.
RAG 파이프라인을 따라가 보면 이렇습니다. 원문을 청킹하고, 임베딩해서, 검색 인덱스에 넣습니다. 인덱스에 벡터만 들어간다고 생각하기 쉽지만, 검색 결과로 원문 조각을 돌려줘야 하므로 임베딩 벡터와 함께 청크 원문이 저장되는 구성이 일반적입니다. 인덱스를 읽을 수 있는 사람은 원문을 읽을 수 있습니다. 프롬프트와 응답이 쌓이는 로그, 반복 질의를 흡수하는 캐시도 마찬가지로 원문의 조각을 품고 있습니다.
그래서 심사를 준비하며 우리가 세운 원칙은 이것입니다. 파생 데이터를 원문과 동일 등급으로 취급한다. 벡터니까 괜찮다거나 캐시는 임시니까 괜찮다는 논리는 보안 검토에서 통하지 않고, 통해서도 안 됩니다.
키 관리는 AWS KMS로 일원화하되 용도별로 분리합니다. 문서 저장소용, 데이터 계층용, 로그용 고객 관리형 키(CMK)를 나누면 접근 권한과 감사 범위가 용도 단위로 좁혀집니다. 문서 키에 접근할 수 있는 주체와 로그 키에 접근할 수 있는 주체가 다르다는 사실 자체가 검토 문서의 답이 됩니다.
여기서 흔한 오해를 하나 짚겠습니다. S3는 2023년 1월 5일부터 모든 신규 객체 업로드에 SSE-S3를 기본 암호화로 적용하며, 신규 업로드의 암호화는 끌 수 없습니다.[11] 그 전에 저장된 비암호화 객체까지 소급해서 암호화하지는 않으므로, 과거 객체는 S3 Inventory 등으로 따로 확인하고 필요하면 다시 암호화해야 합니다.
이제 질문은 암호화 여부보다 어느 키로 암호화되는가로 옮겨갑니다. 버킷 기본 암호화를 (지정한 고객 관리형 KMS 키의) SSE-KMS로 설정하면 암호화 헤더가 없는 업로드(PUT) 요청에는 그 설정이 적용됩니다. 그러나 요청에 암호화 정보가 들어 있으면 요청값이 버킷 기본값보다 우선합니다. 따라서 버킷 정책으로 SSE-KMS가 아닌 요청과 지정한 KMS 키 ARN이 아닌 요청을 거부해야 키 분리 원칙을 저장소 수준에서 강제할 수 있습니다.
정책에 따라 암호화 헤더가 없는 요청까지 거부될 수 있으므로, 버킷 기본 암호화에 의존할지 모든 업로드가 키 ARN을 명시하게 할지를 먼저 결정해야 합니다. S3는 일반 목적 버킷의 기본 암호화 설정에 입력한 KMS 키 ID를 설정 시점에 검증하지 않으므로, 설정 뒤에는 실제 업로드 결과까지 시험해야 합니다.
이 구조는 삭제 요청에도 영향을 줍니다. 문서를 지워달라는 요청이 오면 원문만 지워서는 끝나지 않습니다. 임베딩, 검색 인덱스, 캐시, 로그, 복제본과 백업까지 파생 데이터 전체의 위치를 먼저 확인해야 합니다. S3 Versioning이 활성화된 버킷에서 version ID 없는 단순 삭제는 객체를 영구 삭제하지 않고 delete marker만 추가합니다. Object Lock의 legal hold나 compliance mode 보존 기간이 적용된 객체 버전은 해당 보호가 끝나거나 해제되기 전까지 영구 삭제할 수 없습니다. governance mode는 다릅니다. s3:BypassGovernanceRetention 권한이 있는 주체가 우회 헤더를 명시하면 보존 기간 중에도 삭제할 수 있고, S3 콘솔은 이 헤더를 기본으로 포함합니다. 지워지지 않는다는 답은 모드와 우회 권한 설계까지 확인한 뒤에야 할 수 있습니다.
모든 조각을 직접 지우기 어려울 때는 cryptographic erasure를 설계할 수 있습니다. 데이터가 오직 최종 삭제된 고객 관리형 대칭 KMS 키에만 의존한다면 그 암호문은 복구할 수 없습니다. 다만 키 삭제에는 7일에서 30일의 대기 기간이 붙고 기본값은 30일이며, 예약 즉시 키는 Pending deletion 상태가 되어 암호 연산에 쓸 수 없습니다.[12] 이 기간은 키가 계속 동작하는 유예가 아니라 잘못 예약한 삭제를 취소할 수 있는 복구 창구입니다. 그런데 앞에서 나눈 용도별 KMS 키는 저장소 전체를 덮고 있으므로, 키 하나를 삭제하면 그 키를 사용하는 모든 데이터가 함께 영향을 받습니다. 개별 정보주체의 삭제 수단으로는 쓸 수 없습니다.
정보주체별 cryptographic erasure가 필요하다면 클라이언트 측 envelope encryption 등으로 정보주체별 데이터 키를 분리하고, 암호화된 데이터 키의 모든 사본과 평문 캐시까지 제거하는 설계가 필요합니다. 일반적인 S3 SSE-KMS에서는 고객이 객체별 데이터 키를 직접 폐기하지 못합니다. 또한 KMS 키가 사용 불가가 되어도 서비스가 이미 복호화해 캐시한 데이터 키는 캐시가 만료되거나 리소스가 다시 연결될 때까지 영향을 받지 않을 수 있습니다. 키 삭제를 법적 삭제의 동의어로 쓰기 전에 복제본, 백업, 캐시와 보존 의무까지 함께 확인해야 합니다.
4구간. 로그를 켜면 위험하고, 끄면 더 위험하다
네 번째 질문은 추적입니다. 누가 언제 무엇을 호출했는지 되짚을 수 있습니까. 여기에는 많이 알려지지 않은 사실이 두 가지 있는데, 둘이 합쳐지면 얄궂은 딜레마가 됩니다.
첫째, Amazon Bedrock의 모델 호출 로깅(model invocation logging)은 기본적으로 꺼져 있습니다. 활성화하지 않으면 지원되는 bedrock-runtime 호출의 프롬프트와 응답 본문이 model invocation logging 목적지에 수집되지 않습니다. 이는 CloudTrail의 API 활동 기록이나 애플리케이션이 직접 남기는 로그와는 별개입니다.
현재 이 기능은 bedrock-runtime 엔드포인트를 통한 호출만 수집합니다. AWS 문서는 여기에 같은 엔드포인트의 OpenAI 호환 Responses·Chat Completions API도 포함된다고 명시하면서, 같은 API를 bedrock-mantle 엔드포인트로 호출하면 현재 수집되지 않는다고 덧붙입니다. 경계는 API 이름이 아니라 엔드포인트입니다. 감사 추적이 필요한 워크로드라면 로깅을 켰다는 확인에서 멈추지 말고, 애플리케이션이 실제로 어느 엔드포인트로 나가는지부터 확인해야 합니다.
둘째, 로깅을 켜는 순간 로그 자체가 민감 데이터가 됩니다. model invocation logging은 지원되는 호출의 전체 요청 데이터와 응답 데이터, 메타데이터를 수집합니다. 여기에 기록되는 입력은 가드레일을 거치기 전, 호출자가 보낸 그대로입니다. 마스킹 가드레일을 붙이고 로깅을 켠 뒤 로그 그룹을 직접 열어보니 응답 쪽에는 마스킹된 문자열이 들어 있었지만, 입력 쪽에는 이름과 이메일, 전화번호, 주민등록번호가 원문 그대로 남아 있었습니다. 차단된 요청도 다르지 않았습니다. 가드레일이 입력에서 막아 모델이 보지도 못한 요청의 원문이 로그에는 그대로 기록됐습니다. 가드레일은 로그를 가려주지 않습니다.[13]
우리는 이것을 로그의 역설이라고 불렀습니다. 추적성을 확보하려고 켠 기능이 새로운 보호 대상을 만들어냅니다. 그렇다고 끄면 사고 조사가 어려워집니다. 답은 켜느냐 끄느냐가 아니라, 켜되 로그 저장소를 처음부터 민감 데이터 저장소로 설계하는 것입니다.
CloudWatch Logs의 모든 로그 그룹은 기본적으로 저장 데이터를 암호화합니다. 별도의 키 정책과 감사 경계가 필요하다면 로그 그룹에 로그 전용 고객 관리형 KMS 키를 연결하고, 키 사용과 로그 조회 권한을 보안 담당 역할로 좁힐 수 있습니다. 다만 기존 로그 그룹에 KMS 키를 나중에 연결하면 그 뒤에 수집되는 데이터부터 해당 키가 적용되고 과거 로그가 새 키로 다시 암호화되지는 않습니다.
CloudWatch Logs 데이터 보호에서도 1구간에서 만난 공백이 나타납니다. 관리형 데이터 식별자에는 한국 주민등록번호 전용 식별자가 없습니다. 이름이나 이메일처럼 지역과 무관한 식별자는 있지만, 주민등록번호를 탐지하고 가리려면 custom data identifier를 직접 정의해야 합니다. 데이터 보호 정책 하나에는 custom data identifier를 최대 10개까지 정의할 수 있고, 각 정규식은 최대 200자입니다.[14]
데이터 보호 정책은 정책이 적용된 뒤 로그 그룹에 수집되는 이벤트에서 민감 정보를 탐지합니다. 정책보다 먼저 들어온 로그에는 소급 적용되지 않습니다. 탐지된 값은 CloudWatch Logs Insights와 metric filters, subscription filters를 포함한 egress에서 기본적으로 마스킹되지만, logs:Unmask 권한이 있는 주체는 원문을 볼 수 있습니다. 따라서 이 기능은 원문을 없애는 비가역적 삭제가 아니라 조회와 전달 경로의 마스킹으로 이해해야 합니다. 다만 앞서 원문이 남던 그 로그 그룹에 이메일 관리형 식별자로 데이터 보호 정책을 걸고 같은 호출을 반복하자, 입력 필드의 이메일이 별표로 치환된 채 기록됐습니다. 가드레일이 덮지 못한 로그 경로를 메우는 자리는 여기입니다.[13]
S3 경로도 하나로 묶으면 안 됩니다. 데이터 보호 정책이 적용된 CloudWatch 로그 그룹에서 subscription filter를 통해 내려가는 데이터는 기본적으로 마스킹됩니다. 반면 Bedrock가 model invocation log를 S3 목적지로 직접 전달하거나 대용량·바이너리 입력을 S3 객체로 저장하는 경로는 CloudWatch Logs 데이터 보호 정책을 거치지 않습니다. 이 S3 버킷은 마스킹되지 않은 원문이 들어올 수 있는 민감 데이터 저장소로 보고 별도의 KMS 키, 접근 통제와 수명 주기 정책을 적용해야 합니다.
그림 3. action만 지정한 기본 구성에서 Guardrails의 마스킹은 모델 응답 경로에만 적용됩니다. 호출 로그에는 가드레일을 거치기 전 원문이 기록되고 차단된 요청도 예외가 아니므로, 로그 저장소는 처음부터 민감 데이터 저장소로 설계해야 합니다. 데이터 보호 정책을 거친 subscription 경로는 기본적으로 마스킹되지만, Bedrock가 S3로 직접 전달하는 로그는 별도의 보호 대상입니다.
네 구간을 한 장으로
지금까지의 내용을 검토 질문과 확인 방법으로 정리하면 이렇습니다. 도입을 검토 중이라면 이 표의 오른쪽 열이 제안사에 요구할 증빙 목록이 됩니다.
| 구간 | 검토 질문 | 핵심 장치 | 확인 방법 |
|---|---|---|---|
| 1. 입출력 필터 | 개인정보가 모델과 도구 호출 경로까지 가는가 | Guardrails 민감 정보 필터, 한국 식별자용 커스텀 정규식, 도구 호출 검증 | 대표 테스트 세트, trace·assessment, 스트리밍 처리 모드, tool_use 인자와 결과 검사 |
| 2. 네트워크 | 퍼블릭 엔드포인트와 IGW·NAT를 쓰는가, 어느 국가의 리전에서 처리될 수 있는가 | bedrock-runtime VPC 인터페이스 엔드포인트와 프라이빗 DNS, 프로파일 목적지 리전 목록 | DNS 해석과 라우팅 경로, GetInferenceProfile 결과, IAM·SCP 정책 |
| 3. 저장 | 학습에 쓰이는가, 어디에 얼마나 남고 파생 데이터까지 보호되는가 | Bedrock 데이터 보관 모드, 파생 데이터 목록, 용도별 키 분리, 버킷 정책 | 계정·프로젝트의 유효 보관 모드와 모델 allowed_modes, 저장소별 키·보존·삭제 매핑표 |
| 4. 기록 | 모든 호출 경로가 기록되는가, 원문을 누가 볼 수 있는가 | model invocation logging(bedrock-runtime 한정), 로그 그룹 KMS 키, 데이터 보호 정책 | 애플리케이션이 쓰는 호출 엔드포인트와 수집 범위, logs:Unmask 권한, CloudWatch·S3 목적지와 보존 기간 |
마치며: 이름이 아니라, 구간별 답
네 구간을 관통하는 원칙은 하나입니다. 데이터가 지나는 모든 구간에 대해 무엇이 언제 어떻게 보호되는지 답할 수 있어야 하고, 그 답은 설정과 정책으로 확인 가능해야 합니다. 서비스 이름을 나열하는 것은 답이 아닙니다. 같은 서비스로도 안전한 구성과 위험한 구성을 모두 만들 수 있기 때문입니다.
컴피턴시 심사를 준비하며 배운 것은 새로운 기술이 아니라 그것을 설명하는 방식이었습니다. 무엇을 구축했는지는 어렵지 않게 쓸 수 있습니다. 어떻게 하고 있는지를 쓰려면 근거와 절차를 함께 갖춰야 하고, 그 과정에서 안다고 생각했던 것과 설명할 수 있는 것 사이에 거리가 있음을 확인했습니다. 심사를 준비하면서 정리했던 문서와 프로세스들이 지금은 고객의 보안 검토에 답하는 자료로 쓰이고, 도입 검토 미팅에서 침묵이 흐르던 자리에 구간별 답이 놓입니다.
생성형 AI 도입을 검토하고 있다면, 제안사에 이 네 가지를 물어보시기 바랍니다. 프롬프트의 개인정보는 어디에서 걸러지고 도구 호출 경로는 어떻게 검사하는지. 퍼블릭 엔드포인트와 IGW·NAT를 사용하는지, 어느 국가의 리전에서 요청이 처리될 수 있는지. 입력과 응답이 모델 학습에 쓰이는지, 실제 데이터 보관 모드는 무엇이며 원문과 파생 데이터가 어디에 얼마나 오래 남는지. 애플리케이션이 쓰는 모든 호출 엔드포인트의 기록이 남는지, 남는다면 원문을 누가 볼 수 있고 언제 삭제되는지.
네 질문에 모델 이름, API, 설정값, 리전, 저장소와 보존 기간으로 답하는 제안사라면 신뢰할 수 있는 출발점입니다. 말로만 안전하다고 답하는 제안사라면, 이 글을 보여주셔도 좋습니다.
참고 문헌
본문의 사실 확인은 2026년 8월 16~19일 기준입니다. AWS의 서비스 사양과 요금은 변경되므로, 이 내용을 검토 문서에 옮길 때는 확인 날짜를 함께 적고 원문을 다시 확인하시기 바랍니다. 본문에서 [n]으로 표시한 수치는 아래 같은 번호의 문서에 근거합니다.
프로그램
- AWS AI 컴피턴시 확대 개편 발표 (2025-11-30) — https://aws.amazon.com/about-aws/whats-new/2025/11/aws-ai-competency-agentic-ai-categories
1구간. 입출력 필터
- 민감 정보 필터 동작, 마스킹 표기,
tool_use출력 미탐지 — https://docs.aws.amazon.com/bedrock/latest/userguide/guardrails-sensitive-filters.html - 내장 PII 유형 목록(미국·캐나다·영국 식별자) — https://docs.aws.amazon.com/bedrock/latest/APIReference/API_runtime_GuardrailPiiEntityFilter.html
- 지원 언어 — https://docs.aws.amazon.com/bedrock/latest/userguide/guardrails-supported-languages.html
- [1] 내장 PII 필터가 이름·이메일·전화번호는 탐지하고 주민등록번호 형식 값은 탐지하지 않는다는 점 — 2026-08-18 ap-northeast-2에서 직접 확인.
NAME·EMAIL·PHONE·ADDRESS·US_SOCIAL_SECURITY_NUMBER다섯 유형만 넣은 임시 가드레일에 본문의 문장을 합친 한 문장("제 이름은 홍길동이고 이메일은 hong@example.com, 전화번호는 010-1234-5678입니다. 제 주민등록번호는 001231-4567890인데 장학금 신청 자격이 되나요.")을ApplyGuardrail(INPUT/OUTPUT)과Converse로 호출했습니다. 두 경로 모두NAME(홍길동),EMAIL,PHONE만detected: true로 돌아왔고001231-4567890은 어떤 유형으로도 탐지되지 않았습니다.guardrailCoverage는 101자 전체를 검사했다고 보고했습니다. 미국 주민번호 유형을 함께 켜 두어도 한국 형식은 걸리지 않았습니다. - [2]
[1-4]와[1-8]의 탐지 범위 차이, 법인등록번호와 긴 숫자열의 과탐 — 2026-08-18 ap-northeast-2에서 직접 확인.\d{6}[-]?[1-4]\d{6}과\d{6}[-]?[1-8]\d{6}두 정규식을 한 가드레일에 넣고 다섯 입력을ApplyGuardrail로 대조했습니다. 내국인 형식(001231-4567890)은 두 패턴 모두 매치, 외국인 형식(001231-5678901)은[1-8]만 매치, 법인등록번호 형식(110111-1234567)은 두 패턴 모두 매치, 하이픈 없는 13자리(0012314567890)도 두 패턴 모두 매치했습니다. 20자리 숫자열(12345678901234567890)에서는 두 패턴이 각각 다른 시작 위치에서 13자리 부분 문자열을 잡아냈습니다. - [3]
action에만NONE을 지정하면 차단되고,inputAction·outputAction까지 지정해야 탐지 전용이 된다는 점 — 2026-08-18 ap-northeast-2에서 직접 확인.action: NONE만 넣은 가드레일은GetGuardrail에NONE으로 저장되지만,ApplyGuardrail과Converse양쪽에서actionReason: "Guardrail blocked."와 함께 각 항목이action: BLOCKED로 돌아오고 요청이 멈췄습니다. 같은 유형에inputAction·outputAction을NONE으로 명시하자action: NONE,detected: true로 탐지만 되고 조치는 일어나지 않았습니다. 대조군으로action: ANONYMIZE만 지정한 가드레일은 두 필드 없이도 응답에서 정상 마스킹됐으므로, 이 현상은NONE에 한정됩니다.inputAction·outputAction은GuardrailPiiEntityConfigAPI 레퍼런스(https://docs.aws.amazon.com/bedrock/latest/APIReference/API_GuardrailPiiEntityConfig.html)에BLOCK·ANONYMIZE·NONE유효값과 함께 문서화되어 있고, 문서는NONE을 마스킹도 차단도 하지 않는 탐지 전용으로 설명합니다. 문서에 없는 것은 필드가 아니라,action: NONE단독 지정이 차단으로 동작한다는 사실입니다. - [4] 마스킹·차단·탐지 동작,
match필드의 원문 노출,action만 지정한 마스킹이 입력에 적용되지 않고inputAction을 명시하면 적용된다는 점 — 2026-08-16 ap-northeast-2에서 임시 가드레일을 만들어 직접 확인. 본문 테스트 문장을ApplyGuardrail(INPUT/OUTPUT)과Converse(trace: enabled)로 각각 호출하고 결과를 대조했습니다. 입력 마스킹 여부는 모델의 답으로 판별했습니다. 이메일을 마스킹으로 설정하고@앞부분의 글자 수를 묻자 모델이 정답을 답했고, 같은 호출의inputAssessment는guardrailCoverage로 입력 전체를 검사했다고 보고하면서piiEntities는 비어 있었습니다. 같은 문자열도 차단으로 설정하면 입력에서match와 함께 탐지되고 요청이 멈췄습니다. 차단 유형과 마스킹 유형을 한 가드레일에 함께 넣은 구성에서도 결과는 같았습니다. 같은 확인에서 내장 PII 유형이 31개이며 국가 특화 항목은 미국·캐나다·영국뿐임을CreateGuardrail의 열거형 검증 오류로 확인했습니다. 입력 마스킹은 2026-08-18 us-east-1에서 추가로 확인했습니다.inputAction·outputAction을ANONYMIZE로 명시한 가드레일은ApplyGuardrail(source: INPUT)에서actionReason: "Guardrail masked."와 함께 입력을My email is {EMAIL}로 치환해 반환했고,action: ANONYMIZE만 지정한 가드레일은 같은 입력에 조치 없음을 반환했습니다. - [5] 정규식 규칙 이름 1~100자, 패턴 1~500자 — https://docs.aws.amazon.com/bedrock/latest/APIReference/API_GuardrailRegexConfig.html (같은 날 101자 이름과 501자 패턴으로 각각 거부되는 것을 확인)
BLOCK·ANONYMIZE·NONE동작 — https://docs.aws.amazon.com/AWSCloudFormation/latest/TemplateReference/aws-properties-bedrock-guardrail-regexconfig.html- [6]
\b가 한글을 단어 문자로 취급하지 않는다는 점 — 2026-08-18 ap-northeast-2에서 직접 확인.\b\d{6}[-]?[1-8]\d{6}\b패턴에 대해번호는 001231-4567890 입니다(공백)와번호는001231-4567890입니다(한글 인접)는 모두 매치했고,no001231-4567890yes(영문 인접)는 매치하지 않았습니다. - 가드레일 처리 흐름(입력은 모델 호출 전에 평가), 필터별 과금 구조 — https://docs.aws.amazon.com/bedrock/latest/userguide/guardrails-how.html
- [7] 텍스트 유닛 1,000자, 민감 정보 필터 1,000유닛당 $0.10, 정규식 필터 무료 — https://aws.amazon.com/bedrock/pricing/ (2026-08-17 AWS Price List API로 확인. ap-northeast-2의
Guardrail-SensitiveInformationPolicyPaidUnitsConsumed는 TextUnit당 $0.0001로 1,000유닛당 $0.10과 일치하고,Guardrail-SensitiveInformationPolicyFreeUnitsConsumed는 $0입니다. 즉 유료 유닛과 무료 유닛이 별도 SKU로 집계된다는 점까지가 API로 확인되는 범위이고, 무료 SKU가 정규식 필터에 대응한다는 것은 요금 페이지 서술에 근거합니다) - 스트리밍 처리 모드와 비동기 모드의 마스킹 미지원 — https://docs.aws.amazon.com/bedrock/latest/userguide/guardrails-streaming.html · https://docs.aws.amazon.com/bedrock/latest/APIReference/API_runtime_GuardrailStreamConfiguration.html
- [8] 비동기 스트리밍에서 마스킹이 적용되지 않고
outputAssessments는ANONYMIZED로 보고한다는 점 — 2026-08-18 ap-northeast-2에서 직접 확인.EMAIL을ANONYMIZE로 설정한 같은 가드레일로ConverseStream(boto3)을streamProcessingModesync와async로 각각 호출했습니다. sync는연락처는 {EMAIL} 입니다.를, async는연락처는 hong@example.com 입니다.를 반환했고, 예외나 경고는 없었습니다. 두 호출 모두outputAssessments의 해당 항목은action: ANONYMIZED,detected: true였습니다. - [9]
InvokeModelWithResponseStream의streamProcessingMode에SYNCHRONOUS가 유효한 값이라는 점 — 공식 문서에는 비동기 전환 예시의ASYNCHRONOUS만 표기되어 있어, 2026-08-19 us-east-1에서 boto3로 직접 확인했습니다.SYNCHRONOUS와ASYNCHRONOUS는 정상 수락되어 스트리밍이 진행됐고, 무효 토큰과ConverseStream용 소문자sync는ValidationException("Guardrail was enabled but input is in incorrect format")으로 거부됐습니다. 필드가 실제로 검증되므로SYNCHRONOUS수락은 우연한 통과가 아니지만, 문서에 없는 값인 만큼 확인 날짜 기준으로 읽어야 합니다.
2구간. 네트워크
- AWS 네트워크 내부에 머무는 트래픽 — https://aws.amazon.com/vpc/faqs/
- 전송 구간 TLS 1.2 — https://docs.aws.amazon.com/bedrock/latest/userguide/data-encryption.html
bedrock-runtime인터페이스 VPC 엔드포인트와 프라이빗 DNS — https://docs.aws.amazon.com/bedrock/latest/userguide/vpc-interface-endpoints.html- 지리권 교차 리전 추론, 목적지 리전 저장, 두 문장 IAM 정책 구조 — https://docs.aws.amazon.com/bedrock/latest/userguide/geographic-cross-region-inference.html
- 프로파일을 거치지 않은 직접 모델 호출 제한(
InferenceProfileArn조건 키) — https://docs.aws.amazon.com/bedrock/latest/userguide/inference-profiles-prereq.html - 글로벌 교차 리전 추론 — https://docs.aws.amazon.com/bedrock/latest/userguide/global-cross-region-inference.html
GetInferenceProfile— https://docs.aws.amazon.com/bedrock/latest/APIReference/API_GetInferenceProfile.html- [10]
apac.·global.프로파일 구성, Claude Sonnet 4의LEGACY표시, 두 APAC 프로파일의 목적지 리전 — 2026-08-16 ap-northeast-2에서 직접 확인.aws bedrock list-inference-profiles,aws bedrock list-foundation-models --by-provider anthropic(modelLifecycle.status),aws bedrock get-inference-profile --inference-profile-identifier(apac.anthropic.claude-3-5-sonnet-20241022-v2:0,apac.anthropic.claude-sonnet-4-20250514-v1:0) - 명시적 Deny와 Allow의 평가 순서 — https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_evaluation-logic_policy-eval-denyallow.html
3구간. 저장
- 입력·출력을 기반 모델 학습에 사용하지 않음 — https://aws.amazon.com/bedrock/faqs/
- 악용 탐지 목적의 보관과 최대 30일 저장 — https://docs.aws.amazon.com/bedrock/latest/userguide/abuse-detection.html
- 데이터 보관 모드 네 가지, 유효 모드 결정 규칙,
allowed_modes, 출시 시점 콘솔 UI 부재 — https://docs.aws.amazon.com/bedrock/latest/userguide/data-retention.html - [11] 2023-01-05부터 신규 업로드에 SSE-S3 기본 적용 — https://docs.aws.amazon.com/AmazonS3/latest/userguide/default-encryption-faq.html
- 요청 헤더가 버킷 기본값보다 우선 — https://docs.aws.amazon.com/AmazonS3/latest/userguide/default-bucket-encryption.html
- 버킷 정책으로 특정 KMS 키 강제 — https://repost.aws/knowledge-center/s3-bucket-store-kms-encrypted-objects
- 기본 암호화 설정 시 KMS 키 ID를 검증하지 않음 — https://docs.aws.amazon.com/AmazonS3/latest/API/API_PutBucketEncryption.html
- [12] KMS 키 삭제 대기 기간 7~30일, 기본값 30일,
Pending deletion— https://docs.aws.amazon.com/kms/latest/developerguide/deleting-keys.html - 버전 관리 버킷의 delete marker — https://docs.aws.amazon.com/AmazonS3/latest/userguide/DeletingObjectVersions.html
- Object Lock 보존 기간과 legal hold — https://docs.aws.amazon.com/AmazonS3/latest/userguide/object-lock.html
- 사용 불가 키와 캐시된 데이터 키 — https://docs.aws.amazon.com/kms/latest/developerguide/unusable-kms-keys.html
4구간. 기록
- 모델 호출 로깅 기본 비활성, 수집 범위,
bedrock-runtime엔드포인트 한정 — https://docs.aws.amazon.com/bedrock/latest/userguide/model-invocation-logging.html - Bedrock 엔드포인트 구분 — https://docs.aws.amazon.com/bedrock/latest/userguide/endpoints.html
- 로그 그룹의 저장 데이터 암호화와 KMS 키 연결 — https://docs.aws.amazon.com/AmazonCloudWatch/latest/logs/encrypt-log-data-kms.html
- [13] 호출 로그에 남는 입력 원문, 차단된 요청의 로그 기록, 데이터 보호 정책 적용 뒤의 마스킹 — 2026-08-16 ap-northeast-2에서 직접 확인. 임시 로그 그룹과 전달 역할을 만들어
aws bedrock put-model-invocation-logging-configuration으로 CloudWatch 전달을 켜고, 마스킹 가드레일과 차단 가드레일을 각각 붙인Converse호출의 로그 이벤트를aws logs get-log-events로 읽어input.inputBodyJson과output.outputBodyJson을 대조했습니다. 이어aws logs put-data-protection-policy로arn:aws:dataprotection::aws:data-identifier/EmailAddress식별자에Deidentify를 적용하고 같은 호출을 반복했습니다. 확인 후 로깅 설정·로그 그룹·역할·가드레일은 모두 삭제했습니다. - 관리형 데이터 식별자 목록 — https://docs.aws.amazon.com/AmazonCloudWatch/latest/logs/CWL-managed-data-identifiers.html
- [14] 정책당 custom data identifier 최대 10개, 정규식 최대 200자 — https://docs.aws.amazon.com/AmazonCloudWatch/latest/logs/CWL-custom-data-identifiers.html (2026-08-18
PutDataProtectionPolicy로 직접 확인. 10개·200자는 수락되고, 11개는You cannot have more than 10 Custom Data Identifiers, 201자는Custom Data Identifier regex cannot be longer than 200으로 각각InvalidParameterException이 반환됩니다) - egress 마스킹과
logs:Unmask— https://docs.aws.amazon.com/AmazonCloudWatch/latest/logs/mask-sensitive-log-data.html - 데이터 보호 정책의 소급 미적용 — https://docs.aws.amazon.com/AmazonCloudWatchLogs/latest/APIReference/API_PutDataProtectionPolicy.html
작성: 넥스트클라우드 최민철 CTO, 김현민 AI & Architect Lead