{# T27.1: CSRF token for the rerun + feedback POSTs. Empty when the #} {# report is rendered for an anonymous viewer (no dashboard cookie). #} {# Bearer-auth callers (CLI report-cache export) ignore this meta. #} {# T52: csrf meta is stripped entirely in download_mode (the token is #} {# stale once the file is saved; including it would just confuse #} {# whoever opens the offline copy). #} {% if not download_mode %} {% endif %} Scan Report — {{ report.target_domain }} {# T52: in download_mode the stylesheets + scripts are inlined into #} {# the file so the saved HTML renders correctly offline. Otherwise #} {# we keep the linked-asset form so browsers can cache them. #} {% if download_mode %} {% else %} {% endif %} {# FOUC-prevention: set data-theme synchronously before paint. Default dark unless the user's saved preference says light. #}
{% include 'partials/topbar.html' %} {# T12 header -- intent banner replaces title.html + meta_grid.html. #} {% include 'partials/intent_banner.html' %} {# Confidence ribbon -- always renders when replay ran, both directions (high + low). Replaces the v0.1.0 replay_banner.html low-confidence-only variant. #} {% include 'partials/confidence_ribbon.html' %} {# 01 / DETECTION -- anti-bot stack. Show every anti-bot finding the detection layer surfaced; do NOT filter by bucket. Real-world anti- bot vendors (PerimeterX, DataDome, Akamai Bot Manager, Cloudflare Bot Management) are almost always served from THIRD-PARTY CDN hosts (px-cloud.net, datadome.co, akamai.com, cloudflare.com, etc.) and the user's first-party domain just embeds their tag/script. T12's first cut filtered Section 01 by Bucket A/B endpoints, which dropped PerimeterX from the visible anti-bot stack on Target's scans even though the synthesis layer (Section 05) correctly identified it as the dominant blocker. The filter is removed. The detection-rule-level fixes (T7's first-party scoping in the auth detector, the bare-state= regex tightening) are the right place to filter false-positive findings -- not the renderer. #}
01 / DETECTION

Anti-bot stack

{# T42.1: dedup-and-tag is now a Python helper (sort_anti_bot_findings) registered as a Jinja global in templates/__init__.py. Each entry in the returned list carries {finding, is_primary}; the template no longer needs the inline severity_tier sort or the loop.first PRIMARY guard, which means the multi-vendor stack can be unit- tested directly against the helper. Existing detection_card.html still binds to the ``finding`` + ``is_primary`` names in its include context. #} {% set tagged_anti_bot = sort_anti_bot_findings(report.detection_result.findings) %} {% if tagged_anti_bot %}

{{ tagged_anti_bot | length }} anti-bot finding{{ '' if (tagged_anti_bot | length) == 1 else 's' }} captured. Cards are ordered by severity tier descending.

{% for entry in tagged_anti_bot %} {% set finding = entry.finding %} {% set is_primary = entry.is_primary %} {% include 'partials/detection_card.html' %} {% endfor %} {% else %}

No anti-bot protection detected on this site.

No protection detected
Clean
No Cloudflare, DataDome, PerimeterX, or comparable bot-management vendor was observed during the capture session.
{% endif %}
{# 02 / SCRAPING PLAN -- click-driven action cards with curl + httpx snippets per Bucket A call. Replaces the v0.1.0 endpoints_table. #} {% include 'partials/scraping_plan.html' %} {# 02b / VALIDATION RESULTS -- T34.4: per-endpoint validation outcomes (Bucket A full pipeline + Bucket B reachability). Hidden entirely when validation did not run for this scan. #} {% include 'partials/validation_results.html' %} {# 02c / VALIDATION (T51.8) -- richer per-endpoint cards driven by the new ``per_endpoint_report_view`` projection. Renders only when the CLI uploaded the T51.8 projection set; the 02b legacy table above still covers older scans. #} {% include 'partials/validation_section.html' %} {# 03 / PREREQUISITES -- Bucket B "must call first" list. #} {% include 'partials/prerequisites.html' %} {# 04 / DIFFICULTY -- synthesis-curated drivers (unchanged from v0.1.0). #} {% include 'partials/difficulty_drivers.html' %} {# 05 / RECOMMENDATION + COST -- merges the v0.1.0 verdict block and the cost_breakdown partial into one section. #} {% include 'partials/recommendation_cost.html' %} {# 06 / STARTER CODE -- renders only when synthesis populated it. #} {% include 'partials/starter_code.html' %} {# 07 / EVIDENCE -- collapsibles + bucket counts (no longer a wall of 50 endpoint URLs). #} {% include 'partials/evidence_trail.html' %} {# 08 / DRIFT -- last_validated_at + a placeholder re-validate link. #} {% include 'partials/drift_footer.html' %} {% include 'partials/footer.html' %}
{# T52: in download_mode the JS gets inlined; otherwise we keep the #} {# linked-asset form. The report.js feedback/retry POSTs still rely #} {# on the CSRF meta tag and a live origin -- they no-op gracefully #} {# from a saved offline file (fetch will just fail and the button #} {# stays inert). The topbar Download button + feedback footer are #} {# stripped entirely in download_mode so the user never clicks them #} {# from a static save. #} {% if download_mode %} {% else %} {% endif %}