Constrain IROS automation to a reviewed upstream boundary

The IROS skill delegates sensitive browser automation to an upstream Playwright implementation, so the execution guide now checks out a reviewed SHA and keeps real inputs and generated files in a private workdir instead of the clone. Regression coverage locks the pin and privacy-path contract to prevent future docs drift.\n\nConstraint: PR #177 review required an enforceable upstream execution boundary before merge\nConstraint: Live IROS login and payment smoke requires user credentials and card authority\nRejected: Continue documenting mutable upstream HEAD | unsafe for authenticated legal-document/payment-adjacent flows\nConfidence: high\nScope-risk: narrow\nDirective: Do not update iros-registry-automation/scripts/upstream.pin without reviewing the new upstream diff and updating the documented checkout SHA\nTested: node --test --test-name-pattern='iros-registry-automation' scripts/skill-docs.test.js\nTested: ./scripts/validate-skills.sh\nTested: npm run lint && npm run typecheck && npm test\nTested: npm run ci\nTested: cloned upstream, checked out pinned SHA, rewired config.json to a private temp workdir, and verified selected paths stay under that workdir\nNot-tested: Live IROS login/payment smoke; requires user credentials, certificate/authentication, and payment authority
This commit is contained in:
Jeffrey (Dongkyu) Kim 2026-04-28 14:38:14 +09:00
commit bb12f433b2
6 changed files with 116 additions and 7 deletions

View file

@ -30,7 +30,7 @@ Claude Code, Codex, OpenCode, OpenClaw/ClawHub 등 각종 코딩 에이전트
| 사용자 위치 미세먼지 조회 | `fine-dust-location` | 현재 위치 또는 지역 기준 PM10/PM2.5 미세먼지 조회 | 불필요 | [사용자 위치 미세먼지 조회 가이드](docs/features/fine-dust-location.md) |
| 한강 수위 정보 조회 | `han-river-water-level` | 한강 관측소 기준 현재 수위·유량·기준수위 확인 | 불필요 | [한강 수위 정보 가이드](docs/features/han-river-water-level.md) |
| 한국 법령 검색 | `korean-law-search` | 한국 법령/조문/판례/유권해석 검색 | 불필요 | [한국 법령 검색 가이드](docs/features/korean-law-search.md) |
| 등기부등본 자동화 | `iros-registry-automation` | 인터넷등기소(IROS)에서 법인/부동산 등기부등본 장바구니, 수동 결제 후 열람·저장 흐름을 보조 | 필요 | [등기부등본 자동화 가이드](docs/features/iros-registry-automation.md) |
| 등기부등본 자동화 | `iros-registry-automation` | 인터넷등기소(IROS)에서 법인/부동산 등기부등본 장바구니, 수동 결제 후 열람·저장 흐름을 보조 | 필요(수동 로그인·결제/TouchEn) | [등기부등본 자동화 가이드](docs/features/iros-registry-automation.md) |
| 법인등기 신청 컨설팅 | `corporate-registration-consulting` | 법인명·이사·주소 등 사용자 결정사항을 받아 표준 정관, 설립등기 첨부서류, 등록면허세·과밀억제권역 중과 체크, rhwp 기반 HWP 양식 작성 흐름을 참고용으로 안내 | 불필요 | [법인등기 신청 컨설팅 가이드](docs/features/corporate-registration-consulting.md) |
| 한국 개인정보처리방침·이용약관 자동 생성 | `korean-privacy-terms` | Next.js 프로젝트에 개인정보보호법·약관규제법·전자상거래법 기반 개인정보처리방침/이용약관/쿠키 배너/동의 모달을 생성하는 `kimlawtech/korean-privacy-terms` (Apache-2.0) thin wrapper | 불필요 | [한국 개인정보처리방침·이용약관 자동 생성 가이드](docs/features/korean-privacy-terms.md) |
| 한국 부동산 실거래가 조회 | `real-estate-search` | 아파트/오피스텔/빌라/단독주택 실거래가·전월세·지역코드 조회 | 불필요 | [한국 부동산 실거래가 조회 가이드](docs/features/real-estate-search.md) |

View file

@ -22,9 +22,12 @@
## 설치
이 스킬은 로그인·인증·결제 인접 브라우저 자동화를 mutable upstream `HEAD`에 맡기지 않는다. 실행 전 이 저장소의 `iros-registry-automation/scripts/upstream.pin`에 적힌 reviewed SHA로 checkout한다.
```bash
git clone https://github.com/challengekim/iros-registry-automation.git
cd iros-registry-automation
git checkout 7c6924b2ff88d693a12556659188cb91041e5097
python3 -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt
@ -32,6 +35,8 @@ playwright install chromium
cp config.json.example config.json
```
업스트림 핀 업데이트는 새 upstream diff 검토가 필요한 보안/신뢰 경계 변경이다. `scripts/upstream.pin`과 설치 예시의 `git checkout` SHA를 같은 PR에서 함께 갱신한다.
Chrome/Chromium, Python 3.10+, IROS 로그인 수단, 결제 카드, TouchEn nxKey가 필요하다.
## 안전한 작업 폴더
@ -41,6 +46,50 @@ Chrome/Chromium, Python 3.10+, IROS 로그인 수단, 결제 카드, TouchEn nxK
```bash
workdir="$(mktemp -d "${TMPDIR:-/tmp}/iros-registry.XXXXXX")"
chmod 700 "$workdir"
mkdir -p "$workdir/downloads" "$workdir/logs" "$workdir/output"
```
실제 입력은 upstream repo `data/`가 아니라 `$workdir/corp-input.json`, `$workdir/realty-input.json`처럼 저장소 밖에 둔다. upstream `data/` 디렉터리는 샘플 형식 확인용으로만 보고, 실제 법인등록번호·주소·동호수 원문을 넣지 않는다.
```bash
cat > "$workdir/corp-input.json" <<'JSON'
{
"1101111234567": "예시 주식회사",
"1101117654321": "샘플 주식회사"
}
JSON
```
`config.json`의 입력·로그·save_dir 관련 값을 `$workdir`로 돌리면 upstream 스크립트를 실행해도 저장소 하위 `data/`, `logs/`, `output/`에 실제 업무 정보가 남지 않는다.
```bash
python3 - "$workdir" <<'PY'
import json
import pathlib
import sys
workdir = pathlib.Path(sys.argv[1])
config = json.loads(pathlib.Path("config.json").read_text())
config.update({
"corpnum_list": str(workdir / "corp-input.json"),
"companies_list": str(workdir / "companies-input.json"),
"realty_list": str(workdir / "realty-input.json"),
"save_dir": str(workdir / "downloads"),
"realty_save_dir": str(workdir / "downloads" / "realty"),
"pdf_dir": str(workdir / "downloads"),
"report_output": str(workdir / "output" / "corp-report.xlsx"),
"extract_output": str(workdir / "output" / "corp-extract.json"),
"bizno_cache": str(workdir / "logs" / "bizno-cache.json"),
"bizno_results": str(workdir / "logs" / "bizno-results.json"),
"realty_cart_log": str(workdir / "logs" / "cart-realty-log.json"),
"realty_download_log": str(workdir / "logs" / "download-realty-log.json"),
"cart_log": str(workdir / "logs" / "cart-log.json"),
"cart_corpnum_log": str(workdir / "logs" / "cart-corpnum-log.json"),
"download_log": str(workdir / "logs" / "download-log.json"),
"download_temp": str(workdir / "tmp-downloads"),
})
pathlib.Path("config.json").write_text(json.dumps(config, ensure_ascii=False, indent=2) + "\n")
PY
```
## 법인등기부등본 흐름
@ -57,6 +106,8 @@ python iros_cart_by_corpnum.py
python iros_download.py
```
위 명령은 로컬 `config.json`을 읽으므로, 먼저 `corpnum_list``save_dir``$workdir/corp-input.json`, `$workdir/downloads`를 가리키는지 확인한다.
## 부동산등기부등본 흐름
1. 주소/동호수 JSON을 준비한다.

View file

@ -37,7 +37,7 @@
- corporate-registration-consulting skill: local://corporate-registration-consulting
- 대법원 인터넷등기소: https://www.iros.go.kr
- 온라인법인설립시스템: https://www.startbiz.go.kr
- 등기부등본 자동화 참고 구현(`challengekim/iros-registry-automation`, MIT): https://github.com/challengekim/iros-registry-automation
- 등기부등본 자동화 참고 구현(`challengekim/iros-registry-automation`, MIT): https://github.com/challengekim/iros-registry-automation — 실행 가이드는 `iros-registry-automation/scripts/upstream.pin`의 reviewed SHA로 checkout한 버전을 기준으로 한다.
- 위택스 등록면허세 신고/납부: https://www.wetax.go.kr
- 국가법령정보센터 지방세법/지방세법 시행령/상법/상업등기법/조세특례제한법: https://www.law.go.kr
- 국세청 창업중소기업 세액감면 안내: https://www.nts.go.kr

View file

@ -36,11 +36,12 @@ metadata:
- IROS 로그인 수단(아이디, 공동인증서, 간편인증 등)
- 결제 카드
- TouchEn nxKey 사전 설치
- upstream 참고 구현 clone 또는 사용자가 관리하는 로컬 사본
- upstream 참고 구현 clone 또는 사용자가 관리하는 로컬 사본. 실행 전 반드시 이 스킬 저장소의 `iros-registry-automation/scripts/upstream.pin`에 적힌 reviewed SHA로 고정한다.
```bash
git clone https://github.com/challengekim/iros-registry-automation.git
cd iros-registry-automation
git checkout 7c6924b2ff88d693a12556659188cb91041e5097
python3 -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt
@ -48,6 +49,8 @@ playwright install chromium
cp config.json.example config.json
```
업스트림 핀 업데이트는 로그인·인증·결제 인접 브라우저 자동화의 신뢰 경계를 바꾸는 작업이다. `scripts/upstream.pin` 값을 바꾸기 전에는 새 upstream diff를 검토하고, 설치 예시의 `git checkout` SHA와 함께 같은 PR에서 갱신한다.
## Workflow
### 1. 입력 파일을 저장소 밖 안전한 폴더에 준비한다
@ -57,18 +60,53 @@ cp config.json.example config.json
```bash
workdir="$(mktemp -d "${TMPDIR:-/tmp}/iros-registry.XXXXXX")"
chmod 700 "$workdir"
mkdir -p "$workdir/downloads" "$workdir/logs" "$workdir/output"
```
법인등록번호 기반 입력 예시:
법인등록번호 기반 입력은 upstream repo `data/`가 아니라 `$workdir/corp-input.json` 같은 저장소 밖 파일에 둔다. 실제 법인등록번호/주소 원문을 upstream `data/` 디렉터리, git 저장소, PR 첨부, 테스트 로그에 넣지 않는다.
```json
```bash
cat > "$workdir/corp-input.json" <<'JSON'
{
"1101111234567": "예시 주식회사",
"1101117654321": "샘플 주식회사"
}
JSON
```
부동산 주소 기반 입력 예시는 동/호수까지 필요한 경우가 있으므로 `data/iros_realties.json` 형식을 upstream README에서 확인하고, 실제 주소 원문은 로컬 파일에만 둔다.
부동산 주소 기반 입력 예시는 동/호수까지 필요한 경우가 있으므로 `data/iros_realties.json` 형식을 upstream README에서 확인하되, 실제 주소 원문은 `$workdir/realty-input.json` 같은 로컬 파일에만 둔다.
`config.json`도 저장소에 커밋하지 않는 로컬 파일로 두고, 민감 입력·로그·산출물 경로를 모두 `$workdir` 아래로 돌린다.
```bash
python3 - "$workdir" <<'PY'
import json
import pathlib
import sys
workdir = pathlib.Path(sys.argv[1])
config = json.loads(pathlib.Path("config.json").read_text())
config.update({
"corpnum_list": str(workdir / "corp-input.json"),
"companies_list": str(workdir / "companies-input.json"),
"realty_list": str(workdir / "realty-input.json"),
"save_dir": str(workdir / "downloads"),
"realty_save_dir": str(workdir / "downloads" / "realty"),
"pdf_dir": str(workdir / "downloads"),
"report_output": str(workdir / "output" / "corp-report.xlsx"),
"extract_output": str(workdir / "output" / "corp-extract.json"),
"bizno_cache": str(workdir / "logs" / "bizno-cache.json"),
"bizno_results": str(workdir / "logs" / "bizno-results.json"),
"realty_cart_log": str(workdir / "logs" / "cart-realty-log.json"),
"realty_download_log": str(workdir / "logs" / "download-realty-log.json"),
"cart_log": str(workdir / "logs" / "cart-log.json"),
"cart_corpnum_log": str(workdir / "logs" / "cart-corpnum-log.json"),
"download_log": str(workdir / "logs" / "download-log.json"),
"download_temp": str(workdir / "tmp-downloads"),
})
pathlib.Path("config.json").write_text(json.dumps(config, ensure_ascii=False, indent=2) + "\n")
PY
```
### 2. TouchEn nxKey와 로그인 수단을 먼저 확인한다
@ -97,7 +135,7 @@ python iros_cart.py
python iros_download.py
```
저장 경로는 `config.json``save_dir`로 관리하되, 공개 저장소 하위 경로를 사용하지 않는다.
저장 경로는 `config.json``save_dir`로 관리하되, 위 예시처럼 `$workdir/downloads`를 사용하고 공개 저장소 하위 경로를 사용하지 않는다.
### 5. 부동산등기부등본 장바구니 담기

View file

@ -0,0 +1 @@
7c6924b2ff88d693a12556659188cb91041e5097

View file

@ -3023,17 +3023,22 @@ test("corporate-registration-consulting skill covers court registry workflow, ta
test("iros-registry-automation skill documents safe IROS registry certificate automation and upstream credit", () => {
const skillPath = path.join(repoRoot, "iros-registry-automation", "SKILL.md");
const featureDocPath = path.join(repoRoot, "docs", "features", "iros-registry-automation.md");
const upstreamPinPath = path.join(repoRoot, "iros-registry-automation", "scripts", "upstream.pin");
assert.ok(fs.existsSync(skillPath), "expected iros-registry-automation/SKILL.md to exist");
assert.ok(fs.existsSync(featureDocPath), "expected iros-registry-automation feature doc to exist");
assert.ok(fs.existsSync(upstreamPinPath), "expected iros-registry-automation/scripts/upstream.pin to exist");
const skill = read(path.join("iros-registry-automation", "SKILL.md"));
const featureDoc = read(path.join("docs", "features", "iros-registry-automation.md"));
const upstreamPin = read(path.join("iros-registry-automation", "scripts", "upstream.pin")).trim();
const readme = read("README.md");
const install = read(path.join("docs", "install.md"));
const sources = read(path.join("docs", "sources.md"));
const roadmap = read(path.join("docs", "roadmap.md"));
assert.match(upstreamPin, /^[0-9a-f]{40}$/, "upstream pin should be a reviewed 40-character Git SHA");
assert.match(skill, /^---\nname: iros-registry-automation\n/);
assert.match(skill, /등기부등본|등기사항증명서/);
assert.match(skill, /법인/);
@ -3057,6 +3062,13 @@ test("iros-registry-automation skill documents safe IROS registry certificate au
assert.match(skill, /https:\/\/github\.com\/challengekim\/iros-registry-automation/);
assert.match(skill, /MIT/);
assert.match(skill, /원 저작자|원저작자|upstream|참고 구현/);
assert.match(skill, new RegExp(`git checkout ${upstreamPin}`), "skill install flow should check out the pinned upstream SHA");
assert.match(skill, /scripts\/upstream\.pin/, "skill should document where the reviewed upstream pin lives");
assert.match(skill, /pin update|핀 업데이트|업스트림 핀|review/i, "skill should require review before updating the pin");
assert.match(skill, /\$workdir\/corp-input\.json/, "skill should put real corporate inputs under the private workdir");
assert.match(skill, /\$workdir\/downloads/, "skill should point save_dir/download output at the private workdir");
assert.match(skill, /config\.json[\s\S]*save_dir[\s\S]*\$workdir/, "skill should wire config.json save_dir to the private workdir");
assert.match(skill, /upstream repo `data\/`|upstream `data\/`|data\/.*실제/, "skill should warn not to use upstream data/ for real inputs");
for (const doc of [featureDoc, sources]) {
assert.match(doc, /challengekim/);
@ -3064,6 +3076,9 @@ test("iros-registry-automation skill documents safe IROS registry certificate au
assert.match(doc, /인터넷등기소|IROS|iros\.go\.kr/);
}
assert.match(featureDoc, new RegExp(`git checkout ${upstreamPin}`), "feature doc install flow should check out the pinned upstream SHA");
assert.match(featureDoc, /scripts\/upstream\.pin/, "feature doc should document where the reviewed upstream pin lives");
assert.match(featureDoc, /pin update|핀 업데이트|업스트림 핀|review/i, "feature doc should require review before updating the pin");
assert.match(featureDoc, /로그인[\s\S]*수동|사용자.*직접.*로그인/);
assert.match(featureDoc, /결제[\s\S]*수동|사용자.*직접.*결제/);
assert.match(featureDoc, /법인[\s\S]*장바구니[\s\S]*열람[\s\S]*저장/);
@ -3073,6 +3088,10 @@ test("iros-registry-automation skill documents safe IROS registry certificate au
assert.match(featureDoc, /i?ros_cart_by_corpnum\.py|법인등록번호 기반/);
assert.match(featureDoc, /i?ros_cart_realty\.py|부동산 장바구니/);
assert.match(featureDoc, /저장소 밖|레포 밖|커밋하지/);
assert.match(featureDoc, /\$workdir\/corp-input\.json/, "feature doc should put real corporate inputs under the private workdir");
assert.match(featureDoc, /\$workdir\/downloads/, "feature doc should point save_dir/download output at the private workdir");
assert.match(featureDoc, /config\.json[\s\S]*save_dir[\s\S]*\$workdir/, "feature doc should wire config.json save_dir to the private workdir");
assert.match(featureDoc, /upstream repo `data\/`|upstream `data\/`|data\/.*실제/, "feature doc should warn not to use upstream data/ for real inputs");
assert.doesNotMatch(featureDoc, /결제.*자동화.*지원/);
assert.match(readme, /\| 등기부등본 자동화 \| `iros-registry-automation` \|/);