diff --git a/Readme.md b/Readme.md index def7c61..5eba4f6 100644 --- a/Readme.md +++ b/Readme.md @@ -43,7 +43,6 @@ SneakyScope fetches a page in a sandbox, enriches with WHOIS/GeoIP, and runs a u * **Playwright** for headless page fetch/render * **BeautifulSoup4** for parsing * **Rules Engine** - * YAML regex rules (`config/suspicious_rules.yaml`) * Function rules (`app/rules/function_rules.py`) registered on startup * **Artifacts**: persistent path mounted at `/data` (configurable) diff --git a/app/__init__.py b/app/__init__.py index eb45184..b9dacea 100644 --- a/app/__init__.py +++ b/app/__init__.py @@ -12,6 +12,7 @@ from app.app_settings import AppSettings from app.blueprints.main import bp as main_bp # ui blueprint from app.blueprints.api import api_bp as api_bp # api blueprint from app.blueprints.roadmap import bp as roadmap_bp # roadmap +from app.blueprints.changelog import bp as changelog_bp # changelog @@ -63,6 +64,7 @@ def create_app() -> Flask: # roadmap file app.config["ROADMAP_FILE"] = str(Path(app.root_path) / "docs" / "roadmap.yaml") + app.config["CHANGELOG_FILE"] = str(Path(app.root_path) / "docs" / "changelog.yaml") # Configure storage directory (bind-mount is still handled by sandbox.sh) sandbox_storage_default = Path("/data") @@ -73,6 +75,7 @@ def create_app() -> Flask: app.register_blueprint(main_bp) app.register_blueprint(api_bp) app.register_blueprint(roadmap_bp) + app.register_blueprint(changelog_bp) app_logger = get_app_logger() diff --git a/app/blueprints/changelog.py b/app/blueprints/changelog.py new file mode 100644 index 0000000..eb2dbcf --- /dev/null +++ b/app/blueprints/changelog.py @@ -0,0 +1,71 @@ +# app/services/changelog_loader.py +from __future__ import annotations +from dataclasses import dataclass +from pathlib import Path +from typing import Any, List, Optional, Dict +import yaml + +from flask import Blueprint, current_app, render_template + +@dataclass +class ChangeItem: + title: str + details: List[str] + +@dataclass +class VersionLog: + version: str + features: List[ChangeItem] + refactors: List[ChangeItem] + fixes: List[ChangeItem] + notes: List[str] + +@dataclass +class Changelog: + unreleased: Dict[str, List[ChangeItem]] + versions: List[VersionLog] + +def _coerce_items(items: Optional[List[Dict[str, Any]]]) -> List[ChangeItem]: + out: List[ChangeItem] = [] + for it in items or []: + title = str(it.get("title", "")).strip() + details = [str(d) for d in (it.get("details") or [])] + out.append(ChangeItem(title=title, details=details)) + return out + +def load_changelog(path: Path) -> Changelog: + """ + Load changelog.yaml and coerce into dataclasses. + """ + data = yaml.safe_load(path.read_text(encoding="utf-8")) + + unreleased = { + "features": _coerce_items(data.get("unreleased", {}).get("features")), + "refactors": _coerce_items(data.get("unreleased", {}).get("refactors")), + "fixes": _coerce_items(data.get("unreleased", {}).get("fixes")), + } + + versions: List[VersionLog] = [] + for v in data.get("versions", []): + versions.append( + VersionLog( + version=str(v.get("version")), + features=_coerce_items(v.get("features")), + refactors=_coerce_items(v.get("refactors")), + fixes=_coerce_items(v.get("fixes")), + notes=[str(n) for n in (v.get("notes") or [])], + ) + ) + + return Changelog(unreleased=unreleased, versions=versions) + + +bp = Blueprint("changelog", __name__) + +@bp.route("/changelog") +def view_changelog(): + # Configurable path with sensible default at project root + cfg_path = current_app.config.get("CHANGELOG_FILE") + path = Path(cfg_path) if cfg_path else (Path(current_app.root_path).parent / "changelog.yaml") + changelog = load_changelog(path) + return render_template("changelog.html", changelog=changelog) diff --git a/app/docs/changelog.yaml b/app/docs/changelog.yaml new file mode 100644 index 0000000..02a0064 --- /dev/null +++ b/app/docs/changelog.yaml @@ -0,0 +1,80 @@ +# changelog.yaml +unreleased: + features: [] + refactors: [] + fixes: [] + +versions: + - version: "v0.2" + features: + - title: "UI Modernization" + details: + - "Migrated front-end to Tailwind CSS (compiled) with Flowbite JS components." + - "New navbar and layout system; better navigation and future expansion." + - "Docker-based CSS build for reproducible, lightweight builds." + - title: "Reusable CSS Components" + details: + - "Custom utilities: badge, badge-ok, badge-warn, badge-danger, chip, card, etc." + - "Reduces repetition and enforces consistent look." + - title: "Roadmap / Changelog (YAML-driven + in-app UI)" + details: + - "YAML-backed roadmap, in-app view at `/roadmap`." + - "Roadmap Filters: q, tag, min_priority, milestone; tag chips; Details modal that renders `details`." + - "YAML-backed Changelog, in-app view at `/changelog`." + - title: "Modal sizing & ergonomics" + details: + - "Wider modal at larger breakpoints; scrollable body for long content." + - title: "GeoIP Results Uplift" + details: + - "Cloudflare detection via GeoIP ASN; badge on results page." + - "Country/ASN notes shown beside collapsed IP next to GeoIP results." + - title: "Text Analysis Pipeline (Rules)" + details: + - "`analyse_text()` extracts visible text and evaluates `category: text` rules." + - "Captures matched phrases into deduped `content_snippet` (len capped via `settings.ui.snippet_preview_len`)." + - "Results exposed in JSON as `suspicious_text`; UI via `templates/partials/result_text.html`." + refactors: + - title: "Template Includes" + details: + - "Common UI (headers/footers/layout) extracted into Jinja includes." + - title: "Roadmap loader simplification" + details: + - "Removed cache; returns typed dataclasses and normalizes `details`." + - title: "Safer JSON in templates" + details: + - "Use `|tojson|forceescape` for embedding payloads in data attributes." + - title: "Rules Engine Regex handling" + details: + - "Honor per-rule regex flags; default IGNORECASE for `category: text` if no `i` flag." + - title: "Engine/Scanner logging" + details: + - "Dispatch-time visibility; gated by `settings.app.print_rule_dispatch`." + - title: "Code cleanup" + details: + - "Removed obsolete paths/utilities; removed duplicate `enrich_url` call." + fixes: + - title: "Table Rendering" + details: + - "Locked column widths; fixed snippet scaling to prevent reflow." + - title: "Rules Engine State" + details: + - "Fix pulling engine from app state; restores proper detections." + - title: "YAML parsing edge cases" + details: + - "Quote scalars containing `:`/`#`; use explicit `null` as needed." + - title: "/roadmap page stability" + details: + - "Return structured objects; fix `AttributeError: 'dict' object has no attribute 'roadmap'`." + - title: "Modal population" + details: + - "Pass `details` through route; DOM-ready + delegation populate reliably." + - title: "Text indicators not displayed" + details: + - "Add text analyzer; align result shape with `result_text` partial." + + - version: "v0.1" + notes: + - "Initial Flask web UI for URL submission and analysis." + - "Domain & IP enrichment (WHOIS, GeoIP, ASN/ISP)." + - "First Suspicious Rules Engine for scripts/forms." + - "Basic Docker setup for sandboxed deployment." diff --git a/app/templates/base.html b/app/templates/base.html index b66e0c9..42c33d9 100644 --- a/app/templates/base.html +++ b/app/templates/base.html @@ -35,6 +35,11 @@ Roadmap +
  • + + Changelog + +
  • {# Mobile toggle #} @@ -62,6 +67,11 @@ Roadmap +
  • + + Chnagelog + +
  • diff --git a/app/templates/changelog.html b/app/templates/changelog.html new file mode 100644 index 0000000..432abfa --- /dev/null +++ b/app/templates/changelog.html @@ -0,0 +1,136 @@ +{# templates/changelog.html #} +{% extends "base.html" %} +{% block title %}Changelog{% endblock %} + +{% block content %} +
    + +
    +
    +

    SneakyScope Changelog

    + {% if updated %} +

    Last updated: {{ updated }}

    + {% endif %} +
    +
    + + {# Unreleased #} + {% set ur = changelog.unreleased %} + {% if ur.features or ur.refactors or ur.fixes %} +
    +
    +

    Unreleased

    + WIP +
    + +
    + {% for title, items, icon in [ + ("✨ Features", ur.features, "✨"), + ("πŸ› οΈ Refactors", ur.refactors, "πŸ› οΈ"), + ("πŸ› Fixes", ur.fixes, "πŸ›"), + ] %} +
    +

    {{ title }}

    + {% if items and items|length %} +
      + {% for it in items %} +
    • +
      {{ it.title }}
      + {% if it.details %} +
        + {% for d in it.details %} +
      • {{ d }}
      • + {% endfor %} +
      + {% endif %} +
    • + {% endfor %} +
    + {% else %} +

    Nothing yet β€” add upcoming {{ title.split(' ')[1] | lower }} here.

    + {% endif %} +
    + {% endfor %} +
    +
    + {% endif %} + + {# Versions Accordion #} +
    +
    + {% for v in changelog.versions %} +

    + +

    + +
    +
    + + {% if v.notes and v.notes|length %} +
    +

    Notes

    +
      + {% for n in v.notes %} +
    • {{ n }}
    • + {% endfor %} +
    +
    + {% endif %} + + {% for section_title, items in [ + ("✨ Features", v.features), + ("πŸ› οΈ Refactors", v.refactors), + ("πŸ› Fixes", v.fixes), + ] %} + {% if items and items|length %} +
    +

    {{ section_title }}

    +
    + {% for it in items %} +
    +

    {{ it.title }}

    + {% if it.details %} +
      + {% for d in it.details %} +
    • {{ d }}
    • + {% endfor %} +
    + {% endif %} +
    + {% endfor %} +
    +
    + {% endif %} + {% endfor %} + +
    +
    + {% endfor %} +
    +
    +
    +{% endblock %} + +{% block scripts %} +{# If you’re not auto-initializing Flowbite elsewhere, ensure its JS is loaded globally. #} + +{% endblock %} diff --git a/docs/changelog.md b/docs/changelog.md index f4a7d0e..c4f431a 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -17,7 +17,7 @@ This project follows [Semantic Versioning](https://semver.org/). - _Nothing yet β€” add upcoming fixes here._ --- - +## [v0.2] ### ✨ Features @@ -37,6 +37,10 @@ This project follows [Semantic Versioning](https://semver.org/). * **Modal sizing & ergonomics** Increased modal width at larger breakpoints and made the body scrollable so long details don’t squish other content. +* **GeoIP Results Uplift** + Added Cloudflare detection via Geoip ASN results and Cloudflare badge on results page + Added Country - ASN notes beside collapsed IP next to GeoIP results for quick viewing. + * **Text Analysis Pipeline (Rules)** Implemented `analyse_text()` to extract visible page text and evaluate `category: text` rules. Captures matched phrases into a deduped `content_snippet` (length capped via `settings.ui.snippet_preview_len`). @@ -88,7 +92,7 @@ This project follows [Semantic Versioning](https://semver.org/). --- -## [v0.1] – Initial Work +## [v0.1] - Implemented initial **Flask-based web UI** for URL submission and analysis. - Added **domain & IP enrichment** (WHOIS, GeoIP, ASN/ISP lookups).