redactai / scan.html
Flamki's picture
Upload folder using huggingface_hub
4c1fb5d verified
Raw
History Blame Contribute Delete
34.5 kB
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Website Privacy Scanner โ€” RedactAI</title>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jspdf/2.5.2/jspdf.umd.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jspdf-autotable/3.8.4/jspdf.plugin.autotable.min.js"></script>
<script src="report.js" defer></script>
<style>
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&display=swap');
* { box-sizing: border-box; margin: 0; padding: 0; }
body {
background-color: #000000;
color: #ffffff;
font-family: 'Inter', sans-serif;
min-height: 100vh;
overflow-x: hidden;
-webkit-font-smoothing: antialiased;
}
body::before {
content: '';
position: fixed; top: -50%; left: -50%;
width: 200%; height: 200%;
background: radial-gradient(circle at 50% 50%, rgba(20, 20, 30, 0.4), transparent 60%);
pointer-events: none; z-index: 0;
}
.container { max-width: 860px; margin: 0 auto; padding: 0 24px; position: relative; z-index: 1; }
.back-link {
position: fixed; top: 24px; left: 24px;
color: #9ca3af; text-decoration: none; font-size: 14px;
display: flex; align-items: center; gap: 8px;
transition: color 0.2s; z-index: 100;
}
.back-link:hover { color: #ffffff; }
/* ---- STEP 1: INPUT ---- */
.scan-step { padding: 20vh 0 40px; text-align: center; }
.scan-step.hidden { display: none; }
.scan-title { font-size: 32px; font-weight: 600; letter-spacing: -0.5px; margin-bottom: 12px; }
.scan-subtitle { font-size: 15px; color: #9ca3af; margin-bottom: 40px; line-height: 1.7; max-width: 600px; margin-left: auto; margin-right: auto; }
.input-wrapper {
position: relative; width: 100%; max-width: 680px; margin: 0 auto;
background: #1e1e24; border-radius: 40px;
display: flex; align-items: center;
padding: 6px 6px 6px 24px;
border: 1px solid rgba(255,255,255,0.08);
transition: all 0.3s ease;
box-shadow: 0 10px 30px rgba(0,0,0,0.5);
}
.input-wrapper:focus-within {
background: #232329; border-color: rgba(108,92,231,0.5);
box-shadow: 0 0 0 4px rgba(108,92,231,0.1), 0 10px 30px rgba(0,0,0,0.5);
}
.input-field {
flex: 1; background: transparent; border: none;
color: #fff; font-size: 16px; font-family: inherit; outline: none; padding: 14px 0;
}
.input-field::placeholder { color: #6b7280; }
.input-btn {
background: #fff; color: #000; border: none;
height: 44px; padding: 0 24px; border-radius: 22px;
font-weight: 600; font-size: 14px; font-family: inherit;
cursor: pointer; transition: all 0.2s; flex-shrink: 0; margin-left: 12px;
display: flex; align-items: center; gap: 8px;
}
.input-btn:hover { background: #e5e7eb; transform: scale(1.02); }
.input-btn:disabled { background: #374151; color: #9ca3af; cursor: not-allowed; transform: none; }
.tags-row {
display: flex; align-items: center; justify-content: center;
gap: 12px; margin-top: 24px; flex-wrap: wrap;
}
.tag {
background: transparent; border: 1px solid rgba(255,255,255,0.12);
color: #d1d5db; padding: 8px 16px; border-radius: 20px;
font-size: 13px; font-weight: 500;
}
/* ---- SCANNING STATE ---- */
.scanning-state { padding: 25vh 0; text-align: center; }
.scanning-state.hidden { display: none; }
.spinner {
width: 48px; height: 48px; border: 3px solid rgba(255,255,255,0.1);
border-top-color: #fff; border-radius: 50%;
animation: spin 0.8s linear infinite; margin: 0 auto 24px;
}
@keyframes spin { to { transform: rotate(360deg); } }
.scan-log { max-width: 500px; margin: 24px auto 0; text-align: left; }
.scan-log-item {
display: flex; align-items: center; gap: 10px;
padding: 6px 0; font-size: 14px; color: #9ca3af;
animation: fadeIn 0.3s ease forwards;
}
.scan-log-item .dot { width: 6px; height: 6px; border-radius: 50%; background: #32d74b; flex-shrink: 0; }
.scan-log-item.pending .dot { background: #ffd60a; }
@keyframes fadeIn { from { opacity: 0; transform: translateX(-10px); } to { opacity: 1; transform: translateX(0); } }
/* ---- RESULTS ---- */
.results { padding: 80px 0 60px; }
.results.hidden { display: none; }
.results-header { text-align: center; margin-bottom: 48px; }
.results-header h1 { font-size: 28px; font-weight: 700; margin-bottom: 8px; }
.results-header .scanned-url { font-size: 14px; color: #9ca3af; word-break: break-all; }
/* Risk gauge */
.risk-gauge { text-align: center; margin-bottom: 48px; }
.risk-circle {
width: 160px; height: 160px; border-radius: 50%;
display: flex; flex-direction: column; align-items: center; justify-content: center;
margin: 0 auto 16px;
border: 4px solid; position: relative;
}
.risk-circle.low { border-color: #32d74b; }
.risk-circle.medium { border-color: #ffd60a; }
.risk-circle.high { border-color: #ff9500; }
.risk-circle.critical { border-color: #ff453a; }
.risk-score { font-size: 48px; font-weight: 800; line-height: 1; }
.risk-label { font-size: 12px; text-transform: uppercase; letter-spacing: 1px; color: #9ca3af; margin-top: 4px; }
.risk-level-text { font-size: 18px; font-weight: 600; }
.risk-level-text.low { color: #32d74b; }
.risk-level-text.medium { color: #ffd60a; }
.risk-level-text.high { color: #ff9500; }
.risk-level-text.critical { color: #ff453a; }
/* Risk factors */
.risk-factors { max-width: 600px; margin: 24px auto; }
.risk-factor {
display: flex; align-items: flex-start; gap: 10px;
padding: 8px 0; font-size: 14px; color: #d1d5db;
}
.risk-factor::before { content: 'โš '; flex-shrink: 0; }
/* Section cards */
.report-section {
background: rgba(20,20,28,0.8); border: 1px solid rgba(255,255,255,0.06);
border-radius: 16px; padding: 24px; margin-bottom: 20px;
backdrop-filter: blur(8px);
}
.report-section h3 {
font-size: 16px; font-weight: 600; margin-bottom: 16px;
display: flex; align-items: center; gap: 10px;
}
.report-section h3 .count {
background: rgba(255,255,255,0.08); padding: 2px 10px;
border-radius: 12px; font-size: 13px; font-weight: 500; color: #9ca3af;
}
/* Tracker items */
.tracker-item {
display: flex; align-items: center; justify-content: space-between;
padding: 10px 0; border-bottom: 1px solid rgba(255,255,255,0.04);
font-size: 14px;
}
.tracker-item:last-child { border-bottom: none; }
.tracker-name { font-weight: 500; }
.tracker-meta { display: flex; align-items: center; gap: 8px; }
.tracker-badge {
padding: 3px 10px; border-radius: 12px; font-size: 11px; font-weight: 600; text-transform: uppercase;
}
.tracker-badge.analytics { background: rgba(10,132,255,0.15); color: #0a84ff; }
.tracker-badge.advertising { background: rgba(255,69,58,0.15); color: #ff453a; }
.tracker-badge.session_recording { background: rgba(255,149,0,0.15); color: #ff9500; }
.tracker-badge.cdp { background: rgba(50,215,75,0.15); color: #32d74b; }
.tracker-badge.fingerprinting { background: rgba(175,82,222,0.15); color: #af52de; }
.risk-dot { width: 8px; height: 8px; border-radius: 50%; }
.risk-dot.low { background: #32d74b; }
.risk-dot.medium { background: #ffd60a; }
.risk-dot.high { background: #ff453a; }
/* Compliance checks */
.compliance-row {
display: flex; align-items: center; justify-content: space-between;
padding: 10px 0; border-bottom: 1px solid rgba(255,255,255,0.04);
font-size: 14px;
}
.compliance-row:last-child { border-bottom: none; }
.check-pass { color: #32d74b; }
.check-fail { color: #ff453a; }
/* PII items */
.pii-item {
display: inline-flex; align-items: center; gap: 6px;
padding: 6px 14px; border-radius: 8px; font-size: 13px;
background: rgba(255,69,58,0.1); border: 1px solid rgba(255,69,58,0.2);
color: #fca5a5; margin: 0 6px 6px 0;
}
/* CTA */
.report-cta {
text-align: center; margin-top: 40px; padding: 32px;
background: rgba(108,92,231,0.08); border: 1px solid rgba(108,92,231,0.2);
border-radius: 16px;
}
.report-cta h3 { font-size: 18px; margin-bottom: 8px; justify-content: center; }
.report-cta p { font-size: 14px; color: #9ca3af; margin-bottom: 20px; }
.btn-primary {
background: #fff; color: #000; border: none; padding: 12px 28px;
border-radius: 8px; font-weight: 600; font-size: 14px; font-family: inherit;
cursor: pointer; transition: all 0.2s;
}
.btn-primary:hover { background: #e5e7eb; }
.empty-state { text-align: center; padding: 20px; color: #6b7280; font-size: 14px; }
/* Animations */
.fade-up { animation: fadeUp 0.5s ease forwards; }
@keyframes fadeUp { from { opacity: 0; transform: translateY(20px); } to { opacity: 1; transform: translateY(0); } }
/* ---- Blacklight Deep Analysis Styles ---- */
.bl-test {
padding: 14px 18px; border-radius: 10px; margin-bottom: 8px;
border: 1px solid rgba(255,255,255,0.06);
transition: all 0.2s;
}
.bl-test:hover { border-color: rgba(255,255,255,0.12); }
.bl-pass { background: rgba(50,215,75,0.06); border-color: rgba(50,215,75,0.15); }
.bl-fail { background: rgba(255,69,58,0.08); border-color: rgba(255,69,58,0.2); }
.bl-warn { background: rgba(255,214,10,0.06); border-color: rgba(255,214,10,0.15); }
.bl-test-header {
display: flex; justify-content: space-between; align-items: center;
font-size: 14px; font-weight: 500;
}
.bl-badge {
font-size: 11px; font-weight: 600; padding: 3px 10px;
border-radius: 20px; letter-spacing: 0.5px; text-transform: uppercase;
}
.bl-pass .bl-badge { background: rgba(50,215,75,0.15); color: #86efac; }
.bl-fail .bl-badge { background: rgba(255,69,58,0.2); color: #fca5a5; }
.bl-warn .bl-badge { background: rgba(255,214,10,0.15); color: #fde68a; }
.bl-detail {
margin-top: 8px; font-size: 12px; color: #9ca3af;
line-height: 1.6; word-break: break-all;
}
</style>
</head>
<body>
<a href="index.html" class="back-link">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m15 18-6-6 6-6"/></svg>
Back to Home
</a>
<!-- STEP 1: URL Input -->
<div class="scan-step" id="step-input">
<div class="container">
<h1 class="scan-title">Website Privacy Scanner</h1>
<p class="scan-subtitle">
Enter any URL to discover exposed PII, tracking scripts, shadow AI endpoints,
and compliance gaps โ€” powered by our Presidio engine. Real results, not fake.
</p>
<div class="input-wrapper">
<svg style="margin-right:12px;color:#6b7280;flex-shrink:0;" xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><path d="M12 2a14.5 14.5 0 0 0 0 20 14.5 14.5 0 0 0 0-20"/><path d="M2 12h20"/></svg>
<input type="url" id="urlInput" class="input-field" placeholder="https://example.com" autocomplete="off" />
<button class="input-btn" id="btnScan" onclick="startScan()">
Scan Website โ†’
</button>
</div>
<div class="tags-row">
<div class="tag">๐Ÿ”’ Zero data stored</div>
<div class="tag">โšก Takes ~10 seconds</div>
<div class="tag">๐Ÿ” Public pages only</div>
</div>
</div>
</div>
<!-- STEP 2: Scanning -->
<div class="scanning-state hidden" id="step-scanning">
<div class="container">
<div class="spinner"></div>
<h2 style="font-size:20px;font-weight:600;margin-bottom:4px;" id="scan-status-text">Scanning...</h2>
<p style="font-size:14px;color:#9ca3af;" id="scan-url-display"></p>
<div class="scan-log" id="scan-log"></div>
</div>
</div>
<!-- STEP 3: Results -->
<div class="results hidden" id="step-results">
<div class="container">
<div class="results-header fade-up">
<h1>Privacy Scan Report</h1>
<div class="scanned-url" id="result-url"></div>
</div>
<!-- Risk Score -->
<div class="risk-gauge fade-up" id="risk-gauge"></div>
<!-- Risk Factors -->
<div class="risk-factors" id="risk-factors"></div>
<!-- Trackers -->
<div class="report-section fade-up" id="section-trackers"></div>
<!-- PII Collection -->
<div class="report-section fade-up" id="section-pii-collection"></div>
<!-- Exposed PII -->
<div class="report-section fade-up" id="section-exposed-pii"></div>
<!-- AI Endpoints -->
<div class="report-section fade-up" id="section-ai"></div>
<!-- Blacklight Deep Analysis -->
<div class="report-section fade-up" id="section-blacklight"></div>
<!-- SSL/TLS Certificate -->
<div class="report-section fade-up" id="section-ssl"></div>
<!-- Technology Stack -->
<div class="report-section fade-up" id="section-tech"></div>
<!-- Advanced Security -->
<div class="report-section fade-up" id="section-advanced"></div>
<!-- Compliance -->
<div class="report-section fade-up" id="section-compliance"></div>
<!-- DPDP Act 2023 Compliance -->
<div class="report-section fade-up" id="section-dpdp"></div>
<!-- CTA -->
<div class="report-cta fade-up" id="report-actions" style="display:none">
<button onclick="downloadPdfReport()" class="btn-primary" style="background:linear-gradient(135deg,#6366f1,#8b5cf6);border:none;padding:14px 32px;border-radius:12px;color:#fff;font-size:15px;font-weight:600;cursor:pointer;display:inline-flex;align-items:center;gap:8px;transition:all 0.3s ease;">
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><polyline points="7,10 12,15 17,10"/><line x1="12" y1="15" x2="12" y2="3"/></svg>
Download PDF Report
</button>
<a href="dashboard.html" style="margin-left:16px"><button class="btn-secondary" style="background:rgba(255,255,255,0.06);border:1px solid rgba(255,255,255,0.1);padding:14px 28px;border-radius:12px;color:#9ca3af;font-size:14px;cursor:pointer;">Go to Dashboard โ†’</button></a>
</div>
</div>
</div>
<script>
const API_BASE = window.location.origin + '/api/v1';
// Allow Enter key
document.getElementById('urlInput').addEventListener('keydown', function(e) {
if (e.key === 'Enter') { e.preventDefault(); startScan(); }
});
async function startScan() {
const url = document.getElementById('urlInput').value.trim();
if (!url) { document.getElementById('urlInput').focus(); return; }
// Switch to scanning state
document.getElementById('step-input').classList.add('hidden');
document.getElementById('step-scanning').classList.remove('hidden');
document.getElementById('step-results').classList.add('hidden');
document.getElementById('scan-url-display').textContent = url;
// Animate scan log
const log = document.getElementById('scan-log');
log.innerHTML = '';
const steps = [
'Fetching page via dual-engine (requests + Jina Reader)...',
'Parsing HTML structure with BeautifulSoup...',
'Scanning for third-party trackers (40+ signatures)...',
'Detecting hidden tracking pixels...',
'Analyzing data collection forms for PII fields...',
'Searching for AI/LLM endpoints & leaked API keys...',
'[Blacklight] Detecting canvas fingerprinting...',
'[Blacklight] Scanning for key logging scripts...',
'[Blacklight] Checking session recording tools...',
'[Blacklight] Analyzing Facebook Pixel events...',
'[Blacklight] Scanning Google Analytics tracking...',
'[Blacklight] Checking Disconnect.me tracking domains...',
'Running PII detection (Microsoft Presidio NLP)...',
'Checking GDPR/DPDP compliance posture...',
'Calculating risk score...',
];
let stepIdx = 0;
const logInterval = setInterval(() => {
if (stepIdx < steps.length) {
const item = document.createElement('div');
item.className = 'scan-log-item';
item.innerHTML = '<span class="dot"></span>' + steps[stepIdx];
log.appendChild(item);
stepIdx++;
}
}, 800);
// Call the real API
try {
const res = await fetch(API_BASE + '/scan/url', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ url: url })
});
clearInterval(logInterval);
if (!res.ok) {
const err = await res.json().catch(() => ({ detail: 'Unknown error' }));
throw new Error(err.detail || 'Scan failed');
}
const report = await res.json();
// Final log item
const done = document.createElement('div');
done.className = 'scan-log-item';
done.innerHTML = '<span class="dot" style="background:#32d74b"></span>Scan complete! Generating report...';
log.appendChild(done);
// Short delay for UX then show results
await new Promise(r => setTimeout(r, 1000));
renderReport(report);
} catch (err) {
clearInterval(logInterval);
document.getElementById('scan-status-text').textContent = 'Scan Failed';
const errItem = document.createElement('div');
errItem.className = 'scan-log-item';
errItem.innerHTML = '<span class="dot" style="background:#ff453a"></span>' + err.message;
log.appendChild(errItem);
// Add retry button
const retry = document.createElement('button');
retry.className = 'input-btn';
retry.style.cssText = 'margin:24px auto 0;display:block;';
retry.textContent = 'โ† Try Again';
retry.onclick = () => {
document.getElementById('step-scanning').classList.add('hidden');
document.getElementById('step-input').classList.remove('hidden');
};
log.appendChild(retry);
}
}
function renderReport(r) {
document.getElementById('step-scanning').classList.add('hidden');
document.getElementById('step-results').classList.remove('hidden');
// URL
document.getElementById('result-url').textContent = r.url + ' ยท Scanned in ' + (r.scan_time_ms / 1000).toFixed(1) + 's';
// Risk gauge
const rg = document.getElementById('risk-gauge');
rg.innerHTML = `
<div class="risk-circle ${r.risk_level}">
<div class="risk-score">${r.risk_score}</div>
<div class="risk-label">/ 100</div>
</div>
<div class="risk-level-text ${r.risk_level}">${r.risk_level.toUpperCase()} RISK</div>
`;
// Risk factors
const rf = document.getElementById('risk-factors');
if (r.risk_factors.length > 0) {
rf.innerHTML = r.risk_factors.map(f => '<div class="risk-factor">' + f + '</div>').join('');
} else {
rf.innerHTML = '<div class="risk-factor" style="color:#32d74b;">No significant risks detected!</div>';
}
// Trackers
const ts = document.getElementById('section-trackers');
ts.innerHTML = '<h3>๐Ÿ•ต๏ธ Third-Party Trackers <span class="count">' + r.trackers.count + ' found</span></h3>';
if (r.trackers.items.length > 0) {
ts.innerHTML += r.trackers.items.map(t => `
<div class="tracker-item">
<span class="tracker-name">${t.name}</span>
<div class="tracker-meta">
<span class="tracker-badge ${t.category}">${t.category.replace('_', ' ')}</span>
<span class="risk-dot ${t.risk}"></span>
</div>
</div>
`).join('');
} else {
ts.innerHTML += '<div class="empty-state">No third-party trackers detected โœ“</div>';
}
// PII Collection
const pc = document.getElementById('section-pii-collection');
pc.innerHTML = '<h3>๐Ÿ“ Data Collection Forms <span class="count">' + r.pii_collection.pii_input_count + ' PII fields</span></h3>';
if (r.pii_collection.inputs.length > 0) {
pc.innerHTML += '<div style="margin-bottom:12px;font-size:13px;color:#9ca3af;">This site collects the following personal data via forms:</div>';
pc.innerHTML += r.pii_collection.inputs.map(inp =>
'<div class="pii-item">' + inp.type.toUpperCase() + ' (' + (inp.field_name || 'unnamed') + ')</div>'
).join('');
} else {
pc.innerHTML += '<div class="empty-state">No PII-collecting form fields detected โœ“</div>';
}
// Exposed PII
const ep = document.getElementById('section-exposed-pii');
ep.innerHTML = '<h3>๐Ÿšจ Exposed PII in Page Content <span class="count">' + r.exposed_pii.count + ' items</span></h3>';
if (r.exposed_pii.items.length > 0) {
ep.innerHTML += '<div style="margin-bottom:12px;font-size:13px;color:#9ca3af;">Personal data found visible in the page HTML:</div>';
ep.innerHTML += r.exposed_pii.items.map(p =>
'<div class="pii-item">' + p.label + ': "' + p.text + '" <span style="opacity:0.6">(' + (p.score * 100).toFixed(0) + '%)</span></div>'
).join('');
} else {
ep.innerHTML += '<div class="empty-state">No exposed PII found in page content โœ“</div>';
}
// AI Endpoints
const ai = document.getElementById('section-ai');
ai.innerHTML = '<h3>๐Ÿค– AI / LLM Endpoints <span class="count">' + r.ai_endpoints.count + ' found</span></h3>';
if (r.ai_endpoints.items.length > 0) {
ai.innerHTML += r.ai_endpoints.items.map(a => `
<div class="tracker-item">
<span class="tracker-name">${a.pattern}</span>
<div class="tracker-meta">
<span class="tracker-badge ${a.risk === 'critical' ? 'advertising' : 'analytics'}">${a.type.replace('_', ' ')}</span>
<span class="risk-dot ${a.risk === 'critical' ? 'high' : 'medium'}"></span>
</div>
</div>
`).join('');
} else {
ai.innerHTML += '<div class="empty-state">No AI/LLM API endpoints detected โœ“</div>';
}
// ---- BLACKLIGHT DEEP ANALYSIS ----
const bl = document.getElementById('section-blacklight');
if (r.blacklight) {
const b = r.blacklight;
let blHtml = '<h3>๐Ÿ”ฌ Blacklight Deep Analysis</h3>';
blHtml += '<div style="font-size:12px;color:#6b7280;margin-bottom:16px;">Methodology: <a href="https://themarkup.org/blacklight/2020/09/22/how-we-built-a-real-time-privacy-inspector" target="_blank" style="color:#818cf8">The Markup\'s Blacklight</a></div>';
// Canvas Fingerprinting
blHtml += `<div class="bl-test ${b.canvas_fingerprinting.detected ? 'bl-fail' : 'bl-pass'}">
<div class="bl-test-header">
<span>${b.canvas_fingerprinting.detected ? '๐Ÿ”ด' : '๐ŸŸข'} Canvas Fingerprinting</span>
<span class="bl-badge">${b.canvas_fingerprinting.detected ? 'DETECTED' : 'Not Found'}</span>
</div>
${b.canvas_fingerprinting.detected ? '<div class="bl-detail">Signals: ' + b.canvas_fingerprinting.signals.join(', ') + '</div>' : ''}
</div>`;
// Key Logging
blHtml += `<div class="bl-test ${b.key_logging.detected ? 'bl-fail' : 'bl-pass'}">
<div class="bl-test-header">
<span>${b.key_logging.detected ? '๐Ÿ”ด' : '๐ŸŸข'} Key Logging</span>
<span class="bl-badge">${b.key_logging.detected ? 'DETECTED' : 'Not Found'}</span>
</div>
${b.key_logging.detected ? '<div class="bl-detail">Captures keystrokes before form submission. Signals: ' + b.key_logging.signals.slice(0,5).join(', ') + '</div>' : ''}
</div>`;
// Session Recording
blHtml += `<div class="bl-test ${b.session_recording.detected ? 'bl-fail' : 'bl-pass'}">
<div class="bl-test-header">
<span>${b.session_recording.detected ? '๐Ÿ”ด' : '๐ŸŸข'} Session Recording</span>
<span class="bl-badge">${b.session_recording.detected ? 'DETECTED' : 'Not Found'}</span>
</div>
${b.session_recording.detected ? '<div class="bl-detail">Mouse/scroll/click recording active. Signals: ' + b.session_recording.signals.slice(0,5).join(', ') + '</div>' : ''}
</div>`;
// Facebook Pixel
blHtml += `<div class="bl-test ${b.facebook_pixel.detected ? 'bl-fail' : 'bl-pass'}">
<div class="bl-test-header">
<span>${b.facebook_pixel.detected ? '๐ŸŸก' : '๐ŸŸข'} Facebook Pixel Events</span>
<span class="bl-badge">${b.facebook_pixel.detected ? b.facebook_pixel.events.length + ' events' : 'Not Found'}</span>
</div>
${b.facebook_pixel.detected ? '<div class="bl-detail">Events: ' + b.facebook_pixel.events.join(', ') + '</div>' : ''}
</div>`;
// Google Analytics
blHtml += `<div class="bl-test ${b.google_analytics.user_tracking ? 'bl-fail' : b.google_analytics.detected ? 'bl-warn' : 'bl-pass'}">
<div class="bl-test-header">
<span>${b.google_analytics.user_tracking ? '๐Ÿ”ด' : b.google_analytics.detected ? '๐ŸŸก' : '๐ŸŸข'} Google Analytics</span>
<span class="bl-badge">${b.google_analytics.user_tracking ? 'USER TRACKING' : b.google_analytics.detected ? b.google_analytics.events.length + ' events' : 'Not Found'}</span>
</div>
${b.google_analytics.detected ? '<div class="bl-detail">Events: ' + b.google_analytics.events.join(', ') + '</div>' : ''}
</div>`;
// Tracking Domains
blHtml += `<div class="bl-test ${b.tracking_domains.count > 5 ? 'bl-fail' : b.tracking_domains.count > 0 ? 'bl-warn' : 'bl-pass'}">
<div class="bl-test-header">
<span>${b.tracking_domains.count > 5 ? '๐Ÿ”ด' : b.tracking_domains.count > 0 ? '๐ŸŸก' : '๐ŸŸข'} Ad/Tracking Domains (Disconnect.me)</span>
<span class="bl-badge">${b.tracking_domains.count} found</span>
</div>
${b.tracking_domains.count > 0 ? '<div class="bl-detail">' + b.tracking_domains.domains.join(', ') + '</div>' : ''}
</div>`;
bl.innerHTML = blHtml;
}
// SSL/TLS Certificate
const sslEl = document.getElementById('section-ssl');
if (r.ssl?.analyzed) {
const s = r.ssl;
const sslCls = s.rating === 'PASS' ? 'bl-pass' : s.rating === 'WARN' ? 'bl-warn' : 'bl-fail';
sslEl.innerHTML = `<h3>๐Ÿ”’ SSL/TLS Certificate</h3>
<div class="bl-test ${sslCls}">
<div class="bl-test-header">
<span>${s.rating === 'PASS' ? '๐ŸŸข' : s.rating === 'WARN' ? '๐ŸŸก' : '๐Ÿ”ด'} ${s.protocol} โ€” ${s.cipher_name} (${s.cipher_bits}-bit)</span>
<span class="bl-badge">${s.rating}</span>
</div>
<div class="bl-detail">Issuer: ${s.issuer} (${s.issuer_cn})</div>
<div class="bl-detail">Valid: ${new Date(s.valid_from).toLocaleDateString()} โ†’ ${new Date(s.valid_until).toLocaleDateString()} (${s.days_remaining} days remaining)</div>
<div class="bl-detail">Subject: ${s.subject} | SANs: ${s.san_count} domains</div>
</div>`;
}
// Technology Stack
const techEl = document.getElementById('section-tech');
if (r.technology_stack?.length > 0) {
techEl.innerHTML = `<h3>๐Ÿ›  Technology Stack <span class="bl-badge" style="margin-left:8px">${r.technology_stack.length} detected</span></h3>
<div style="display:flex;flex-wrap:wrap;gap:8px;margin-top:10px">
${r.technology_stack.map(t => `<span style="background:rgba(99,102,241,0.15);border:1px solid rgba(99,102,241,0.3);color:#a5b4fc;padding:6px 14px;border-radius:20px;font-size:12px;font-weight:500">${t}</span>`).join('')}
</div>`;
}
// Advanced Security (Info Disclosure + Mixed Content + SRI)
const advEl = document.getElementById('section-advanced');
let advHtml = '<h3>๐Ÿ›ก Advanced Security Analysis</h3>';
let hasAdvanced = false;
// Info Disclosure
if (r.info_disclosure?.length > 0) {
hasAdvanced = true;
r.info_disclosure.forEach(d => {
advHtml += `<div class="bl-test bl-warn">
<div class="bl-test-header">
<span>๐ŸŸก ${d.header}: ${d.value}</span>
<span class="bl-badge">INFO LEAK</span>
</div>
<div class="bl-detail">${d.note}</div>
</div>`;
});
}
// Mixed Content
if (r.mixed_content?.count > 0) {
hasAdvanced = true;
advHtml += `<div class="bl-test bl-fail">
<div class="bl-test-header">
<span>๐Ÿ”ด Mixed Content</span>
<span class="bl-badge">${r.mixed_content.count} HTTP resources</span>
</div>
<div class="bl-detail">HTTP resources loaded on HTTPS page: ${r.mixed_content.items.map(i => i.tag + ': ' + i.url.substring(0,60)).join(', ')}</div>
</div>`;
} else if (r.compliance?.https) {
hasAdvanced = true;
advHtml += `<div class="bl-test bl-pass"><div class="bl-test-header"><span>๐ŸŸข No Mixed Content</span><span class="bl-badge">CLEAN</span></div></div>`;
}
// SRI
if (r.sri) {
hasAdvanced = true;
const sriCls = r.sri.rating === 'PASS' ? 'bl-pass' : r.sri.rating === 'WARN' ? 'bl-warn' : 'bl-fail';
advHtml += `<div class="bl-test ${sriCls}">
<div class="bl-test-header">
<span>${r.sri.rating === 'PASS' ? '๐ŸŸข' : '๐ŸŸก'} Subresource Integrity (SRI)</span>
<span class="bl-badge">${r.sri.with_integrity}/${r.sri.cross_origin_scripts} scripts</span>
</div>
<div class="bl-detail">${r.sri.cross_origin_scripts} cross-origin scripts, ${r.sri.with_integrity} with integrity hash</div>
</div>`;
}
if (hasAdvanced) advEl.innerHTML = advHtml;
// Compliance
const cc = document.getElementById('section-compliance');
const c = r.compliance;
cc.innerHTML = `<h3>๐Ÿ“‹ Compliance & Security Headers <span class="bl-badge" style="margin-left:8px;background:${c.security_header_grade === 'A' ? 'rgba(50,215,75,0.2)' : c.security_header_grade === 'F' ? 'rgba(255,69,58,0.2)' : 'rgba(255,214,10,0.2)'};color:${c.security_header_grade === 'A' ? '#86efac' : c.security_header_grade === 'F' ? '#fca5a5' : '#fde68a'}">Grade ${c.security_header_grade}</span></h3>`;
// Basic compliance checks
const basicChecks = [
['HTTPS Encryption', c.https, c.https ? 'Secure connection' : 'Data transmitted in plain text'],
['Privacy Policy', c.privacy_policy, c.privacy_policy_url ? 'Found: ' + c.privacy_policy_url : (c.privacy_policy ? 'Link detected' : 'No privacy policy link found')],
['Cookie Consent / CMP', c.cookie_consent, c.cmp_platforms?.length ? 'Platforms: ' + c.cmp_platforms.join(', ') : (c.cookie_consent ? 'Consent language detected' : 'No consent mechanism found')],
['Terms of Service', c.terms_of_service, c.terms_of_service ? 'Link detected' : 'No terms link found'],
];
cc.innerHTML += basicChecks.map(([name, pass, note]) => `
<div class="bl-test ${pass ? 'bl-pass' : 'bl-fail'}">
<div class="bl-test-header">
<span>${pass ? '๐ŸŸข' : '๐Ÿ”ด'} ${name}</span>
<span class="bl-badge">${pass ? 'PASS' : 'FAIL'}</span>
</div>
<div class="bl-detail">${note}</div>
</div>
`).join('');
// Security headers (detailed Observatory-style)
cc.innerHTML += '<div style="margin-top:16px;font-size:13px;font-weight:600;color:#9ca3af;margin-bottom:8px">HTTP Security Headers</div>';
const headerNames = {
'content-security-policy': 'Content-Security-Policy',
'x-frame-options': 'X-Frame-Options',
'strict-transport-security': 'Strict-Transport-Security (HSTS)',
'x-content-type-options': 'X-Content-Type-Options',
'referrer-policy': 'Referrer-Policy',
'permissions-policy': 'Permissions-Policy',
};
for (const [key, label] of Object.entries(headerNames)) {
const h = c.security_headers?.[key];
if (!h) continue;
const cls = h.rating === 'pass' ? 'bl-pass' : h.rating === 'warn' ? 'bl-warn' : h.rating === 'info' ? 'bl-warn' : 'bl-fail';
const badge = h.present ? (h.rating === 'pass' ? 'SET' : h.rating === 'warn' ? 'WEAK' : 'SET') : 'MISSING';
cc.innerHTML += `<div class="bl-test ${cls}">
<div class="bl-test-header">
<span>${h.rating === 'pass' ? '๐ŸŸข' : h.rating === 'warn' ? '๐ŸŸก' : h.present ? '๐ŸŸก' : '๐Ÿ”ด'} ${label}</span>
<span class="bl-badge">${badge}</span>
</div>
<div class="bl-detail">${h.note || ''}</div>
</div>`;
}
// DPDP Act 2023 Compliance
const dp = document.getElementById('section-dpdp');
if (r.dpdp) {
const d = r.dpdp;
let dpHtml = `<h3>๐Ÿ‡ฎ๐Ÿ‡ณ DPDP Act 2023 Compliance <span class="bl-badge" style="margin-left:8px;background:${d.grade === 'A' ? 'rgba(50,215,75,0.2)' : d.grade === 'F' ? 'rgba(255,69,58,0.2)' : 'rgba(255,214,10,0.2)'};color:${d.grade === 'A' ? '#86efac' : d.grade === 'F' ? '#fca5a5' : '#fde68a'}">Grade ${d.grade} (${d.score}/${d.total_checks})</span></h3>`;
dpHtml += '<div style="font-size:12px;color:#6b7280;margin-bottom:14px;">Digital Personal Data Protection Act, 2023 โ€” India</div>';
const labelMap = {
consent_mechanism: 'Consent Mechanism',
privacy_notice: 'Privacy Notice',
grievance_officer: 'Grievance Officer / DPO',
data_retention_policy: 'Data Retention Policy',
children_protection: "Children's Data Protection",
consent_withdrawal: 'Consent Withdrawal',
breach_notification: 'Breach Notification',
};
for (const [key, val] of Object.entries(d.checks)) {
dpHtml += `<div class="bl-test ${val.passed ? 'bl-pass' : 'bl-fail'}">
<div class="bl-test-header">
<span>${val.passed ? '๐ŸŸข' : '๐Ÿ”ด'} ${labelMap[key] || key}</span>
<span class="bl-badge">${val.passed ? 'COMPLIANT' : 'MISSING'}</span>
</div>
<div class="bl-detail">${val.section}</div>
</div>`;
}
dp.innerHTML = dpHtml;
}
// Show download button
document.getElementById('report-actions').style.display = 'flex';
document.getElementById('report-actions').style.justifyContent = 'center';
document.getElementById('report-actions').style.alignItems = 'center';
document.getElementById('report-actions').style.gap = '12px';
document.getElementById('report-actions').style.marginTop = '24px';
// Store for PDF
window.__scanReport = r;
// Scroll to top
window.scrollTo({ top: 0, behavior: 'smooth' });
}
// Auto-start if URL is passed via query param (from index.html redirect)
(function() {
const params = new URLSearchParams(window.location.search);
const autoUrl = params.get('url');
if (autoUrl) {
document.getElementById('urlInput').value = autoUrl;
setTimeout(() => startScan(), 300);
}
})();
</script>
</body>
</html>