sumunjang 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- sumunjang-0.1.0/.github/workflows/ci.yml +94 -0
- sumunjang-0.1.0/.gitignore +15 -0
- sumunjang-0.1.0/LICENSE +202 -0
- sumunjang-0.1.0/PKG-INFO +269 -0
- sumunjang-0.1.0/README.md +245 -0
- sumunjang-0.1.0/SBOM.md +68 -0
- sumunjang-0.1.0/goldenset/01-/354/227/205/353/254/264/353/251/224/354/235/274.txt +16 -0
- sumunjang-0.1.0/goldenset/02-/354/236/245/354/225/240/353/241/234/352/267/270.txt +12 -0
- sumunjang-0.1.0/goldenset/03-/352/263/240/352/260/235/353/254/270/354/235/230.txt +13 -0
- sumunjang-0.1.0/goldenset/04-/352/263/204/354/225/275/354/204/234/353/260/234/354/267/214.txt +19 -0
- sumunjang-0.1.0/goldenset/05-/354/204/244/354/240/225/355/214/214/354/235/274.txt +16 -0
- sumunjang-0.1.0/goldenset-gaps/G1-/352/263/204/354/242/214/354/231/200/354/246/235/354/204/234/353/262/210/355/230/270.txt +18 -0
- sumunjang-0.1.0/goldenset-gaps/G2-/354/236/220/354/234/240/354/204/234/354/210/240.txt +11 -0
- sumunjang-0.1.0/goldenset-gaps/G3-/355/221/234/352/270/260/355/225/234/352/263/204.txt +15 -0
- sumunjang-0.1.0/goldenset-hard/06-/355/221/234/352/270/260/353/263/200/355/230/225.txt +16 -0
- sumunjang-0.1.0/goldenset-hard/08-/354/230/244/355/203/220/355/225/250/354/240/225.txt +37 -0
- sumunjang-0.1.0/goldenset-hard/09-/354/232/264/354/230/201/353/241/234/352/267/270.txt +18 -0
- sumunjang-0.1.0/pyproject.toml +54 -0
- sumunjang-0.1.0/src/sumunjang/__init__.py +0 -0
- sumunjang-0.1.0/src/sumunjang/anthropic.py +80 -0
- sumunjang-0.1.0/src/sumunjang/cli.py +294 -0
- sumunjang-0.1.0/src/sumunjang/detect.py +341 -0
- sumunjang-0.1.0/src/sumunjang/goldenset.py +109 -0
- sumunjang-0.1.0/src/sumunjang/mask.py +176 -0
- sumunjang-0.1.0/src/sumunjang/proxy.py +249 -0
- sumunjang-0.1.0/tests/test_anthropic.py +80 -0
- sumunjang-0.1.0/tests/test_cli.py +107 -0
- sumunjang-0.1.0/tests/test_detect.py +381 -0
- sumunjang-0.1.0/tests/test_goldenset.py +69 -0
- sumunjang-0.1.0/tests/test_leak.py +108 -0
- sumunjang-0.1.0/tests/test_mask.py +97 -0
- sumunjang-0.1.0/tests/test_proxy.py +252 -0
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
# 검증 단계에서 심사자가 클린 환경에서 직접 돌린다. 그 환경을 그대로 흉내낸다 —
|
|
4
|
+
# API 키도 네트워크도 없이 전체 테스트가 돌아야 한다.
|
|
5
|
+
on:
|
|
6
|
+
push:
|
|
7
|
+
branches: [main]
|
|
8
|
+
pull_request:
|
|
9
|
+
workflow_dispatch:
|
|
10
|
+
|
|
11
|
+
permissions:
|
|
12
|
+
contents: read
|
|
13
|
+
|
|
14
|
+
jobs:
|
|
15
|
+
test:
|
|
16
|
+
name: 테스트 (Python ${{ matrix.python }})
|
|
17
|
+
runs-on: ubuntu-latest
|
|
18
|
+
strategy:
|
|
19
|
+
fail-fast: false
|
|
20
|
+
matrix:
|
|
21
|
+
# pyproject 의 requires-python = ">=3.10" 을 실제로 시험한다.
|
|
22
|
+
# 적어 놓기만 하고 확인하지 않으면 그것은 주장이지 사실이 아니다.
|
|
23
|
+
python: ["3.10", "3.11", "3.12", "3.13"]
|
|
24
|
+
|
|
25
|
+
steps:
|
|
26
|
+
- uses: actions/checkout@v4
|
|
27
|
+
|
|
28
|
+
- uses: astral-sh/setup-uv@v5
|
|
29
|
+
with:
|
|
30
|
+
enable-cache: true
|
|
31
|
+
|
|
32
|
+
- name: 의존성 설치
|
|
33
|
+
run: uv venv && uv pip install -e ".[dev]"
|
|
34
|
+
|
|
35
|
+
- name: 테스트
|
|
36
|
+
# API 키를 명시적으로 비워 둔다. 테스트가 실수로 네트워크에 나가려 하면
|
|
37
|
+
# 조용히 통과하는 대신 실패해야 한다.
|
|
38
|
+
env:
|
|
39
|
+
ANTHROPIC_API_KEY: ""
|
|
40
|
+
run: uv run pytest -q --cov=sumunjang --cov-report=term-missing
|
|
41
|
+
|
|
42
|
+
- name: 골든셋 채점 (README 수치가 재현되는지)
|
|
43
|
+
run: uv run sumunjang report
|
|
44
|
+
|
|
45
|
+
package:
|
|
46
|
+
name: 패키지 (설치본이 저장소 없이 동작하는가)
|
|
47
|
+
runs-on: ubuntu-latest
|
|
48
|
+
steps:
|
|
49
|
+
- uses: actions/checkout@v4
|
|
50
|
+
- uses: astral-sh/setup-uv@v5
|
|
51
|
+
|
|
52
|
+
- name: 빌드
|
|
53
|
+
run: uv build
|
|
54
|
+
|
|
55
|
+
- name: 저장소 밖에서 설치·실행
|
|
56
|
+
# 검증기관은 클론하지 않고 설치본만 받을 수 있다. 골든셋이 wheel 에
|
|
57
|
+
# 실려 있지 않으면 report 가 여기서 깨진다.
|
|
58
|
+
run: |
|
|
59
|
+
mkdir -p /tmp/clean && cd /tmp/clean
|
|
60
|
+
uv venv
|
|
61
|
+
uv pip install --python .venv/bin/python "$GITHUB_WORKSPACE"/dist/*.whl
|
|
62
|
+
echo "고객 900101-1234568 / 성명: 최윤서" | .venv/bin/sumunjang mask -
|
|
63
|
+
.venv/bin/sumunjang report
|
|
64
|
+
|
|
65
|
+
license:
|
|
66
|
+
name: 라이선스 (의존성 계보)
|
|
67
|
+
runs-on: ubuntu-latest
|
|
68
|
+
steps:
|
|
69
|
+
- uses: actions/checkout@v4
|
|
70
|
+
- uses: astral-sh/setup-uv@v5
|
|
71
|
+
|
|
72
|
+
- name: 런타임 의존성 라이선스 수집
|
|
73
|
+
# 대회에 라이선스 검증 단계가 있다. SBOM.md 를 손으로 관리하면 반드시
|
|
74
|
+
# 어긋나므로 실제 설치된 것에서 뽑는다. 개발 의존성은 배포물에 들어가지
|
|
75
|
+
# 않으므로 런타임만 설치한다.
|
|
76
|
+
run: |
|
|
77
|
+
uv venv && uv pip install -e . pip-licenses
|
|
78
|
+
uv run pip-licenses --format=markdown --with-urls --order=license
|
|
79
|
+
|
|
80
|
+
- name: 허용 목록 밖의 라이선스 차단
|
|
81
|
+
# 금지 목록이 아니라 허용 목록으로 막는다. "GPL 을 걸러낸다" 는 식으로
|
|
82
|
+
# 짜면 목록에 없는 새 라이선스가 조용히 통과한다. 허용 목록은 모르는
|
|
83
|
+
# 것이 들어오는 순간 실패하므로, 심사 전에 사람이 먼저 본다.
|
|
84
|
+
#
|
|
85
|
+
# 같은 라이선스가 두 표기로 나오는 것은 패키지마다 분류자(classifier)를
|
|
86
|
+
# 쓰기도 하고 SPDX 를 쓰기도 하기 때문이다. 둘 다 적는다.
|
|
87
|
+
run: |
|
|
88
|
+
uv run pip-licenses --allow-only=\
|
|
89
|
+
"Apache Software License;\
|
|
90
|
+
BSD License;BSD-3-Clause;\
|
|
91
|
+
MIT;MIT License;\
|
|
92
|
+
Mozilla Public License 2.0 (MPL 2.0);\
|
|
93
|
+
PSF-2.0;Python Software Foundation License" \
|
|
94
|
+
--ignore-packages pip-licenses prettytable wcwidth tomli
|
sumunjang-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
|
|
2
|
+
Apache License
|
|
3
|
+
Version 2.0, January 2004
|
|
4
|
+
http://www.apache.org/licenses/
|
|
5
|
+
|
|
6
|
+
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
|
7
|
+
|
|
8
|
+
1. Definitions.
|
|
9
|
+
|
|
10
|
+
"License" shall mean the terms and conditions for use, reproduction,
|
|
11
|
+
and distribution as defined by Sections 1 through 9 of this document.
|
|
12
|
+
|
|
13
|
+
"Licensor" shall mean the copyright owner or entity authorized by
|
|
14
|
+
the copyright owner that is granting the License.
|
|
15
|
+
|
|
16
|
+
"Legal Entity" shall mean the union of the acting entity and all
|
|
17
|
+
other entities that control, are controlled by, or are under common
|
|
18
|
+
control with that entity. For the purposes of this definition,
|
|
19
|
+
"control" means (i) the power, direct or indirect, to cause the
|
|
20
|
+
direction or management of such entity, whether by contract or
|
|
21
|
+
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
|
22
|
+
outstanding shares, or (iii) beneficial ownership of such entity.
|
|
23
|
+
|
|
24
|
+
"You" (or "Your") shall mean an individual or Legal Entity
|
|
25
|
+
exercising permissions granted by this License.
|
|
26
|
+
|
|
27
|
+
"Source" form shall mean the preferred form for making modifications,
|
|
28
|
+
including but not limited to software source code, documentation
|
|
29
|
+
source, and configuration files.
|
|
30
|
+
|
|
31
|
+
"Object" form shall mean any form resulting from mechanical
|
|
32
|
+
transformation or translation of a Source form, including but
|
|
33
|
+
not limited to compiled object code, generated documentation,
|
|
34
|
+
and conversions to other media types.
|
|
35
|
+
|
|
36
|
+
"Work" shall mean the work of authorship, whether in Source or
|
|
37
|
+
Object form, made available under the License, as indicated by a
|
|
38
|
+
copyright notice that is included in or attached to the work
|
|
39
|
+
(an example is provided in the Appendix below).
|
|
40
|
+
|
|
41
|
+
"Derivative Works" shall mean any work, whether in Source or Object
|
|
42
|
+
form, that is based on (or derived from) the Work and for which the
|
|
43
|
+
editorial revisions, annotations, elaborations, or other modifications
|
|
44
|
+
represent, as a whole, an original work of authorship. For the purposes
|
|
45
|
+
of this License, Derivative Works shall not include works that remain
|
|
46
|
+
separable from, or merely link (or bind by name) to the interfaces of,
|
|
47
|
+
the Work and Derivative Works thereof.
|
|
48
|
+
|
|
49
|
+
"Contribution" shall mean any work of authorship, including
|
|
50
|
+
the original version of the Work and any modifications or additions
|
|
51
|
+
to that Work or Derivative Works thereof, that is intentionally
|
|
52
|
+
submitted to Licensor for inclusion in the Work by the copyright owner
|
|
53
|
+
or by an individual or Legal Entity authorized to submit on behalf of
|
|
54
|
+
the copyright owner. For the purposes of this definition, "submitted"
|
|
55
|
+
means any form of electronic, verbal, or written communication sent
|
|
56
|
+
to the Licensor or its representatives, including but not limited to
|
|
57
|
+
communication on electronic mailing lists, source code control systems,
|
|
58
|
+
and issue tracking systems that are managed by, or on behalf of, the
|
|
59
|
+
Licensor for the purpose of discussing and improving the Work, but
|
|
60
|
+
excluding communication that is conspicuously marked or otherwise
|
|
61
|
+
designated in writing by the copyright owner as "Not a Contribution."
|
|
62
|
+
|
|
63
|
+
"Contributor" shall mean Licensor and any individual or Legal Entity
|
|
64
|
+
on behalf of whom a Contribution has been received by Licensor and
|
|
65
|
+
subsequently incorporated within the Work.
|
|
66
|
+
|
|
67
|
+
2. Grant of Copyright License. Subject to the terms and conditions of
|
|
68
|
+
this License, each Contributor hereby grants to You a perpetual,
|
|
69
|
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
70
|
+
copyright license to reproduce, prepare Derivative Works of,
|
|
71
|
+
publicly display, publicly perform, sublicense, and distribute the
|
|
72
|
+
Work and such Derivative Works in Source or Object form.
|
|
73
|
+
|
|
74
|
+
3. Grant of Patent License. Subject to the terms and conditions of
|
|
75
|
+
this License, each Contributor hereby grants to You a perpetual,
|
|
76
|
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
77
|
+
(except as stated in this section) patent license to make, have made,
|
|
78
|
+
use, offer to sell, sell, import, and otherwise transfer the Work,
|
|
79
|
+
where such license applies only to those patent claims licensable
|
|
80
|
+
by such Contributor that are necessarily infringed by their
|
|
81
|
+
Contribution(s) alone or by combination of their Contribution(s)
|
|
82
|
+
with the Work to which such Contribution(s) was submitted. If You
|
|
83
|
+
institute patent litigation against any entity (including a
|
|
84
|
+
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
|
85
|
+
or a Contribution incorporated within the Work constitutes direct
|
|
86
|
+
or contributory patent infringement, then any patent licenses
|
|
87
|
+
granted to You under this License for that Work shall terminate
|
|
88
|
+
as of the date such litigation is filed.
|
|
89
|
+
|
|
90
|
+
4. Redistribution. You may reproduce and distribute copies of the
|
|
91
|
+
Work or Derivative Works thereof in any medium, with or without
|
|
92
|
+
modifications, and in Source or Object form, provided that You
|
|
93
|
+
meet the following conditions:
|
|
94
|
+
|
|
95
|
+
(a) You must give any other recipients of the Work or
|
|
96
|
+
Derivative Works a copy of this License; and
|
|
97
|
+
|
|
98
|
+
(b) You must cause any modified files to carry prominent notices
|
|
99
|
+
stating that You changed the files; and
|
|
100
|
+
|
|
101
|
+
(c) You must retain, in the Source form of any Derivative Works
|
|
102
|
+
that You distribute, all copyright, patent, trademark, and
|
|
103
|
+
attribution notices from the Source form of the Work,
|
|
104
|
+
excluding those notices that do not pertain to any part of
|
|
105
|
+
the Derivative Works; and
|
|
106
|
+
|
|
107
|
+
(d) If the Work includes a "NOTICE" text file as part of its
|
|
108
|
+
distribution, then any Derivative Works that You distribute must
|
|
109
|
+
include a readable copy of the attribution notices contained
|
|
110
|
+
within such NOTICE file, excluding those notices that do not
|
|
111
|
+
pertain to any part of the Derivative Works, in at least one
|
|
112
|
+
of the following places: within a NOTICE text file distributed
|
|
113
|
+
as part of the Derivative Works; within the Source form or
|
|
114
|
+
documentation, if provided along with the Derivative Works; or,
|
|
115
|
+
within a display generated by the Derivative Works, if and
|
|
116
|
+
wherever such third-party notices normally appear. The contents
|
|
117
|
+
of the NOTICE file are for informational purposes only and
|
|
118
|
+
do not modify the License. You may add Your own attribution
|
|
119
|
+
notices within Derivative Works that You distribute, alongside
|
|
120
|
+
or as an addendum to the NOTICE text from the Work, provided
|
|
121
|
+
that such additional attribution notices cannot be construed
|
|
122
|
+
as modifying the License.
|
|
123
|
+
|
|
124
|
+
You may add Your own copyright statement to Your modifications and
|
|
125
|
+
may provide additional or different license terms and conditions
|
|
126
|
+
for use, reproduction, or distribution of Your modifications, or
|
|
127
|
+
for any such Derivative Works as a whole, provided Your use,
|
|
128
|
+
reproduction, and distribution of the Work otherwise complies with
|
|
129
|
+
the conditions stated in this License.
|
|
130
|
+
|
|
131
|
+
5. Submission of Contributions. Unless You explicitly state otherwise,
|
|
132
|
+
any Contribution intentionally submitted for inclusion in the Work
|
|
133
|
+
by You to the Licensor shall be under the terms and conditions of
|
|
134
|
+
this License, without any additional terms or conditions.
|
|
135
|
+
Notwithstanding the above, nothing herein shall supersede or modify
|
|
136
|
+
the terms of any separate license agreement you may have executed
|
|
137
|
+
with Licensor regarding such Contributions.
|
|
138
|
+
|
|
139
|
+
6. Trademarks. This License does not grant permission to use the trade
|
|
140
|
+
names, trademarks, service marks, or product names of the Licensor,
|
|
141
|
+
except as required for reasonable and customary use in describing the
|
|
142
|
+
origin of the Work and reproducing the content of the NOTICE file.
|
|
143
|
+
|
|
144
|
+
7. Disclaimer of Warranty. Unless required by applicable law or
|
|
145
|
+
agreed to in writing, Licensor provides the Work (and each
|
|
146
|
+
Contributor provides its Contributions) on an "AS IS" BASIS,
|
|
147
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
|
148
|
+
implied, including, without limitation, any warranties or conditions
|
|
149
|
+
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
|
150
|
+
PARTICULAR PURPOSE. You are solely responsible for determining the
|
|
151
|
+
appropriateness of using or redistributing the Work and assume any
|
|
152
|
+
risks associated with Your exercise of permissions under this License.
|
|
153
|
+
|
|
154
|
+
8. Limitation of Liability. In no event and under no legal theory,
|
|
155
|
+
whether in tort (including negligence), contract, or otherwise,
|
|
156
|
+
unless required by applicable law (such as deliberate and grossly
|
|
157
|
+
negligent acts) or agreed to in writing, shall any Contributor be
|
|
158
|
+
liable to You for damages, including any direct, indirect, special,
|
|
159
|
+
incidental, or consequential damages of any character arising as a
|
|
160
|
+
result of this License or out of the use or inability to use the
|
|
161
|
+
Work (including but not limited to damages for loss of goodwill,
|
|
162
|
+
work stoppage, computer failure or malfunction, or any and all
|
|
163
|
+
other commercial damages or losses), even if such Contributor
|
|
164
|
+
has been advised of the possibility of such damages.
|
|
165
|
+
|
|
166
|
+
9. Accepting Warranty or Additional Liability. While redistributing
|
|
167
|
+
the Work or Derivative Works thereof, You may choose to offer,
|
|
168
|
+
and charge a fee for, acceptance of support, warranty, indemnity,
|
|
169
|
+
or other liability obligations and/or rights consistent with this
|
|
170
|
+
License. However, in accepting such obligations, You may act only
|
|
171
|
+
on Your own behalf and on Your sole responsibility, not on behalf
|
|
172
|
+
of any other Contributor, and only if You agree to indemnify,
|
|
173
|
+
defend, and hold each Contributor harmless for any liability
|
|
174
|
+
incurred by, or claims asserted against, such Contributor by reason
|
|
175
|
+
of your accepting any such warranty or additional liability.
|
|
176
|
+
|
|
177
|
+
END OF TERMS AND CONDITIONS
|
|
178
|
+
|
|
179
|
+
APPENDIX: How to apply the Apache License to your work.
|
|
180
|
+
|
|
181
|
+
To apply the Apache License to your work, attach the following
|
|
182
|
+
boilerplate notice, with the fields enclosed by brackets "[]"
|
|
183
|
+
replaced with your own identifying information. (Don't include
|
|
184
|
+
the brackets!) The text should be enclosed in the appropriate
|
|
185
|
+
comment syntax for the file format. We also recommend that a
|
|
186
|
+
file or class name and description of purpose be included on the
|
|
187
|
+
same "printed page" as the copyright notice for easier
|
|
188
|
+
identification within third-party archives.
|
|
189
|
+
|
|
190
|
+
Copyright [yyyy] [name of copyright owner]
|
|
191
|
+
|
|
192
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
193
|
+
you may not use this file except in compliance with the License.
|
|
194
|
+
You may obtain a copy of the License at
|
|
195
|
+
|
|
196
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
197
|
+
|
|
198
|
+
Unless required by applicable law or agreed to in writing, software
|
|
199
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
200
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
201
|
+
See the License for the specific language governing permissions and
|
|
202
|
+
limitations under the License.
|
sumunjang-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: sumunjang
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: AI API 경계에서 한국 개인정보를 막는 로컬 게이트웨이 — base_url 한 줄로 붙이는 온디바이스 마스킹 프록시
|
|
5
|
+
Project-URL: Homepage, https://github.com/HaChanho/sumunjang
|
|
6
|
+
Project-URL: Repository, https://github.com/HaChanho/sumunjang
|
|
7
|
+
Author: 문지기 (sumunjang)
|
|
8
|
+
License: Apache-2.0
|
|
9
|
+
License-File: LICENSE
|
|
10
|
+
Keywords: korean,llm,masking,pii,privacy,proxy,가명처리,개인정보
|
|
11
|
+
Classifier: Development Status :: 3 - Alpha
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: License :: OSI Approved :: Apache Software License
|
|
14
|
+
Classifier: Natural Language :: Korean
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Topic :: Security
|
|
17
|
+
Requires-Python: >=3.10
|
|
18
|
+
Requires-Dist: httpx>=0.27
|
|
19
|
+
Requires-Dist: uvicorn>=0.30
|
|
20
|
+
Provides-Extra: dev
|
|
21
|
+
Requires-Dist: pytest-cov>=5.0; extra == 'dev'
|
|
22
|
+
Requires-Dist: pytest>=8.0; extra == 'dev'
|
|
23
|
+
Description-Content-Type: text/markdown
|
|
24
|
+
|
|
25
|
+
# 수문장 (sumunjang)
|
|
26
|
+
|
|
27
|
+
**AI에게 물어보기 전에, 개인정보는 문 앞에서 멈춘다.**
|
|
28
|
+
|
|
29
|
+
Claude Code나 ChatGPT에 로그·문서·설정 파일을 붙여넣는 순간, 주민등록번호·계좌번호·
|
|
30
|
+
API 키가 그대로 해외 API 서버로 전송됩니다. 개인정보보호위원회의 「생성형 AI 서비스
|
|
31
|
+
이용자를 위한 개인정보 보호 가이드」(2026.5)조차 *"이름은 가명으로, 숫자는 XXX로"* 라는
|
|
32
|
+
**수동 마스킹**을 권고할 뿐, 이를 자동화하는 무료 도구는 안내되지 않습니다.
|
|
33
|
+
|
|
34
|
+
수문장은 `base_url` 한 줄만 바꾸면 동작하는 **로컬 게이트웨이**입니다. 나가는 요청에서
|
|
35
|
+
한국 개인정보를 찾아 가명값으로 바꾸고, 돌아온 답변에서는 원문으로 되돌립니다.
|
|
36
|
+
|
|
37
|
+
```
|
|
38
|
+
당신 수문장 Anthropic API
|
|
39
|
+
│ │ │
|
|
40
|
+
│─ 900101-1234568 조회해줘 ─→ │
|
|
41
|
+
│ │─ [주민등록번호_1] 조회해줘 ─→│ ← 서버는 원문을 본 적이 없다
|
|
42
|
+
│ │←─ [주민등록번호_1]는 ... ──│
|
|
43
|
+
│←─ 900101-1234568 는 ... ─│ │ ← 당신 화면에는 원문이 보인다
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## 빠른 시작
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
# 게이트웨이 실행
|
|
50
|
+
uvx sumunjang proxy --port 4000
|
|
51
|
+
|
|
52
|
+
# Claude Code가 수문장을 거쳐 나가게 한다
|
|
53
|
+
export ANTHROPIC_BASE_URL=http://127.0.0.1:4000
|
|
54
|
+
claude
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
파일이나 파이프에도 바로 쓸 수 있습니다.
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
$ echo "고객 900101-1234568, 010-9876-5432, kim@corp.co.kr" | sumunjang mask -
|
|
61
|
+
고객 [주민등록번호_1], [전화번호_1], [이메일_1]
|
|
62
|
+
|
|
63
|
+
$ sumunjang scan config.env # 찾기만 한다. 발견 시 종료 코드 1 → CI 게이트로 사용
|
|
64
|
+
$ sumunjang report # 골든셋으로 탐지 성능을 스스로 채점
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
## 무엇을 막는가
|
|
68
|
+
|
|
69
|
+
**검증식으로 판별하는 것** — 값 스스로가 자기가 무엇인지 증명합니다.
|
|
70
|
+
|
|
71
|
+
| 종류 | 검증 방식 |
|
|
72
|
+
|---|---|
|
|
73
|
+
| 주민등록번호 | 검증식(가중치 mod 11) **또는** 생년월일 정합성 |
|
|
74
|
+
| 사업자등록번호 | 검증식(가중치 1371 3713 5) |
|
|
75
|
+
| 카드번호 | 카드사 식별번호 대역 + Luhn (국내전용은 예외, 아래) |
|
|
76
|
+
| 휴대전화번호 | 통신사 식별번호 + 자릿수 (`+82` 표기 포함) |
|
|
77
|
+
| 이메일 | 형식 + 최상위 도메인 조건 |
|
|
78
|
+
| API 키·토큰 | Anthropic / OpenAI / GitHub / AWS / Slack 공표 접두사 |
|
|
79
|
+
|
|
80
|
+
**문맥으로 판별하는 것** — 검증식이 없거나 공개되지 않아, 앞에 붙은 말을 증거로 씁니다.
|
|
81
|
+
|
|
82
|
+
| 종류 | 왜 형태만으로는 안 되는가 | 앵커 예 |
|
|
83
|
+
|---|---|---|
|
|
84
|
+
| 계좌번호 | 검증식이 없고 형식이 은행마다 다르다 (3-3-6, 6-2-6, 4-3-6 …) | `계좌:`, `예금주`, `입금` |
|
|
85
|
+
| 여권번호 | 영문 1자 + 숫자 8자. 제품 코드·대기번호와 겹친다 | `여권번호:` |
|
|
86
|
+
| 운전면허번호 | 검사번호 산출식이 공개돼 있지 않다 | `면허:` |
|
|
87
|
+
| 이름 | 형태 자체가 없다. `최윤서`와 `배송팀`은 생김새가 같다 | `성명:`, `예금주:`, `담당:` |
|
|
88
|
+
|
|
89
|
+
이름은 **구조화된 문서 한정**입니다. `성명: 최윤서`처럼 `키: 값` 형태만 잡고,
|
|
90
|
+
`어제 김수현 책임이랑 통화했는데` 같은 자유서술 속 이름은 잡지 않습니다. 성씨 사전
|
|
91
|
+
(상위 50개, 인구 약 97%)으로 한 겹 더 거릅니다 — 커버리지가 아니라 오탐 때문입니다.
|
|
92
|
+
연·소·도·선·설 같은 희귀 성씨는 *연락·소속·도착·선택·설정* 의 첫 음절이기도 해서,
|
|
93
|
+
3%를 더 얻자고 오탐을 몇 배로 늘립니다.
|
|
94
|
+
|
|
95
|
+
표기 변형도 함께 받습니다. 하이픈 없이 붙여 쓴 주민등록번호(`8803121000068`), 점으로
|
|
96
|
+
구분한 전화번호(`010.9876.5432`), 국가번호를 붙인 전화번호(`+82-10-2255-8830`), 전각
|
|
97
|
+
숫자, 보이지 않는 문자(제로폭)를 끼워 넣은 우회까지. 제로폭은 정규화한 뒤 매칭하되
|
|
98
|
+
좌표는 원문 기준으로 되돌리므로 가려지는 위치가 어긋나지 않습니다.
|
|
99
|
+
|
|
100
|
+
**2020년 10월 이후 발급된 주민등록번호는 뒷자리가 임의번호**라 검증식을 통과하지 않습니다.
|
|
101
|
+
검증식만 관문으로 두면 그 세대를 통째로 놓치므로, 생년월일 정합성을 별도 관문으로 둡니다.
|
|
102
|
+
|
|
103
|
+
**국내전용 카드(9로 시작)는 Luhn을 따르지 않습니다.** 검증번호 산출 방식과 위치가
|
|
104
|
+
카드사마다 달라서입니다. Luhn만 관문으로 두면 한국 카드를 체계적으로 놓치므로 따로
|
|
105
|
+
다룹니다. 반대로 Luhn은 16자리 숫자 열 개 중 하나를 그냥 통과시키므로, 카드사 식별번호
|
|
106
|
+
대역이 아닌 숫자(전표번호 등)는 먼저 잘라냅니다.
|
|
107
|
+
|
|
108
|
+
### 증거가 없으면 관문을 더 요구한다
|
|
109
|
+
|
|
110
|
+
규칙이 열 종류지만 원칙은 하나입니다. **값이 스스로 증명하는 만큼만 관문을 덜어줍니다.**
|
|
111
|
+
|
|
112
|
+
| 상황 | 요구하는 것 | 이유 |
|
|
113
|
+
|---|---|---|
|
|
114
|
+
| 하이픈 **있는** 주민등록번호 | 검증식 **또는** 생년월일 | 하이픈 자체가 의도의 증거다 |
|
|
115
|
+
| 하이픈 **없는** 주민등록번호 | 검증식 **그리고** 생년월일 | 13자리는 밀리초 타임스탬프·상품 바코드와 겹친다 |
|
|
116
|
+
| 국제 브랜드 카드 | Luhn | 국제 규격을 따른다 |
|
|
117
|
+
| 국내전용 카드 | 구분자 표기 | 검증할 수단이 없다. 사람이 옮겨적은 흔적을 대신 본다 |
|
|
118
|
+
|
|
119
|
+
같은 원칙이 **앵커의 사정거리**도 정합니다. 값이 증거를 담고 있으면(검증식, 하이픈 묶음,
|
|
120
|
+
생년월일 관문) 앵커는 같은 줄 안에서 느슨해도 됩니다 — `계좌: 국민은행 110-234-567890`
|
|
121
|
+
처럼 은행 이름이 끼어드는 쪽이 오히려 보통이니까요. 반대로 값이 아무것도 증명하지
|
|
122
|
+
못하면(이름, 맨 숫자 계좌번호, 여권번호, 구분자 없는 국내전용 카드) `키: 값` 형태를
|
|
123
|
+
요구합니다.
|
|
124
|
+
|
|
125
|
+
이 구분을 처음엔 지키지 않았다가 오탐 셋을 만들었습니다 — `여권 발급 대기열 A00000001`,
|
|
126
|
+
`이체 수수료 정산 2026080612345678`, `카드 결제 모듈 롤백 커밋 9410123456789012`.
|
|
127
|
+
셋 다 앵커 근처에 우연히 놓인 숫자였습니다. 지금은 오탐 골든셋에 박혀 있습니다.
|
|
128
|
+
|
|
129
|
+
대가도 함께 적습니다. 앵커 없이 붙여 쓴 2020년 10월 이후 발급분, 구분자 없는 국내전용
|
|
130
|
+
카드, 자유서술 속 이름과 주소는 **놓칩니다.** `goldenset-gaps/` 에 정답으로 박아 두고
|
|
131
|
+
점수로 공표합니다.
|
|
132
|
+
|
|
133
|
+
### 겹치는 탐지는 합집합으로 가린다
|
|
134
|
+
|
|
135
|
+
전화번호와 카드번호는 자릿수를 공유할 수 있습니다. `010-9921-3348-0000-0002` 에서
|
|
136
|
+
전화번호는 앞 13자, 카드번호는 뒤 19자를 차지합니다. 겹침을 "앞선 탐지가 이겼으니
|
|
137
|
+
뒤는 버린다"로 처리하면 카드번호 뒷 8자리가 평문으로 남습니다.
|
|
138
|
+
|
|
139
|
+
겹치는 탐지는 합집합 한 구간으로 병합해 **탐지기가 표시한 바이트를 하나도 남기지
|
|
140
|
+
않습니다.** 합쳐진 자리의 이름은 민감도 서열을 따릅니다 — 축은 *이 값 하나로 다른 문을
|
|
141
|
+
얼마나 열 수 있는가, 그 자물쇠를 바꿀 수 있는가* 하나입니다. 주민등록번호가 1위인 것은
|
|
142
|
+
한국 본인확인의 마스터키이면서 유출돼도 바꿀 수 없기 때문이고, 사업자등록번호가
|
|
143
|
+
최하위인 것은 국세청에서 공개 조회되기 때문입니다.
|
|
144
|
+
|
|
145
|
+
## 설계 원칙
|
|
146
|
+
|
|
147
|
+
**개인정보가 지나가는 경로에 서드파티 코드를 두지 않습니다.**
|
|
148
|
+
탐지·마스킹 모듈은 파이썬 표준 라이브러리만 사용합니다. 외부 패키지는 네트워크 계층에만
|
|
149
|
+
쓰이며 직접 의존성은 `httpx`, `uvicorn` 둘뿐입니다 ([SBOM.md](SBOM.md)).
|
|
150
|
+
|
|
151
|
+
**결정적입니다.** 같은 입력이면 언제나 같은 출력이고, API 키도 네트워크도 필요 없습니다.
|
|
152
|
+
AI 모델을 탑재하지 않으므로 실행할 때마다 결과가 달라지지 않습니다.
|
|
153
|
+
|
|
154
|
+
**조용히 실패하지 않습니다.** 복원은 정확히 일치하는 표시만 되돌립니다. 모델이 형태를
|
|
155
|
+
바꿔버린 경우 추측해서 복구하지 않습니다 — 잘못된 복원이 미복원보다 위험하기 때문입니다.
|
|
156
|
+
|
|
157
|
+
**스트리밍은 신뢰성을 택했습니다.** 조각난 응답에서 가명 표시를 복원하면 청크 경계에서
|
|
158
|
+
깨집니다. 그래서 업스트림에는 통짜로 요청해 복원을 끝낸 뒤, 클라이언트에게만 스트리밍
|
|
159
|
+
형태로 다시 흘려보냅니다. 첫 응답이 조금 늦는 대신 복원이 깨지지 않습니다.
|
|
160
|
+
|
|
161
|
+
## 실환경 검증 기록 (2026-08-06)
|
|
162
|
+
|
|
163
|
+
Claude Code를 수문장에 연결해 실제 API까지 왕복시켰습니다. 사용자가 입력한 것은
|
|
164
|
+
**파일 경로 한 줄**뿐입니다.
|
|
165
|
+
|
|
166
|
+
```
|
|
167
|
+
$ claude
|
|
168
|
+
> /tmp/결제장애-로그.txt 읽고 왜 결제가 실패했는지 분석해줘
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
수문장이 기록한 **업스트림으로 실제 나간 본문**:
|
|
172
|
+
|
|
173
|
+
```
|
|
174
|
+
user_rrn=[주민등록번호_1] ← 원문: 850312-1000003
|
|
175
|
+
user_phone=[전화번호_1]
|
|
176
|
+
user_email=[이메일_3]
|
|
177
|
+
card=[카드번호_1]
|
|
178
|
+
merchant_brn=[사업자등록번호_1]
|
|
179
|
+
ANTHROPIC_API_KEY=[시크릿_1]
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
사용자는 개인정보를 붙여넣은 적이 없습니다. 도구가 파일을 읽어 `tool_result`에 담는
|
|
183
|
+
순간 6종이 전송될 뻔했고, 여기에 도구가 시스템 프롬프트에 넣는 사용자 이메일까지 더해
|
|
184
|
+
한 요청에서 8건이 가려졌습니다. 업스트림 본문에 남은 원문은 0건이었습니다.
|
|
185
|
+
|
|
186
|
+
**가려도 답변 품질이 떨어지지 않았습니다.** 마스킹된 로그를 받은 모델은 장애 원인을
|
|
187
|
+
분석하면서 "로그에 크레덴셜이 평문으로 찍혀 있다"는 보안 문제까지 지적했습니다.
|
|
188
|
+
장애 분석에 필요한 것은 *누구의* 카드인지가 아니라 카드 결제가 실패했다는 구조이기
|
|
189
|
+
때문입니다.
|
|
190
|
+
|
|
191
|
+
이 검증 과정에서 자체 테스트로는 잡히지 않던 결함 두 건도 드러났습니다. 추론(thinking)
|
|
192
|
+
블록을 SSE 규약대로 `delta`로 흘리지 않아 다음 턴 요청이 400으로 거부되던 문제와,
|
|
193
|
+
반복되는 값을 "0건"으로 보고해 작동이 멈춘 것처럼 보이던 문제입니다. 둘 다 저장소에
|
|
194
|
+
포함된 에코 서버로는 재현되지 않는 종류였습니다.
|
|
195
|
+
|
|
196
|
+
## 무엇을 못 잡는지도 점수로 냅니다
|
|
197
|
+
|
|
198
|
+
골든셋은 고치는 순간 포화됩니다. 어려운 문서를 넣어 점수를 떨어뜨려도, 드러난 결함을
|
|
199
|
+
고치고 나면 다시 만점이 되어 처음과 구분이 가지 않습니다. 만점 셋만 내놓으면 *"쉬운 것만
|
|
200
|
+
골라 놓고 만점이라 하는 것 아니냐"* 는 물음에 답할 수 없습니다.
|
|
201
|
+
|
|
202
|
+
그래서 **0점 셋을 나란히 공개합니다.**
|
|
203
|
+
|
|
204
|
+
| 셋 | 의미 | 현재 |
|
|
205
|
+
|---|---|---|
|
|
206
|
+
| `goldenset/` | 회귀 기준선. 깨지면 되던 것이 망가진 것 | 재현율 1.000 · 정밀도 1.000 |
|
|
207
|
+
| `goldenset-hard/` | 표기 변형과 오탐 함정 | 재현율 1.000 · 정밀도 1.000 |
|
|
208
|
+
| `goldenset-gaps/` | **못 잡는다고 선언한 것들.** 낮은 것이 정상 | 재현율 0.765 |
|
|
209
|
+
|
|
210
|
+
`goldenset-hard/` 는 도입 당시 **0.684 / 0.765** 였습니다. 그 자리가 드러낸 결함 넷을
|
|
211
|
+
고쳐 지금 수치가 됐습니다. `goldenset-gaps/` 는 **0.235에서 시작했습니다** — 문맥 앵커
|
|
212
|
+
규칙으로 계좌·여권·면허·이름과 좁혀 뒀던 관문 셋을 되찾아 0.765가 됐고, 남은 것은
|
|
213
|
+
자유서술 속 이름과 주소입니다. gaps 항목이 `hard` 로 옮겨가는 것이 곧 개선 기록입니다.
|
|
214
|
+
|
|
215
|
+
```bash
|
|
216
|
+
sumunjang report # 세 셋을 나란히 채점
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
이 수치는 **독립적인 성능 증명이 아니라 회귀를 감시하는 기준선**입니다. 골든셋을 만든
|
|
220
|
+
사람이 도구도 만들었습니다.
|
|
221
|
+
|
|
222
|
+
## 한계
|
|
223
|
+
|
|
224
|
+
정직하게 적습니다.
|
|
225
|
+
|
|
226
|
+
- **자유서술 속 이름과 주소는 못 잡습니다.** 이름은 `성명: 최윤서` 같은 구조화된 표기만
|
|
227
|
+
잡습니다. `어제 김수현 책임이랑 통화했는데` 는 앵커가 없어 규칙으로 판별할 수 없습니다.
|
|
228
|
+
주소는 아예 다루지 않습니다 — 어디서 끝나는지를 규칙으로 정할 수 없습니다.
|
|
229
|
+
둘 다 `goldenset-gaps/G2` 에 정답으로 박아 두고 0점으로 공표합니다.
|
|
230
|
+
- **앵커 없는 계좌·여권·면허번호도 못 잡습니다.** 검증식이 없거나(계좌) 공개되지
|
|
231
|
+
않아서(면허 검사번호), 앞에 붙은 말이 유일한 근거입니다. `정산 항목 110-234-567890`
|
|
232
|
+
처럼 앵커 없이 놓인 숫자는 계좌번호로 보지 않습니다.
|
|
233
|
+
- **관문을 좁힌 대가가 있습니다.** 앵커 없이 붙여 쓴 2020년 10월 이후 주민등록번호와,
|
|
234
|
+
앵커 없이 붙여 쓴 국내전용 카드는 놓칩니다. 위 표의 `gaps` 셋에 들어 있습니다.
|
|
235
|
+
- **이름 탐지에는 남은 오탐 여지가 있습니다.** `담당: 배송팀` 처럼 성씨로 시작하는
|
|
236
|
+
3자 낱말이 `키: 값` 자리에 오면 이름으로 읽힙니다. 개인정보가 아닌 값을 가리는
|
|
237
|
+
쪽이 이름을 흘리는 쪽보다 낫다고 보고 이 방향을 택했습니다.
|
|
238
|
+
- **Luhn은 오탐을 완전히 막지 못합니다.** 전치·오타 검출용이라 `1111-2222-3333-4444`
|
|
239
|
+
같은 규칙적 반복도 통과합니다. 카드사 식별번호 대역으로 한 겹 더 거르지만 그 안에서는
|
|
240
|
+
여전히 통과합니다.
|
|
241
|
+
- **모델이 가명 표시를 값이 아니라 설명 대상으로 언급하면 복원이 문장을 왜곡합니다.**
|
|
242
|
+
모델이 "마스킹 표시(`[주민등록번호_1]`)가 붙어 있었다"고 메타 언급하면 그 자리도
|
|
243
|
+
원문으로 되돌아갑니다. 값으로 쓴 것인지 표시를 설명한 것인지 구분하려면 문맥 판단이
|
|
244
|
+
필요해 현재 구조로는 완전히 풀 수 없습니다.
|
|
245
|
+
- 현재 Anthropic Messages API(`/v1/messages`)만 지원합니다.
|
|
246
|
+
|
|
247
|
+
## 개발
|
|
248
|
+
|
|
249
|
+
```bash
|
|
250
|
+
uv venv && uv pip install -e ".[dev]"
|
|
251
|
+
uv run pytest
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
테스트는 API 키도 네트워크도 없이 돕니다. 업스트림은 저장소에 포함된 에코 서버를 실제
|
|
255
|
+
ASGI 앱으로 띄워 검증하므로, 누구나 같은 결과를 재현할 수 있습니다.
|
|
256
|
+
|
|
257
|
+
`tests/test_leak.py` 는 다른 테스트와 성격이 다릅니다. 카테고리도 좌표도 묻지 않고
|
|
258
|
+
**"골든셋의 정답 값이 마스킹 결과에 부분 문자열로도 남아 있지 않은가"** 하나만 검사합니다.
|
|
259
|
+
재현율·정밀도는 스팬이 정확히 일치했는지를 세므로 "절반만 가렸다"는 잡아도 *남은 절반이
|
|
260
|
+
평문으로 나갔다*는 사실 자체는 드러내지 못합니다. 실제로 그 틈으로 결함이 하나 빠져나간
|
|
261
|
+
적이 있습니다.
|
|
262
|
+
|
|
263
|
+
## 라이선스
|
|
264
|
+
|
|
265
|
+
Apache License 2.0
|
|
266
|
+
|
|
267
|
+
---
|
|
268
|
+
|
|
269
|
+
2026 오픈소스 개발자대회 출품작 (팀 문지기)
|