Files
SneakyScope/app/templates/partials/result_ssl_tls.html
Phillip Tarrant 55cd81aec0 feat(text): add text analysis pipeline & surface results in UI
- engine: add analyse_text() to extract visible page text and evaluate
  category="text" rules; collect matched phrases and expose as
  `content_snippet` (deduped, length-capped via settings.ui.snippet_preview_len).
- engine: removed unused code
- browser: removed double call for enrichment
- engine: improve regex compilation — honor per-rule flags (string or list)
  and default IGNORECASE when category=="text".
- engine: add dispatch logging "[engine] applying categories: …" gated by
  settings.app.print_rule_dispatch.
- ui(templates): add `templates/partials/result_text.html` mirroring the forms
  table; renders page-level records and their matched rules.
- ui(controller): wire `analyse_text()` into scan path and expose
  `payload["suspicious_text"]`.
- rules(text): add `identity_verification_prompt`, `gated_document_access`,
  `email_collection_prompt`; broaden `credential_reset`.

fix: text indicators were not displayed due to missing analyzer and mismatched result shape.

Result shape:
  suspicious_text: [
    {
      "type": "page",
      "content_snippet": "...matched phrases…",
      "rules": [
        {"name": "...", "description": "...", "severity": "medium", "tags": ["..."]}
      ]
    }
  ]
2025-08-22 17:18:50 -05:00

201 lines
9.2 KiB
HTML

{# templates/result_ssl_tls.html #}
{% macro ssl_tls_card(ssl_tls) %}
<section id="ssl" class="card">
<h2 class="text-lg font-semibold mb-3">TLS / Certs</h2>
<div class="space-y-4">
{# -------- 1) Error branch -------- #}
{% if ssl_tls is none or (ssl_tls.error is defined) or ('error' in ssl_tls) %}
<span class="badge badge-danger">Error</span>
<p class="text-sm text-gray-500">SSL/TLS enrichment failed or is unavailable.</p>
{% if ssl_tls and ssl_tls.error %}
<pre class="bg-[#0b0f14] border border-gray-800 rounded-lg p-3 overflow-x-auto text-xs mt-1">{{ ssl_tls.error }}</pre>
{% endif %}
{# -------- 2) Skipped branch -------- #}
{% elif ssl_tls.skipped %}
<span class="chip">Skipped</span>
{% if ssl_tls.reason %}<span class="text-gray-400 text-sm ml-2">{{ ssl_tls.reason }}</span>{% endif %}
<details class="mt-3">
<summary class="cursor-pointer text-blue-300 hover:underline">Raw TLS JSON</summary>
<pre class="bg-[#0b0f14] border border-gray-800 rounded-lg p-3 overflow-x-auto text-xs mt-1">{{ ssl_tls|tojson(indent=2) }}</pre>
</details>
{# -------- 3) Normal branch (render probe + crt.sh) -------- #}
{% else %}
{# ===================== LIVE PROBE ===================== #}
{% set probe = ssl_tls.probe if ssl_tls else None %}
<section class="space-y-2">
<div class="flex items-center gap-2">
<h3 class="text-base font-semibold">Live TLS Probe</h3>
{% if probe %}
<span class="text-gray-400 text-sm">Host:</span>
<code class="text-sm">{{ probe.hostname }}:{{ probe.port }}</code>
{% endif %}
</div>
{% if not probe %}
<p class="text-sm text-gray-500">No probe data.</p>
{% else %}
<div class="overflow-x-auto">
<table class="min-w-full text-sm">
<thead class="text-gray-400 border-b border-gray-800">
<tr>
<th class="text-left py-2 pr-4">Version</th>
<th class="text-left py-2 pr-4">Status</th>
<th class="text-left py-2 pr-4">Selected Cipher</th>
<th class="text-left py-2 pr-4">Latency</th>
</tr>
</thead>
<tbody>
{% set versions = ['TLS1.0','TLS1.1','TLS1.2','TLS1.3'] %}
{% for v in versions %}
{% set r = probe.results_by_version.get(v) if probe.results_by_version else None %}
<tr class="border-b border-gray-900">
<td class="py-2 pr-4 whitespace-nowrap">{{ v }}</td>
<td class="py-2 pr-4">
{% if r and r.supported %}
<span class="badge badge-success">Supported</span>
{% else %}
<span class="badge badge-info">Not Supported</span>
{% endif %}
</td>
<td class="py-2 pr-4">
{% if r and r.selected_cipher %}
<span class="badge badge-warn">{{ r.selected_cipher }}</span>
{% elif r and r.error %}
<span class="text-gray-500 text-xs">({{ r.error }})</span>
{% else %}
<span class="text-gray-500"></span>
{% endif %}
</td>
<td class="py-2 pr-4 whitespace-nowrap">
{% if r and r.handshake_seconds is not none %}
<span class="text-gray-400">{{ '%.0f' % (r.handshake_seconds*1000) }} ms</span>
{% else %}
<span class="text-gray-500"></span>
{% endif %}
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
<div class="flex flex-wrap items-center gap-2 mt-2">
{% if probe.weak_protocols and probe.weak_protocols|length > 0 %}
<span class="badge badge-warn">Weak Protocols</span>
{% for wp in probe.weak_protocols %}
<span class="chip">{{ wp }}</span>
{% endfor %}
{% endif %}
{% if probe.weak_ciphers and probe.weak_ciphers|length > 0 %}
<span class="badge badge-warn">Weak Ciphers</span>
{% for wc in probe.weak_ciphers %}
<span class="chip">{{ wc }}</span>
{% endfor %}
{% endif %}
</div>
{% if probe.errors and probe.errors|length > 0 %}
<details class="mt-2">
<summary class="cursor-pointer text-blue-300 hover:underline">Probe Notes</summary>
<ul class="list-disc list-inside text-sm text-gray-400 space-y-1 mt-1">
{% for e in probe.errors %}
<li>{{ e }}</li>
{% endfor %}
</ul>
</details>
{% endif %}
{% endif %}
</section>
<hr class="border-gray-800 my-4"/>
{# ===================== CRT.SH ===================== #}
{% set crtsh = ssl_tls.crtsh if ssl_tls else None %}
<section class="space-y-2">
<div class="flex items-center flex-wrap gap-2">
<h3 class="text-base font-semibold">Certificate Transparency (crt.sh)</h3>
{% if crtsh %}
<span class="text-gray-400 text-sm">Parsed:</span>
<code class="text-sm">{{ crtsh.hostname or 'n/a' }}</code>
{% if crtsh.root_domain %}
<span class="text-gray-400 text-sm">• Root:</span>
<code class="text-sm">{{ crtsh.root_domain }}</code>
{% if crtsh.is_root_domain %}
<span class="badge badge-success">Root</span>
{% else %}
<span class="badge badge-info">Subdomain</span>
{% endif %}
{% endif %}
{% endif %}
</div>
{% if not crtsh %}
<p class="text-sm text-gray-500">No CT data.</p>
{% else %}
<div class="grid md:grid-cols-2 gap-4">
<div>
<h4 class="text-gray-400 text-sm mb-2">Host Certificates</h4>
{% set host_certs = crtsh.crtsh.host_certs if 'crtsh' in crtsh and crtsh.crtsh else None %}
{% if host_certs and host_certs|length > 0 %}
<ul class="space-y-1 text-xs">
{% for c in host_certs[:10] %}
<li class="font-mono">
<span class="chip">{{ c.get('issuer_name','issuer n/a') }}</span>
<span class="text-gray-500"></span>
<strong class="break-all">{{ c.get('name_value','(name n/a)') }}</strong>
<span class="text-gray-500"> • not_before:</span> {{ c.get('not_before','?') }}
</li>
{% endfor %}
</ul>
{% if host_certs|length > 10 %}
<div class="text-gray-500 text-xs mt-1">(+ {{ host_certs|length - 10 }} more)</div>
{% endif %}
{% else %}
<p class="text-sm text-gray-500">No active host certs found.</p>
{% endif %}
</div>
<div>
<h4 class="text-gray-400 text-sm mb-2">Wildcard on Root</h4>
{% set wc = crtsh.crtsh.wildcard_root_certs if 'crtsh' in crtsh and crtsh.crtsh else None %}
{% if wc and wc|length > 0 %}
<ul class="space-y-1 text-xs">
{% for c in wc[:10] %}
<li class="font-mono">
<span class="chip">{{ c.get('issuer_name','issuer n/a') }}</span>
<span class="text-gray-500"></span>
<strong class="break-all">{{ c.get('name_value','(name n/a)') }}</strong>
<span class="text-gray-500"> • not_before:</span> {{ c.get('not_before','?') }}
</li>
{% endfor %}
</ul>
{% if wc|length > 10 %}
<div class="text-gray-500 text-xs mt-1">(+ {{ wc|length - 10 }} more)</div>
{% endif %}
{% else %}
<p class="text-sm text-gray-500">No wildcard/root certs found.</p>
{% endif %}
</div>
</div>
{% endif %}
</section>
<!-- Raw JSON toggle -->
<details class="mt-3">
<summary class="cursor-pointer text-blue-300 hover:underline">Raw TLS JSON</summary>
<pre class="bg-[#0b0f14] border border-gray-800 rounded-lg p-3 overflow-x-auto text-xs mt-1">{{ ssl_tls|tojson(indent=2) }}</pre>
</details>
{% endif %}
<p class="mt-2"><a href="#url-overview" class="text-sm text-gray-400 hover:text-blue-400">Back to top</a></p>
</div>
</section>
{% endmacro %}