Image rights / copyright detection system: SQLite store, HTTP app, search integrations (Naver, Google Custom Search, Google Cloud Vision web detection), image analysis (fingerprints, face/person detection, evidence enrichment, risk scoring), an admin/review layer, governance and retention policies, batch jobs, and a browser-based operator GUI. This baseline incorporates a full code-review remediation pass (46 fixes; 358 tests passing). Highlights: CRITICAL - Prevent evidence cascade-delete during the schema-constraint migration by disabling FK enforcement around the table rebuild. Security - Sandbox served media (neutralize stored XSS from uploaded/collected SVGs) via CSP + nosniff on the untrusted media routes. - Strip embedded EXIF/GPS from external image derivatives before they are sent to third-party APIs. - Return a clean 404 (not an uncaught StopIteration) for PATCH on an unknown provider. Correctness - LLM-summary failures no longer add +30 to the risk score. - Decode only explicit JS escapes so Korean image URLs are not mangled. - Consume search quota only after a successful request. - Naver/Google adapters map responses inside the failure boundary, so a malformed response degrades to evidence instead of crashing enrichment. - Domain-aware provider attribution; face-box IoU de-duplication; count searches (not result items); per-box crop isolation; clamp evidence confidence and Google CSE num; real submittedEpoch; and more. Robustness - Offline LLM connect fast-fails (short connect timeout) so seed/reload requests are not stalled; full read timeout preserved for generation. - Malformed numeric env vars fall back to defaults instead of crashing startup. Performance - Per-submission evidence reads (no full-table scan per rescore), audit-log LIMIT, lazy active-store lookup, hoisted timestamps. Tests - ~24 regression tests added pinning the above fixes. Runtime data (data/, outputs/, *.sqlite3, *.log), secrets (.env), and node_modules are gitignored.
15 KiB
15 KiB
Copyrighter 운영 연결 상태
기준 포트는 9500이다.
이번에 연결된 항목
- 9500 API 서버 골격 생성 완료
- 정적 운영자 콘솔을 9500 서버에서 함께 제공
web/operator-gui/app.js가/api/bootstrapAPI 응답으로 초기 데이터를 갱신하도록 변경- SQLite 저장소 연결 완료
- 로컬 제출 이미지 폴더 연결 완료
- 기본 헬스체크 추가
.env또는 프로세스 환경변수 기반 외부 API/로컬 LLM 설정 로딩- Naver 이미지 검색 API 연결: 수동 검색에서 텍스트 쿼리만 전송
- Google Cloud Vision Web Detection 연결: 신규 로컬 제출 분석 시 파생 이미지 전송
- 내부 Ollama 연결: 재분석 시 기존 근거 기반 LLM 요약 생성
- 증거 상태 기록: 판단에 사용, 무관, 오탐, 보류를 증거별로 저장
- 보류/반려 판정 기반 주의 후보 자동 생성
- 주의 후보 이미지 유사도 매칭, 확정 DB 편입, 오탐 제외 흐름 추가
실행 방법
.env.example을 참고해 루트에 .env를 만들고 필요한 키만 채운다. 키가 없는 외부 API provider는 자동으로 disabled 상태가 된다. Ollama LLM은 로컬 기본값으로 켜진다.
NAVER_CLIENT_ID=
NAVER_CLIENT_SECRET=
GOOGLE_CLOUD_VISION_API_KEY=
COPYRIGHTER_GOOGLE_FACE_CROP_SEARCH=false
OLLAMA_BASE_URL=http://localhost:11434
OLLAMA_MODEL=qwen2.5:0.5b-instruct
cd C:\Users\USER\Desktop\complete\copyrighter
python run_copyrighter_server.py
브라우저:
http://127.0.0.1:9500/
헬스체크:
http://127.0.0.1:9500/health
http://127.0.0.1:9500/api/providers/health
기본 저장 위치:
data/copyrighter.sqlite3
data/submissions/submissions.json
data/submissions/images/
env 키와 연결 동작
| env | 용도 | 연결되는 흐름 |
|---|---|---|
NAVER_CLIENT_ID |
Naver Open API 클라이언트 ID | POST /api/search/manual에서 provider가 naver일 때 사용 |
NAVER_CLIENT_SECRET |
Naver Open API 클라이언트 Secret | Naver 요청 헤더에 사용 |
NAVER_SEARCH_DISPLAY |
검색 결과 개수, 기본 10 |
Naver 이미지 검색 쿼리 |
NAVER_SEARCH_PAGES |
이미지 검색 페이지 수, 기본 1, 최대 10 |
display 단위로 다음 결과 페이지까지 가져온다. 페이지 수만큼 API 호출량이 늘어난다. |
NAVER_SEARCH_SORT |
정렬, 기본 sim |
Naver 이미지 검색 쿼리 |
NAVER_BLOG_SEARCH_DISPLAY |
블로그 검색 결과 개수, 기본 3 |
이미지 검색이 직접 매칭을 만들지 못할 때 원문 페이지 대표 이미지를 찾는 보조 검색 |
NAVER_BLOG_SEARCH_PAGES |
블로그 검색 페이지 수, 기본 1, 최대 10 |
블로그 보조 검색의 다음 결과 페이지까지 가져온다. 페이지 수만큼 API 호출량이 늘어난다. |
NAVER_BLOG_SEARCH_SORT |
블로그 정렬, 기본 sim |
Naver 블로그 검색 쿼리 |
NAVER_WEB_SEARCH_DISPLAY |
웹문서 검색 결과 개수, 기본 3 |
이미지/블로그 검색이 매칭 이미지를 만들지 못할 때 일반 웹문서 페이지 대표 이미지를 찾는 보조 검색 |
NAVER_WEB_SEARCH_PAGES |
웹문서 검색 페이지 수, 기본 1, 최대 10 |
웹문서 보조 검색의 다음 결과 페이지까지 가져온다. 페이지 수만큼 API 호출량이 늘어난다. |
GOOGLE_CLOUD_VISION_API_KEY |
Cloud Vision REST API 키 | 신규 제출 seed 분석에서 Web Detection 사용 |
GOOGLE_CLOUD_VISION_PARENT |
선택 project/location parent | Cloud Vision 요청 body에 선택적으로 포함 |
COPYRIGHTER_GOOGLE_FACE_CROP_SEARCH |
얼굴 영역 Google Web Detection 사용 여부, 기본 false |
재분석 시 감지된 얼굴 영역 crop만 별도 파생 이미지로 보내 웹 근거를 수집한다. 동일인 판정이나 얼굴 인식 점수로 쓰지 않는다. |
GOOGLE_CUSTOM_SEARCH_IMAGE_RESULTS |
Google 이미지 검색 1페이지 결과 개수, 기본 3 |
Google Custom Search 이미지 검색 쿼리 |
GOOGLE_CUSTOM_SEARCH_IMAGE_PAGES |
Google 이미지 검색 페이지 수, 기본 1, 최대 10 |
num 단위로 다음 결과 페이지까지 가져온다. 페이지 수만큼 API 호출량이 늘어난다. |
GOOGLE_CUSTOM_SEARCH_WEB_RESULTS |
Google 웹 검색 1페이지 결과 개수, 기본 3 |
이미지 검색이 직접 매칭을 만들지 못할 때 웹 검색 결과 페이지의 대표 이미지를 찾는다. |
GOOGLE_CUSTOM_SEARCH_WEB_PAGES |
Google 웹 검색 페이지 수, 기본 1, 최대 10 |
웹 검색의 다음 결과 페이지까지 가져온다. 페이지 수만큼 API 호출량이 늘어난다. |
COPYRIGHTER_AUTO_NAVER_QUERY_LIMIT |
Google 근거에서 자동 생성해 실행할 Naver 쿼리 수, 기본 3, 최대 10 |
Google 페이지 제목과 엔티티를 우선순위로 정렬해 여러 텍스트 이미지 검색을 자동 실행한다. |
COPYRIGHTER_AUTO_NAVER_BLOG_QUERY_LIMIT |
이미지 검색 매칭이 없을 때 추가 실행할 Naver 블로그 쿼리 수, 기본 1, 최대 10 |
블로그 검색 결과 페이지의 대표 이미지를 추출해 제출 이미지와 지문 비교한다. |
COPYRIGHTER_AUTO_NAVER_WEB_QUERY_LIMIT |
이미지/블로그 검색 매칭이 없을 때 추가 실행할 Naver 웹문서 쿼리 수, 기본 1, 최대 10 |
웹문서 검색 결과 페이지의 대표 이미지를 추출해 제출 이미지와 지문 비교한다. |
COPYRIGHTER_SEARCH_RESULT_COMPARE_LIMIT |
검색 결과 이미지 URL을 내려받아 제출 이미지와 지문 비교할 건수, 기본 3, 최대 20 |
Naver/Google 결과 이미지를 로컬 저장소에 저장한 뒤 pHash로 제출 이미지와 직접 비교한다. |
COPYRIGHTER_SEARCH_RESULT_PAGE_IMAGE_LIMIT |
검색 결과 원문 페이지에서 추출할 대표 이미지 수, 기본 3, 최대 10 |
결과 자체에 이미지 URL이 없으면 og:image, twitter:image, img 후보를 제한된 수만 내려받아 제출 이미지와 비교한다. |
COPYRIGHTER_SEARCH_RESULT_SIMILARITY_THRESHOLD |
검색 결과 이미지 유사도 필터 임계치, 기본 0.9, 범위 0.0~1.0 |
Naver/Google 결과 후보 이미지의 pHash 유사도 기반 매칭 임계치를 조절한다. |
OLLAMA_BASE_URL |
Ollama 로컬 서버 URL, 기본 http://localhost:11434 |
POST /api/submissions/{id}/rerun-enrichment에서 LLM 요약 사용 |
OLLAMA_MODEL |
Ollama 모델, 기본 qwen2.5:0.5b-instruct |
Ollama /api/generate payload의 model |
COPYRIGHTER_NAVER_DAILY_LIMIT |
Naver 일일 호출 제한 | 로컬 policy gate |
COPYRIGHTER_GOOGLE_DAILY_LIMIT |
Google 일일 호출 제한 | 로컬 policy gate |
COPYRIGHTER_LLM_DAILY_LIMIT |
LLM 일일 호출 제한 | provider 상태 표시용 |
환경변수는 .env보다 우선한다. 예를 들어 PowerShell에서 이미 $env:OLLAMA_MODEL이 있으면 .env 값으로 덮어쓰지 않는다.
공식 문서 기준
- Naver 이미지 검색 API: https://developers.naver.com/docs/serviceapi/search/image/image.md
- Google Cloud Vision Web Detection: https://cloud.google.com/vision/docs/detecting-web
- Ollama Generate API: https://docs.ollama.com/api/generate
- qwen2.5 0.5B Instruct 모델: https://ollama.com/library/qwen2.5:0.5b-instruct
현재 API
GET /health
GET /api/providers/health
GET /api/bootstrap
GET /api/review-queue
GET /api/submissions/{submission_id}/review
POST /api/submissions/reload
POST /api/submissions/{submission_id}/rerun-enrichment
POST /api/submissions/{submission_id}/decision
POST /api/evidence/{evidence_id}/status
POST /api/knowledge/{entry_id}/promote-watchlist
POST /api/knowledge/{entry_id}/exclude-watchlist
POST /api/search/manual
GET /api/providers
PATCH /api/providers/{provider_id}
POST /api/providers/emergency-disable
GET /api/audit-events
GET /media/{image_path}
로컬 이미지 저장 방식
제출 이미지는 두 방식으로 넣을 수 있다.
- 빠른 방식: 이미지 파일을
data/submissions/images/아래에 복사한 뒤 화면에서새 제출 불러오기를 누른다. 이 경우 제출 ID와 제목은 파일명 기준으로 자동 생성된다. - 명시 방식:
data/submissions/submissions.json에 ID, 제목, 크기, 제출 시간을 직접 등록한 뒤 화면에서새 제출 불러오기를 누른다.
예시:
[
{
"id": "SUB-LOCAL-001",
"title": "로컬 얼굴 이미지 샘플",
"file": "images/local-face.svg",
"width": 1200,
"height": 900,
"submitted_at": "2026-05-26 10:00"
}
]
이미지 파일은 data/submissions/images/ 아래에 둔다. 서버는 이 폴더 밖으로 나가는 경로를 거부한다.
서버를 재시작하지 않아도 된다. 운영 콘솔의 심사 큐 상단에서 새 제출 불러오기를 누르면 submissions.json 변경분과 폴더에 새로 복사한 이미지가 SQLite DB로 import된다.
운영자 판정 흐름
- 제출 이미지를 선택하고 상단의
선택 재분석또는 행 안의 증거를 확인한다. - 증거 행에서
판단에 사용,무관,오탐,보류를 표시한다. 이 표시는 케이스 기록과 점수 반영 여부를 정리할 뿐, 기준 DB 후보를 만들지는 않는다. - 케이스 판정을 먼저 내린다.
승인은 자동 후보를 만들지 않는다.보류와반려는 제출 이미지 지문과 선택된 근거를 묶어주의 후보를 만든다. - 이후 같은 이미지가 들어오면
주의 후보 근거그룹에 별도로 표시되고 높은 위험 신호로 반영된다. 그래도 최종 판정은 자동 변경되지 않는다. - 지식 DB 화면에서 주의 후보를 검토한 뒤
확정 DB 편입또는오탐 제외를 누른다. 제외된 후보는 다음 내부 유사도 분석에서 사용하지 않는다.
외부 API 연결 상태
연결 완료:
- Naver:
https://openapi.naver.com/v1/search/image에GET요청을 보낸다.query,display,start,sort를 쿼리스트링으로 보내고X-Naver-Client-Id,X-Naver-Client-Secret헤더를 사용한다. - Google:
https://vision.googleapis.com/v1/images:annotate에WEB_DETECTION요청을 보낸다. 서버는 내부 파생 이미지 bytes를 base64로 인코딩해 전송한다. - Provider 상태: 외부 API는 키가 있으면 enabled, 없으면 disabled와 missing env 사유를
/api/providers에 표시한다. - Provider Controls: 화면의 provider 활성/비활성 상태는 DB에 저장된다.
아직 운영 검증이 필요한 것:
- 실제 운영 키로 Naver/Google 샘플 호출 품질 확인
- 공급자별 재시도/backoff 정책
- 일일 한도 초과 시 관리자 알림
- API 키를 Windows 서비스/작업 스케줄러/배포 환경의 시크릿 저장소로 옮기는 운영 방식
운영 경계:
- Naver에는 텍스트 쿼리만 보낸다.
- Naver로 원본 이미지나 파생 이미지를 보내지 않는다.
- Google에는 승인된 파생 이미지만 보낸다.
- Google Cloud Vision 사용 전 계약, DPA, 데이터 보존, 메타데이터 로깅, 리전, 자격 증명 정책을 확인한다.
LLM 연결 상태
연결 완료:
- Ollama 로컬 API에
POST /api/generate요청을 보낸다. - 기본 URL은
http://localhost:11434, 기본 모델은qwen2.5:0.5b-instruct다. - Ollama 요청은 API 키를 쓰지 않는다.
- 응답 스트리밍은 끄고
stream: false로 한 번에 요약을 받는다. rerun-enrichment는 이미 저장된 내부/Naver/Google 근거만 LLM 입력으로 넘긴다.- LLM 요약은
source_evidence_ids또는 출처 URL을 가진 보조 근거로만 저장한다. - LLM 실패는 기존 근거를 낮추지 않고 failure evidence로 남긴다.
설치 명령:
ollama pull qwen2.5:0.5b-instruct
아직 운영 검증이 필요한 것:
- 실제 운영 PC에서
ollama pull qwen2.5:0.5b-instruct후 샘플 재분석 호출 품질 확인 - 프롬프트/응답 로그 저장 여부와 마스킹 정책 확정
- LLM 요약을 신청자에게 노출하지 않는 정책 재검증
LLM이 해도 되는 일:
- 검색 쿼리 후보 생성
- 증거 요약
- 중복/상충 증거 정리
- 운영자가 읽기 쉬운 근거 메모 생성
LLM이 하면 안 되는 일:
- 최종 승인/보류/반려 결정
- 단독 점수 산정
- 출처 없는 유명인/작품/IP 단정
- 신청자에게 보여줄 자동 설명 생성
아직 안 된 것: 운영용 인증과 배포
현재 9500 서버는 로컬 실행용이다. 실운영 전에 아래가 필요하다.
- 로그인/세션 또는 사내 SSO
- 일반 운영자와 관리자 권한 분리
- Provider Controls 관리자 전용 접근
- 운영자 결정/보정/지식 DB 변경 감사 로그 강화
- 9500 서버 프로세스 관리 방식 확정
- 백업, 복구, 모니터링, 알림
Google Custom Search 설정
| env | 용도 | 연결되는 흐름 |
|---|---|---|
GOOGLE_CUSTOM_SEARCH_API_KEY |
Google Programmable Search JSON API 키 | Google Vision 텍스트 단서를 Google 이미지/웹 검색으로 확장 |
GOOGLE_CUSTOM_SEARCH_CX |
Programmable Search Engine ID | Custom Search JSON API cx 값 |
GOOGLE_CUSTOM_SEARCH_IMAGE_RESULTS |
이미지 검색 결과 개수, 기본 3 |
텍스트 쿼리 기반 이미지 결과를 내려받아 제출 이미지와 pHash 비교 |
GOOGLE_CUSTOM_SEARCH_WEB_RESULTS |
웹 검색 결과 개수, 기본 3 |
이미지 검색이 비었을 때 웹 결과 페이지 대표 이미지를 비교 |
COPYRIGHTER_AUTO_GOOGLE_CUSTOM_QUERY_LIMIT |
자동 Google Custom Search 쿼리 수, 기본 2, 최대 10 |
제출 이미지를 보내지 않고 텍스트 쿼리만 전송 |
COPYRIGHTER_GOOGLE_CUSTOM_SEARCH_DAILY_LIMIT |
Google Custom Search 일일 호출 제한 | 로컬 policy gate |
자동 검색 쿼리 원천:
- Google 페이지 제목과 엔티티가 가장 높은 우선순위다.
- Google best guess label만 있는 경우에도
person,gentleman,portrait같은 일반어는 버리고,IU official profile처럼 구체적인 문구만 낮은 우선순위 쿼리로 사용한다. - 얼굴 영역 Google Web Detection은 동일 인물 판정 근거로 쓰지 않는다. 다만 페이지 제목이나 엔티티가 구체적이면 낮은 우선순위의 텍스트 검색 쿼리로만 재사용한다.
- 페이지 제목이 영어권 문구면 보조 쿼리에
image를 붙이고, 한국어 문구면이미지를 붙인다. 검색엔진에 깨진 한글 템플릿을 보내지 않는다. - Google Custom Search와 Naver에는 제출 이미지를 보내지 않고 텍스트 쿼리만 보낸 뒤, 검색 결과 이미지나 원문 페이지 대표 이미지를 내려받아 로컬에서 pHash 비교한다.
설정 진단:
/api/providers/health와 공급자 화면은requiredEnv,configuredEnv를 비밀값 없이 표시한다.google_search가 disabled이면 우선GOOGLE_CUSTOM_SEARCH_API_KEY,GOOGLE_CUSTOM_SEARCH_CX가 둘 다.env에 있는지 확인한다.