> ## Documentation Index
> Fetch the complete documentation index at: https://docs.starfort.io/llms.txt
> Use this file to discover all available pages before exploring further.

# 효과적인 PII 정책 작성

> Starfort 기본 PII 정책을 예시로, 탐지 엔진이 정확하게 동작하도록 규칙 내용과 우선순위를 설계하는 방법을 설명합니다.

[맞춤형 PII 정책 추가](/ko/v1.3/admin/how-to/add-pii-policy)가 콘솔에서 정책을 만들고 적용하는 **절차**를 다룬다면, 이 페이지는 규칙의 **내용**을 다룹니다 — 무엇을 어느 필드에 적어야 탐지 엔진이 정확하게 동작하는지, 그리고 규칙들이 서로 충돌하지 않도록 우선순위를 어떻게 설계하는지. 모든 예시는 Starfort가 제공하는 **기본 PII 정책**(한국형 주민등록번호·여권·운전면허·휴대폰·계좌·카드 등 27개 정규식 + NER 3종 + 키워드)에서 발췌했습니다.

## 정책 구조와 처리 순서

PII 정책은 세 가지 규칙 배열로 구성됩니다. 허용되는 최상위 키는 `ner`, `regex`, `keyword`이며, 엔진은 항상 **Keyword → Regular Expression → NER** 순서로 검사합니다. 앞 단계가 확정한 텍스트는 뒤 단계에 **가려진 상태로 전달**되므로, 앞 단계의 판정이 그 텍스트에 대해 최종입니다.

```mermaid theme={"dark"}
flowchart LR
    T["입력 텍스트"] --> K["1. Keyword<br/>정확 일치"]
    K -->|"남은 텍스트"| R["2. Regular Expression<br/>패턴 일치"]
    R -->|"남은 텍스트"| N["3. NER Entity<br/>AI 모델 탐지"]
    K & R & N --> D["전체 탐지 결과"]
    D --> F{"최종 action"}
    F -->|"BLOCKING 규칙 매치"| B["BLOCK"]
    F -->|"그 외 마스킹 매치"| M["MASK"]
    F -->|"매치 없음"| P["PASS"]
```

| 규칙 유형                  | 특성                        | 적합한 대상                               |
| ---------------------- | ------------------------- | ------------------------------------ |
| **Keyword**            | 대소문자 구분 정확 일치. 가장 빠르고 결정적 | 내부 코드네임, 알려진 안전 값(allowlist)         |
| **Regular Expression** | 패턴 일치. 결정적                | 형식이 고정된 식별자(주민등록번호, 전화번호, 카드번호, 이메일) |
| **NER Entity**         | AI 모델 탐지. 문맥 이해           | 문맥에 의존하는 정보(이름, 주소), 패턴이 놓치는 변형      |

## 우선순위 설계

규칙을 늘릴수록 규칙끼리의 상호작용이 결과를 좌우합니다. 세 가지 우선순위를 기억하세요.

1. **규칙 유형 우선순위: Keyword > Regular Expression > NER.** 앞 단계가 확정한 텍스트는 뒤 단계가 다시 판정하지 않습니다.
2. **PASSING은 allowlist입니다.** `policy_type: "PASSING"` 규칙이 매치한 텍스트는 **안전으로 확정**되어, 이후 어떤 마스킹·차단 규칙도 덮어쓰지 못합니다. 같은 유형 안에서 같은 텍스트를 두 규칙이 매치하면 **id가 낮은 규칙**이 우선합니다.
3. **action 우선순위: BLOCK > MASK > PASS.** 요청 안에서 규칙 하나라도 BLOCK을 결정하면, 다른 규칙이 마스킹만 했더라도 **요청 전체가 차단**됩니다.

기본 정책은 이 원칙을 그대로 활용합니다. 실데이터 마스킹 정규식(id 10 이상)보다 **앞선 id(0–9)에 테스트 패턴 PASSING 규칙**을 배치해, 문서 예시나 QA에 쓰이는 더미 값이 마스킹되지 않게 합니다:

```json theme={"dark"}
{
  "id": 0,
  "rule_id": "test_data:_resident_number_test_pattern",
  "regex": "(?<!\\d)(?:(\\d)\\1{5}[-\\. ]\\1{7}|(?:000000|111111|222222|333333|999999|123456|000101|010101|990101)[-\\. ](?:0000000|1111111|2222222|9999999|1234567))(?!\\d)",
  "description": "000000-0000000, 111111-1111111, 123456-1234567 (주민번호 테스트)",
  "policy_type": "PASSING",
  "mask_word": "TEST_DATA",
  "alert_message": "test data pattern - allowed"
}
```

같은 목적의 keyword 규칙도 있습니다 — 예시 문서의 단골 이름 `홍길동`을 PASSING으로 선점해, NER 이름 규칙이 마스킹하지 못하게 합니다. Keyword 단계가 NER보다 먼저 실행되기 때문에 확실하게 동작합니다.

## Regular Expression 규칙 작성

기본 정책의 주민등록번호 규칙입니다:

```json theme={"dark"}
{
  "id": 10,
  "rule_id": "resident_number:_all_gender_with_separators",
  "regex": "(?<!\\d)(?:[0-9]{2}(?:0[1-9]|1[0-2])(?:0[1-9]|[12][0-9]|3[01]))[-\\. ][1-8]\\d{6}(?!\\d)",
  "description": "901231-1234567, 901231-5234567, 901231.1234567, 901231 1234567",
  "policy_type": "MASKING",
  "mask_word": "RESIDENT_NUMBER",
  "alert_message": "resident number detected"
}
```

이 규칙이 보여주는 작성 관행:

* **숫자 경계를 고정하세요.** 앞뒤의 `(?<!\d)` / `(?!\d)`가 없으면 더 긴 숫자열(계좌번호, 바코드)의 **중간 조각**이 주민등록번호로 오탐됩니다.
* **`description`에 매치 예시를 기록하세요.** 이 필드는 탐지에 쓰이지 않지만, 나중에 규칙을 검토하는 사람이 패턴을 다시 해독하지 않아도 되게 합니다.
* **다른 규칙과의 충돌을 패턴에서 해결하세요.** 기본 정책의 휴대폰 규칙은 후행 `(?![-\.]?\d)` 가드를 두어, `013-12345678-901` 같은 은행 계좌번호의 앞부분을 전화번호로 선점하지 않게 합니다.

<Warning>
  빈 문자열에 매치될 수 있는 패턴(`\d*`, `secret|`)은 아무것도 탐지하지 못하고 건너뛰어집니다. 또한 패턴 앞뒤의 공백은 저장 시 정리되므로, 의도적인 경계 공백은 `\s`나 `[ ]`처럼 패턴 안에 표현하세요.
</Warning>

## NER Entity 규칙 작성

NER 규칙에서 AI 모델에 전달되는 필드는 **Name, Description, Positive Examples, Negative Examples** 네 가지뿐입니다. **Policy Type, Mask Word, Alert Message**는 탐지 *이후*의 처리만 결정하며 탐지 자체에는 영향을 주지 않습니다. 즉, 탐지 정확도를 좌우하는 것은 앞의 네 필드입니다.

기본 정책의 한국 주소 규칙을 설명에 맞춰 간추린 예시입니다. 이 규칙의 핵심은 **구체성의 경계**입니다 — 같은 "위치 이야기"라도 특정 개인·사업장을 찾아갈 수 있을 만큼 구체적이면 민감정보이고, 일반적인 위치 언급이면 아닙니다. 아래 각 절이 예시의 어느 부분을 가리키는지 보면서 읽어보세요:

```json theme={"dark"}
{
  "id": 2,
  "name": "주소 (한국)",
  "description": "특정 개인이나 사업장의 위치를 식별할 수 있는 한국 주소. 반드시 도로명+건물번호 또는 동/리+번지 같은 구체적 요소를 포함해야 하며, 동·호·층 같은 상세 정보가 따라올 수 있다. 랜드마크, 지하철역 출구, 건물명 단독, 번지 없는 행정구역 같은 일반적인 위치 언급은 주소가 아니다.",
  "policy_type": "MASKING",
  "mask_word": "ADDRESS",
  "alert_message": "주소 감지됨",
  "positive_examples": [
    "서울특별시 강남구 테헤란로 152 강남파이낸스센터 28층 →테헤란로 152 강남파이낸스센터 28층",
    "거주지: 서울 서초구 반포동 123-45 래미안 아파트 503동 201호 →반포동 123-45 래미안 아파트 503동 201호",
    "사무실 주소는 종로구 새문안로 76 에스타워 12F입니다 →새문안로 76 에스타워 12F"
  ],
  "negative_examples": [
    "강남역 11번 출구에서 만나",
    "스타벅스 종로점에서 보자",
    "신사동 가로수길 카페 추천"
  ]
}
```

### Description — 정의 + 구체성 조건

위 Description은 네 가지 요소를 순서대로 담고 있습니다: **정의**(개인·사업장 위치 식별), **필수 구체성 조건**("도로명+건물번호 또는 동/리+번지"), **허용 변형**(동·호·층 상세), 그리고 **제외 조건**(랜드마크, 지하철역 출구, 건물명 단독, 번지 없는 행정구역). 민감/비민감을 가르는 경계선을 Description이 문장으로 그어주는 것입니다 — 모델은 "이 경우는 아니다"라고 적힌 케이스에 NO라고 답하도록 지시받으므로, **제외 조건은 실제로 동작합니다**. 아래 시나리오 3이 이 경계가 실전에서 갈리는 모습을 그대로 보여줍니다.

### Positive Examples — 화살표 규약

모든 긍정 예시는 `문맥 →값` 형태로 작성합니다. 모델은 `→` 뒤가 **탐지해야 할 정확한 값**이라고 학습합니다.

* `→` 앞의 문장은 **언제** 발동할지(재현율)를, `→` 뒤의 값은 **정확한 출력 형태**(정밀도)를 가르칩니다.
* 값은 **마스킹 범위**도 가르칩니다. 위 세 예시 모두 값이 광역 단위(서울특별시, 서초구 등)를 빼고 **구체 요소부터** 시작합니다 — 규칙이 어디서부터 어디까지를 민감정보로 볼지, 예시의 값으로 알려주는 것입니다.
* 값은 입력에서 **다시 찾을 수 있어야** 합니다. 나타난 그대로 적는 것이 가장 안전하며(공백 차이 정도는 허용), 도로명과 건물명을 재배열하는 식으로 재구성한 값은 위치를 찾지 못해 버려집니다.
* 위 세 예시는 **실제로 등장할 표면 형태를 하나씩** 가르칩니다: 도로명 주소 기본형, 지번(동+번지)과 호실이 붙는 변형, 레이블 없이 서술문 안에 박힌 주소. 놓치면 안 되는 형태마다 예시를 하나씩 두세요.
* 잡으려는 데이터의 언어로 예시를 쓰세요 — 한국 주소라면 한국어 예시로.

### Negative Examples — 오탐 방어막

형식은 비슷하지만 **구체성이 부족한 근접 사례**를 담으세요. 위 세 negative는 각각 다른 오탐 경로를 막습니다: `강남역 11번 출구`(만남 장소로 쓰이는 랜드마크 — 숫자가 있어도 주소가 아님), `스타벅스 종로점`(상호+지점명), `신사동 가로수길`(번지 없는 동네 이름). 셋 다 Description의 제외 조건과 짝을 이룹니다. NER 규칙은 소규모 배치로 함께 스크리닝되므로, 한 규칙의 부실한 negative가 이웃 규칙의 과탐까지 유발할 수 있습니다.

## Keyword 규칙 작성

Keyword는 대소문자를 구분하는 **정확 일치**입니다. 문맥을 보지 않으므로 짧고 일반적인 단어는 피하세요.

* **좋은 BLOCKING 키워드**: `PROJECT-ORION` 같은 내부 코드네임 — 어디서 나오든 그 자체로 민감.
* **좋은 PASSING 키워드**: 기본 정책의 `홍길동` — 알려진 안전 값을 선점해 뒤 단계가 건드리지 못하게 하는 allowlist.
* **나쁜 키워드**: `계정` 같은 일반 명사 — 등장하는 모든 곳에서 무차별 발동.

## 예시 시나리오

기본 PII 정책이 할당된 Guardian에 요청을 보냈을 때의 동작입니다.

### 시나리오 1 — 휴대폰 번호 마스킹

> "제 번호는 010-2543-2513입니다. 010.5364.1451로도 연락 돼요."

휴대폰 정규식이 두 값을 모두 매치합니다. 결과는 `"action": "MASK"`, 본문은 `제 번호는 [PHONE_NUMBER_1]입니다. [PHONE_NUMBER_2]로도 연락 돼요.` — 마스크 단어에는 등장 순서대로 인덱스가 붙습니다.

### 시나리오 2 — 테스트 데이터는 통과, 실데이터는 마스킹

> "테스트 계정 주민번호는 990101-1234567, 실계정은 901231-1234567입니다."

`990101-1234567`은 날짜가 유효해 주민등록번호 마스킹 규칙(id 10)에도 매치되지만, 잘 알려진 테스트 값이라 PASSING 테스트 패턴(id 0)이 **먼저 선점**해 그대로 통과합니다. `901231-1234567`만 마스킹되어 결과는 `... 990101-1234567, 실계정은 [RESIDENT_NUMBER_1]입니다.` — allowlist 규칙이 실데이터 규칙보다 낮은 id를 가진 이유입니다.

### 시나리오 3 — 구체적인 주소만 민감정보

> "미팅은 강남역 11번 출구 앞 카페에서 해요. 계약서는 서울 강남구 테헤란로 152 강남파이낸스센터 28층으로 보내주세요."

같은 문장에 위치 언급이 두 번 나오지만 판정이 갈립니다. `강남역 11번 출구`는 숫자가 있어도 만남 장소 수준의 일반 언급이라 — Description의 제외 조건과 negative example이 학습시킨 대로 — 통과합니다. 도로명+건물번호가 있는 구체 주소만 마스킹되고, 값이 가르친 대로 마스킹 범위는 구체 요소부터 시작합니다: `미팅은 강남역 11번 출구 앞 카페에서 해요. 계약서는 서울 강남구 [ADDRESS_1]으로 보내주세요.`

### 시나리오 4 — BLOCK은 전부를 이깁니다

마스킹 규칙 열 개가 매치하고 BLOCKING 규칙 하나가 매치하면, 결과는 `"action": "BLOCK"`이고 마스킹된 본문은 반환되지 않습니다. 유출 자체를 막아야 하는 값(사내 기밀 코드네임 등)에만 BLOCKING을 쓰고, 나머지는 MASKING으로 두는 것이 좋습니다.

## 체크리스트

* 정규식에 숫자·단어 경계(`(?<!\d)`, `(?!\d)`)를 넣었는가?
* 테스트·예시 값이 있다면 PASSING 규칙으로 먼저 선점했는가?
* NER Description에 정의 + 구체성·문맥 조건 + 제외 조건을 적었는가?
* Positive Examples를 `문맥 →값` 형태로, 값이 마스킹 범위를 정확히 가리키게 적었는가?
* Negative Examples에 구체성이 부족한 근접 사례를 담았는가?
* BLOCKING은 정말 차단이 필요한 규칙에만 썼는가?

<Tip>
  규칙 정의 전체는 Starfort 기본 PII 정책에서 볼 수 있습니다 — 콘솔의 **JSON** 편집기로 불러온 뒤 필요한 규칙만 남기고 다듬는 것이 가장 빠른 시작 방법입니다. 콘솔 정책 페이지의 **TIP** 버튼에서도 이 가이드의 요약을 볼 수 있습니다.
</Tip>

<Warning>
  규칙 수와 예시 분량은 곧 탐지 비용입니다. 모든 호출에 규칙 정의 전체가 전달되므로, 지나치게 장황한 정책은 응답을 느리게 하고 극단적으로는 모델 입력 한도를 넘겨 요청을 실패시킵니다. 규칙당 날카로운 예시 5\~10개가 망라식 나열보다 낫습니다.
</Warning>
