# -*- coding: utf-8 -*-
"""치토스(CHITOS) 인스턴트 스캔 — 공개 UI(얇은 클라이언트) + 전문 리포트 생성. 한/영 2개국어.
로직·채점은 비공개 엔진(치토스 서버)에서 수행 → 이 공개 소스엔 방법이 없습니다.
진단 문구는 엔진이 KO/EN 양쪽으로 반환 → 토글 시 재스캔 없이 즉시 전환.
2단계 인증: 회사/조직 도메인 메일로 가입한 HF 계정 로그인 → 이메일 도메인 일치 시 심화.
서비스: 퀀트ID(QuantID) · 엔진: 치토스(CHITOS)."""
import os, html, json, tempfile, datetime
from urllib.parse import urlparse
import requests
import gradio as gr
from huggingface_hub import whoami
from gradio_client import Client
ENGINE = os.environ.get("CHITOS_ENGINE_REPO", "")
HFTOKEN = os.environ.get("HF_TOKEN", "")
CONSULT = os.environ.get("CHITOS_CONSULT_ENDPOINT", "")
_eng_client = None
GCOL = {"A+": "#067647", "A": "#067647", "B": "#2563eb", "C": "#b54708", "D": "#b54708", "F": "#b42318", "-": "#9ca3af"}
SEV = {"hi": ("#b42318", "#fef3f2"), "mid": ("#b54708", "#fffaeb"), "low": ("#8a6d00", "#fdfce8")}
SEVLAB = {"ko": {"hi": "높음", "mid": "보완", "low": "낮음"}, "en": {"hi": "High", "mid": "Advisory", "low": "Low"}}
def _L(ko, en, lang):
return ko if lang == "ko" else en
def _P(v, lang):
"""엔진이 [ko,en]로 준 값에서 언어 선택. 문자열이면 그대로."""
if isinstance(v, (list, tuple)) and len(v) == 2 and all(isinstance(e, str) for e in v):
return v[0] if lang == "ko" else v[1]
return str(v)
def _engine():
global _eng_client
if _eng_client is None and ENGINE:
try:
_eng_client = Client(ENGINE, token=HFTOKEN or None)
except TypeError:
_eng_client = Client(ENGINE, hf_token=HFTOKEN or None)
return _eng_client
def _api(path, payload):
global _eng_client
import time as _t
last = ""
for attempt in range(4):
c = _engine()
if c is None:
return {"error": ["진단 엔진이 아직 연결되지 않았습니다(운영 설정 대기).", "Diagnosis engine not connected yet (operational setup pending)."]}
try:
res = c.predict(payload.get("url", ""), api_name="/" + path.strip("/"))
return json.loads(res)
except Exception as e:
last = str(e)
transient = any(k in last for k in ("429", "Too Many", "503", "502", "504",
"timed out", "timeout", "Connection", "starting"))
if transient and attempt < 3:
_eng_client = None
_t.sleep(2 + 3 * attempt)
continue
break
return {"error": ["진단 엔진이 잠시 혼잡합니다. 10~20초 후 다시 눌러 주세요. (%s)" % last[:70],
"Engine is briefly busy. Please try again in 10–20s. (%s)" % last[:70]]}
def _domain(u):
u = (u or "").strip()
if "://" not in u:
u = "https://" + u
return (urlparse(u).hostname or "").replace("www.", "")
# ===== 비주얼 조각 =====
def _letter(p):
return "A+" if p >= 93 else "A" if p >= 85 else "B" if p >= 75 else "C" if p >= 65 else "D" if p >= 50 else "F"
def _overall(d):
sec = d.get("sec_grade", ["-", 0])[1] or 0
seo = d.get("seo_grade", ["-", 0])[1] or 0
geo = d.get("geo_grade", ["-", 0])[1] or 0
pct = round(sec * 0.45 + geo * 0.30 + seo * 0.25)
return pct, _letter(pct)
def _ring(pct, grade, lang, dark=False):
col = GCOL.get(grade, "#6d28d9")
inner = "#1b0f3b" if dark else "#fff"
sub = "#d6bcff" if dark else "#6b7280"
return ('
') % (
col, pct, ("#2a1a52" if dark else "#e9e4f7"), inner, col, grade, sub, pct, sub, _L("종합", "OVERALL", lang))
def _scorebar(label, g):
L, s = g[0], g[1] or 0
col = GCOL.get(L, "#6d28d9")
return ('') % (
label, s, col, col, col, L, s)
def _locks(n, lang):
cells = ""
for i in range(6):
on = i < n
cells += ('') % ("#ecfdf3" if on else "#fef3f2", "🔒" if on else "🔓")
cap = _L("보안 응답 헤더 6종 중 %d개 적용", "%d of 6 security response headers applied", lang) % n
return ('🔐 ' + cap + '
'
'' + cells + '
')
def _metacards(meta, lang):
tl = len(html.unescape(str(meta.get("title") or "")))
data = [(_L("제목 길이", "Title len", lang), "%d%s" % (tl, _L("자", "ch", lang))),
("H1", str(meta.get("h1", 0))),
(_L("구조화", "Schema", lang), str(meta.get("jsonld", 0))),
(_L("홈 용량", "Home", lang), "%dKB" % meta.get("size_kb", 0)),
("alt", "%d%%" % meta.get("alt_cov", 0))]
cells = "".join('' % (v, k) for k, v in data)
return '' + cells + '
'
def _consult_cta(nhi, nmid, nlow, lang):
total = nhi + nmid + nlow
if lang == "ko":
risk = '%d개 개선점(높음 %d · 보완 %d · 낮음 %d)' % (total, nhi, nmid, nlow)
body = ("이 진단에서 " + risk + "이 확인됐습니다. 하지만 비침투 스캔은 ‘밖에서 보이는 표면’까지입니다 — "
"SQL삽입·파일업로드·인증우회·IDOR 같은 실제 취약점, 그리고 ARTEX처럼 AI 에이전트가 자동으로 뚫는 위협은 "
"능동 정밀 진단으로만 확인됩니다.")
areas = "웹서비스 · AX 인프라 · AI 모델 · AI 에이전트(AX-RAY) 4대 진단 + 금융보안원 17항목 레드티밍"
go = "👉 ③ 정밀 진단 상담 탭에서 분야를 선택해 신청하세요. (서면 허가 기반·국내외 준거 수용)"
head = "🎯 다음 단계 — 정밀 진단 상담"
else:
risk = '%d improvement points (High %d · Advisory %d · Low %d)' % (total, nhi, nmid, nlow)
body = ("This scan found " + risk + ". But a non-intrusive scan only shows the surface — "
"real vulnerabilities like SQLi, file upload, auth bypass and IDOR, and threats where an AI agent breaks in automatically (as with ARTEX), "
"are confirmed only by active in-depth testing.")
areas = "Web Service · AX Infra · AI Model · AI Agent (AX-RAY) — 4 diagnoses + FSI 17-item red-teaming"
go = "👉 Request it in the ③ In-Depth Consultation tab (authorization-based · aligned to domestic & international standards)."
head = "🎯 Next step — in-depth consultation"
return (''
'
' + head + '
'
'
' + body + '
'
'
🛡 ' + areas + '
'
'
' + go + '
')
def _upsell(lang):
return (''
'
🔓 ' + _L("전사이트 정밀 리포트 — ② 도메인 인증 탭", "Full-site report — ② Domain Verification tab", lang) + '
'
'
' + _L(
"이 리포트는 홈 1페이지 기준입니다. 회사/조직 도메인 메일로 가입한 Hugging Face 계정으로 로그인하면 — 전사이트 페이지별 이슈 · 구조화데이터 커버리지 · 이메일 위조방지(SPF/DMARC) · 자기모순(주소·전화) 탐지 · 다운로드 리포트까지 받습니다.",
"This report is based on the home page. Sign in with a Hugging Face account on your company/org domain email to unlock — per-page issues across the whole site · structured-data coverage · email anti-spoofing (SPF/DMARC) · self-contradiction (address/phone) detection · downloadable report.", lang)
+ '
')
def _statrow(label, ok, val="", warn=False):
col = "#16a34a" if ok else ("#b54708" if warn else "#dc2626")
sym = "✓" if ok else ("!" if warn else "✕")
v = ('%s' % html.escape(str(val))) if val else ""
return (''
'%s'
'%s%s
') % (col, sym, html.escape(label), v)
def _email_block(ed, es, lang):
if not ed:
return (''
+ _statrow("SPF", bool(es.get("spf")), warn=not es.get("spf"))
+ _statrow("DMARC", bool(es.get("dmarc")), warn=not es.get("dmarc")) + '
')
spf_ok = ed.get("spf") and ed.get("spf_qual") == "-all"
dmarc_ok = ed.get("dmarc") and ed.get("dmarc_p") != "none"
rows = [
_statrow(_L("SPF (발신 서버 인증)", "SPF (sender authorization)", lang), ed.get("spf") and ed.get("spf_qual") != "+all", _L("종료규칙 ", "qualifier ", lang) + str(ed.get("spf_qual", "?")), warn=(ed.get("spf") and not spf_ok)),
_statrow(_L("SPF DNS 룩업 (≤10)", "SPF DNS lookups (≤10)", lang), ed.get("spf_lookups", 0) <= 10, "~%d" % ed.get("spf_lookups", 0), warn=(ed.get("spf_lookups", 0) > 10)),
_statrow(_L("DKIM 서명", "DKIM signing", lang), ed.get("dkim", False), warn=not ed.get("dkim", False)),
_statrow(_L("DMARC 정책", "DMARC policy", lang), dmarc_ok, "p=" + str(ed.get("dmarc_p", "none")), warn=(ed.get("dmarc") and not dmarc_ok)),
_statrow(_L("BIMI (브랜드 로고)", "BIMI (brand logo)", lang), ed.get("bimi", False), warn=not ed.get("bimi", False)),
_statrow(_L("MTA-STS (전송 암호화 강제)", "MTA-STS (enforced mail TLS)", lang), ed.get("mta_sts", False), warn=not ed.get("mta_sts", False)),
_statrow(_L("TLS-RPT (메일 TLS 리포트)", "TLS-RPT (mail TLS reporting)", lang), ed.get("tls_rpt", False), warn=not ed.get("tls_rpt", False)),
]
return '' + "".join(rows) + '
'
def _dns_block(ed, lang):
if not ed:
return "—"
rows = [
_statrow("DNSSEC", ed.get("dnssec", False), warn=not ed.get("dnssec", False)),
_statrow(_L("CAA (인증서 발급 통제)", "CAA (cert issuance control)", lang), ed.get("caa", False), warn=not ed.get("caa", False)),
_statrow(_L("MX (메일 수신)", "MX (mail receiving)", lang), ed.get("mx", 0) > 0, "%d" % ed.get("mx", 0)),
_statrow(_L("NS 이중화 (2+)", "NS redundancy (2+)", lang), ed.get("ns", 0) >= 2, "%d" % ed.get("ns", 0), warn=(ed.get("ns", 0) < 2)),
]
return '' + "".join(rows) + '
'
def _sitesec_block(ss, extra_exposed, lang):
if not ss:
return "—"
n = ss.get("pages", 0) or 1
sri_t = ss.get("sri_total", 0)
rows = [
_statrow(_L("전 페이지 HTTPS", "HTTPS on all pages", lang), ss.get("https_pages", 0) == n, "%d/%d" % (ss.get("https_pages", 0), n)),
_statrow(_L("핵심 보안헤더 적용", "Core security headers", lang), ss.get("missing_hdr_pages", 0) == 0, _L("누락 ", "missing ", lang) + "%d" % ss.get("missing_hdr_pages", 0), warn=ss.get("missing_hdr_pages", 0) > 0),
_statrow(_L("혼합 콘텐츠(HTTP 리소스) 없음", "No mixed content (HTTP resources)", lang), ss.get("mixed_pages", 0) == 0, _L("%d페이지", "%d pages", lang) % ss.get("mixed_pages", 0) if ss.get("mixed_pages", 0) else ""),
_statrow(_L("외부 스크립트 SRI", "External-script SRI", lang), sri_t == 0 or ss.get("sri_with", 0) == sri_t, "%d/%d" % (ss.get("sri_with", 0), sri_t), warn=(sri_t and ss.get("sri_with", 0) < sri_t)),
_statrow(_L("민감정보 패턴 비노출", "No PII patterns exposed", lang), ss.get("pii_pages", 0) == 0, _L("%d페이지", "%d pages", lang) % ss.get("pii_pages", 0) if ss.get("pii_pages", 0) else ""),
_statrow(_L("추가 민감 파일 비노출", "No extra sensitive files", lang), not extra_exposed, (", ".join(extra_exposed) if extra_exposed else "")),
]
ext = ss.get("ext_scripts", [])
extd = ""
if ext:
chips = "".join('%s' % html.escape(x) for x in ext)
extd = '' + _L("외부 스크립트 출처: ", "Third-party script sources: ", lang) + '
' + chips
return '' + "".join(rows) + '
' + extd
def _checklist(items, lang):
if not items:
return ""
ic = {"pass": ("✓", "#16a34a", "#f0fdf4"), "warn": ("!", "#b54708", "#fffaeb"), "fail": ("✕", "#b42318", "#fef2f2")}
npass = len([i for i in items if i[0] == "pass"])
cells = ""
for st, lab in items:
sym, col, bg = ic.get(st, ic["warn"])
cells += (''
'%s'
'%s
') % (bg, col, sym, html.escape(_P(lab, lang)))
head = (_L("점검 항목 전체", "All checks", lang) + ' · %d/%d %s' % (npass, len(items), _L("통과", "pass", lang)))
return ('📋 ' + head + '
'
'' + cells + '
')
def _cards(items, empty, lang):
if not items:
return '%s
' % empty
out = ""
for it in items:
sev = it[0]
title = _P(it[1], lang)
detail = _P(it[2], lang)
c, bg = SEV.get(sev, SEV["low"])
lab = SEVLAB[lang][sev]
out += ('') % (
c, bg, c, lab, html.escape(title), html.escape(detail))
return out
def _traffic(matrix, lang):
rows = ""
for name, code in (matrix or {}).items():
ok = code in ("200", "302", "301")
blocked = code in ("403", "401", "429", "차단")
dot = "#16a34a" if ok else ("#dc2626" if blocked else "#9ca3af")
txt = _L("정상", "OK", lang) if ok else (_L("차단", "Blocked", lang) if blocked else code)
rows += (''
''
'%s%s
') % (dot, html.escape(str(name)), txt)
return '%s
' % rows
def _note(msg, sev="ok"):
c = {"ok": "#067647", "mid": "#b54708", "hi": "#b42318"}.get(sev, "#067647")
bg = {"ok": "#ecfdf3", "mid": "#fffaeb", "hi": "#fef3f2"}.get(sev, "#ecfdf3")
return '%s
' % (c, bg, msg)
def _h2(t):
return '%s
' % t
def _priority(allf, lang):
def li(lst, when, col):
if not lst:
return ""
items = "".join("%s" % html.escape(_P(f[1], lang)) for f in lst)
return ('') % (col, col, when, items)
hi = [f for f in allf if f[0] == "hi"]
mid = [f for f in allf if f[0] == "mid"]
low = [f for f in allf if f[0] == "low"]
out = (li(hi, _L("즉시 (높음)", "Immediate (High)", lang), "#b42318")
+ li(mid, _L("단기 (보완)", "Short-term (Advisory)", lang), "#b54708")
+ li(low, _L("중기 (낮음)", "Mid-term (Low)", lang), "#8a6d00"))
return out or ("%s
" % _L("✓ 시급 조치 없음", "✓ No urgent action needed", lang))
def _badges_labels(lang):
return (_L("🛡 보안", "🛡 Security", lang), _L("📈 SEO", "📈 SEO", lang), _L("🔍 AI가시성", "🔍 AI Visibility", lang))
def build_teaser(d, lang="ko"):
if d.get("error"):
return _note("⚠ " + html.escape(_P(d["error"], lang)), "hi")
if d.get("blocked"):
return ('') % (
_L("자동 공격 차단(WAF) 작동 중", "Automated attack blocking (WAF) active", lang),
_L("비정상 접근을 탐지해 차단했습니다. 방어가 잘 되어 있다는 신호입니다. 치토스는 차단을 우회하지 않습니다.",
"Abnormal access was detected and blocked. A sign your defenses work. CHITOS does not bypass blocks.", lang))
host = d.get("host", "")
today = datetime.date.today().isoformat()
allf = list(d.get("sec", [])) + list(d.get("seo", [])) + list(d.get("geo", []))
nhi = len([f for f in allf if f[0] == "hi"])
nmid = len([f for f in allf if f[0] == "mid"])
top = [f for f in allf if f[0] == "hi"] + [f for f in allf if f[0] == "mid"]
top = top[:3]
topcards = _cards(top, _L("✓ 눈에 띄는 시급 이슈 없음", "✓ No notable urgent issues", lang), lang)
pct, grade = _overall(d)
mx = d.get("matrix", {})
ai_ok = len([1 for c in mx.values() if c in ("200", "301", "302")])
ai_tot = len(mx) or 9
bS, bSEO, bAI = _badges_labels(lang)
return (''
'
'
+ _ring(pct, grade, lang, dark=True)
+ '
'
'
C H I T O S · ' + _L("무료 즉시 진단", "Free Instant Scan", lang) + '
'
'
' + html.escape(host) + '
'
'
' + today + ' · ' + _L("홈 1페이지 · 비침투(읽기전용)", "home page · non-intrusive (read-only)", lang) + '
'
'
'
'🚨 ' + _L("높음", "High", lang) + ' ' + str(nhi) + ''
'⚠ ' + _L("보완", "Advisory", lang) + ' ' + str(nmid) + ''
'🤖 ' + _L("AI로봇", "AI bots", lang) + ' ' + str(ai_ok) + '/' + str(ai_tot) + ''
'
'
+ '
'
+ _scorebar(bS, d["sec_grade"])
+ _scorebar(bSEO, d.get("seo_grade", ["-", 0]))
+ _scorebar(bAI, d["geo_grade"])
+ '
' + _L("종합은 보안 45% · AI가시성 30% · SEO 25% 가중 평균", "Overall = Security 45% · AI Visibility 30% · SEO 25% weighted", lang) + '
'
+ '
⚡ ' + _L("가장 급한 것 (상위 ", "Top priorities (top ", lang) + str(len(top)) + ')
' + topcards
+ '
🤖 ' + _L("AI 로봇이 내 사이트에 들어올 수 있나", "Can AI crawlers reach your site?", lang) + '
'
+ _traffic(mx, lang)
+ '
'
'
🔓 ' + _L("전사이트 정밀 리포트가 더 있습니다", "A full-site detailed report is available", lang) + '
'
'
' + _L(
"지금은 홈 1페이지 요약입니다. ② 도메인 인증 탭에서 회사/조직 도메인 메일로 가입한 Hugging Face 계정으로 로그인하면 — 전사이트 페이지별 이슈 · 구조화데이터 커버리지 · 이메일 위조방지(SPF/DMARC) · 자기모순(주소·전화) 탐지 · 조치 우선순위 · 다운로드 리포트까지 받습니다.",
"This is a home-page summary. In the ② Domain Verification tab, sign in with a Hugging Face account registered on your company/org domain email to unlock — per-page issues across the site · structured-data coverage · email anti-spoofing (SPF/DMARC) · self-contradiction (address/phone) detection · action priorities · downloadable report.", lang)
+ '
'
'
' + _L("서비스: 퀀트ID(QuantID) · 엔진: 치토스(CHITOS)", "Service: QuantID · Engine: CHITOS", lang) + '
')
def build_report(d, deep=False, verified_email=None, lang="ko", show_upsell=False):
if d.get("error"):
return _note("⚠ " + html.escape(_P(d["error"], lang)), "hi")
if d.get("blocked"):
return ('') % (
_L("자동 공격 차단(WAF) 작동 중", "Automated attack blocking (WAF) active", lang),
_L("비정상 접근을 탐지해 차단했습니다. 방어가 잘 되어 있다는 좋은 신호입니다. 치토스는 차단을 우회하지 않습니다(읽기전용).",
"Abnormal access was detected and blocked. A good sign your defenses work. CHITOS does not bypass blocks (read-only).", lang))
host = d.get("host", "")
meta = d.get("meta", {})
today = datetime.date.today().isoformat()
sec, seo, geo = d.get("sec", []), d.get("seo", []), d.get("geo", [])
allf = list(sec) + list(seo) + list(geo)
dp = d.get("deep", {}) if deep else {}
if dp:
allf += dp.get("issues", [])
nhi = len([f for f in allf if f[0] == "hi"])
nmid = len([f for f in allf if f[0] == "mid"])
nlow = len([f for f in allf if f[0] == "low"])
good = "".join('✓ %s' % html.escape(_P(g, lang)) for g in d.get("good", [])) or "—"
bS, bSEO, bAI = _badges_labels(lang)
deep_html = ""
if dp:
es = dp.get("email_sec", {})
def _pill(ok, t):
c = "#16a34a" if ok else "#dc2626"
return '%s %s' % (c, c, ("✓" if ok else "✕"), t)
sch = "".join('%s' % html.escape(x) for x in dp.get("schema_present", [])) or _L("없음", "none", lang)
schm = "".join('%s' % html.escape(x) for x in dp.get("schema_missing", [])) or "—"
incon = ""
if len(dp.get("phones", [])) > 1 or len(dp.get("addrs", [])) > 1 or len(dp.get("bizno", [])) > 1:
r = ""
if len(dp.get("phones", [])) > 1:
r += '| %s | %s |
' % (_L("전화", "Phone", lang), html.escape(", ".join(dp["phones"])))
if len(dp.get("addrs", [])) > 1:
r += '| %s | %s |
' % (_L("주소", "Address", lang), html.escape(" / ".join(dp["addrs"])))
if len(dp.get("bizno", [])) > 1:
r += '| %s | %s |
' % (_L("사업자번호", "Biz reg. no.", lang), html.escape(", ".join(dp["bizno"])))
incon = ('⚠ %s
') % (
_L("확인 필요 — 사이트 안에서 다르게 보이는 값", "Please verify — values that differ across the site", lang),
_L("항목", "Field", lang), _L("발견 값", "Found values", lang), r)
pglist = "".join('%s %s %s
' % (('✓' if c.get('meta') else '⚠'), html.escape(html.unescape(c.get('title') or c.get('url', ''))), html.escape(c.get('url', ''))) for c in dp.get("crawled", [])[:12])
deep_html = (
'🔓 ' + (_L("소유 인증 전용 심화 — 전사이트 %d페이지 정밀 분석 (비침투)", "Verified-owner deep scan — %d pages analyzed (non-intrusive)", lang) % dp.get("pages", 0)) + '
'
+ _h2("🔎 " + _L("심화 발견", "Deep findings", lang)) + _cards(dp.get("issues", []), _L("✓ 심화 점검 추가 이슈 없음", "✓ No additional deep-scan issues", lang), lang)
+ _h2("📊 " + _L("전사이트 요약", "Full-site summary", lang))
+ (''
'| %s | %d | %s | %d |
'
'| %s | %d | %s | %d |
'
'| %s | %d | %s | %d |
') % (
_L("점검 페이지", "Pages scanned", lang), dp.get("pages", 0), _L("제목 중복", "Duplicate titles", lang), dp.get("dup_titles", 0),
_L("메타설명 없음", "No meta desc.", lang), dp.get("no_meta", 0), _L("얇은 페이지", "Thin pages", lang), dp.get("thin", 0),
_L("색인차단", "noindex", lang), dp.get("noindex", 0), _L("canonical 없음", "No canonical", lang), dp.get("no_canonical", 0))
+ _h2("🧩 " + _L("구조화 데이터 커버리지", "Structured-data coverage", lang)) + '' + _L("보유:", "Present:", lang) + ' ' + sch + '
' + _L("누락:", "Missing:", lang) + ' ' + schm + '
'
+ _h2("📧 " + _L("이메일 보안 심화 (SPF·DKIM·DMARC·BIMI)", "Email security — deep (SPF·DKIM·DMARC·BIMI)", lang)) + _email_block(dp.get("email_dns", {}), es, lang)
+ _h2("🌐 " + _L("DNS·도메인 심화", "DNS & domain — deep", lang)) + _dns_block(dp.get("email_dns", {}), lang)
+ _h2("🧱 " + _L("전사이트 보안 일관성", "Site-wide security consistency", lang)) + _sitesec_block(dp.get("site_sec", {}), dp.get("extra_exposed", []), lang)
+ incon
+ _h2("📋 " + _L("전사이트 점검 체크리스트", "Full-site checklist", lang)) + _checklist(dp.get("checks", []), lang)
+ _h2("🗂 " + (_L("점검 페이지 (%d)", "Scanned pages (%d)", lang) % dp.get("pages", 0))) + '' + pglist + '
'
)
vbanner = ('✓ %s
' % (
_L("도메인 소유 인증 완료(%s) — 심화 진단 포함", "Domain ownership verified (%s) — deep scan included", lang) % html.escape(verified_email))) if verified_email else ""
kind = _L("심화 진단", "Deep Scan", lang) if deep else _L("인스턴트 진단", "Instant Scan", lang)
title80 = html.escape(html.unescape(str(meta.get('title') or _L('(없음)', '(none)', lang))))[:80]
pct, grade = _overall(d)
body = (''
'
'
'
'
'
C H I T O S · ' + kind + '
'
'
' + _L("보안 · SEO · AI가시성 진단 리포트", "Security · SEO · AI-Visibility Report", lang) + '
'
'
' + _L("대상", "Target", lang) + ' ' + html.escape(host) + ' · ' + today + ' · ' + _L("비침투(읽기전용)", "non-intrusive (read-only)", lang) + '
'
+ _ring(pct, grade, lang, dark=True) + '
'
+ vbanner
+ '
'
+ _scorebar(bS, d['sec_grade'])
+ _scorebar(bSEO, d.get('seo_grade', ['-', 0]))
+ _scorebar(bAI, d['geo_grade'])
+ '
' + _L("자물쇠", "Locks", lang) + ' ' + str(d.get('locks_on', 0)) + '/6 · ' + _L("종합은 보안 45% · AI가시성 30% · SEO 25% 가중", "Overall = Security 45% · AI Visibility 30% · SEO 25% weighted", lang) + '
'
+ '
'
+ '
' % (nhi, _L("높음", "High", lang))
+ '
' % (nmid, _L("보완", "Advisory", lang))
+ '
' % (nlow, _L("낮음", "Low", lang))
+ '
'
+ _h2("🛡 " + _L("보안 점검", "Security checks", lang)) + _locks(d.get('locks_on', 0), lang) + _cards(sec, _L("✓ 외부에서 보이는 보안 위생 문제 없음", "✓ No externally visible security-hygiene issues", lang), lang) + _checklist(d.get('checks', {}).get('sec', []), lang)
+ _h2("📈 " + _L("SEO 토대 (검색 노출)", "SEO foundation (search visibility)", lang)) + _metacards(meta, lang) + _cards(seo, _L("✓ SEO 기본 요소 양호", "✓ SEO fundamentals are healthy", lang), lang) + _checklist(d.get('checks', {}).get('seo', []), lang)
+ _h2("🤖 " + _L("AI 로봇 접근 (AI 가시성)", "AI crawler access (AI visibility)", lang))
+ '
' + _L("생성형 AI가 당신을 인용하려면 로봇이 먼저 들어와야 합니다.", "For generative AI to cite you, its crawlers must get in first.", lang) + '
'
+ _traffic(d.get('matrix', {}), lang) + _cards(geo, _L("✓ 기계 판독 신호 양호", "✓ Machine-readability signals are healthy", lang), lang) + _checklist(d.get('checks', {}).get('geo', []), lang)
+ '
' + _L("제목", "Title", lang) + ': ' + title80 + ' · H1 ' + str(meta.get('h1', 0)) + ' · ' + _L("구조화", "Schema", lang) + ' ' + str(meta.get('jsonld', 0)) + ' · ' + _L("홈", "Home", lang) + ' ' + str(meta.get('size_kb', 0)) + 'KB · alt ' + str(meta.get('alt_cov', 0)) + '%
'
+ deep_html
+ (_upsell(lang) if show_upsell else "")
+ _h2("✓ " + _L("잘 되어 있는 점", "What's done well", lang)) + '
' + good + '
'
+ _h2("📌 " + _L("조치 우선순위", "Action priorities", lang)) + _priority(allf, lang)
+ _consult_cta(nhi, nmid, nlow, lang)
+ '
' + _L(
"본 진단은 비침투·비파괴로 수행되었으며 대상 서비스에 영향을 주지 않았습니다. 결과는 측정 시점(" + today + ") 기준. 서비스: 퀀트ID(QuantID) · 엔진: 치토스(CHITOS).",
"This diagnosis was non-intrusive and non-destructive and did not affect the target. Results as of " + today + ". Service: QuantID · Engine: CHITOS.", lang) + '
'
+ '
')
return body
def _save_html(body, host, deep):
full = "CHITOS %s%s" % (html.escape(host), body)
fn = os.path.join(tempfile.gettempdir(), "CHITOS_%s_%s.html" % (host.replace(".", "_"), "deep" if deep else "free"))
with open(fn, "w", encoding="utf-8") as f:
f.write(full)
return fn
def _spin(lang):
return ("""
""" + _L("치토스가 점검 중입니다...", "CHITOS is scanning...", lang) + """
""" + _L("보안 · SEO · AI 가시성을 읽는 중 (약 10~25초)", "Reading security · SEO · AI visibility (about 10–25s)", lang) + """
""")
def _spin1(lang):
return _spin(lang)
def _loading(lang):
return gr.update(value=_spin(lang)), gr.update(visible=False)
def free_scan(url, lang):
if not (url or "").strip():
return _note(_L("진단할 주소를 입력하세요. 예: example.com", "Enter a URL to scan. e.g. example.com", lang), "mid"), None
d = _api("/scan", {"url": url})
return build_report(d, deep=False, lang=lang, show_upsell=True), d
def deep_scan(url, lang, profile: gr.OAuthProfile | None = None, oauth_token: gr.OAuthToken | None = None):
if profile is None or oauth_token is None:
return _note(_L('먼저 위의 "Sign in with Hugging Face" 버튼으로 로그인하세요. (회사/조직 도메인 메일로 가입한 계정)',
'First sign in with the "Sign in with Hugging Face" button above. (an account registered on your company/org domain email)', lang), "mid"), gr.update(visible=False), None
if not (url or "").strip():
return _note(_L("진단할 주소를 입력하세요.", "Enter a URL to scan.", lang), "mid"), gr.update(visible=False), None
try:
email = (whoami(token=oauth_token.token).get("email") or "").lower()
except Exception:
email = ""
if not email:
return _note(_L("로그인 계정의 인증된 이메일을 읽지 못했습니다. HF 계정 이메일 인증 후 다시 시도하세요.",
"Could not read a verified email from your account. Verify your HF account email and try again.", lang), "hi"), gr.update(visible=False), None
edom = email.split("@")[-1]
tdom = _domain(url)
if edom != tdom and not tdom.endswith("." + edom) and not edom.endswith("." + tdom):
return _note(_L("도메인 소유 확인 실패. 진단 대상(%s)의 도메인 이메일로 가입한 HF 계정으로 로그인해야 합니다. 현재 계정 도메인: %s" % (html.escape(tdom), html.escape(edom)),
"Ownership check failed. Sign in with an HF account on the target domain (%s). Your account domain: %s" % (html.escape(tdom), html.escape(edom)), lang), "hi"), gr.update(visible=False), None
d = _api("/deep", {"url": url})
body = build_report(d, deep=True, verified_email=email, lang=lang)
if d.get("error") or d.get("blocked"):
return body, gr.update(visible=False), None
return body, gr.update(value=_save_html(body, d.get("host", "site"), True), visible=True), {"d": d, "email": email}
def consult(area, name, company, email, phone, msg, lang):
if not (name and email):
return _note(_L("이름과 이메일은 필수입니다.", "Name and email are required.", lang), "mid")
if not CONSULT:
return _note(_L("상담 접수처가 아직 설정되지 않았습니다.", "Consultation intake is not configured yet.", lang), "mid")
try:
rr = requests.post(CONSULT, json={"진단분야": area or "-", "이름": name, "회사": company or "-", "이메일": email, "연락처": phone or "-", "문의": msg or "-", "_subject": "[CHITOS] consult — %s" % (area or "")}, timeout=15)
if rr.status_code < 300:
return _note(_L("상담 신청이 접수됐습니다. 퀀트ID(QuantID)에서 곧 연락드립니다.", "Your request was received. QuantID will contact you shortly.", lang), "ok")
return _note(_L("접수 중 문제가 발생했습니다. 다시 시도해 주세요.", "Something went wrong submitting. Please try again.", lang), "mid")
except Exception:
return _note(_L("접수 연결 실패. 잠시 후 다시 시도해 주세요.", "Submission connection failed. Please try again shortly.", lang), "mid")
# ===== 언어별 설명/가이드 =====
def _steplead(step, title, lead, bullets, foot):
lis = "".join('%s' % b for b in bullets)
return ('') % (step, title, lead, lis, foot)
def desc1(lang):
if lang == "ko":
return _steplead("①", "무료 즉시 진단 (FREE)",
"주소만 넣으면 약 10~25초 안에 공개적으로 드러난 보안·검색·AI 노출 위생을 비침투(읽기전용)로 점검합니다. 공격·부하·로그인 없이 홈 1페이지를 읽어 핵심만 요약합니다.",
["보안 위생 — HTTPS 강제·HSTS, 보안 응답 헤더(CSP·X-Frame-Options 등), 쿠키 속성, 서버 버전 노출, 민감·백업 파일 노출",
"SEO 토대 — 제목·메타설명·canonical·구조화데이터·이미지 대체텍스트 등 검색 노출의 기본기",
"AI 가시성(GEO) — 생성형 AI·검색 로봇 9종(Googlebot·Bingbot·네이버 Yeti·GPTBot·ClaudeBot·PerplexityBot·OAI-SearchBot 등) 도달성",
"등급 카드 — 보안·SEO·AI가시성 3축 점수 + 가장 급한 상위 이슈"],
"본 단계는 누구나·대상 제한 없이 가능하며 대상 서비스에 영향을 주지 않습니다(읽기전용).")
return _steplead("①", "Free Instant Scan",
"Enter a URL and within ~10–25s CHITOS checks publicly visible security, search and AI-exposure hygiene non-intrusively (read-only) — no attack, no load, no login. It reads one home page and summarizes the essentials.",
["Security hygiene — forced HTTPS/HSTS, security response headers (CSP, X-Frame-Options, …), cookie flags, server-version exposure, sensitive/backup file exposure",
"SEO foundation — title, meta description, canonical, structured data, image alt-text and other search basics",
"AI visibility (GEO) — reachability of 9 generative-AI/search crawlers (Googlebot, Bingbot, Naver Yeti, GPTBot, ClaudeBot, PerplexityBot, OAI-SearchBot, …)",
"Grade cards — Security / SEO / AI-Visibility scores + the most urgent issues"],
"This step is open to anyone, for any target, and does not affect the target service (read-only).")
def desc2(lang):
if lang == "ko":
return _steplead("②", "도메인 인증 심화 진단",
"회사/조직 도메인 이메일로 가입한 Hugging Face 계정으로 로그인하면, 이메일 도메인과 진단 대상 도메인이 일치하는지로 소유를 확인합니다(별도 인증코드 없음). 소유가 확인되면 전사이트 다중 페이지를 크롤링한 정밀 리포트가 열립니다. 여전히 비침투입니다.",
["전사이트 크롤 — 사이트맵 기반 다중 페이지 점검(페이지별 이슈·색인차단·canonical·얇은 페이지)",
"구조화데이터 커버리지 — Organization·Product·Service·FAQ 등 Schema 보유/누락",
"이메일 위조방지 — SPF·DMARC 레코드 점검(사칭·피싱 방어)",
"자기모순 탐지 — 주소·전화·사업자번호가 페이지마다 다른지(AI 신뢰도 핵심)",
"조치 우선순위 + 다운로드 리포트(HTML)"],
"소유 인증은 로그인 계정의 인증된 이메일 도메인으로만 판정합니다. 타인 사이트 무단 정밀점검은 불가합니다.")
return _steplead("②", "Domain-Verified Deep Scan",
"Sign in with a Hugging Face account registered on your company/org domain email. Ownership is confirmed by matching your email domain to the target domain (no separate verification code). Once confirmed, a full-site, multi-page detailed report opens. Still non-intrusive.",
["Full-site crawl — sitemap-based multi-page checks (per-page issues, noindex, canonical, thin pages)",
"Structured-data coverage — Organization/Product/Service/FAQ schema present or missing",
"Email anti-spoofing — SPF/DMARC record checks (impersonation/phishing defense)",
"Self-contradiction detection — address/phone/business-number differing across pages (key for AI trust)",
"Action priorities + downloadable report (HTML)"],
"Ownership is judged only by your account's verified email domain. Unauthorized deep scans of others' sites are not allowed.")
def desc3(lang):
if lang == "ko":
return _steplead("③", "정밀 진단 상담",
"비침투 스캔 너머의 실제 취약점과 AI 시대 심층 진단은 전문가가 서면 허가 기반으로 수행합니다. 아래에서 진단 분야를 선택하고 신청하세요.",
["SQL 삽입·파일 업로드·인증/세션 우회 등 실제 웹 취약점 능동 점검(허가 후)",
"소스 코드 취약점 점검(시큐어코딩)",
"금융보안원 「금융분야 AI 에이전트 보안 평가기준」(6개 분야 17항목) 대응 레드티밍"],
"능동 점검은 반드시 대상 소유자의 서면 허가 범위 안에서만 수행합니다.")
return _steplead("③", "In-Depth Consultation",
"Beyond non-intrusive scanning, real vulnerabilities and AI-era deep diagnosis are performed by experts under written authorization. Pick a diagnosis area below and request a consult.",
["Active testing of real web vulnerabilities — SQLi, file upload, auth/session bypass (after authorization)",
"Source-code vulnerability review (secure coding)",
"Red-teaming against the FSI 「Financial-Sector AI-Agent Security Evaluation Criteria」 (6 domains, 17 items)"],
"Active testing is performed strictly within the target owner's written authorization scope.")
AREAS = [
"웹서비스 보안진단 · Web Service",
"AI 모델 안전진단 · AI Model Safety",
"AX 인프라 보안진단 · AX Infra",
"AI 에이전트 보안진단 · AI Agent (AX-RAY)",
]
def area_help(lang):
if lang == "ko":
cards = [("🌐 웹서비스 보안진단", "웹 애플리케이션 실제 취약점(주입·업로드·인증/세션)·소스 시큐어코딩·능동 점검(허가 기반)."),
("🧠 AI 모델 안전진단", "탈옥·유해·편향·민감정보 유출 등 모델 자체의 안전성 평가."),
("🏗 AX 인프라 보안진단", "AI 전환(AX) 파이프라인·배포·접근통제·비밀/키 관리·로그 등 운영 인프라."),
("🤖 AI 에이전트 보안진단", "에이전트 레드티밍 — 프롬프트 주입·도구 오남용·권한상승·과도 대리행위. (AX-RAY 역량 기반)")]
head = "진단 분야 선택"
else:
cards = [("🌐 Web Service", "Real web-app vulnerabilities (injection, upload, auth/session), secure-coding review, active testing (authorized)."),
("🧠 AI Model Safety", "Model-level safety: jailbreak, harmful/biased output, sensitive-info leakage."),
("🏗 AX Infra Security", "AI-transformation infra: pipelines, deployment, access control, secret/key management, logging."),
("🤖 AI Agent Security", "Agent red-teaming — prompt injection, tool misuse, privilege escalation, excessive agency. (AX-RAY capability)")]
head = "Choose a diagnosis area"
grid = ''
for t, dsc in cards:
grid += ('
') % (t, dsc)
grid += '
'
return '' + head + '
' + grid
def _artex_section(lang):
if lang == "ko":
head = "🚨 왜 지금인가 — 에이전트형(agentic) 공격의 부상"
bullets = ["AI 침투 에이전트가 정찰→침투→탈취를 사람 개입 없이 자동으로 수행하는 공격이 현실화",
"2026년 오픈소스 에이전트 ‘ARTEX’가 실제 공격에 동원 — Microsoft 365 2,250여 테넌트 공격, 전자상거래 주문 DB 침해",
"공통 진입점은 인터넷에 노출된 미패치 시스템과 약한 웹·인프라 위생 — 전 세계·전 산업 공통",
"공격 표면 = 웹·인프라 위생 + AI 에이전트 권한·도구 오남용 (이중용도 도구의 악용)"]
resp = "대응: 웹서비스 · AX 인프라 · AI 모델 · AI 에이전트(AX-RAY) 4대 진단으로 공격·방어 양면을 점검합니다."
src = "참고(2026): therecord.media · bankinfosecurity · techtimes.com"
else:
head = "🚨 Why now — the rise of agentic attacks"
bullets = ["AI penetration agents now run recon → breach → exfiltration autonomously, with no human in the loop",
"In 2026 the open-source agent “ARTEX” was used in real attacks — hitting 2,250+ Microsoft 365 tenants and breaching e-commerce order databases",
"The common entry point is unpatched internet-facing systems and weak web/infra hygiene — across industries worldwide",
"Attack surface = web/infra hygiene + AI-agent privilege & tool misuse (abuse of a dual-use tool)"]
resp = "Response: four diagnoses — Web Service · AX Infra · AI Model · AI Agent (AX-RAY) — covering both attack and defense."
src = "References (2026): therecord.media · bankinfosecurity · techtimes.com"
lis = "".join('%s' % b for b in bullets)
return (''
'
' + head + ''
'
'
'
🛡 ' + resp + '
'
'
' + src + '
')
def guideline_html(lang):
ko = (lang == "ko")
def chk(items):
return "".join('☐ %s
' % x for x in items)
row = lambda a, dom, intl: ('| %s | '
'%s | '
'%s |
') % (a, dom, intl)
if ko:
hero_t = "국내외 보안·AI 안전 기준 수용 현황 · 자가 점검 체크리스트"
hero_s = "치토스 진단은 아래 공개 기준을 참조·반영합니다. (내부 점검 로직·공격셋·임계값은 비공개)"
h_map = "🧭 진단 분야 × 대표 준거 매핑"
th = ("진단 분야", "국내 기준", "국제 기준")
rows = [row("웹서비스
보안진단", "KISA 웹 취약점 진단·제거 가이드 · KISA 소프트웨어 보안약점(시큐어코딩) 가이드 · ISMS-P", "OWASP Top 10 · OWASP ASVS · NIST SSDF(SP 800-218)"),
row("AI 모델
안전진단", "과기정통부·TTA AI 신뢰성/안전 지침 · 금융보안원 AI 평가기준(일부)", "OWASP Top 10 for LLM Apps · NIST AI RMF · MITRE ATLAS · EU AI Act · ISO/IEC 42001"),
row("AX 인프라
보안진단", "ISMS-P · 개인정보 안전성 확보조치 기준 · 전자금융감독규정", "ISO/IEC 27001 · NIST SSDF · OWASP ASVS · Google SAIF"),
row("AI 에이전트
보안진단", "금융보안원 「금융분야 AI 에이전트 보안 평가기준」(6개 분야 17항목, 2026.10) · 전자금융감독규정", "OWASP LLM Top 10(과도 대리·도구 오남용) · MITRE ATLAS · NIST AI RMF")]
note = "※ 금융보안원 기준은 2027년 금융권 레드티밍 정식 평가기준입니다."
h_all = "📚 수용 기준 전체 목록"
dom_t = "🇰🇷 국내"; intl_t = "🌍 국제"
dom_list = ["금융보안원 「금융분야 AI 에이전트 보안 평가기준」", "전자금융감독규정 · 전자금융거래법", "KISA 홈페이지(웹) 취약점 진단·제거 가이드",
"KISA 소프트웨어 보안약점 진단가이드(시큐어코딩)", "ISMS-P(정보보호·개인정보보호 관리체계)", "개인정보 안전성 확보조치 기준(개인정보보호위)", "과기정통부·TTA AI 신뢰성/안전 지침"]
intl_list = ["OWASP Top 10 (웹)", "OWASP Top 10 for LLM Applications", "OWASP ASVS(앱 보안 검증 표준)", "MITRE ATT&CK · MITRE ATLAS(AI 적대적 위협)",
"NIST AI RMF(AI 위험관리) · NIST SSDF", "ISO/IEC 27001 · ISO/IEC 42001(AI 경영시스템)", "EU AI Act · Google SAIF"]
h_chk = "✅ 자가 점검 체크리스트 (공개 베스트프랙티스)"
c1_t = "웹 · 인프라"; c2_t = "AI 모델 · 에이전트"; c3_t = "이메일 · 도메인"; c4_t = "개인정보"
c1 = ["HTTPS 전면 적용 + HSTS", "보안 응답 헤더(CSP·X-Frame-Options·X-Content-Type-Options·Referrer-Policy·Permissions-Policy)", "쿠키 Secure·HttpOnly·SameSite",
"민감·백업 파일(.env·.git·*.bak) 공개 차단", "서버·프레임워크 버전 숨김 + 최신 패치", "관리자 페이지 접근통제 + 다중인증(MFA)", "비밀·키 하드코딩 금지 + 비밀관리 체계"]
c2 = ["프롬프트 주입·탈옥 방어(입력 검증·시스템 프롬프트 격리)", "도구·함수 호출 권한 최소화 + 위험행위 승인 게이트", "민감정보(PII·비밀) 출력 차단 필터",
"과도한 대리행위(자동 송금·삭제 등) 킬스위치", "감사 로그(에이전트 결정·도구호출 기록)", "유해·편향 출력 모니터링", "모델·데이터 공급망(SBOM) 관리"]
c3 = ["SPF 설정", "DKIM 서명", "DMARC 정책 적용", "발신 도메인 사칭 모니터링"]
c4 = ["수집·보관 최소화", "저장·전송 암호화", "접근 권한·접근기록 관리", "파기 절차"]
foot = "본 체크리스트는 공개 베스트프랙티스 요약입니다. 치토스 정밀 진단은 각 항목을 실제 환경에서 측정·검증하고 항목별 처방을 제공합니다. 점검 방법·공격셋·판정 기준 등 내부 방법론은 비공개입니다. 능동 점검은 서면 허가 범위 안에서만 수행합니다."
svc = "서비스: 퀀트ID(QuantID) · 엔진: 치토스(CHITOS)"
else:
hero_t = "Domestic & international security / AI-safety standards we incorporate · self-check checklist"
hero_s = "CHITOS diagnosis references and reflects the public standards below. (internal logic, attack sets and thresholds are confidential)"
h_map = "🧭 Diagnosis area × key standards mapping"
th = ("Diagnosis area", "Domestic (KR)", "International")
rows = [row("Web Service", "KISA web vuln. guide · KISA secure-coding guide · ISMS-P", "OWASP Top 10 · OWASP ASVS · NIST SSDF (SP 800-218)"),
row("AI Model
Safety", "MSIT/TTA AI trustworthiness guidance · FSI AI criteria (partial)", "OWASP Top 10 for LLM Apps · NIST AI RMF · MITRE ATLAS · EU AI Act · ISO/IEC 42001"),
row("AX Infra
Security", "ISMS-P · Personal-data safeguards · E-Financial Supervisory Rules", "ISO/IEC 27001 · NIST SSDF · OWASP ASVS · Google SAIF"),
row("AI Agent
Security", "FSI 「Financial AI-Agent Security Criteria」 (6 domains, 17 items, 2026.10) · E-Financial Supervisory Rules", "OWASP LLM Top 10 (excessive agency, tool misuse) · MITRE ATLAS · NIST AI RMF")]
note = "※ The FSI criteria are the official 2027 financial-sector red-teaming standard."
h_all = "📚 Full list of incorporated standards"
dom_t = "🇰🇷 Domestic (KR)"; intl_t = "🌍 International"
dom_list = ["FSI 「Financial-Sector AI-Agent Security Evaluation Criteria」", "E-Financial Supervisory Rules · E-Financial Transactions Act", "KISA website (web) vulnerability guide",
"KISA software security-weakness (secure coding) guide", "ISMS-P (info & privacy management system)", "Personal-data safeguards standard (PIPC)", "MSIT/TTA AI trustworthiness/safety guidance"]
intl_list = ["OWASP Top 10 (web)", "OWASP Top 10 for LLM Applications", "OWASP ASVS", "MITRE ATT&CK · MITRE ATLAS (adversarial AI threats)",
"NIST AI RMF · NIST SSDF", "ISO/IEC 27001 · ISO/IEC 42001 (AI management system)", "EU AI Act · Google SAIF"]
h_chk = "✅ Self-check checklist (public best practices)"
c1_t = "Web · Infra"; c2_t = "AI Model · Agent"; c3_t = "Email · Domain"; c4_t = "Personal data"
c1 = ["HTTPS everywhere + HSTS", "Security response headers (CSP, X-Frame-Options, X-Content-Type-Options, Referrer-Policy, Permissions-Policy)", "Cookie Secure/HttpOnly/SameSite",
"Block sensitive/backup files (.env, .git, *.bak)", "Hide server/framework version + latest patches", "Admin-page access control + MFA", "No hardcoded secrets/keys + secret management"]
c2 = ["Prompt-injection/jailbreak defense (input validation, system-prompt isolation)", "Least-privilege tool/function calls + approval gate for risky actions", "Sensitive-info (PII/secret) output filter",
"Kill-switch for excessive agency (auto transfer/delete, …)", "Audit logs (agent decisions, tool calls)", "Harmful/biased output monitoring", "Model/data supply chain (SBOM) management"]
c3 = ["SPF configured", "DKIM signing", "DMARC policy applied", "Sender-domain spoofing monitoring"]
c4 = ["Minimize collection/retention", "Encrypt at rest & in transit", "Access rights & access logging", "Disposal procedure"]
foot = "This checklist is a summary of public best practices. CHITOS in-depth diagnosis measures and verifies each item in the real environment and gives per-item remediation. Internal methodology (methods, attack sets, decision thresholds) is confidential. Active testing is performed only within written-authorization scope."
svc = "Service: QuantID · Engine: CHITOS"
html_out = (
''
'
'
'
C H I T O S · ' + _L("준거 체계", "Standards", lang) + '
'
'
' + hero_t + '
'
'
' + hero_s + '
'
+ _artex_section(lang)
+ _h2(h_map)
+ '
'
'| ' + th[0] + ' | '
'' + th[1] + ' | '
'' + th[2] + ' |
'
+ "".join(rows) + '
'
+ '
' + note + '
'
+ _h2(h_all)
+ '
'
'
' + dom_t + ''
'
' + "".join("- %s
" % x for x in dom_list) + '
'
'
' + intl_t + ''
'
' + "".join("- %s
" % x for x in intl_list) + '
'
+ _h2(h_chk)
+ '
'
+ '
' + c1_t + '' + chk(c1) + '
'
+ '
' + c2_t + '' + chk(c2) + '
'
+ '
' + c3_t + '' + chk(c3) + '
'
+ '
' + c4_t + '' + chk(c4) + '
'
+ '
'
+ '
' + foot + '
'
+ '
' + svc + '
'
+ '
')
return html_out
def threat_banner(lang):
if lang == "ko":
t = ("⚠ AI 에이전트가 스스로 침투하는 시대입니다. 2026년 오픈소스 AI 침투 에이전트 ‘ARTEX’가 실제 표적에 동원되어 "
"Microsoft 365 2,250여 테넌트 공격, 전자상거래 주문 DB 침해 등 인터넷에 노출된 미패치 시스템을 정찰부터 탈취까지 자동으로 수행했습니다. "
"공격이 자동화된 지금, 방어도 자동화되어야 합니다 — 웹·인프라·AI 모델·AI 에이전트를 함께 진단하세요.")
else:
t = ("⚠ AI agents now break in on their own. In 2026 the open-source AI penetration agent “ARTEX” was turned on real targets — "
"hitting 2,250+ Microsoft 365 tenants, breaching e-commerce order databases, and automating recon-to-exfiltration against "
"unpatched internet-facing systems worldwide. Attacks are automated now — so must defense be. Diagnose your web, infra, AI models and AI agents together.")
return ('' + t + '
')
def header_html(lang):
return ('C H I T O S
'
'
' + _L("보안 · SEO · AI가시성 진단", "Security · SEO · AI-Visibility Diagnosis", lang) + '
'
'
' + _L("① 무료 즉시 리포트 → ② 도메인 메일 HF 로그인 심화 리포트 → ③ 정밀 진단 상담 · ④ 준거·체크리스트",
"① Free instant report → ② Domain-email HF login deep report → ③ In-depth consult · ④ Standards & checklist", lang) + '
')
def footer_html(lang):
return '' + _L(
"비침투·비파괴 · 타인 사이트 무단 정밀점검 금지 · 능동 점검은 도메인 인증·허가 후 · 서비스 퀀트ID(QuantID) · 엔진 치토스(CHITOS)",
"Non-intrusive & non-destructive · no unauthorized deep scans of others' sites · active testing only after domain verification/authorization · Service: QuantID · Engine: CHITOS", lang) + '
'
def relang(label, last_scan, last_deep):
lang = "en" if str(label).startswith("Eng") else "ko"
o1u = gr.update(value=build_report(last_scan, deep=False, lang=lang, show_upsell=True)) if last_scan else gr.update()
o2u = gr.update(value=build_report(last_deep["d"], deep=True, verified_email=last_deep.get("email"), lang=lang)) if last_deep else gr.update()
return (lang, gr.update(value=label), header_html(lang), threat_banner(lang), desc1(lang), desc2(lang), desc3(lang),
area_help(lang), guideline_html(lang), footer_html(lang), o1u, o2u)
CSS = "#title{text-align:center}.gradio-container{max-width:980px!important;margin:0 auto}.chdk b,.chdk strong{color:#fff}"
with gr.Blocks(title="CHITOS 인스턴트 스캔", css=CSS, theme=gr.themes.Soft(primary_hue="purple")) as demo:
lang_state = gr.State("ko")
last_scan = gr.State(None)
last_deep = gr.State(None)
hdr = gr.HTML(header_html("ko"))
g_threat = gr.HTML(threat_banner("ko"))
with gr.Row():
lang_radio = gr.Radio(["한국어", "English"], value="한국어", label="🌐 언어 / Language", min_width=260)
with gr.Tabs():
with gr.Tab("① 무료 · FREE"):
g_d1 = gr.HTML(desc1("ko"))
with gr.Row():
u1 = gr.Textbox(label="", placeholder="example.com", scale=4, container=False)
b1 = gr.Button("진단 / Scan", variant="primary", scale=1)
o1 = gr.HTML()
b1.click(_spin1, lang_state, o1).then(free_scan, [u1, lang_state], [o1, last_scan])
u1.submit(_spin1, lang_state, o1).then(free_scan, [u1, lang_state], [o1, last_scan])
with gr.Tab("② 심화 · DEEP"):
g_d2 = gr.HTML(desc2("ko"))
gr.LoginButton()
u2 = gr.Textbox(label="진단할 주소 / Target URL", placeholder="example.com")
b2 = gr.Button("인증 후 심화 진단 / Verify & deep scan", variant="primary")
f2 = gr.File(label="리포트 다운로드 / Download report (HTML)", visible=False)
o2 = gr.HTML()
b2.click(_loading, lang_state, [o2, f2]).then(deep_scan, inputs=[u2, lang_state], outputs=[o2, f2, last_deep])
with gr.Tab("③ 상담 · CONSULT"):
g_d3 = gr.HTML(desc3("ko"))
g_ah = gr.HTML(area_help("ko"))
ar = gr.Radio(AREAS, value=AREAS[0], label="진단 분야 / Diagnosis area")
with gr.Row():
n = gr.Textbox(label="이름 Name*"); co = gr.Textbox(label="회사 Company")
with gr.Row():
em = gr.Textbox(label="이메일 Email*"); ph = gr.Textbox(label="연락처 Phone")
mg = gr.Textbox(label="문의 내용 Message", lines=3, placeholder="대상 시스템·범위·일정·허가 여부 / target, scope, timeline, authorization")
bc = gr.Button("상담 신청 / Request consult", variant="primary"); oc = gr.HTML()
bc.click(consult, [ar, n, co, em, ph, mg, lang_state], oc)
with gr.Tab("④ 준거 · STANDARDS"):
g_gl = gr.HTML(guideline_html("ko"))
g_ft = gr.HTML(footer_html("ko"))
_ins = [lang_radio, last_scan, last_deep]
_outs = [lang_state, lang_radio, hdr, g_threat, g_d1, g_d2, g_d3, g_ah, g_gl, g_ft, o1, o2]
lang_radio.change(relang, _ins, _outs)
demo.load(relang, _ins, _outs,
js="() => [ (navigator.language||'en').toLowerCase().startsWith('ko') ? '한국어' : 'English', null, null ]")
if __name__ == "__main__":
demo.launch()