diff --git a/Dockerfile b/Dockerfile index adc9235..94ca760 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,3 +1,24 @@ +# --- Stage 1: CSS builder (no npm) --- +FROM alpine:3.20 AS css-builder +WORKDIR /css +RUN apk add --no-cache curl + +# Download Tailwind standalone CLI +# (Update version if desired; linux-x64 works on Alpine) +RUN curl -sL https://github.com/tailwindlabs/tailwindcss/releases/download/v3.4.10/tailwindcss-linux-x64 \ + -o /usr/local/bin/tailwindcss && chmod +x /usr/local/bin/tailwindcss + +# Config + sources +COPY tailwind/tailwind.config.js ./ +COPY assets ./assets +COPY app/templates ./app/templates +COPY app/static ./app/static + +# Build Tailwind CSS +RUN tailwindcss -i ./assets/input.css -o ./tw.css --minify + +# --- Stage 2: Playwright python image with requirements. + # Use the official Playwright image with browsers preinstalled FROM mcr.microsoft.com/playwright/python:v1.45.0-jammy @@ -19,6 +40,9 @@ RUN pip install --no-cache-dir -r requirements.txt # Copy application code (the double app is needed because the app folder needs to be inside the app folder) COPY app/ /app/app/ +# Bring in the compiled CSS from Stage 1 +COPY --from=css-builder /css/tw.css /app/app/static/tw.css + COPY entrypoint.sh ./entrypoint.sh RUN chmod +x /app/entrypoint.sh diff --git a/app/blueprints/ui.py b/app/blueprints/ui.py index 58a56f6..e861ade 100644 --- a/app/blueprints/ui.py +++ b/app/blueprints/ui.py @@ -69,7 +69,8 @@ def inject_app_info(): """Inject app name and version into all templates.""" return { "app_name": app_name, - "app_version": app_version + "app_version": app_version, + "current_year": datetime.strftime(datetime.now(),"%Y") } @bp.route("/", methods=["GET"]) diff --git a/app/static/style.css b/app/static/style.css index b5f9d56..e69de29 100644 --- a/app/static/style.css +++ b/app/static/style.css @@ -1,472 +0,0 @@ -/* ========================================================================== - SneakyScope Stylesheet - Consolidated + Commented - ========================================================================== */ - -/* ========================================================================== - 0) Theme Variables - -------------------------------------------------------------------------- */ -:root { - /* Typography */ - --font-sans: system-ui, -apple-system, Segoe UI, Roboto, Helvetica, Arial, sans-serif; - - /* Colors (derived from your current palette) */ - --bg: #0b0f14; - --text: #e6edf3; - --header-bg: #0f1720; - --card-bg: #111826; - --border: #1f2a36; /* darker border */ - --border-2: #243041; /* lighter border used on inputs/tables */ - --input-bg: #0b1220; - - --link: #7dd3fc; - --link-hover: #38bdf8; - - /* Accents */ - --accent-pill-bg: rgba(59,130,246,.18); - --accent-pill-bd: rgba(59,130,246,.45); - - /* Radius & Shadows */ - --radius: 12px; -} - -/* ========================================================================== - 1) Base / Reset - -------------------------------------------------------------------------- */ -html { scroll-behavior: smooth; } - -:root { font-family: var(--font-sans); } - -body { - margin: 0; - background: var(--bg); - color: var(--text); -} - -a { - color: var(--link); - text-decoration: underline; -} -a:hover { color: var(--link-hover); } - -img { - max-width: 100%; - height: auto; - border-radius: 8px; - border: 1px solid var(--border-2); -} - -/* ========================================================================== - 2) Layout (header/footer/main/cards) - -------------------------------------------------------------------------- */ -header, footer { - padding: 1rem 1.25rem; - background: var(--header-bg); - border-bottom: 1px solid var(--border); -} - -main { - /* full-width layout */ - padding: 1.5rem 2rem; - max-width: 100%; - width: 100%; - margin: 0; - box-sizing: border-box; -} - -.card { - background: var(--card-bg); - padding: 1rem; - border: 1px solid var(--border); - border-radius: var(--radius); - margin-bottom: 1rem; - /* anchors don't hide under sticky nav */ - scroll-margin-top: 72px; -} - -/* ========================================================================== - 3) Form Controls & Buttons - -------------------------------------------------------------------------- */ -label { - display: block; - margin-bottom: 0.5rem; -} - -input[type="url"] { - width: 100%; - padding: 0.7rem; - border-radius: 8px; - border: 1px solid var(--border-2); - background: var(--input-bg); - color: var(--text); -} - -button, .button { - display: inline-block; - margin-top: 0.75rem; - padding: 0.6rem 1rem; - border-radius: 8px; - border: 1px solid var(--border-2); - background: #1a2535; - color: var(--text); - text-decoration: none; - cursor: pointer; -} -button:hover, .button:hover { filter: brightness(1.05); } - -/* Flash messages */ -.flash { list-style: none; padding: 0.5rem 1rem; } -.flash .error { color: #ff6b6b; } - -/* Simple grid utility */ -.grid { - display: grid; - grid-template-columns: 150px 1fr; - gap: 0.5rem 1rem; -} - -/* ========================================================================== - 4) Code Blocks & Details/Accordion - -------------------------------------------------------------------------- */ -pre.code { - font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; - font-size: 0.9rem; - white-space: pre-wrap; /* wrap long lines */ - word-break: break-all; - background: var(--input-bg); - padding: 0.75rem; - border-radius: 8px; - border: 1px solid var(--border-2); -} - -details summary { - cursor: pointer; - padding: 0.5rem; - font-weight: bold; - border-radius: 8px; - background: var(--card-bg); - border: 1px solid var(--border); - margin-bottom: 0.5rem; - transition: background 0.3s ease; -} -details[open] summary { background: #1a2535; } - -/* inner spacing when expanded */ -details > ul, -details > table { padding-left: 1rem; margin: 0.5rem 0; } - -/* flagged state */ -details.flagged summary { border-left: 4px solid #ff6b6b; } - -/* gentle transitions */ -details ul, details p { transition: all 0.3s ease; } - -/* readable expanded code without blowing layout */ -details pre.code { - white-space: pre-wrap; - word-break: break-word; - max-height: 18rem; - overflow: auto; -} - -/* ========================================================================== - 5) Tables — Enrichment (generic) - -------------------------------------------------------------------------- */ -.enrichment-table { - width: 100%; - border-collapse: collapse; - margin-bottom: 1rem; -} -.enrichment-table th, -.enrichment-table td { - border: 1px solid var(--border-2); - padding: 0.5rem; - vertical-align: top; -} -.enrichment-table th { - background: var(--card-bg); - text-align: left; -} -.enrichment-table td { - width: auto; - word-break: break-word; -} -.enrichment-table tbody tr:hover { background: #1f2a36; } -.enrichment-table thead th { border-bottom: 2px solid var(--border-2); } -/* ensure nested tables don't overflow cards */ -.card table { table-layout: auto; word-break: break-word; } - -/* ========================================================================== - 6) Tables — Shared Rules (Scripts & Forms) - -------------------------------------------------------------------------- */ -.scripts-table, -.forms-table { - table-layout: fixed; - width: 100%; -} -.scripts-table td ul, -.forms-table td ul { - margin: 0.25rem 0 0.25rem 1rem; - padding-left: 1rem; -} -.scripts-table td small, -.forms-table td small { opacity: 0.85; } -/* ellipsize by default */ -.scripts-table td, .scripts-table th, -.forms-table td, .forms-table th { - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; -} -/* allow wrapping inside expanded blocks */ -.scripts-table details, -.forms-table details { white-space: normal; } -.scripts-table details > pre.code, -.forms-table details > pre.code { - white-space: pre-wrap; - max-height: 28rem; - overflow: auto; -} - -/* ========================================================================== - 7) Scripts Table (columns & tweaks) - -------------------------------------------------------------------------- */ -/* compact inline snippet */ -.scripts-table pre.code { margin: 0; padding: 0.25rem; font-size: 0.9rem; } - -/* columns: Type | Source URL | Snippet | Matches */ -.scripts-table th:nth-child(1) { width: 8rem; } -.scripts-table th:nth-child(2) { width: 32rem; } -.scripts-table th:nth-child(3) { width: 24rem; } -.scripts-table th:nth-child(4) { width: auto; } - -/* ========================================================================== - 8) Forms Table (columns & helpers) - -------------------------------------------------------------------------- */ -/* columns: Action | Method | Inputs | Matches | Form Snippet */ -.forms-table th:nth-child(1) { width: 15rem; } /* Action */ -.forms-table th:nth-child(2) { width: 5rem; } /* Method */ -.forms-table th:nth-child(3) { width: 15rem; } /* Inputs */ -.forms-table th:nth-child(5) { width: 24rem; } /* Snippet */ -.forms-table th:nth-child(4) { width: auto; } /* Matches grows */ - -/* input chips layout inside cells */ -.forms-table .chips { - display: flex; - gap: 0.25rem; - flex-wrap: wrap; - white-space: normal; -} - -/* ========================================================================== - 9) Results Table (Recent runs list) - -------------------------------------------------------------------------- */ -.results-table { - width: 100%; - border-collapse: collapse; - background: var(--card-bg); - border: 1px solid var(--border); - border-radius: var(--radius); - overflow: hidden; - table-layout: auto; -} -.results-table thead th { - padding: 0.6rem 0.75rem; - background: var(--header-bg); - border-bottom: 1px solid var(--border); - text-align: left; - font-weight: 600; - white-space: nowrap; -} -.results-table tbody td { - padding: 0.6rem 0.75rem; - border-top: 1px solid var(--border); - vertical-align: top; - text-align: left; -} -.results-table tbody tr:nth-child(odd) { background: #0d1522; } -.results-table a { text-decoration: underline; } - -/* column-specific helpers */ -.results-table td.url, -.results-table td.url a { - overflow-wrap: anywhere; - word-break: break-word; -} -.results-table td.uuid { - font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; - word-break: break-all; - max-width: 28ch; -} -.results-table td.timestamp { - text-align: right; - white-space: nowrap; -} -.results-table tbody tr:first-child { box-shadow: inset 0 0 0 1px var(--border-2); } -.results-table .copy-btn { - margin-left: 0.4rem; - padding: 0.2rem 0.45rem; - border-radius: 6px; - border: 1px solid var(--border-2); - background: #1a2535; - color: var(--text); - cursor: pointer; - line-height: 1; - font-size: 0.9rem; -} -.results-table .copy-btn:hover { filter: brightness(1.1); } - -/* ========================================================================== - 10) Utilities (chips, badges, helpers) - -------------------------------------------------------------------------- */ -.breakable { white-space: normal; overflow-wrap: anywhere; word-break: break-word; } - -/* Generic badge + severities */ -.badge { - display: inline-block; - padding: 0.1rem 0.4rem; - margin-left: 0.35rem; - border-radius: 0.4rem; - font-size: 0.75rem; - line-height: 1; - vertical-align: middle; - user-select: none; - border: 1px solid transparent; /* individual severities add their borders */ -} -.sev-high { background: #fdecea; color: #b71c1c; border-color: #f5c6c4; } -.sev-medium { background: #fff8e1; color: #8a6d3b; border-color: #ffe0a3; } -.sev-low { background: #e8f5e9; color: #1b5e20; border-color: #b9e6be; } - -/* Tag chips */ -.chips { display: flex; gap: 0.25rem; flex-wrap: wrap; } -.chip { - display: inline-block; - padding: 0.1rem 0.35rem; - border-radius: 999px; - font-size: 0.7rem; - line-height: 1; - background: #eef2f7; - color: #425466; - border: 1px solid #d9e2ec; -} - -.checkbox-row { - display: flex; align-items: center; gap: .5rem; - margin: .5rem 0 1rem; -} - -/* ========================================================================== - 11) Sticky Top Jump Navigation - -------------------------------------------------------------------------- */ -.top-jump-nav { - position: sticky; - top: 0; - z-index: 50; - display: flex; - align-items: center; - gap: .5rem .75rem; - padding: .5rem 1rem; - margin: 0 0 1rem 0; - - background: var(--card-bg); - border: 1px solid rgba(255,255,255,.08); - box-shadow: 0 4px 14px rgba(0,0,0,.25); - border-radius: 10px; - - overflow-x: auto; - white-space: nowrap; - -webkit-overflow-scrolling: touch; -} -.top-jump-nav a { - display: inline-block; - padding: .4rem .75rem; - border: 1px solid rgba(255,255,255,.12); - border-radius: 999px; - text-decoration: none; - font-size: .95rem; - line-height: 1; - color: inherit; - opacity: .95; -} -.top-jump-nav a:hover, -.top-jump-nav a:focus { - opacity: 1; - background: rgba(255,255,255,.06); - outline: none; -} -.top-jump-nav a.active { - background: var(--accent-pill-bg); - border-color: var(--accent-pill-bd); - box-shadow: inset 0 0 0 1px rgba(59,130,246,.25); -} - -/* --- Titles and structure --- */ -.card-title { margin: 0 0 .5rem; font-size: 1.1rem; } -.section { margin-top: 1rem; } -.section-header { display: flex; gap: .5rem; align-items: baseline; flex-wrap: wrap; } - -/* --- Divider --- */ -.divider { border: 0; border-top: 1px solid #1f2a36; margin: 1rem 0; } - -/* --- Badges / Chips --- */ -.badge { display: inline-block; padding: .15rem .5rem; border-radius: 999px; font-size: .75rem; border: 1px solid transparent; } -.badge-ok { background: #0e3820; border-color: #2c6e49; color: #bff3cf; } -.badge-warn { background: #3d290e; border-color: #9a6b18; color: #ffe2a8; } -.badge-danger { background: #401012; border-color: #a33a42; color: #ffc1c5; } -.badge-muted { background: #111826; border-color: #273447; color: #9fb0c3; } - -.chip { display: inline-block; padding: .1rem .4rem; border: 1px solid #273447; border-radius: 8px; font-size: .75rem; margin-right: .25rem; } -.chip-warn { border-color: #9a6b18; } - -/* --- Text helpers --- */ -.muted { color: #9fb0c3; } -.small { font-size: .8rem; } -.mono { font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", monospace; } -.prewrap { white-space: pre-wrap; } - -/* --- Lists / details --- */ -.list { margin: .5rem 0; padding-left: 1.1rem; } -.details summary { cursor: pointer; } - -/* --- Grid --- */ -.grid.two { display: grid; grid-template-columns: 1fr; gap: 1rem; } -@media (min-width: 900px) { - .grid.two { grid-template-columns: 1fr 1fr; } -} - -/* --- TLS Matrix --- */ -.tls-matrix { border: 1px solid #1f2a36; border-radius: 10px; overflow: hidden; } -.tls-matrix-row { display: grid; grid-template-columns: 120px 140px 1fr 100px; gap: .5rem; align-items: center; - padding: .5rem .75rem; border-bottom: 1px solid #1f2a36; } -.tls-matrix-row:last-child { border-bottom: none; } - -.tls-cell.version { font-weight: 600; } -.tls-cell.status {} -.tls-cell.cipher {} -.tls-cell.latency { text-align: right; } - - -/* ========================================================================== - 12) Responsive Tweaks - -------------------------------------------------------------------------- */ -@media (max-width: 1200px) { - .forms-table th:nth-child(1) { width: 22rem; } - .forms-table th:nth-child(3) { width: 16rem; } - .forms-table th:nth-child(5) { width: 18rem; } -} - -@media (max-width: 768px) { - main { padding: 1rem; } - - .enrichment-table, - .results-table { - display: block; - overflow-x: auto; - white-space: nowrap; - } -} - -@media (max-width: 640px) { - .top-jump-nav { padding: .4rem .6rem; gap: .4rem .5rem; } - .top-jump-nav a { padding: .35rem .6rem; font-size: .9rem; } -} diff --git a/app/templates/_macros_ssl_tls.html b/app/templates/_macros_ssl_tls.html index 8acf474..12689ce 100644 --- a/app/templates/_macros_ssl_tls.html +++ b/app/templates/_macros_ssl_tls.html @@ -1,99 +1,108 @@ {# templates/_macros_ssl_tls.html #} {% macro ssl_tls_card(ssl_tls) %} -
SSL/TLS enrichment failed or is unavailable.
- {% if ssl_tls and ssl_tls.error %}{{ ssl_tls.error }}{% endif %}
+ {% if ssl_tls is none or (ssl_tls.error is defined) or ('error' in ssl_tls) %}
+ Error
+ SSL/TLS enrichment failed or is unavailable.
+ {% if ssl_tls and ssl_tls.error %} +{{ ssl_tls.error }}
+ {% endif %}
{# -------- 2) Skipped branch -------- #}
{% elif ssl_tls.skipped %}
- {{ ssl_tls|tojson(indent=2) }}
- {{ ssl_tls|tojson(indent=2) }}
+ {{ probe.hostname }}:{{ probe.port }}
+ Host:
+ {{ probe.hostname }}:{{ probe.port }}
{% endif %}
No probe data.
+No probe data.
{% else %} -| Version | +Status | +Selected Cipher | +Latency | +
|---|---|---|---|
| {{ v }} | ++ {% if r and r.supported %} + Supported + {% else %} + Not Supported + {% endif %} + | ++ {% if r and r.selected_cipher %} + {{ r.selected_cipher }} + {% elif r and r.error %} + ({{ r.error }}) + {% else %} + — + {% endif %} + | ++ {% if r and r.handshake_seconds is not none %} + {{ '%.0f' % (r.handshake_seconds*1000) }} ms + {% else %} + — + {% endif %} + | +
{{ crtsh.hostname or 'n/a' }}
+ Parsed:
+ {{ crtsh.hostname or 'n/a' }}
{% if crtsh.root_domain %}
- • Root: {{ crtsh.root_domain }}
- {% if crtsh.is_root_domain %}Root{% else %}Subdomain{% endif %}
+ • Root:
+ {{ crtsh.root_domain }}
+ {% if crtsh.is_root_domain %}
+ Root
+ {% else %}
+ Subdomain
+ {% endif %}
{% endif %}
{% endif %}
No CT data.
+No CT data.
{% else %} -No active host certs found.
+No active host certs found.
{% endif %}No wildcard/root certs found.
+No wildcard/root certs found.
{% endif %}{{ ssl_tls|tojson(indent=2) }}
- {{ ssl_tls|tojson(indent=2) }}
+ Submitted URL: {{ submitted_url }}
-Final URL: {{ final_url }}
-Permalink:
-
- {{ request.host_url }}results/{{ uuid }}
+
+ Submitted URL: {{ submitted_url }}
+ Final URL:
+ {{ final_url }}
+
+ Permalink:
+
+ {{ request.host_url }}results/{{ uuid }}
- URL Overview
+
| Field | -Value | -
|---|
| Field | +Value | +
|---|---|
| {{ k.replace('_', ' ').title() }} | -{{ v }} | -
| {{ k.replace('_', ' ').title() }} | +{{ v }} | +
{{ enrichment.raw_whois }}
+ {{ enrichment.raw_whois }}
{% endif %}
-
{% if enrichment.geoip %}
- | {{ key.replace('_', ' ').title() }} | -{{ val }} | -
| {{ key.replace('_', ' ').title() }} | +{{ val }} | +
No enrichment data available.
+No enrichment data available.
{% endif %} - -| Status | -URL | -
|---|
| Status | +URL | +
|---|---|
| {{ r.status }} | -{{ r.url }} | -
| {{ r.status }} | ++ {{ r.url }} + | +
No redirects detected.
+No redirects detected.
{% endif %} - -| Action | -Method | -Inputs | -Matches (Rules) | -Form Snippet | -||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| - {% if f.action %} - {{ f.action[:25] }}{% if f.action|length > 25 %}…{% endif %} - {% else %} - (no action) - {% endif %} - | + {% if forms and forms|length > 0 %} +
| Action | +Method | +Inputs | +Matches (Rules) | +Form Snippet | +|||
|---|---|---|---|---|---|---|---|
| + {% if f.action %} + {{ f.action[:80] }}{% if f.action|length > 80 %}…{% endif %} + {% else %} + (no action) + {% endif %} + | - -{{ (f.method or 'get')|upper }} | + +{{ (f.method or 'get')|upper }} | - -
- {% if f.inputs and f.inputs|length > 0 %}
-
+
+
+ {% if f.inputs and f.inputs|length > 0 %}
+ |
+ {% else %}
+ None
+ {% endif %}
+
{% for inp in f.inputs %}
-
- {{ inp.name or '(unnamed)' }} : {{ (inp.type or 'text') }}
+
+ {{ inp.name or '(unnamed)' }} : {{ (inp.type or 'text') }}
{% endfor %}
- {% else %}
- None
- {% endif %}
- |
-
-
- {% if f.rules and f.rules|length > 0 %}
-
+ {% if f.rules and f.rules|length > 0 %}
+ |
+ {% else %}
+ N/A
+ {% endif %}
+
|
-
-
- {% if f.content_snippet %}
-
-
- {% else %}
- N/A
- {% endif %}
- View snippet ({{ f.content_snippet|length }} chars)-{{ f.content_snippet }}
- |
-
No form issues detected.
- {% endif %} + +{{ f.content_snippet }}
+ No form issues detected.
+ {% endif %} - -| Type | +Source URL | +Matches (Rules & Heuristics) | +Content Snippet | +||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|
| {{ s.type or 'unknown' }} | - {% if suspicious_scripts %} -
| Type | -Source URL | -Matches (Rules & Heuristics) | -Content Snippet | -||
|---|---|---|---|---|---|
| {{ s.type or 'unknown' }} | ++ {% if s.src %} + + {{ s.src[:100] }}{% if s.src|length > 100 %}…{% endif %} + + {% else %} + N/A + {% endif %} + | - -- {% if s.src %} - {{ s.src[:50] }} - {% else %} N/A {% endif %} - | + ++ {% set has_rules = s.rules and s.rules|length > 0 %} + {% set has_heur = s.heuristics and s.heuristics|length > 0 %} - - |
- {% set has_rules = s.rules and s.rules|length > 0 %}
- {% set has_heur = s.heuristics and s.heuristics|length > 0 %}
+ {% if has_rules %}
+ Rules
+
|
+
+
+
+ {% if s.content_snippet %}
+
+
+ {% else %}
+ {% if s.type == 'external' and s.src %}
+
+ {% else %}
+ N/A
{% endif %}
- {% if r.tags %}
- {% for t in r.tags %}
- {{ t }}
- {% endfor %}
- {% endif %}
- {% if r.description %}
- — {{ r.description }}
- {% endif %}
-
- {% endfor %}
-
- {% endif %}
+ {% endif %}
+ + View snippet ({{ s.content_snippet|length }} chars) ++{{ s.content_snippet }}
+ |
+
No suspicious scripts detected.
+ {% endif %} - {% if not has_rules and not has_heur %} - N/A - {% endif %} - + + - -{{ s.content_snippet }}
-
+
+ + + View Source + +
+ +No suspicious scripts detected.
- {% endif %} - -