SeaWolf-AI commited on
Commit
bece9a3
·
verified ·
1 Parent(s): 7e3f615

feat: render deep email/DNS/site-security sections

Browse files
Files changed (1) hide show
  1. app.py +64 -1
app.py CHANGED
@@ -168,6 +168,67 @@ def _upsell(lang):
168
  + '</div></div>')
169
 
170
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
171
  def _checklist(items, lang):
172
  if not items:
173
  return ""
@@ -351,7 +412,9 @@ def build_report(d, deep=False, verified_email=None, lang="ko", show_upsell=Fals
351
  _L("메타설명 없음", "No meta desc.", lang), dp.get("no_meta", 0), _L("얇은 페이지", "Thin pages", lang), dp.get("thin", 0),
352
  _L("색인차단", "noindex", lang), dp.get("noindex", 0), _L("canonical 없음", "No canonical", lang), dp.get("no_canonical", 0))
353
  + _h2("🧩 " + _L("구조화 데이터 커버리지", "Structured-data coverage", lang)) + '<div style="font-size:12.5px"><b style="color:#067647">' + _L("보유:", "Present:", lang) + '</b> ' + sch + '<br><b style="color:#b42318;display:inline-block;margin-top:6px">' + _L("누락:", "Missing:", lang) + '</b> ' + schm + '</div>'
354
- + _h2("📧 " + _L("이메일 위조 방지", "Email anti-spoofing", lang)) + '<div style="display:flex;gap:10px">' + _pill(es.get("spf"), "SPF") + " " + _pill(es.get("dmarc"), "DMARC") + '</div>'
 
 
355
  + incon
356
  + _h2("📋 " + _L("전사이트 점검 체크리스트", "Full-site checklist", lang)) + _checklist(dp.get("checks", []), lang)
357
  + _h2("🗂 " + (_L("점검 페이지 (%d)", "Scanned pages (%d)", lang) % dp.get("pages", 0))) + '<div style="max-height:220px;overflow:auto;border:1px solid #eef1f6;border-radius:8px;padding:8px 12px">' + pglist + '</div>'
 
168
  + '</div></div>')
169
 
170
 
171
+ def _statrow(label, ok, val="", warn=False):
172
+ col = "#16a34a" if ok else ("#b54708" if warn else "#dc2626")
173
+ sym = "✓" if ok else ("!" if warn else "✕")
174
+ v = ('<span style="color:#6b7280;font-size:11.5px;margin-left:6px">%s</span>' % html.escape(str(val))) if val else ""
175
+ return ('<div style="display:flex;align-items:center;gap:8px;padding:6px 10px;border:1px solid #eef1f6;border-radius:8px">'
176
+ '<span style="color:%s;font-weight:900;width:13px;text-align:center;flex:none">%s</span>'
177
+ '<span style="font-size:12.5px;color:#374151;flex:1">%s</span>%s</div>') % (col, sym, html.escape(label), v)
178
+
179
+
180
+ def _email_block(ed, es, lang):
181
+ if not ed:
182
+ return ('<div style="display:flex;gap:6px">'
183
+ + _statrow("SPF", bool(es.get("spf")), warn=not es.get("spf"))
184
+ + _statrow("DMARC", bool(es.get("dmarc")), warn=not es.get("dmarc")) + '</div>')
185
+ spf_ok = ed.get("spf") and ed.get("spf_qual") == "-all"
186
+ dmarc_ok = ed.get("dmarc") and ed.get("dmarc_p") != "none"
187
+ rows = [
188
+ _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)),
189
+ _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)),
190
+ _statrow(_L("DKIM 서명", "DKIM signing", lang), ed.get("dkim", False), warn=not ed.get("dkim", False)),
191
+ _statrow(_L("DMARC 정책", "DMARC policy", lang), dmarc_ok, "p=" + str(ed.get("dmarc_p", "none")), warn=(ed.get("dmarc") and not dmarc_ok)),
192
+ _statrow(_L("BIMI (브랜드 로고)", "BIMI (brand logo)", lang), ed.get("bimi", False), warn=not ed.get("bimi", False)),
193
+ _statrow(_L("MTA-STS (전송 암호화 강제)", "MTA-STS (enforced mail TLS)", lang), ed.get("mta_sts", False), warn=not ed.get("mta_sts", False)),
194
+ _statrow(_L("TLS-RPT (메일 TLS 리포트)", "TLS-RPT (mail TLS reporting)", lang), ed.get("tls_rpt", False), warn=not ed.get("tls_rpt", False)),
195
+ ]
196
+ return '<div style="display:grid;grid-template-columns:1fr 1fr;gap:6px">' + "".join(rows) + '</div>'
197
+
198
+
199
+ def _dns_block(ed, lang):
200
+ if not ed:
201
+ return "—"
202
+ rows = [
203
+ _statrow("DNSSEC", ed.get("dnssec", False), warn=not ed.get("dnssec", False)),
204
+ _statrow(_L("CAA (인증서 발급 통제)", "CAA (cert issuance control)", lang), ed.get("caa", False), warn=not ed.get("caa", False)),
205
+ _statrow(_L("MX (메일 수신)", "MX (mail receiving)", lang), ed.get("mx", 0) > 0, "%d" % ed.get("mx", 0)),
206
+ _statrow(_L("NS 이중화 (2+)", "NS redundancy (2+)", lang), ed.get("ns", 0) >= 2, "%d" % ed.get("ns", 0), warn=(ed.get("ns", 0) < 2)),
207
+ ]
208
+ return '<div style="display:grid;grid-template-columns:1fr 1fr;gap:6px">' + "".join(rows) + '</div>'
209
+
210
+
211
+ def _sitesec_block(ss, extra_exposed, lang):
212
+ if not ss:
213
+ return "—"
214
+ n = ss.get("pages", 0) or 1
215
+ sri_t = ss.get("sri_total", 0)
216
+ rows = [
217
+ _statrow(_L("전 페이지 HTTPS", "HTTPS on all pages", lang), ss.get("https_pages", 0) == n, "%d/%d" % (ss.get("https_pages", 0), n)),
218
+ _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),
219
+ _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 ""),
220
+ _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)),
221
+ _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 ""),
222
+ _statrow(_L("추가 민감 파일 비노출", "No extra sensitive files", lang), not extra_exposed, (", ".join(extra_exposed) if extra_exposed else "")),
223
+ ]
224
+ ext = ss.get("ext_scripts", [])
225
+ extd = ""
226
+ if ext:
227
+ chips = "".join('<span style="background:#f5f3ff;color:#5b21b6;border-radius:6px;padding:2px 8px;font-size:11px;margin:2px;display:inline-block">%s</span>' % html.escape(x) for x in ext)
228
+ extd = '<div style="font-size:11.5px;color:#6b7280;margin-top:6px">' + _L("외부 스크립트 출처: ", "Third-party script sources: ", lang) + '</div>' + chips
229
+ return '<div style="display:grid;grid-template-columns:1fr 1fr;gap:6px">' + "".join(rows) + '</div>' + extd
230
+
231
+
232
  def _checklist(items, lang):
233
  if not items:
234
  return ""
 
412
  _L("메타설명 없음", "No meta desc.", lang), dp.get("no_meta", 0), _L("얇은 페이지", "Thin pages", lang), dp.get("thin", 0),
413
  _L("색인차단", "noindex", lang), dp.get("noindex", 0), _L("canonical 없음", "No canonical", lang), dp.get("no_canonical", 0))
414
  + _h2("🧩 " + _L("구조화 데이터 커버리지", "Structured-data coverage", lang)) + '<div style="font-size:12.5px"><b style="color:#067647">' + _L("보유:", "Present:", lang) + '</b> ' + sch + '<br><b style="color:#b42318;display:inline-block;margin-top:6px">' + _L("누락:", "Missing:", lang) + '</b> ' + schm + '</div>'
415
+ + _h2("📧 " + _L("이메일 보안 심화 (SPF·DKIM·DMARC·BIMI)", "Email security — deep (SPF·DKIM·DMARC·BIMI)", lang)) + _email_block(dp.get("email_dns", {}), es, lang)
416
+ + _h2("🌐 " + _L("DNS·도메인 심화", "DNS & domain — deep", lang)) + _dns_block(dp.get("email_dns", {}), lang)
417
+ + _h2("🧱 " + _L("전사이트 보안 일관성", "Site-wide security consistency", lang)) + _sitesec_block(dp.get("site_sec", {}), dp.get("extra_exposed", []), lang)
418
  + incon
419
  + _h2("📋 " + _L("전사이트 점검 체크리스트", "Full-site checklist", lang)) + _checklist(dp.get("checks", []), lang)
420
  + _h2("🗂 " + (_L("점검 페이지 (%d)", "Scanned pages (%d)", lang) % dp.get("pages", 0))) + '<div style="max-height:220px;overflow:auto;border:1px solid #eef1f6;border-radius:8px;padding:8px 12px">' + pglist + '</div>'