만들어 보기 · 글 11편
만들어 보기이 글에서 다루는 것문서 텍스트에서 주민등록번호·전화번호·이메일 같은 개인정보 후보를 브라우저 안에서 찾아 가려 주는 도구를 만들고, 로컬 모델로 보완하는 방향을 정리합니다.
무엇을 만드나
문서 내용을 붙여 넣고 점검을 누르면 개인정보로 보이는 부분을 찾아 주는 도구입니다. 결과로 네 가지가 나옵니다.
- 찾은 항목 목록(유형, 위험 수준, 원문과 가린 모습)
- 위험도 점수(0~100)와 권장 조치
- 가려진 문서 미리보기
- HTML로 저장되는 점검 보고서
이 도구에는 AI 모델이 들어 있지 않습니다. 정해진 규칙(정규식)으로만 찾습니다. 이 시리즈에 넣은 이유는 두 가지입니다. 첫째, 처리가 전부 브라우저 안에서 끝나므로 문서가 PC 밖으로 나가지 않습니다. 둘째, 로컬 모델이든 외부 AI 서비스든 문서를 넘기기 전에 거치는 앞 단계로 쓰기 좋습니다.
이럴 때 쓸모가 있습니다.
- 문서를 메일로 보내거나 외부 AI 서비스에 넣기 전에 훑어볼 때
- 보고서, 회의록, 답변 초안에 개인정보가 남았는지 확인할 때
- 가린 사본을 빨리 만들어야 할 때
준비물
| 항목 | 내용 |
|---|---|
| 브라우저 | 크롬, 엣지 등 |
| 그 밖 | 없음. 서버, Python, Ollama 모두 필요 없습니다 |
index.html을 더블클릭해 열면 바로 동작합니다.
구조
| 파일 | 역할 |
|---|---|
index.html |
입력 칸, 결과 목록, 위험도, 미리보기 |
app.js |
탐지 규칙, 가리기 함수, 점수 계산, 보고서 생성 |
styles.css |
모양 |
붙여 넣은 텍스트
→ 규칙마다 정규식으로 후보 찾기
→ 후보마다 가린 모습 만들기
→ 점수 합산(최대 100) → 권장 조치 결정
→ 원문에서 후보를 가린 모습으로 바꿔 미리보기
→ HTML 보고서로 저장
탐지 규칙은 여섯 가지입니다.
| 유형 | 수준 | 점수 | 가리는 방식 |
|---|---|---|---|
| 주민등록번호 | 높음 | 35 | 뒤 여섯 자리를 *로 |
| 카드번호 후보 | 높음 | 28 | 끝 네 자리만 남기도록 작성 |
| 계좌번호 후보 | 중간 | 16 | 끝 네 자리만 남기도록 작성 |
| 전화번호 | 중간 | 14 | 가운데 자리를 ****로 |
| 이메일 | 중간 | 12 | 앞 두 글자만 남김 |
| 주소 후보 | 낮음 | 8 | 앞 네 글자만 남김 |
계좌번호, 카드번호, 주소에 “후보”가 붙은 것은 숫자와 글자의 모양만 보고 찾기 때문입니다. 주문번호나 관리번호도 같은 모양이면 잡힙니다.
에이전트에게 줄 요청
개인정보 점검 도구를 HTML + JS + CSS 세 파일로 만들어 줘.
서버 없이 index.html을 열면 동작하고, 외부 라이브러리와 외부 호출은 쓰지 않는다.
- 텍스트를 붙여 넣는 칸, 글자 수와 줄 수 표시.
- 규칙 목록(rules 배열): id, 이름, 위험 수준, 점수, 정규식, 가리기 함수.
주민등록번호, 전화번호, 이메일, 계좌번호 후보, 카드번호 후보, 주소 후보.
- 점검하면 찾은 항목을 문서에 나온 순서대로 보여 준다(유형, 수준, 원문 → 가린 모습).
- 점수는 항목 점수의 합, 최대 100. 70 이상 "반출 중지", 35 이상 "수정 필요", 그 아래 "확인 권장".
- 가려진 문서 미리보기를 보여 준다.
- 점검 보고서를 HTML 파일로 저장한다(탐지 목록 표 + 가린 문서).
- 화면에 넣는 모든 글은 HTML 특수문자를 바꿔서 넣는다.
- 지어낸 예시 문서를 채워 주는 "샘플" 단추와 "비우기", "보고서 저장" 단추를 둔다.
핵심 코드
규칙은 배열 하나로 관리합니다. 새 유형을 넣을 때 이 배열에 한 덩어리만 더하면 됩니다.
const rules = [
{
id: "rrn",
label: "주민등록번호",
severity: "high",
score: 35,
pattern: /\b\d{6}[- ]?[1-4]\d{6}\b/g,
mask: (value) => value.replace(/(\d{6})[- ]?([1-4])\d{6}/, "$1-$2******"),
},
{
id: "email",
label: "이메일",
severity: "medium",
score: 12,
pattern: /\b[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}\b/gi,
mask: (value) => value.replace(/^(.{2}).*(@.*)$/u, "$1***$2"),
},
// 전화번호, 계좌번호 후보, 카드번호 후보, 주소 후보 ...
];
점검은 규칙을 차례로 돌며 일치하는 부분을 모읍니다.
for (const rule of rules) {
const matches = [...text.matchAll(rule.pattern)];
for (const match of matches) {
const value = match[0];
findings.push({
type: rule.label, severity: rule.severity, value,
masked: rule.mask(value), index: match.index, score: rule.score,
});
}
}
findings.sort((a, b) => a.index - b.index);
가릴 때는 긴 항목부터 바꿉니다. 짧은 항목이 긴 항목의 일부일 때 먼저 바뀌어 버리는 일을 막습니다.
for (const item of [...findings].sort((a, b) => b.value.length - a.value.length)) {
masked = masked.replaceAll(item.value, item.masked);
}
const score = Math.min(100, findings.reduce((sum, item) => sum + item.score, 0));
보고서는 브라우저 안에서 HTML 문자열을 만들어 파일로 내려받습니다. 서버로 보내지 않습니다.
const blob = new Blob([html], { type: "text/html;charset=utf-8" });
const link = document.createElement("a");
link.href = URL.createObjectURL(blob);
link.download = `privacy-check-report-${Date.now()}.html`;
link.click();
실행하고 확인하기
index.html을 엽니다.- 지어낸 시험 문서를 붙여 넣습니다. 실제 자료로 시험하지 않습니다. 예:
담당자 연락처: 010-0000-0000
이메일: tester@example.com
등록번호: 000000-1000000
- 점검을 눌러 세 항목이 잡히는지, 가린 모습이 표의 방식과 같은지 봅니다. 탐지와 가리기는 따로 확인합니다. 찾기는 했는데 가려지지 않은 값이 있을 수 있습니다.
- 일부러 헷갈리는 값을 넣어 봅니다. 예: 주문번호
1234-5678-9012-3456. 카드번호 후보로 잡히는 것이 정상입니다. - 보고서를 저장해 열어 봅니다. 보고서의 탐지 목록에는 원문 값이 그대로 들어가므로, 보고서 파일도 원본과 같은 수준으로 다룹니다.
- 브라우저 개발자 도구의 네트워크 탭을 열고 점검을 눌러, 밖으로 나가는 요청이 없는지 확인합니다.
자주 생기는 문제
| 증상 | 원인과 해결 |
|---|---|
| 개인정보가 아닌 번호가 잡힘 | 모양만 보는 규칙의 한계입니다. 사람이 목록을 보고 판단합니다 |
| 이름이 잡히지 않음 | 사람 이름은 정해진 모양이 없어 규칙에 없습니다. 아래 확장 항목을 봅니다 |
| 하이픈으로 나뉜 카드번호가 가려지지 않음 | 가리기 식이 “뒤에 숫자 네 개가 바로 이어지는 숫자”만 *로 바꾸게 되어 있습니다. 네 자리씩 끊어 쓴 번호에서는 조건에 맞는 숫자가 없어 그대로 남습니다. 구분 기호를 뺀 뒤 가리고 다시 넣도록 고칩니다 |
| 띄어 쓴 번호를 놓침 | “010 0000 0000”처럼 구분이 다른 표기를 규칙에 더합니다 |
| 주소가 일부만 가려짐 | 주소 규칙은 “시·도 이름으로 시작해 로, 길, 동으로 끝나는 부분”만 잡습니다. 상세 주소는 직접 확인합니다 |
“탐지 0건”은 “개인정보가 없다”는 뜻이 아닙니다. 규칙에 걸린 것이 없다는 뜻입니다. 화면 문구도 “탐지된 개인정보 후보가 없습니다”로 되어 있습니다.
확장하기: 로컬 모델로 후보 다시 보기
여기서부터는 아직 만들지 않은, 설계 단계의 확장입니다. 규칙이 잡은 후보를 Ollama의 작은 모델에 넘겨 문맥을 보고 다시 판단하게 하는 방식입니다.
| 단계 | 내용 |
|---|---|
| 1 | 규칙으로 후보를 찾습니다(지금의 기능) |
| 2 | 후보마다 앞뒤 한두 문장을 잘라 모델에 보냅니다 |
| 3 | 모델이 “개인정보 / 아님 / 불확실” 중 하나와 이유 한 줄을 답합니다 |
| 4 | 목록에 모델의 판단을 덧붙입니다. 가릴지 말지는 사람이 정합니다 |
만들 때 지킬 점은 세 가지입니다.
- 모델은 후보를 지우는 쪽이 아니라 표시를 덧붙이는 쪽으로만 씁니다. 모델이 “아님”이라고 해도 목록에서 빼지 않습니다.
- 이름처럼 규칙으로 못 찾는 유형은 문서 전체를 모델에 보내 “사람 이름으로 보이는 낱말”을 뽑게 하는 별도 단계로 둡니다. 놓치는 것이 있다는 전제로 씁니다.
- Ollama를 부르기 시작하면
index.html을 더블클릭해 여는 방식으로는 호출이 막힐 수 있습니다. CSV 분류 도구처럼 간이 서버로 띄웁니다.
그 밖의 확장으로는 텍스트 파일 직접 불러오기, 우리 조직의 처리 기준표에 맞춘 점수와 조치 문구가 있습니다. CSV 파일을 통째로 가리는 창 프로그램으로 만들고 싶다면 요약 창 프로그램의 뼈대에 같은 규칙을 넣으면 됩니다.