// ==UserScript==
// @name ShadowSec Panel v13
// @namespace http://tampermonkey.net/
// @version 13.0.1
// @description Shadow DOM UI with advanced OWASP-aligned checks: v10.3 UI + v5 depth + intrusive probes (SQLi/IDOR/SSRF/Rate-limit) and heuristics (ports/cache/fingerprinting). Live summary, filters, search, export, copy, and a Settings page for wordlists and options.
// @author AbyssBite
// @license MIT
// @match *://*/*
// @grant GM_addStyle
// @grant GM_download
// @grant GM_setValue
// @grant GM_getValue
// @grant GM_notification
// ==/UserScript==
(function () {
'use strict';
if (window.top !== window.self) return;
if (window.__SEC_TOOL_LOADED) return;
window.__SEC_TOOL_LOADED = true;
// ------------------ Persistent Settings ------------------
const getVal = (k, d) => {
try { return GM_getValue(k, d); } catch { return d; }
};
const setVal = (k, v) => {
try { GM_setValue(k, v); } catch {}
};
const settings = {
wordlistUrl: getVal('sec_wordlist_url', ''), // external wordlist raw URL (optional)
dirProbeLimit: getVal('sec_dir_limit', 200), // max paths to probe in Extended Directory Probing
execXss: getVal('sec_exec_xss', false), // execute XSS payloads (default false)
extraPorts: getVal('sec_extra_ports', ''), // e.g., "9090, 9200"
graphqlPaths: getVal('sec_graphql_paths', ''), // e.g., "/graphql,/api/graphql"
rateBurst: getVal('sec_rate_burst', 8) // burst requests for Rate Limiting Test
};
// ------------------ Config (some overridable via settings) ------------------
let EXECUTE_XSS_PAYLOADS = !!settings.execXss;
const DIRECTORY_PROBE_PATHS_BASE = [
"/robots.txt","/sitemap.xml","/admin/","/backup/","/.git/","/.env","/phpinfo.php",
"/.htaccess","/config.php","/config/","/wp-admin/","/wp-login.php","/vendor/","/logs/","/old/","/test/","/staging/","/dev/",
"/_next/","/graphql","/api","/api/graphql","/server-status"
];
const OUTDATED_LIBS = [
{ name: "jQuery", regex: /jquery(?:\.min)?-([0-9.]+)\.js/i, min: "3.5.0" },
];
const OPEN_REDIRECT_KEYS = ["url","next","redirect","return","rurl","dest","goto","redir","u","continue"];
const SQLI_PAYLOADS = [
"' OR 1=1 --", "' OR '1'='1", "'; WAITFOR DELAY '0:0:3' --", "') OR ('1'='1", "SLEEP(3))--", "' OR 1=1#"
];
const SQL_ERROR_PATTERNS = [
/you have an error in your sql syntax/i,
/unclosed quotation mark/i,
/warning: mysql/i, /mysqli?/i,
/psql:.+ERROR/i, /postgresql/i,
/sql server/i, /odbc/i,
/ORA-\d+/i, /SQLite\/JDBCDriver/i
];
const SSRF_PARAM_HINTS = /(url|uri|target|dest|image|feed|proxy|callback|return|endpoint)/i;
let GRAPHQL_PATHS = ["/graphql", "/api/graphql"];
if (settings.graphqlPaths && typeof settings.graphqlPaths === 'string') {
const extra = settings.graphqlPaths.split(',').map(s=>s.trim()).filter(Boolean);
GRAPHQL_PATHS = [...new Set([...GRAPHQL_PATHS, ...extra])];
}
const PORTS_WEB_HTTP = [80, 8080, 8000, 3000];
const PORTS_WEB_HTTPS = [443, 8443];
const PORTS_MISC = [9200, 27017, 5432, 3306, 6379, 15672, 5000];
const SETTINGS_EXTRA_PORTS = (settings.extraPorts || '')
.split(',').map(s=>parseInt(s.trim(),10))
.filter(n=>Number.isInteger(n) && n>0);
const EXTRA_PORTS = [...new Set(SETTINGS_EXTRA_PORTS)];
const REFLECTION_PARAM_LIMIT = 10;
// ------------------ Shadow DOM UI ------------------
const wrapper = document.createElement('div');
wrapper.id = 'sec_wrapper';
document.body.appendChild(wrapper);
const shadow = wrapper.attachShadow({ mode: 'open' });
const panel = document.createElement('div');
panel.id = 'sec_panel';
shadow.appendChild(panel);
panel.innerHTML = `
<div id="sec_header">
<span>🔐 ShadowSec Panel v13</span>
<div>
<button id="sec_settings" title="Settings">⚙️</button>
<button id="sec_minimize" title="Minimize">−</button>
<button id="sec_close" title="Close">×</button>
</div>
</div>
<div id="sec_controls">
<button id="sec_clear" title="Clear logs">Clear</button>
<button id="sec_run_all" title="Run all tests">Run All</button>
<button id="sec_export" title="Export report (JSON)">Export</button>
<button id="sec_copy" title="Copy report (JSON)">Copy</button>
<label><input type="checkbox" id="sec_intrusive"> Intrusive</label>
<label>Filter:
<select id="sec_filter">
<option>All</option><option>High</option><option>Medium</option><option>Low</option>
</select>
</label>
<input type="text" id="sec_search" placeholder="Search logs...">
<button id="sec_theme" title="Toggle Dark/Light">☀️</button>
</div>
<div id="sec_summary">
<div class="chip high">High: <span id="sum_high">0</span></div>
<div class="chip medium">Medium: <span id="sum_medium">0</span></div>
<div class="chip low">Low: <span id="sum_low">0</span></div>
</div>
<div id="sec_buttons"></div>
<div id="sec_output"></div>
<div id="sec_loading" style="display:none;">⏳ Running tests...</div>
<!-- Settings Modal -->
<div id="sec_modal_backdrop" hidden>
<div id="sec_modal">
<div id="sec_modal_header">
<span>Settings</span>
<button id="sec_modal_close" title="Close">×</button>
</div>
<div id="sec_modal_body">
<label class="field">
<span>External wordlist URL (raw):</span>
<input type="url" id="inp_wordlist_url" placeholder="https://raw.githubusercontent.com/.../common.txt">
</label>
<label class="field">
<span>Directory probe limit:</span>
<input type="number" id="inp_dir_limit" min="10" max="2000" step="10">
</label>
<label class="field">
<span>Execute XSS payloads (dangerous):</span>
<input type="checkbox" id="inp_exec_xss">
</label>
<label class="field">
<span>Extra probe ports (comma-separated):</span>
<input type="text" id="inp_extra_ports" placeholder="9090, 9200, 5001">
</label>
<label class="field">
<span>GraphQL paths (comma-separated):</span>
<input type="text" id="inp_graphql_paths" placeholder="/graphql, /api/graphql, /gql">
</label>
<label class="field">
<span>Rate-limit burst size:</span>
<input type="number" id="inp_rate_burst" min="1" max="50" step="1">
</label>
</div>
<div id="sec_modal_actions">
<button id="sec_modal_save">Save</button>
<button id="sec_modal_cancel">Cancel</button>
</div>
</div>
</div>
`;
const style = document.createElement('style');
style.textContent = `
:host { all: initial; font-family: Inter, Segoe UI, system-ui, sans-serif !important; }
#sec_panel { position:fixed; bottom:16px; right:16px; width:580px; height:600px;
display:flex; flex-direction:column; background:#181818; color:#eee;
border-radius:10px; box-shadow:0 6px 20px rgba(0,0,0,0.55); z-index:2147483647;
overflow:hidden; transition:all .3s ease; font-size:13px; }
#sec_panel.light { background:#fdfdfd; color:#222; }
#sec_header { display:flex; justify-content:space-between; align-items:center;
background:#202020; padding:8px 12px; font-weight:600; cursor:move; user-select:none; }
#sec_panel.light #sec_header { background:#e6e6e6; }
#sec_header button { all: unset; cursor:pointer; margin-left:6px; padding:2px 6px;
border-radius:4px; background:#444; color:#fff; font-weight:bold; }
#sec_header button:hover { background:#666; }
#sec_panel.light #sec_header button { background:#ccc; color:#222; }
#sec_controls { display:flex; flex-wrap:wrap; gap:6px; padding:8px;
background:#252525; align-items:center; }
#sec_panel.light #sec_controls { background:#f1f1f1; }
#sec_controls button, #sec_buttons button, #sec_controls select {
all: unset; cursor:pointer; padding:6px 10px; border-radius:6px;
background:linear-gradient(180deg,#444,#333); color:#fff; font-size:13px; text-align:center; }
#sec_controls button:hover, #sec_buttons button:hover { background:#666; }
#sec_panel.light #sec_controls button, #sec_panel.light #sec_buttons button {
background:linear-gradient(180deg,#ccc,#bbb); color:#222; }
#sec_search { all: unset; flex:1; padding:6px; border:1px solid #444;
border-radius:6px; background:#2a2a2a; color:#eee; font-size:13px; }
#sec_panel.light #sec_search { background:#fff; color:#222; border:1px solid #ccc; }
#sec_summary { display:flex; gap:8px; padding:8px; background:#1d1d1d; }
#sec_panel.light #sec_summary { background:#ececec; }
.chip { padding:4px 8px; border-radius:999px; font-weight:600; }
.chip.high { background:#3b1212; color:#ff6969; }
.chip.medium { background:#3b2a12; color:#ffc56a; }
.chip.low { background:#123b18; color:#75d98a; }
#sec_buttons { display:grid; grid-template-columns:repeat(auto-fill,minmax(170px,1fr));
gap:6px; padding:8px; max-height:160px; overflow-y:auto; }
#sec_output { flex:1; background:#101010; padding:8px; overflow-y:auto; }
#sec_panel.light #sec_output { background:#fff; }
#sec_output details { margin:4px 0; border:1px solid #333; border-radius:6px; overflow:hidden; }
#sec_panel.light #sec_output details { border:1px solid #ccc; }
#sec_output summary { background:#1f1f1f; padding:4px 8px; cursor:pointer; font-weight:500; }
#sec_panel.light #sec_output summary { background:#eaeaea; }
.log-entry { padding:3px 8px; border-top:1px solid rgba(255,255,255,0.05); }
.high { color:#ff4c4c; }
.medium { color:#ffb347; }
.low { color:#4caf50; }
#sec_loading { position:absolute; top:50%; left:50%; transform:translate(-50%,-50%);
background:rgba(0,0,0,0.75); padding:10px 18px; border-radius:6px; font-size:14px; color:#fff; }
.minimized { height:34px !important; overflow:hidden; }
/* Settings modal */
#sec_modal_backdrop[hidden] { display:none; }
#sec_modal_backdrop { position: fixed; inset: 0; background: rgba(0,0,0,0.5);
display:flex; align-items:center; justify-content:center; z-index:2147483648; }
#sec_modal { width: 520px; background: #222; color:#eee; border-radius: 10px; box-shadow: 0 10px 40px rgba(0,0,0,0.6); overflow:hidden; }
#sec_panel.light #sec_modal { background: #fff; color:#222; }
#sec_modal_header { display:flex; justify-content:space-between; align-items:center; padding:10px 12px; background:#333; font-weight:700; }
#sec_panel.light #sec_modal_header { background:#eee; }
#sec_modal_header button { all:unset; cursor:pointer; padding:4px 8px; background:#444; color:#fff; border-radius:6px; }
#sec_panel.light #sec_modal_header button { background:#ccc; color:#222; }
#sec_modal_body { padding:12px; display:flex; flex-direction:column; gap:10px; max-height: 50vh; overflow:auto; }
.field { display:grid; grid-template-columns: 200px 1fr; gap:8px; align-items:center; }
.field input[type="text"], .field input[type="url"], .field input[type="number"] {
all:unset; border:1px solid #444; border-radius:6px; padding:8px; background:#2a2a2a; color:#eee; }
#sec_panel.light .field input[type="text"], #sec_panel.light .field input[type="url"], #sec_panel.light .field input[type="number"] {
background:#fff; color:#222; border:1px solid #ccc; }
.field input[type="checkbox"] { transform: scale(1.2); }
#sec_modal_actions { display:flex; gap:8px; justify-content:flex-end; padding:10px 12px; background:#2b2b2b; }
#sec_panel.light #sec_modal_actions { background:#f2f2f2; }
#sec_modal_actions button { all:unset; cursor:pointer; padding:8px 12px; border-radius:6px; background:#444; color:#fff; font-weight:600; }
#sec_panel.light #sec_modal_actions button { background:#ccc; color:#222; }
#sec_modal_actions button:hover { filter:brightness(1.1); }
`;
shadow.appendChild(style);
// ------------------ Elements & State ------------------
const outputDiv = panel.querySelector('#sec_output');
const buttonsDiv = panel.querySelector('#sec_buttons');
const loadingDiv = panel.querySelector('#sec_loading');
const filterSelect = panel.querySelector('#sec_filter');
const intrusiveCheckbox = panel.querySelector('#sec_intrusive');
const searchInput = panel.querySelector('#sec_search');
const themeButton = panel.querySelector('#sec_theme');
const settingsBtn = panel.querySelector('#sec_settings');
const sumHighEl = panel.querySelector('#sum_high');
const sumMedEl = panel.querySelector('#sum_medium');
const sumLowEl = panel.querySelector('#sum_low');
const modalBackdrop = panel.querySelector('#sec_modal_backdrop');
const modalClose = panel.querySelector('#sec_modal_close');
const modalSave = panel.querySelector('#sec_modal_save');
const modalCancel = panel.querySelector('#sec_modal_cancel');
const inpWordlist = panel.querySelector('#inp_wordlist_url');
const inpDirLimit = panel.querySelector('#inp_dir_limit');
const inpExecXss = panel.querySelector('#inp_exec_xss');
const inpExtraPorts = panel.querySelector('#inp_extra_ports');
const inpGraphqlPaths = panel.querySelector('#inp_graphql_paths');
const inpRateBurst = panel.querySelector('#inp_rate_burst');
let findings = [];
let isLightMode = false;
// ------------------ Utils ------------------
const now = () => new Date().toLocaleTimeString();
const sanitize = (s) => (s ?? "").toString().replace(/[<>]/g, (c) => ({'<':'<','>':'>'}[c]));
const debounce = (fn, d=100) => { let t; return (...a)=>{ clearTimeout(t); t=setTimeout(()=>fn(...a), d); } };
const sameOrigin = (u) => { try { const x = new URL(u, location.href); return x.origin === location.origin; } catch { return false; } };
const base64urlDecode = (b64u) => {
try {
const pad = '='.repeat((4 - (b64u.length % 4)) % 4);
const s = (b64u + pad).replace(/-/g,'+').replace(/_/g,'/');
return JSON.parse(decodeURIComponent(escape(atob(s))));
} catch { return null; }
};
const log = (msg, severity="Low", testName="General") => {
findings.push({ msg, severity, testName, timestamp: now() });
renderLogs();
};
const toggleLoading = (s) => loadingDiv.style.display = s ? "block" : "none";
const updateSummary = () => {
const stats = { High:0, Medium:0, Low:0 };
findings.forEach(f => { if (stats[f.severity] != null) stats[f.severity]++; });
sumHighEl.textContent = stats.High; sumMedEl.textContent = stats.Medium; sumLowEl.textContent = stats.Low;
};
const renderLogs = debounce(() => {
const filter = filterSelect.value;
const needle = (searchInput.value || "").toLowerCase();
outputDiv.innerHTML = "";
const grouped = {};
findings
.filter(f => (filter === "All" || f.severity === filter) && f.msg.toLowerCase().includes(needle))
.forEach(f => { grouped[f.testName] = grouped[f.testName] || []; grouped[f.testName].push(f); });
for (const [test, logs] of Object.entries(grouped)) {
const details = document.createElement('details'); details.open = true;
const summary = document.createElement('summary'); summary.textContent = `${test} (${logs.length})`;
details.appendChild(summary);
logs.forEach(f => {
const div = document.createElement('div');
div.className = `log-entry ${f.severity.toLowerCase()}`;
div.textContent = `[${f.timestamp}] [${f.severity}] ${sanitize(f.msg)}`;
details.appendChild(div);
});
outputDiv.appendChild(details);
}
updateSummary();
});
// ------------------ Tests (merged baseline + advanced) ------------------
const tests = {
"HTTPS & Mixed Content": async () => {
const T = "HTTPS";
if (location.protocol !== "https:") {
log("Page not served over HTTPS", "High", T);
} else {
let mixed = false;
document.querySelectorAll("img,script,link,iframe,source,object,embed,video,audio").forEach(el => {
const src = el.src || el.href || el.data;
if (src && src.startsWith("http:")) { log(`Mixed content: ${src}`, "High", T); mixed = true; }
});
if (!mixed) log("No mixed content detected", "Low", T);
}
},
"Cookies (Heuristics)": () => {
const T = "Cookies";
const cookies = document.cookie ? document.cookie.split(";").map(s => s.trim()) : [];
if (!cookies.length) { log("No cookies visible via document.cookie", "Low", T); return; }
cookies.forEach(c => {
const [name, ...rest] = c.split('=');
const value = rest.join('=');
log(`Cookie readable by JS (not HttpOnly): ${name}=${(value||'').slice(0,40)}${(value||'').length>40?'...':''}`, "Medium", T);
if (name.startsWith("__Host-")) log(`Cookie ${name} uses __Host- prefix (requires Secure, path=/, no Domain)`, "Low", T);
else if (name.startsWith("__Secure-")) log(`Cookie ${name} uses __Secure- prefix (should be Secure over HTTPS)`, "Low", T);
});
log("Note: Secure/SameSite/HttpOnly flags are not exposed to JS; above are heuristics only.", "Low", T);
},
"Forms & CSRF": () => {
const T = "Forms";
const forms = Array.from(document.forms);
if (!forms.length) { log("No forms found", "Low", T); return; }
forms.forEach((form, i) => {
const idx = `Form #${i+1}`;
const hasCSRF = Array.from(form.elements).some(e => /csrf|_token/i.test(e.name));
if (!hasCSRF) log(`${idx} lacks CSRF token`, "High", T);
const action = form.getAttribute('action') || "(same-page)";
if (/^http:\/\//i.test(action)) log(`${idx} action over HTTP: ${action}`, "High", T);
try {
const url = action === "(same-page)" ? location : new URL(action, location.href);
if (url.origin !== location.origin) log(`${idx} posts cross-origin to ${url.origin}`, "Medium", T);
} catch {}
Array.from(form.elements).forEach(el => {
if (el.type === 'password') {
const ac = (el.getAttribute('autocomplete') || form.getAttribute('autocomplete') || "").toLowerCase();
if (ac !== "off" && ac !== "new-password") {
log(`${idx} password input without autocomplete="off/new-password"`, "Medium", T);
}
}
if (el.type === 'file' && !el.getAttribute('accept')) {
log(`${idx} file input lacks accept attribute`, "Medium", T);
}
});
});
},
"Inline Event Handlers": () => {
const T = "DOM";
let found = false;
document.querySelectorAll("*").forEach(el => {
for (const a of el.attributes) {
if (a.name.startsWith("on")) { log(`Inline handler ${a.name} on <${el.tagName.toLowerCase()}>`, "Medium", T); found = true; }
}
});
if (!found) log("No inline handlers found", "Low", T);
},
"Security Headers (deep)": async () => {
const T = "Headers";
try {
const res = await fetch(location.href, { method: "HEAD", credentials: "same-origin" });
const h = (k) => res.headers.get(k);
const lower = {}; for (const [k,v] of res.headers.entries()) lower[k.toLowerCase()] = v;
const csp=h("content-security-policy"), xfo=h("x-frame-options"), hsts=h("strict-transport-security");
const xcto=h("x-content-type-options"), refp=h("referrer-policy"), pp=h("permissions-policy");
const coop=h("cross-origin-opener-policy"), coep=h("cross-origin-embedder-policy"), corp=h("cross-origin-resource-policy");
const cache=h("cache-control");
["content-security-policy","x-frame-options","referrer-policy","x-content-type-options"].forEach(n => { if (!lower[n]) log(`Missing header: ${n}`, "High", T); });
if (csp) {
if (!/default-src/i.test(csp)) log("CSP lacks default-src directive", "Medium", T);
if (/unsafe-inline/i.test(csp)) log("CSP allows 'unsafe-inline'", "High", T);
if (/unsafe-eval/i.test(csp)) log("CSP allows 'unsafe-eval'", "High", T);
if (!/object-src\s+('none'|none)/i.test(csp)) log("CSP should set object-src 'none'", "Medium", T);
if (!xfo && !/frame-ancestors/i.test(csp)) log("No X-Frame-Options and CSP lacks frame-ancestors", "High", T);
}
if (xcto && xcto.toLowerCase() !== 'nosniff') log("X-Content-Type-Options should be 'nosniff'", "Medium", T);
if (!refp) log("Missing Referrer-Policy", "Medium", T);
else if (/unsafe-url/i.test(refp)) log("Referrer-Policy 'unsafe-url' is weak", "Medium", T);
if (location.protocol === "https:" && !hsts) log("Missing Strict-Transport-Security on HTTPS", "Medium", T);
if (!pp) log("Missing Permissions-Policy", "Medium", T);
if (!coop) log("Missing Cross-Origin-Opener-Policy", "Medium", T);
if (!coep) log("Missing Cross-Origin-Embedder-Policy", "Medium", T);
if (!corp) log("Missing Cross-Origin-Resource-Policy", "Medium", T);
if (cache && !/no-store|no-cache|must-revalidate/i.test(cache)) { log(`Cache-Control may allow caching: ${cache}`, "Medium", T); }
} catch (e) {
log("Could not read headers (CORS or opaque). Falling back to meta tags.", "Low", T);
const metaCSP = document.querySelector('meta[http-equiv="Content-Security-Policy"]')?.content;
if (!metaCSP) log("No CSP meta found", "High", T);
else {
if (/unsafe-inline/i.test(metaCSP)) log("CSP meta allows 'unsafe-inline'", "High", T);
if (/unsafe-eval/i.test(metaCSP)) log("CSP meta allows 'unsafe-eval'", "High", T);
if (!/default-src/i.test(metaCSP)) log("CSP meta lacks default-src", "Medium", T);
}
}
},
"OWASP Headers Compliance": async () => {
const T = "OWASP Headers";
try {
const res = await fetch(location.href, { method: "HEAD", credentials: "same-origin" });
const h = k => (res.headers.get(k) || "");
const csp=h("content-security-policy"), xfo=h("x-frame-options"), hsts=h("strict-transport-security");
const xcto=h("x-content-type-options"), refp=h("referrer-policy"), pp=h("permissions-policy");
const coop=h("cross-origin-opener-policy"), coep=h("cross-origin-embedder-policy"), corp=h("cross-origin-resource-policy");
if (!csp) log("Missing CSP", "High", T);
if (!xfo && !(csp && /frame-ancestors/i.test(csp))) log("Missing X-Frame-Options and CSP frame-ancestors", "High", T);
if (!xcto || xcto.toLowerCase() !== "nosniff") log("X-Content-Type-Options should be 'nosniff'", "Medium", T);
if (!refp) log("Missing Referrer-Policy", "Medium", T);
if (location.protocol === "https:") {
if (!hsts) log("Missing HSTS on HTTPS", "High", T);
else {
const m = hsts.match(/max-age=(\d+)/i);
const age = m ? parseInt(m[1], 10) : 0;
if (age < 31536000) log(`HSTS max-age too low (${age})`, "Medium", T);
if (!/includeSubDomains/i.test(hsts)) log("HSTS missing includeSubDomains", "Low", T);
}
}
if (!pp) log("Missing Permissions-Policy", "Medium", T);
if (!coop) log("Missing COOP", "Medium", T);
if (!coep) log("Missing COEP", "Medium", T);
if (!corp) log("Missing CORP", "Medium", T);
if (csp) {
if (/unsafe-inline/i.test(csp)) log("CSP allows 'unsafe-inline'", "High", T);
if (/unsafe-eval/i.test(csp)) log("CSP allows 'unsafe-eval'", "High", T);
if (!/default-src/i.test(csp)) log("CSP lacks default-src", "Medium", T);
if (!/object-src\s+('none'|none)/i.test(csp)) log("CSP should set object-src 'none'", "Medium", T);
}
log("OWASP header audit complete", "Low", T);
} catch {
log("Header audit failed (CORS/opaque). Consider server-side check.", "Low", T);
}
},
"CORS Policy": async () => {
const T = "CORS";
try {
const res = await fetch(location.origin + "/", {
method: "GET",
headers: { "Origin": "https://evil.example" },
credentials: "omit",
cache: "no-store"
});
const acao = res.headers.get("access-control-allow-origin");
const acac = res.headers.get("access-control-allow-credentials");
if (acao === "*") log("Wildcard ACAO '*'", "Medium", T);
else if (acao) log(`ACAO: ${acao}`, "Low", T);
else log("No CORS headers on GET", "Low", T);
if (acac === "true" && acao === "*") log("ACAC true with ACAO '*' is invalid/insecure", "High", T);
} catch {
log("CORS test failed (blocked or network error)", "Low", T);
}
},
"Open Redirects": () => {
const T = "Redirects";
const params = new URLSearchParams(location.search);
let cnt = 0;
for (const key of OPEN_REDIRECT_KEYS) {
if (params.has(key)) {
const v = params.get(key) || "";
cnt++;
if (/^https?:\/\//i.test(v) || /^\/\//.test(v) || v.includes("..")) {
log(`Potential open redirect param: ${key}=${v}`, "High", T);
} else {
log(`Redirect-like param present: ${key}=${v}`, "Medium", T);
}
}
if (cnt >= REFLECTION_PARAM_LIMIT) break;
}
if (!cnt) log("No typical redirect parameters found", "Low", T);
},
"Exposed Storage": () => {
const T = "Storage";
const patterns = [/token/i, /key/i, /pass/i, /secret/i, /credential/i, /email/i];
[["localStorage", localStorage], ["sessionStorage", sessionStorage]].forEach(([name, store]) => {
if (!store || Object.keys(store).length === 0) { log(`${name}: empty`, "Low", T); return; }
for (const k in store) {
const v = String(store[k] ?? "").slice(0, 80);
const flag = patterns.some(p => p.test(k) || p.test(v)) ? "High" : "Medium";
log(`${name}: ${k}=${v}${(store[k] && store[k].length>80)?'...':''}`, flag, T);
}
});
},
"Clickjacking": () => {
const T = "Clickjacking";
if (window.top !== window.self) log("Page is inside an iframe", "High", T);
const iframes = document.querySelectorAll('iframe');
iframes.forEach((iframe, i) => {
if (!iframe.hasAttribute('sandbox')) log(`Iframe #${i+1} lacks sandbox`, "Medium", T);
else log(`Iframe #${i+1} sandbox="${iframe.getAttribute('sandbox')}"`, "Low", T);
const allow = iframe.getAttribute('allow');
if (allow && /camera|microphone|geolocation|payment|usb/i.test(allow)) {
log(`Iframe #${i+1} allow contains powerful features: ${allow}`, "Medium", T);
}
});
if (!iframes.length) log("No iframes found", "Low", T);
},
"WebSocket Security": () => {
const T = "WebSockets";
const WS = window.WebSocket;
window.WebSocket = function (url, ...rest) {
try {
const u = new URL(url, location.href);
if (u.protocol === "ws:") log(`Insecure WebSocket: ${url}`, "High", T);
else log(`WebSocket: ${url}`, "Low", T);
} catch { log(`Invalid WebSocket URL: ${url}`, "Medium", T); }
return new WS(url, ...rest);
};
setTimeout(() => { window.WebSocket = WS; log("WebSocket hook complete", "Low", T); }, 1200);
},
"Service Workers": async () => {
const T = "SW";
if (!("serviceWorker" in navigator)) { log("Service Workers not supported", "Low", T); return; }
const regs = await navigator.serviceWorker.getRegistrations();
if (!regs.length) { log("No Service Workers registered", "Low", T); return; }
regs.forEach((r, i) => {
const scope = r.scope;
const url = r.active?.scriptURL || r.installing?.scriptURL || r.waiting?.scriptURL || "(unknown)";
log(`SW #${i+1}: scope=${scope}, script=${url}`, "Medium", T);
});
},
"Third-Party Scripts": () => {
const T = "Scripts";
let found = false;
document.querySelectorAll("script[src]").forEach(s => {
try {
const u = new URL(s.src, location.href);
if (u.hostname !== location.hostname) {
found = true;
const hasSRI = !!s.integrity;
log(`3rd-party script: ${s.src} ${hasSRI?'(with SRI)':'(no SRI)'}`, hasSRI ? "Low" : "Medium", T);
}
} catch {}
});
if (!found) log("No 3rd-party scripts", "Low", T);
},
"DOM-based XSS (reflections)": () => {
const T = "DOM-XSS";
const params = new URLSearchParams(location.search);
const docHTML = document.documentElement.innerHTML;
let hits = 0;
params.forEach((v, k) => {
if (!v) return;
if (docHTML.includes(v)) { log(`Param reflected in DOM: ${k}=${v.slice(0,80)}`, "High", T); hits++; }
});
if (!hits) log("No obvious param reflections in DOM", "Low", T);
},
"CSP Effectiveness": () => {
const T = "CSP";
const csp = document.querySelector('meta[http-equiv="Content-Security-Policy"]')?.content;
if (!csp) { log("No CSP meta tag found (header may still exist)", "Medium", T); return; }
log(`CSP meta: ${csp.slice(0,180)}${csp.length>180?'...':''}`, "Low", T);
if (/unsafe-inline/i.test(csp)) log("CSP allows 'unsafe-inline'", "High", T);
if (/unsafe-eval/i.test(csp)) log("CSP allows 'unsafe-eval'", "High", T);
if (!/default-src/i.test(csp)) log("CSP lacks default-src", "Medium", T);
if (!/object-src\s+('none'|none)/i.test(csp)) log("CSP should set object-src 'none'", "Medium", T);
},
"Subresource Integrity": () => {
const T = "SRI";
let flagged = 0;
document.querySelectorAll("script[src], link[rel=stylesheet][href]").forEach(el => {
const url = el.src || el.href;
if (!url) return;
const xorigin = !sameOrigin(url);
if (xorigin && !el.integrity) { log(`Cross-origin resource without SRI: ${url}`, "Medium", T); flagged++; }
else if (el.integrity) { log(`SRI present: ${url}`, "Low", T); }
});
if (!flagged) log("No cross-origin resources missing SRI", "Low", T);
},
"Privacy APIs": () => {
const T = "Privacy";
const canvas = document.createElement("canvas");
if (canvas.getContext("2d")) log("Canvas API available (fingerprinting vector)", "Medium", T);
try {
const gl = canvas.getContext("webgl") || canvas.getContext("experimental-webgl");
if (gl) log("WebGL available (fingerprinting vector)", "Medium", T);
} catch {}
if (navigator.getBattery) log("Battery Status API available (fingerprinting vector)", "Low", T);
if ("RTCPeerConnection" in window) log("WebRTC available (IP leak vector)", "Medium", T);
if ("geolocation" in navigator) log("Geolocation API available", "Low", T);
},
"JavaScript Eval": () => {
const T = "JS";
const origEval = window.eval;
const origSetTimeout = window.setTimeout;
const origSetInterval = window.setInterval;
window.eval = (...args) => { log("eval() used", "High", T); return origEval(...args); };
window.setTimeout = (...args) => {
if (typeof args[0] === "string") log("setTimeout called with string", "High", T);
return origSetTimeout(...args);
};
window.setInterval = (...args) => {
if (typeof args[0] === "string") log("setInterval called with string", "High", T);
return origSetInterval(...args);
};
setTimeout(() => {
window.eval = origEval;
window.setTimeout = origSetTimeout;
window.setInterval = origSetInterval;
log("Eval hooks restored", "Low", T);
}, 1200);
},
"HTTP Methods": async () => {
const T = "Methods";
for (const m of ["PUT","DELETE","TRACE"]) {
try {
const r = await fetch(location.origin + "/", { method: m });
if (r.status < 400) log(`${m} allowed (potential risk)`, "High", T);
else log(`${m} rejected with status ${r.status}`, "Low", T);
} catch { log(`${m} request blocked`, "Low", T); }
}
},
"Sensitive Attributes": () => {
const T = "Attributes";
const patterns = /token|key|pass|secret|cred|email/i;
const all = document.querySelectorAll("*");
let found = 0;
all.forEach(el => {
for (const a of el.attributes) {
if (a.name === "value" || a.name.startsWith("data-")) {
if (patterns.test(String(a.value))) {
log(`Sensitive data in ${a.name} on <${el.tagName.toLowerCase()}>: ${String(a.value).slice(0,80)}`, "High", T);
found++;
}
}
}
});
if (!found) log("No sensitive-looking attribute values detected", "Low", T);
},
"XSS Payload Fuzzing": () => {
const T = "XSS-Fuzz";
if (!intrusiveCheckbox.checked) { log("Fuzzing skipped (Intrusive disabled)", "Low", T); return; }
const nonExecPayloads = [
`"><img src=x onerror=1>`,
`<svg onload=1>`,
`"><svg><desc>__XSS__</desc></svg>`,
`"><b id="__XSS__">x</b>`,
`</textarea>__XSS__<textarea>`,
`' onmouseover=1 '`,
`javascript:1`,
`"><iframe srcdoc="__XSS__"></iframe>`
];
const execPayloads = [
`"><img src=x onerror=alert(1)>`,
`<svg onload=alert(1)>`,
`<a href="javascript:alert(1)">x</a>`,
`<input value="x" onfocus=alert(1)>`
];
const payloads = EXECUTE_XSS_PAYLOADS ? nonExecPayloads.concat(execPayloads) : nonExecPayloads;
const inputs = Array.from(document.querySelectorAll("input[type=text],input[type=search],textarea"));
if (!inputs.length) { log("No inputs found to fuzz", "Low", T); return; }
inputs.forEach((input, i) => {
const rn = input.getRootNode && input.getRootNode();
if (rn && rn !== document) return; // avoid touching Shadow DOM (panel)
payloads.forEach(p => {
try {
const original = input.value;
input.value = p;
input.dispatchEvent(new Event("input", { bubbles: true }));
input.dispatchEvent(new Event("change", { bubbles: true }));
input.value = original;
log(`Fuzzed input #${i+1} with payload: ${p}`, "Low", T);
} catch (e) {
log(`Error fuzzing input #${i+1}: ${e.message}`, "Medium", T);
}
});
});
log("XSS fuzzing complete (check UI for unexpected reflections/popups)", "Low", T);
},
"Outdated Libraries": () => {
const T = "Libs";
document.querySelectorAll("script[src]").forEach(s => {
const src = s.getAttribute("src") || "";
OUTDATED_LIBS.forEach(lib => {
const m = src.match(lib.regex);
if (m) {
const ver = m[1];
log(`${lib.name} detected: ${ver} (recommended >= ${lib.min})`, "Medium", T);
}
});
});
},
"Directory Probing": async () => {
const T = "Dirs";
for (const p of ["/robots.txt", "/sitemap.xml", "/admin/", "/backup/"]) {
try {
const r = await fetch(p, { cache: "no-store" });
if (r.ok) log(`Accessible: ${p} (status ${r.status})`, "Medium", T);
else if (r.status === 403) log(`Forbidden but present: ${p}`, "Low", T);
} catch {}
}
},
"Extended Directory Probing": async () => {
const T = "Dirs+";
const set = new Set(DIRECTORY_PROBE_PATHS_BASE);
// Optional external wordlist from settings
const WORDLIST_URL = settings.wordlistUrl;
if (WORDLIST_URL) {
try {
log(`Fetching external wordlist from ${WORDLIST_URL}`, "Low", T);
const txt = await fetch(WORDLIST_URL, { cache:"no-store" }).then(r => r.text());
txt.split(/\r?\n/).forEach(line => {
const l = line.trim();
if (!l || l.startsWith("#")) return;
const norm = l.startsWith("/") ? l : "/" + l;
set.add(norm);
});
log(`Wordlist loaded, total candidates so far: ${set.size}`, "Low", T);
} catch (e) {
log(`Failed to fetch wordlist: ${e.message}`, "Medium", T);
}
}
// Harvest from page links/scripts for path candidates
const harvest = (url) => {
try {
const u = new URL(url, location.href);
if (u.origin !== location.origin) return;
const path = u.pathname;
const parts = path.split('/').filter(Boolean);
if (parts.length) {
for (let i=1; i<=parts.length; i++) set.add("/" + parts.slice(0,i).join("/") + "/");
if (/\.[a-z0-9]{1,6}$/i.test(parts[parts.length-1])) {
set.add(path + ".bak");
set.add(path + "~");
set.add(path.replace(/\.[^.]+$/, ".old"));
}
}
} catch {}
};
document.querySelectorAll('a[href],link[href],script[src]').forEach(el => {
const u = el.getAttribute('href') || el.getAttribute('src');
if (u) harvest(u);
});
const limit = Math.max(10, Math.min(2000, Number(settings.dirProbeLimit) || 200));
const paths = Array.from(set).slice(0, limit);
for (const p of paths) {
try {
const r = await fetch(p, { cache:"no-store" });
if (r.ok) log(`Accessible: ${p} (status ${r.status})`, "Medium", T);
else if (r.status === 403) log(`Forbidden but present: ${p}`, "Low", T);
} catch {}
}
log(`Directory probing complete (${paths.length} checks)`, "Low", T);
},
"Reflected Params": () => {
const T = "Reflections";
const params = new URLSearchParams(location.search);
let any = 0;
params.forEach((v, k) => {
if (!v) return;
if (document.body.innerHTML.includes(v)) {
any++;
log(`Param ${k} appears reflected in body`, "High", T);
}
});
if (!any) log("No param reflections found in body", "Low", T);
},
"Weak Form Inputs": () => {
const T = "Weak Inputs";
document.querySelectorAll("input[type=text],input[type=password]").forEach(el => {
if (!el.hasAttribute("maxlength")) log(`Input "${el.name || el.id || '(unnamed)'}" missing maxlength`, "Low", T);
if (el.type === "password" && !el.hasAttribute("minlength")) log(`Password input "${el.name || el.id || '(unnamed)'}" missing minlength`, "Low", T);
});
},
"Links (_blank) Hygiene": () => {
const T = "Links";
document.querySelectorAll('a[target="_blank"]').forEach((a, i) => {
const rel = (a.getAttribute('rel') || "").toLowerCase();
if (!/\bnoopener\b/.test(rel) && !/\bnoreferrer\b/.test(rel)) {
log(`Link #${i+1} opens new tab without rel="noopener"`, "Medium", T);
}
});
},
"Meta / Version Disclosure": async () => {
const T = "Meta";
const gen = document.querySelector('meta[name="generator"]')?.getAttribute('content');
if (gen) log(`Meta generator: ${gen}`, "Medium", T);
try {
const res = await fetch(location.href, { method: "HEAD", credentials: "same-origin" });
const xp = res.headers.get("x-powered-by");
if (xp) log(`Header X-Powered-By: ${xp}`, "Medium", T);
} catch {}
},
// -------- Advanced Additions --------
"Open Ports (Heuristic)": async () => {
const T = "Ports";
if (!intrusiveCheckbox.checked) { log("Skipped (Intrusive disabled)", "Low", T); return; }
const host = location.hostname;
const isHTTPS = location.protocol === "https:";
const candidates = [
...(isHTTPS ? PORTS_WEB_HTTPS : PORTS_WEB_HTTP),
...PORTS_MISC,
...EXTRA_PORTS
];
log(`Probing ports on ${host} (heuristics)`, "Low", T);
for (const p of candidates) {
try {
const proto = isHTTPS ? "https" : "http";
if (isHTTPS && (p === 80 || p === 8080 || p === 8000 || p === 3000)) {
log(`Skipping http://${host}:${p} due to mixed-content restrictions`, "Low", T);
} else {
const resp = await fetch(`${proto}://${host}:${p}/`, { mode: "no-cors", cache:"no-store", redirect:"follow" });
log(`Fetch to ${proto}://${host}:${p} resolved (possible service)`, (p===3306||p===5432||p===6379||p===27017) ? "High":"Medium", T);
}
} catch {
try {
const wsProto = isHTTPS ? "wss" : "ws";
const ws = new WebSocket(`${wsProto}://${host}:${p}`);
ws.onopen = () => { log(`WebSocket open on port ${p}`, "Medium", T); ws.close(); };
ws.onerror = () => {};
await new Promise(r => setTimeout(r, 150));
} catch {}
}
}
log("Port probing completed (best-effort client heuristic)", "Low", T);
},
"SQL Injection Hints": async () => {
const T = "SQLi";
if (!intrusiveCheckbox.checked) { log("Skipped (Intrusive disabled)", "Low", T); return; }
const url = new URL(location.href);
const baseline = await fetch(url.toString(), { credentials:"include", cache:"no-store" }).then(r => r.text()).catch(()=>null);
if (!baseline) { log("Baseline fetch failed", "Low", T); return; }
const params = Array.from(url.searchParams.keys()).slice(0, 8);
if (!params.length) { log("No query params to fuzz", "Low", T); }
for (const k of params) {
for (const p of SQLI_PAYLOADS) {
const u = new URL(url.toString());
u.searchParams.set(k, p);
const t0 = performance.now();
try {
const txt = await fetch(u.toString(), { credentials:"include", cache:"no-store" }).then(r => r.text());
const dt = (performance.now()-t0)|0;
if (SQL_ERROR_PATTERNS.some(rx => rx.test(txt))) log(`DB error pattern after ${k}='${p}'`, "High", T);
if (dt > 2500) log(`Slow response (${dt}ms) after ${k}='${p}' (time-based hint)`, "Medium", T);
} catch {
log(`Request failed for ${k}='${p}'`, "Low", T);
}
}
}
log("SQLi fuzzing complete (heuristics only)", "Low", T);
},
"Insecure Direct Object References (IDOR)": async () => {
const T = "IDOR";
if (!intrusiveCheckbox.checked) { log("Skipped (Intrusive disabled)", "Low", T); return; }
const url = new URL(location.href);
const baseline = await fetch(url.toString(), { credentials:"include", cache:"no-store" }).then(r=>r.text()).catch(()=>null);
if (!baseline) { log("Baseline fetch failed", "Low", T); return; }
let candidates = [];
url.searchParams.forEach((v,k) => { const m = (v||"").match(/^\d{2,8}$/); if (m) candidates.push({type:"param", key:k, val:parseInt(v,10)}); });
const segs = url.pathname.split('/').filter(Boolean);
segs.forEach((s, idx) => { if (/^\d{2,8}$/.test(s)) candidates.push({type:"path", index:idx, val:parseInt(s,10)}); });
if (!candidates.length) { log("No obvious numeric IDs detected in URL", "Low", T); return; }
for (const c of candidates.slice(0,5)) {
for (const delta of [1, -1, 100]) {
const u = new URL(location.href);
if (c.type === "param") u.searchParams.set(c.key, String(c.val + delta));
else {
const parts = u.pathname.split('/');
const nonEmpty = parts.filter(Boolean);
nonEmpty[c.index] = String(c.val + delta);
u.pathname = "/" + nonEmpty.join("/") + (location.pathname.endsWith('/')?'/':'');
}
try {
const txt = await fetch(u.toString(), { credentials:"include", cache:"no-store" }).then(r=>r.text());
if (txt && Math.abs(txt.length - baseline.length) > 500) {
log(`Response size changed for ID mutation (${c.type}:${c.val}→${c.val+delta})`, "High", T);
}
} catch {}
}
}
log("IDOR heuristic probing complete", "Low", T);
},
"Server-Side Request Forgery (SSRF)": async () => {
const T = "SSRF";
if (!intrusiveCheckbox.checked) { log("Skipped (Intrusive disabled)", "Low", T); return; }
const endpoints = new Set();
Array.from(document.forms).forEach(f => {
const a = f.getAttribute('action') || location.href;
try { const u = new URL(a, location.href); if (u.origin === location.origin) endpoints.add(u.pathname + (u.search||"")); } catch {}
});
Array.from(document.querySelectorAll('a[href]')).forEach(a => {
try { const u = new URL(a.href, location.href); if (u.origin === location.origin && SSRF_PARAM_HINTS.test(u.search)) endpoints.add(u.pathname + u.search); } catch {}
});
Array.from(document.querySelectorAll('script[src],link[href]')).forEach(el => {
const href = el.getAttribute('src') || el.getAttribute('href');
try { const u = new URL(href, location.href); if (u.origin === location.origin && SSRF_PARAM_HINTS.test(u.search)) endpoints.add(u.pathname + u.search); } catch {}
});
const targets = ["http://127.0.0.1", "http://169.254.169.254/latest/meta-data/"];
if (!endpoints.size) { log("No candidate endpoints with URL-like params found", "Low", T); return; }
for (const path of Array.from(endpoints).slice(0,6)) {
for (const target of targets) {
try {
const u = new URL(path, location.origin);
const sp = new URLSearchParams(u.search);
let hit = false;
for (const [k,v] of sp.entries()) { if (SSRF_PARAM_HINTS.test(k)) { sp.set(k, target); hit = true; } }
if (!hit) continue;
u.search = sp.toString();
const r = await fetch(u.toString(), { credentials:"include", cache:"no-store" });
if (r.ok) {
const text = await r.text();
if (/ami-|instance|meta-|availability-zone|hostname/i.test(text) || text.length > 0) {
log(`Potential SSRF via ${u.pathname}?${sp} → returned ${text.length} bytes`, "High", T);
} else {
log(`Endpoint responded (status ${r.status}) to internal URL probe`, "Medium", T);
}
} else if (r.status === 403) {
log(`Endpoint forbids internal URL (403) — SSRF mitigated at ${u.pathname}`, "Low", T);
}
} catch {}
}
}
log("SSRF probing complete (same-origin endpoints only)", "Low", T);
},
"Prototype Pollution": async () => {
const T = "Prototype Pollution";
const cleanUp = (key) => { try { delete Object.prototype[key]; } catch {} };
const payload = JSON.parse('{"__proto__":{"__sec_polluted__":true}}');
let polluted = false;
if (window._ && typeof _.merge === "function") {
try {
_.merge({}, payload);
if (({}).__sec_polluted__ === true) { polluted = true; log("_.merge produced prototype pollution", "High", T); }
cleanUp("__sec_polluted__");
} catch {}
}
if (window.jQuery && typeof jQuery.extend === "function") {
try {
jQuery.extend(true, {}, payload);
if (({}).__sec_polluted__ === true) { polluted = true; log("$.extend(true, ...) produced prototype pollution", "High", T); }
cleanUp("__sec_polluted__");
} catch {}
}
Array.from(document.querySelectorAll('script:not([src])')).forEach(s => {
const t = (s.textContent || "").slice(0, 4000);
if (/extend\s*\(\s*true|merge\s*\(/i.test(t) && /__proto__|constructor|prototype/i.test(t)) {
log("Inline script contains risky deep-merge patterns", "Medium", T);
}
});
if (!polluted) log("No prototype pollution detected in quick checks", "Low", T);
},
"Web Cache Poisoning (Heuristics)": async () => {
const T = "Cache Poisoning";
try {
const r = await fetch(location.href, { method:"GET", cache:"no-store", headers: { "X-SEC-TEST": "A" } });
const vary = r.headers.get("vary") || "";
const cache = r.headers.get("cache-control") || "";
if (!/no-store|no-cache|private/i.test(cache)) log(`Cache-Control may allow caching HTML: ${cache}`, "Medium", T);
if (vary && !/accept-encoding|accept-language|origin/i.test(vary)) log(`Vary header lacks common keys: ${vary}`, "Low", T);
else if (!vary) log("No Vary header present", "Low", T);
log("Note: Browser cannot confirm poisoning; review headers and CDN config", "Low", T);
} catch {
log("Cache heuristic failed (request error)", "Low", T);
}
},
"GraphQL Introspection": async () => {
const T = "GraphQL";
const q = { query: "query { __schema { queryType { name } types { name } } }" };
let tried = 0, hits = 0;
for (const path of GRAPHQL_PATHS) {
tried++;
try {
const r = await fetch(path, {
method:"POST",
headers: { "content-type":"application/json" },
credentials:"include",
body: JSON.stringify(q),
});
const js = await r.json().catch(()=> ({}));
if (js.data && js.data.__schema) { log(`${path}: Introspection ENABLED`, "High", T); hits++; }
else if (js.errors) { log(`${path}: Errors returned (likely disabled)`, "Low", T); }
else { log(`${path}: No GraphQL response`, "Low", T); }
} catch {}
}
if (!tried) log("No /graphql endpoints tried", "Low", T);
if (!hits) log("No introspection enabled detected", "Low", T);
},
"Advanced Fingerprinting Detection": async () => {
const T = "Fingerprinting";
let flagged = 0;
Array.from(document.querySelectorAll('script:not([src])')).forEach(s => {
const t = (s.textContent || "");
if (/toDataURL\s*\(|getImageData\s*\(|AudioContext|webkitAudioContext|getChannelData/i.test(t)) {
flagged++; log("Inline script uses canvas/audio APIs (fingerprinting vector)", "Medium", T);
}
if (/fingerprint|fp2|device.+id/i.test(t)) { flagged++; log("Inline script mentions fingerprint identifiers", "Medium", T); }
});
let canvasCalls = 0;
const canvasProto = HTMLCanvasElement.prototype;
const _toDataURL = canvasProto.toDataURL;
canvasProto.toDataURL = function(...a){ canvasCalls++; try { return _toDataURL.apply(this,a); } catch(e){ throw e; } };
await new Promise(r => setTimeout(r, 1000));
canvasProto.toDataURL = _toDataURL;
if (canvasCalls>0) log(`Canvas toDataURL invoked ${canvasCalls}x during probe window`, "Medium", T);
if (!flagged && canvasCalls===0) log("No obvious fingerprinting signals in brief probe", "Low", T);
},
"Broken Authentication (Heuristics)": () => {
const T = "Auth";
const jwtRx = /eyJ[A-Za-z0-9_\-]+\.[A-Za-z0-9_\-]+\.[A-Za-z0-9_\-]*/g;
const scanStr = (s) => { const found = (s||"").match(jwtRx) || []; return [...new Set(found)]; };
let any = 0;
[["localStorage", localStorage], ["sessionStorage", sessionStorage]].forEach(([name, store]) => {
try {
for (const k in store) {
const vals = scanStr(String(store[k] || ""));
vals.forEach(tok => {
any++; log(`${name} contains JWT-like token (${k})`, "Medium", T);
const [h,p] = tok.split('.'); const header = base64urlDecode(h); const payload = base64urlDecode(p);
if (header && header.alg && /none/i.test(header.alg)) log("JWT alg=none (weak)", "High", T);
if (payload && payload.exp && (payload.exp*1000 < Date.now())) log("JWT appears expired", "Low", T);
});
}
} catch {}
});
(document.cookie || "").split(';').forEach(c => {
const parts = c.trim().split('=');
if (!parts[0]) return;
const vals = scanStr(decodeURIComponent(parts.slice(1).join('=')));
vals.forEach(tok => {
any++; log(`Cookie contains JWT-like token: ${parts[0]} (readable ⇒ not HttpOnly)`, "High", T);
});
});
if (!any) log("No JWT-like tokens detected in quick scan", "Low", T);
log("Session fixation requires auth flow observation; not auto-tested", "Low", T);
},
"Rate Limiting Test": async () => {
const T = "Rate Limit";
if (!intrusiveCheckbox.checked) { log("Skipped (Intrusive disabled)", "Low", T); return; }
const url = new URL(location.href);
url.searchParams.set("sec_rate_test", String(Date.now()%100000));
const N = Math.max(1, Math.min(50, Number(settings.rateBurst) || 8));
let got429 = 0;
await Promise.all(Array.from({length:N}, async () => {
try {
const r = await fetch(url.toString(), { credentials:"include", cache:"no-store" });
if (r.status === 429) got429++;
} catch {}
}));
if (got429>0) log(`429 detected (${got429}/${N}) — rate limiting present`, "Low", T);
else log(`No 429 responses in ${N} rapid requests — rate limit possibly absent`, "Medium", T);
},
"Client-Side Template Injection (CSTI)": async () => {
const T = "CSTI";
const inputs = Array.from(document.querySelectorAll("input[type=text],input[type=search],textarea"))
.filter(el => (el.getRootNode && el.getRootNode() === document));
if (!inputs.length) { log("No candidate inputs", "Low", T); return; }
const exprs = ["{{7*7}}", "<%= 7*7 %>", "${{7*7}}", "${7*7}"];
const beforeHTML = document.documentElement.innerHTML;
for (const el of inputs.slice(0,8)) {
const orig = el.value;
for (const ex of exprs) {
try {
el.value = ex;
el.dispatchEvent(new Event("input", { bubbles:true }));
el.dispatchEvent(new Event("change", { bubbles:true }));
} catch {}
}
el.value = orig;
}
await new Promise(r => setTimeout(r, 300));
const afterHTML = document.documentElement.innerHTML;
if (afterHTML.includes("49") && !beforeHTML.includes("49")) {
log("Found '49' after injecting '{{7*7}}' expressions — CSTI hint", "High", T);
} else {
log("No clear CSTI signal from simple expressions", "Low", T);
}
}
};
// ------------------ Buttons ------------------
for (const [name, fn] of Object.entries(tests)) {
const b = document.createElement("button");
b.textContent = name;
b.onclick = async () => {
findings = []; renderLogs(); toggleLoading(true);
try { await fn(); }
catch (e) { log(`Unexpected error in ${name}: ${e.message}`, "Medium", name); }
toggleLoading(false);
};
buttonsDiv.appendChild(b);
}
// ------------------ Controls ------------------
panel.querySelector('#sec_clear').onclick = () => { findings = []; renderLogs(); };
panel.querySelector('#sec_close').onclick = () => wrapper.remove();
panel.querySelector('#sec_minimize').onclick = () => panel.classList.toggle("minimized");
panel.querySelector('#sec_run_all').onclick = async () => {
findings = []; renderLogs(); toggleLoading(true);
for (const [name, fn] of Object.entries(tests)) {
try { await fn(); }
catch (e) { log(`Unexpected error in ${name}: ${e.message}`, "Medium", name); }
}
const stats = { High:0, Medium:0, Low:0 };
findings.forEach(f => { if (stats[f.severity] != null) stats[f.severity]++; });
log(`Summary: High=${stats.High}, Medium=${stats.Medium}, Low=${stats.Low}`, "Low", "Summary");
toggleLoading(false);
};
panel.querySelector('#sec_export').onclick = () => {
const report = { url: location.href, time: new Date().toISOString(), findings };
GM_download({
url: "data:application/json," + encodeURIComponent(JSON.stringify(report, null, 2)),
name: `sec_report_${Date.now()}.json`,
saveAs: true
});
};
panel.querySelector('#sec_copy').onclick = () => {
const report = { url: location.href, time: new Date().toISOString(), findings };
navigator.clipboard.writeText(JSON.stringify(report, null, 2)).then(
() => GM_notification("Report copied to clipboard!", "Success"),
() => GM_notification("Failed to copy report.", "Error")
);
};
filterSelect.onchange = renderLogs;
searchInput.oninput = renderLogs;
themeButton.onclick = () => {
isLightMode = !isLightMode;
panel.classList.toggle("light", isLightMode);
themeButton.textContent = isLightMode ? "🌙" : "☀️";
};
// ------------------ Settings Modal Logic ------------------
const openSettings = () => {
inpWordlist.value = settings.wordlistUrl || '';
inpDirLimit.value = Number(settings.dirProbeLimit || 200);
inpExecXss.checked = !!settings.execXss;
inpExtraPorts.value = settings.extraPorts || '';
inpGraphqlPaths.value = settings.graphqlPaths || '';
inpRateBurst.value = Number(settings.rateBurst || 8);
modalBackdrop.hidden = false;
};
const closeSettings = () => { modalBackdrop.hidden = true; };
settingsBtn.onclick = openSettings;
modalClose.onclick = closeSettings;
modalCancel.onclick = closeSettings;
modalSave.onclick = () => {
const wl = inpWordlist.value.trim();
const dl = Math.max(10, Math.min(2000, Number(inpDirLimit.value) || 200));
const ex = !!inpExecXss.checked;
const ep = inpExtraPorts.value.trim();
const gp = inpGraphqlPaths.value.trim();
const rb = Math.max(1, Math.min(50, Number(inpRateBurst.value) || 8));
settings.wordlistUrl = wl;
settings.dirProbeLimit = dl;
settings.execXss = ex;
settings.extraPorts = ep;
settings.graphqlPaths = gp;
settings.rateBurst = rb;
setVal('sec_wordlist_url', wl);
setVal('sec_dir_limit', dl);
setVal('sec_exec_xss', ex);
setVal('sec_extra_ports', ep);
setVal('sec_graphql_paths', gp);
setVal('sec_rate_burst', rb);
// apply in-memory for current session
EXECUTE_XSS_PAYLOADS = ex;
// Ports
const newExtra = (ep || '').split(',').map(s=>parseInt(s.trim(),10)).filter(n=>Number.isInteger(n) && n>0);
EXTRA_PORTS.length = 0;
newExtra.forEach(n => { if (!EXTRA_PORTS.includes(n)) EXTRA_PORTS.push(n); });
// GraphQL paths
GRAPHQL_PATHS = ["/graphql", "/api/graphql"];
if (gp) {
const extra = gp.split(',').map(s=>s.trim()).filter(Boolean);
GRAPHQL_PATHS = [...new Set([...GRAPHQL_PATHS, ...extra])];
}
closeSettings();
GM_notification("Settings saved.", "Security Panel");
};
// ------------------ Dragging ------------------
let dragging = false, offX=0, offY=0;
panel.querySelector('#sec_header').addEventListener('mousedown', e => {
dragging = true; const r = panel.getBoundingClientRect();
offX = e.clientX - r.left; offY = e.clientY - r.top; e.preventDefault();
});
document.addEventListener('mousemove', e => {
if (!dragging) return;
panel.style.left = `${e.clientX - offX}px`;
panel.style.top = `${e.clientY - offY}px`;
panel.style.right = "auto"; panel.style.bottom = "auto";
});
document.addEventListener('mouseup', () => dragging = false);
// ------------------ Init ------------------
log("Security Panel v13 initialized", "Low", "General");
})();