diff --git a/public/tools/tls-test/results.html b/public/tools/tls-test/results.html
index dc4a863..9437ff2 100644
--- a/public/tools/tls-test/results.html
+++ b/public/tools/tls-test/results.html
@@ -56,6 +56,7 @@
+
@@ -399,6 +400,30 @@
+ {# -------- テスト履歴 -------- #}
+
+
+ 過去のテスト結果 (同一対象)
+ {% if history %}
+
+ | 実行日時 | ランク | スコア | リンク |
+
+ {% for h in history %}
+
+ | {{ h.finished_at_display or '—' }} |
+ {{ h.rank or '—' }} |
+ {{ "%.0f"|format(h.score or 0) }} |
+ {{ h.id[:8] }}… |
+
+ {% endfor %}
+
+
+ {% else %}
+ [info]過去のテスト結果はありません
+ {% endif %}
+
+
+
{# -------- JSON -------- #}
diff --git a/src/nercone_website/tools/tls_test/db.py b/src/nercone_website/tools/tls_test/db.py
index 3d514a0..64d2925 100644
--- a/src/nercone_website/tools/tls_test/db.py
+++ b/src/nercone_website/tools/tls_test/db.py
@@ -58,6 +58,7 @@ class TlsTestDB:
);
CREATE INDEX IF NOT EXISTS idx_tests_expires ON tests(expires_at);
CREATE INDEX IF NOT EXISTS idx_tests_ip_created ON tests(client_ip, created_at);
+ CREATE INDEX IF NOT EXISTS idx_tests_target ON tests(target, created_at);
CREATE TABLE IF NOT EXISTS test_progress (
test_id TEXT NOT NULL,
@@ -185,6 +186,30 @@ class TlsTestDB:
finally:
conn.close()
+ def get_history_by_target(self, target: str, exclude_id: str | None = None, limit: int = 10) -> list[dict[str, Any]]:
+ conn = self._conn()
+ try:
+ cur = conn.cursor()
+ if exclude_id:
+ cur.execute(
+ "SELECT id, target, status, created_at, finished_at, rank, score, error_message"
+ " FROM tests WHERE target = ? AND id != ? AND status = 'done'"
+ " ORDER BY created_at DESC LIMIT ?",
+ (target, exclude_id, limit),
+ )
+ else:
+ cur.execute(
+ "SELECT id, target, status, created_at, finished_at, rank, score, error_message"
+ " FROM tests WHERE target = ? AND status = 'done'"
+ " ORDER BY created_at DESC LIMIT ?",
+ (target, limit),
+ )
+ rows = cur.fetchall()
+ cols = [c[0] for c in cur.description]
+ return [dict(zip(cols, r)) for r in rows]
+ finally:
+ conn.close()
+
def count_ip_in_window(self, client_ip: str, window_seconds: int) -> int:
cutoff = int(time.time()) - window_seconds
conn = self._conn()
diff --git a/src/nercone_website/tools/tls_test/engine.py b/src/nercone_website/tools/tls_test/engine.py
index 37c50e0..4c7e2b1 100644
--- a/src/nercone_website/tools/tls_test/engine.py
+++ b/src/nercone_website/tools/tls_test/engine.py
@@ -159,8 +159,12 @@ async def _gather(report: ReportProgress, finds: ReportFinding, result: ScanResu
# SSL 2/3 → vulnerability findings; also record them as safety findings.
if sslv2_supported:
await emit(Finding("SSL/TLS Version", "SSL 2.0 supported", "SSLv2 は完全に破綻しています (DROWN)", "serious", 10, group=G_SAF))
+ else:
+ await emit(Finding("SSL/TLS Version", "SSL 2.0 disabled", "", "good", 0, group=G_SAF))
if sslv3_supported:
await emit(Finding("SSL/TLS Version", "SSL 3.0 supported", "POODLE 攻撃が可能", "serious", 10, group=G_SAF))
+ else:
+ await emit(Finding("SSL/TLS Version", "SSL 3.0 disabled", "", "good", 0, group=G_SAF))
if version_support.get(C.TLS_1_0):
await emit(Finding("SSL/TLS Version", "TLS 1.0 supported", "RFC 8996 で廃止済み", "notgood", 4, group=G_SAF))
else:
diff --git a/src/nercone_website/tools/tls_test/views.py b/src/nercone_website/tools/tls_test/views.py
index a7d7ef8..4dbd680 100644
--- a/src/nercone_website/tools/tls_test/views.py
+++ b/src/nercone_website/tools/tls_test/views.py
@@ -80,6 +80,15 @@ def tls_results_context(job: dict, test_id: str, request: Request, tls_test_db)
if step not in seen_steps:
log_entries += [{"kind": "finding", "finding": f} for f in fs]
+ target = (result.get("target") or job.get("target") or "").strip()
+ history = tls_test_db.get_history_by_target(target, exclude_id=test_id, limit=10) if target else []
+ for h in history:
+ try:
+ ts = h.get("finished_at") or h.get("created_at") or 0
+ h["finished_at_display"] = datetime.datetime.fromtimestamp(int(ts)).strftime("%Y-%m-%dT%H:%M:%S") if ts else ""
+ except Exception:
+ h["finished_at_display"] = ""
+
return {
"test_id": test_id,
"job": job,
@@ -90,4 +99,5 @@ def tls_results_context(job: dict, test_id: str, request: Request, tls_test_db)
"finished_at_display": finished_at_display,
"share_url": str(request.url).split("#", 1)[0],
"log_entries": log_entries,
+ "history": history,
}