sherlock의 정신적 후계자이면서, 사이트 시그니처 DB·오탐(false positive) 처리·재귀 검색·자동 DB 업데이트까지 갖춰 한 단계 더 나아갔다. (저장소: soxoj/maigret · Python 3.10+ · MIT 라이선스 · GitHub 스타 31k+ · 누적 1,369 커밋)이 레포가 무엇을 하는 물건인가.
당신이 어떤 닉네임 johndoe가 트위터·깃허브·레딧 등에 있는지 알고 싶다면, 보통은 각 사이트를 직접 열어 site.com/johndoe를 쳐 봐야 한다. maigret은 이 "URL 조립 → 요청 → 페이지가 '있다/없다'를 판정" 과정을 사이트마다 어떻게 판정해야 하는지를 적은 거대한 JSON DB와 asyncio 기반 동시 실행 엔진으로 자동화한다.
핵심은 단순한 "URL을 1초마다 두드리기"가 아니다. 사이트마다 "계정이 없을 때 어떻게 반응하는가"가 전부 달라서(404를 주거나, 200에 '없음' 문구를 주거나, 다른 URL로 리다이렉트하거나), maigret은 그 사이트별 시그니처를 데이터로 들고 있다.
maigret은 사용자명 열거(username enumeration) 계열의 OSINT 도구다. API 키가 전혀 필요 없고, 매 실행마다 GitHub에서 최신 사이트 DB를 자동으로 받아(24시간마다 1회) 쓰며, 오프라인이면 내장 DB로 폴백한다. 발견한 사용자명을 씨앗 삼아 재귀 검색까지 수행하고, 결과를 PDF·HTML·CSV·JSON(ndjson)·XMind·D3 그래프로 내보낸다. 기본 실행은 트래픽 상위 500개 사이트만 점검하고, -a를 붙이면 전체를 훑는다.
트렌딩 이유 · sherlock 등 경쟁 대비 장점.
username OSINT 도구의 원조는 sherlock이다. maigret이 꾸준히 스타를 모으는 이유는, sherlock이 멈춘 지점에서 "실전에서 진짜 골치 아픈 부분"을 풀었기 때문이다. 첫째 압도적 사이트 수(3,000+)와 자동 갱신 DB, 둘째 오탐을 줄이는 정교한 판정·에러 시그니처, 셋째 프로필에서 추가 정보를 긁어 재귀로 파고드는 능력, 넷째 다양한 리포트 포맷과 웹 UI다.
| 비교 항목 | sherlock (원조) | maigret |
|---|---|---|
| 점검 사이트 수 | 약 400개대 | 3,000+(기본 상위 500, -a로 전체) |
| 사이트 DB 갱신 | 레포 업데이트에 의존 | 실행 시 GitHub에서 자동 갱신(24h, 오프라인 폴백) |
| 판정 방식 | 주로 상태코드 / 문구 | 상태코드·문구·리다이렉트 + 에러 시그니처로 오탐 억제 |
| 정보 추출 | "있다/없다"만 | 프로필에서 ID·이름·링크 추출(socid_extractor) |
| 재귀 검색 | 없음 | 발견한 username·ID로 꼬리 물기 검색 |
| 리포트 | txt / csv | HTML·PDF·CSV·JSON·XMind·D3 그래프 + 웹 UI |
| 차단 우회 | 제한적 | 프록시·Tor·I2P·FlareSolverr 연동(Cloudflare) |
모든 사이트에 똑같이 "404면 없음, 200이면 있음"을 적용하면 망한다. 어떤 사이트는 없는 계정에도 200을 주면서 본문에만 'not found'를 넣고, 어떤 곳은 존재하는 계정을 다른 URL로 리다이렉트하며, 또 어떤 곳은 점검 자체를 WAF·CAPTCHA·Cloudflare로 막는다. 단순 도구는 이 차이를 무시해 거짓 양성(false positive)을 쏟아내고, 곧 IP가 차단된다.
maigret은 사이트별 판정 규칙을 data.json 시그니처 DB로 외부화했다. 새 사이트 추가가 코드 수정이 아니라 데이터 한 줄 추가로 끝나고, 커뮤니티가 규칙을 손쉽게 기여한다. 거기에 asyncio 큐 + 세마포어로 동시성을 조절하고, 공급자(사이트) 응답에 맞춰 타임아웃·재시도·프록시를 다뤄 차단을 줄인다. "넓게, 그러나 정확하게"가 설계 철학이다.
백엔드(엔진)·프론트엔드(리포트/웹UI)·인프라 각각.
maigret은 100% 파이썬이며(코드의 79%), 사이트 점검은 본질적으로 I/O 바운드(네트워크 대기가 대부분) 작업이라 asyncio + aiohttp로 짜였다. CPU가 아니라 "수천 개의 HTTP 요청을 동시에 기다리는" 문제이기 때문이다.
| 요소 | 역할 |
|---|---|
| Python 3.10+ | 전체 언어. 타입힌트·async/await 적극 사용. |
asyncio | 동시성 런타임. 큐·세마포어·wait_for 타임아웃·as_completed. |
aiohttp | 비동기 HTTP 클라이언트. ClientSession·TCPConnector로 커넥션 풀 관리. |
aiodns | 비동기 DNS 해석. 수천 호스트 조회를 막히지 않게. |
python-socks | SOCKS/HTTP 프록시 — Tor·I2P·일반 프록시 라우팅. |
socid_extractor | 같은 저자(soxoj)의 자매 라이브러리. 프로필 페이지/HTML에서 실명·ID·타 계정 링크 추출. |
alive-progress | 터미널 진행 막대(수천 작업의 완료율 표시). |
| 요소 | 역할 |
|---|---|
| Jinja 템플릿 | HTML 리포트 생성(코드의 18%가 HTML — 리포트 템플릿). |
| PDF 백엔드 | pip install 'maigret[pdf]' 선택 설치. 시스템 그래픽 라이브러리 필요. |
| D3.js 그래프 | --graph — 발견 계정 간 관계를 인터랙티브 노드 그래프로. |
| XMind / CSV / JSON | 마인드맵·표·기계 판독용(ndjson·simple) 내보내기. |
| 웹 UI (Flask류) | maigret --web 5000 — 브라우저에서 검색·그래프·리포트 다운로드. |
| 요소 | 역할 |
|---|---|
| 패키징 | pyproject.toml + Poetry(poetry.lock). PyPI 배포(pip install maigret). |
| Docker | 두 이미지: soxoj/maigret:latest(CLI)·soxoj/maigret:web(웹UI). 멀티스테이지 빌드. |
| Windows | PyInstaller로 maigret_standalone.exe 단일 실행 파일. |
| 사이트 DB | maigret/resources/data.json — 3,000+ 사이트 시그니처. 실행 시 GitHub에서 자동 갱신. |
| 차단 우회 | FlareSolverr(별도 도커) 연동 → Cloudflare JS 챌린지 해결(실험적). |
| AI 요약 | --ai — 결과를 OpenAI 호환 API로 보내 중립적 조사 요약 생성(선택). |
사용자명 → 비동기 점검 → 판정 → 리포트, 그리고 오탐 처리.
입력은 사용자명 1개. 엔진은 사이트 DB를 읽어 각 사이트마다 점검 작업(coroutine)을 만들고, 이걸 큐에 넣어 일정 수의 워커가 동시에 처리한다. 각 작업은 URL을 조립해 HTTP 요청을 던지고, 응답을 사이트별 판정 규칙에 통과시켜 CLAIMED/AVAILABLE/UNKNOWN/ILLEGAL 중 하나로 분류한다. CLAIMED면 프로필을 긁어 추가 ID를 뽑고, 그게 새 검색의 씨앗이 된다(재귀). 마지막에 리포터가 모아 보고서를 만든다.
maigret의 신뢰성은 "있다/없다"를 이분법이 아니라 4상태로 본다는 데서 나온다. 소스의 MaigretCheckStatus 열거형이 바로 그것이다.
| 상태 (열거형) | 의미 |
|---|---|
| CLAIMED | 계정이 존재(탐지됨). 프로필 추출·재귀의 대상. |
| AVAILABLE | 계정 없음(미탐지). 사용 가능한 이름. |
| UNKNOWN | 에러로 판정 불가 — 차단·CAPTCHA·타임아웃·DNS 실패 등. "없음"으로 오판하지 않고 따로 표시. |
| ILLEGAL | 이 사이트 규칙상 허용되지 않는 사용자명(애초에 점검 불가). |
data.json의 각 사이트는 "어떻게 판정하는가"를 필드로 갖는다. checkType은 판정 방식(상태코드·본문문구·리다이렉트URL), presenseStrs는 "계정이 있을 때만 나타나는 문구", absenceStrs는 "없을 때 나타나는 문구", errors는 "이 문구가 보이면 차단/에러다(=UNKNOWN)"를 매핑한 사전이다. 즉 판정 로직이 코드가 아니라 데이터다."ExampleSite": {
"urlMain": "https://example.com/",
"url": "https://example.com/{username}",
"checkType": "message",
"presenseStrs": ["\"profile\":", "data-user-id"],
"absenceStrs": ["User not found", "page-404"],
"errors": { "Captcha required": "CAPTCHA detected",
"Rate limit": "Too many requests" },
"tags": ["us", "social"],
"usernameClaimed": "blue", // 자가검증용: 실제 존재 계정
"usernameUnclaimed": "noonewouldeverusethis8675"
}
{username} 자리에 입력을 끼워 URL을 만들고, checkType대로 판정한다. usernameClaimed/usernameUnclaimed는 --self-check가 "이 규칙이 아직 살아 있는가"를 실제 사이트로 검증할 때 쓰는 알려진 정답쌍이다.
| 패턴 | maigret에서의 구현 |
|---|---|
| 데이터 주도 설계 | 판정 로직을 data.json으로 외부화. 새 사이트 = 데이터 추가(코드 무수정). |
| 큐 기반 워커 풀 | asyncio.Queue + N개 워커. in_parallel로 동시성 상한 조절. |
| 작업별 타임아웃 | asyncio.wait_for(task, timeout) — 느린 사이트가 전체를 막지 못하게. |
| 생성기 스트리밍 | 큐 실행기가 결과를 yield로 흘려보내, 끝나는 대로 즉시 처리. |
| 에러 분류 | DNS 실패·연결거부·차단을 구분(error_detection.py). "병렬 줄여라" vs "DNS 확인해라" 다른 조언. |
| 공유 엔진(engine) | 같은 플랫폼을 쓰는 사이트군은 공통 engine 템플릿으로 규칙 재사용. |
| 재귀 + 추출 | CLAIMED → socid_extractor로 ID/링크 추출 → 새 씨앗 → 재검색. |
maigret을 "3,000명의 안내원에게 동시에 전화 거는 콜센터 관리자"로 생각하자. 어떤 안내원(사이트)은 "그런 분 없습니다"라고 또박또박 말하고(404), 어떤 안내원은 "있긴 한데요…"라며 말끝을 흐리고(200+본문문구), 어떤 안내원은 통화 중이거나 "지금 바쁘니 나중에"라고 끊는다(차단·CAPTCHA). 단순 도구는 "끊겼으니 없는 거네"로 오판한다. maigret은 "끊김(UNKNOWN)"과 "없음(AVAILABLE)"을 절대 같게 취급하지 않는다 — 이것이 오탐을 줄이는 핵심이다.
핵심 패키지 maigret/ 안에 엔진·DB·리포트가 모두 있다.
① result.py의 4상태 열거형으로 "무엇을 판정하나" 파악 → ② sites.py로 사이트 모델 구조 → ③ data.json 샘플 몇 개로 시그니처 실물 → ④ checking.py로 판정·요청·에러분류 → ⑤ executors.py로 동시성 패턴(큐·세마포어). 코드의 핵심 통찰은 거의 다 이 다섯 파일에 있다.
OSINT 도구 아키텍처에서 무엇을 배우나.
maigret은 "수천 개의 네트워크 요청을 어떻게 동시에, 그러나 폭주 없이 처리하나"의 살아 있는 교과서다. 핵심은 ① asyncio.Queue + 고정 워커 수로 동시성 상한을 두고, ② 작업마다 asyncio.wait_for(..., timeout)로 느린 사이트 격리, ③ as_completed/생성기로 끝나는 대로 스트리밍 처리. 무한정 gather로 다 던지면 커넥션 풀이 고갈되고 차단당한다 — 그래서 세마포어/큐로 조이는 것이 핵심.
asyncio.Queue와 워커 5개로 "URL 리스트를 동시에 GET하되 동시 5개로 제한, 각 요청 3초 타임아웃"하는 40줄짜리 미니 크롤러를 직접 짜 보라. gather로만 짠 버전과 비교."사이트마다 다른 판정"을 if-else로 짰다면 3,000개 분기로 코드가 폭발했을 것이다. maigret은 그걸 data.json 스키마(checkType·presenseStrs·absenceStrs·errors)로 외부화했다. 덕분에 기여자가 코드를 몰라도 규칙을 추가하고, --self-check로 규칙의 생존을 자동 검증한다. "변하는 부분은 데이터로, 안 변하는 부분만 코드로"의 모범 사례다.
가장 배울 점은 "확실치 않으면 '없음'이 아니라 '모름(UNKNOWN)'"이라는 자세다. 차단·타임아웃·CAPTCHA를 "없음"으로 뭉개면 결과가 거짓이 된다. errors{} 매핑과 error_detection.py가 이 경계를 코드로 강제한다. 이는 OSINT뿐 아니라 모든 외부 시스템 점검(헬스체크·모니터링)에 그대로 옮길 수 있는 원칙이다.
하나의 결과 모델(MaigretCheckResult)에서 HTML·PDF·CSV·JSON·그래프가 갈라져 나온다. "데이터 모델과 표현(렌더링)의 분리" 패턴이다. report.py를 읽으면 같은 결과 객체를 각 포맷별 직렬화기에 통과시키는 구조가 보인다.
설치는 가볍지만, '사용 권한'이 가장 중요한 요구사항이다.
| 항목 | 요구사항 |
|---|---|
| 런타임 | Python 3.10 이상. pip install maigret 한 줄. |
| 네트워크 | 인터넷(사이트 점검·DB 자동 갱신). 오프라인이면 내장 DB로 동작. |
| PDF 리포트 | 선택: pip install 'maigret[pdf]' + 시스템 그래픽 라이브러리. |
| 차단 우회(선택) | Tor/I2P 데몬 또는 프록시; Cloudflare는 FlareSolverr 도커. |
| AI 요약(선택) | OPENAI_API_KEY 또는 OpenAI 호환 엔드포인트. |
| 배포 옵션 | Docker(soxoj/maigret)·Windows 단일 exe·Cloud Shell·Colab. |
| 사용 권한(필수) | 본인 계정·동의를 받은 대상·인가된 평가 범위에 한해서만. 이것이 가장 중요한 "요구사항"이다. |
maigret의 공식 README도 못 박는다 — "For educational and lawful purposes only." 같은 닉네임이 여러 사이트에 존재한다는 사실이 곧 "동일인"을 뜻하지 않으며(동명이인·우연), 공개 정보라도 이를 결합해 특정 개인을 식별·추적하는 행위는 다수 국가에서 법으로 규제된다.
허용되는 사용: ① 본인의 디지털 발자국 점검(내 닉네임이 어디 노출됐나), ② 명시적 서면 동의·계약 범위가 있는 인가된 침투/OSINT 평가, ③ 위협 인텔리전스·계정 탈취 대응 등 정당한 보안 업무.
금지되는 사용: 동의 없는 특정 개인 추적·신상털이(doxxing)·스토킹·괴롭힘·차별. 한국 개인정보보호법, EU GDPR, 미국 CCPA 등 관할지 법률을 반드시 준수해야 하며, 위반 책임은 전적으로 사용자에게 있다(저자는 오남용에 책임지지 않음). 이 문서는 아키텍처 학습용이며, 실존 인물을 대상으로 한 운영 절차를 제공하지 않는다.
난이도별 5단계 — 모두 본인 계정 / 허가된 대상으로만 안전하게.
본인이 쓰는 닉네임으로 maigret 내닉네임 --html을 돌려, 내가 어디에 계정을 남겼는지 HTML 리포트로 확인한다. 잊고 있던 노출을 찾아 정리(비공개 전환·삭제)하는 것이 목적. 타인 닉네임은 입력하지 말 것.
result.py의 MaigretCheckStatus와 checking.py의 판정 분기를 읽고, "왜 UNKNOWN을 AVAILABLE과 분리하는가"를 한 문단으로 설명해 본다. 그다음 본인 계정 점검 결과에서 UNKNOWN이 뜬 사이트를 골라 왜 모름인지(차단·타임아웃 등) 로그로 확인.
당신이 계정을 가진 사이트 하나에 대해 data.json 스키마(checkType·presenseStrs·absenceStrs·errors·usernameClaimed/Unclaimed)로 시그니처를 작성하고, --self-check로 규칙이 실제로 맞는지 검증한다. usernameClaimed는 본인 계정으로 둘 것.
asyncio + aiohttp로, 당신이 직접 정의한 3~5개 사이트 시그니처에 대해서만 점검하는 50~80줄짜리 미니 maigret을 만든다. 큐+세마포어로 동시성 제한, 작업별 타임아웃, captcha/rate-limit 시 UNKNOWN 처리를 모두 넣는다. 점검 대상은 본인 계정 닉네임만.
maigret을 파이썬 라이브러리로 import해(공식 library-usage 가이드 참조) 본인 닉네임에 대해 비동기 검색을 호출하고, 반환된 MaigretCheckResult 객체에서 status·query_time·ids_data를 직접 꺼내 본다. 그 데이터로 CSV·JSON·간단 HTML 표를 각각 내보내며 "데이터 모델 ↔ 표현 분리"를 체감한다.
6주 코스 — asyncio 기초부터 OSINT 도구 설계까지.
| 주차 | 주제 | 실습 · 참고 |
|---|---|---|
| 1주차 | asyncio / aiohttp 기초 — 코루틴·이벤트 루프·gather | 공식 asyncio 문서 · 미니 동시 크롤러(과제 4 전 단계) |
| 2주차 | 동시성 제어 — 큐·세마포어·타임아웃·백프레셔 | executors.py 정독 · 워커 풀 직접 구현 |
| 3주차 | 데이터 주도 설계 — 로직을 스키마로 외부화 | data.json 스키마 분석 · sites.py 모델 |
| 4주차 | 견고한 외부 점검 — 4상태 판정·에러 분류 | checking.py·error_detection.py · UNKNOWN 처리 |
| 5주차 | 스크래핑·추출 윤리 — robots·레이트리밋·프록시·차단 대응 | socid_extractor · TROUBLESHOOTING.md · 법규 학습 |
| 6주차 | 리포트 파이프라인 — 모델/표현 분리, 다포맷 내보내기 | report.py · D3 그래프 · 미니 리포터 구현 |
본문·소스에 나온 용어 빠른 참조.
| 용어 | 의미 |
|---|---|
| OSINT | 공개 출처 정보 수집·분석. maigret은 username 재사용 단서를 활용. |
| username enumeration | "이 사이트에 이 사용자명이 존재하는가"를 외부에서 판정하는 기법. |
| data.json (사이트 DB) | 3,000+ 사이트의 판정 시그니처 모음. maigret의 단일 진실원. |
| checkType | 판정 방식: status_code·message(본문 문구)·response_url(리다이렉트). |
| presenseStrs / absenceStrs | 계정이 있을 때 / 없을 때만 페이지에 나타나는 표식 문자열. |
| errors{} | "이 문구가 보이면 차단/에러(UNKNOWN)"를 매핑한 사전. |
| MaigretCheckStatus | 판정 4상태: CLAIMED·AVAILABLE·UNKNOWN·ILLEGAL. |
| UNKNOWN | 차단·CAPTCHA·타임아웃 등으로 판정 불가. "없음"과 절대 구분. |
| asyncio / aiohttp | 비동기 동시성 런타임 / 비동기 HTTP 클라이언트. I/O 바운드의 정석. |
| in_parallel | 동시 워커 수(동시성 상한). 너무 크면 차단·풀 고갈 위험. |
| self-check | usernameClaimed/Unclaimed 정답쌍으로 사이트 규칙 생존을 자동 검증. |
| 재귀 검색 (recursive) | 발견 계정에서 추출한 새 username·ID로 다시 검색하는 꼬리 물기. |
| socid_extractor | 프로필/HTML에서 실명·ID·타 계정 링크를 뽑는 자매 라이브러리. |
| FlareSolverr | Cloudflare JS 챌린지를 실제 브라우저로 풀어 주는 별도 도커 서비스. |
| sherlock | username OSINT의 원조 도구. maigret의 정신적 전신. |