mirror of
				https://github.com/HackTricks-wiki/hacktricks.git
				synced 2025-10-10 18:36:50 +00:00 
			
		
		
		
	Translated ['src/network-services-pentesting/pentesting-web/nextjs-1.md'
This commit is contained in:
		
							parent
							
								
									52211084b2
								
							
						
					
					
						commit
						ecddafd95a
					
				| @ -393,8 +393,6 @@ | ||||
|     - [Electron contextIsolation RCE via Electron internal code](network-services-pentesting/pentesting-web/electron-desktop-apps/electron-contextisolation-rce-via-electron-internal-code.md) | ||||
|     - [Electron contextIsolation RCE via IPC](network-services-pentesting/pentesting-web/electron-desktop-apps/electron-contextisolation-rce-via-ipc.md) | ||||
|   - [Flask](network-services-pentesting/pentesting-web/flask.md) | ||||
|   - [NextJS](network-services-pentesting/pentesting-web/nextjs.md) | ||||
|   - [NodeJS Express](network-services-pentesting/pentesting-web/nodejs-express.md) | ||||
|   - [Git](network-services-pentesting/pentesting-web/git.md) | ||||
|   - [Golang](network-services-pentesting/pentesting-web/golang.md) | ||||
|   - [GWT - Google Web Toolkit](network-services-pentesting/pentesting-web/gwt-google-web-toolkit.md) | ||||
| @ -409,8 +407,9 @@ | ||||
|   - [JSP](network-services-pentesting/pentesting-web/jsp.md) | ||||
|   - [Laravel](network-services-pentesting/pentesting-web/laravel.md) | ||||
|   - [Moodle](network-services-pentesting/pentesting-web/moodle.md) | ||||
|   - [NextJS](network-services-pentesting/pentesting-web/nextjs.md) | ||||
|   - [Nginx](network-services-pentesting/pentesting-web/nginx.md) | ||||
|   - [NextJS](network-services-pentesting/pentesting-web/nextjs-1.md) | ||||
|   - [NodeJS Express](network-services-pentesting/pentesting-web/nodejs-express.md) | ||||
|   - [PHP Tricks](network-services-pentesting/pentesting-web/php-tricks-esp/README.md) | ||||
|     - [PHP - Useful Functions & disable_functions/open_basedir bypass](network-services-pentesting/pentesting-web/php-tricks-esp/php-useful-functions-disable_functions-open_basedir-bypass/README.md) | ||||
|       - [disable_functions bypass - php-fpm/FastCGI](network-services-pentesting/pentesting-web/php-tricks-esp/php-useful-functions-disable_functions-open_basedir-bypass/disable_functions-bypass-php-fpm-fastcgi.md) | ||||
| @ -439,6 +438,7 @@ | ||||
|   - [Symfony](network-services-pentesting/pentesting-web/symphony.md) | ||||
|   - [Tomcat](network-services-pentesting/pentesting-web/tomcat/README.md) | ||||
|   - [Uncovering CloudFlare](network-services-pentesting/pentesting-web/uncovering-cloudflare.md) | ||||
|   - [Vuejs](network-services-pentesting/pentesting-web/vuejs.md) | ||||
|   - [VMWare (ESX, VCenter...)](network-services-pentesting/pentesting-web/vmware-esx-vcenter....md) | ||||
|   - [Web API Pentesting](network-services-pentesting/pentesting-web/web-api-pentesting.md) | ||||
|   - [WebDav](network-services-pentesting/pentesting-web/put-method-webdav.md) | ||||
|  | ||||
| @ -1,5 +0,0 @@ | ||||
| # NextJS | ||||
| 
 | ||||
| {{#include ../../banners/hacktricks-training.md}} | ||||
| 
 | ||||
| {{#include ../../banners/hacktricks-training.md}} | ||||
							
								
								
									
										128
									
								
								src/network-services-pentesting/pentesting-web/vuejs.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										128
									
								
								src/network-services-pentesting/pentesting-web/vuejs.md
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,128 @@ | ||||
| # Vue.js | ||||
| 
 | ||||
| {{#include ../../banners/hacktricks-training.md}} | ||||
| 
 | ||||
| ## XSS Sinks in Vue.js | ||||
| 
 | ||||
| ### v-html Directive | ||||
| A diretiva `v-html` renderiza **HTML** bruto, então qualquer `<script>` (ou um atributo como `onerror`) incorporado em entradas de usuário não sanitizadas é executado imediatamente. | ||||
| ```html | ||||
| <div id="app"> | ||||
| <div v-html="htmlContent"></div> | ||||
| </div> | ||||
| <script> | ||||
| new Vue({ | ||||
| el: '#app', | ||||
| data: { | ||||
| htmlContent: '<img src=x onerror=alert(1)>' | ||||
| } | ||||
| }) | ||||
| </script> | ||||
| ``` | ||||
| ### v-bind com src ou href | ||||
| Vincular uma string de usuário a atributos que contêm URL (`href`, `src`, `xlink:href`, `formaction` …) permite que cargas úteis como `javascript:alert(1)` sejam executadas quando o link é seguido. | ||||
| ```html | ||||
| <div id="app"> | ||||
| <a v-bind:href="userInput">Click me</a> | ||||
| </div> | ||||
| <script> | ||||
| new Vue({ | ||||
| el: '#app', | ||||
| data: { | ||||
| userInput: 'javascript:alert(1)' | ||||
| } | ||||
| }) | ||||
| </script> | ||||
| ``` | ||||
| ### v-on com manipuladores controlados pelo usuário | ||||
| `v-on` compila seu valor com `new Function`; se esse valor vem do usuário, você entrega a execução de código em uma bandeja. | ||||
| ```html | ||||
| <div id="app"> | ||||
| <button v-on:click="malicious">Click me</button> | ||||
| </div> | ||||
| <script> | ||||
| new Vue({ | ||||
| el: '#app', | ||||
| data: { malicious: 'alert(1)' } | ||||
| }) | ||||
| </script> | ||||
| ``` | ||||
| ### Nomes de atributos / eventos dinâmicos | ||||
| Nomes fornecidos pelo usuário em `v-bind:[attr]` ou `v-on:[event]` permitem que atacantes criem qualquer atributo ou manipulador de eventos, contornando a análise estática e muitas regras de CSP. | ||||
| ```html | ||||
| <img v-bind:[userAttr]="payload"> | ||||
| <!-- userAttr = 'onerror', payload = 'alert(1)' --> | ||||
| ``` | ||||
| ### Componente dinâmico (`<component :is>`) | ||||
| Permitir strings de usuário em `:is` pode montar componentes arbitrários ou templates inline—perigoso no navegador e catastrófico em SSR. | ||||
| ```html | ||||
| <component :is="userChoice"></component> | ||||
| <!-- userChoice = '<script>alert(1)</script>' --> | ||||
| ``` | ||||
| ### Modelos não confiáveis em SSR | ||||
| Durante a renderização do lado do servidor, o modelo é executado **no seu servidor**; injetar HTML do usuário pode escalar XSS para execução remota de código completo (RCE). CVEs em `vue-template-compiler` provam o risco. | ||||
| ```js | ||||
| // DANGER – never do this | ||||
| const app = createSSRApp({ template: userProvidedHtml }) | ||||
| ``` | ||||
| ### Filtros / funções de renderização que avaliam | ||||
| Filtros legados que constroem strings de renderização ou chamam `eval`/`new Function` em dados do usuário são outro vetor de XSS—substitua-os por propriedades computadas. | ||||
| ```js | ||||
| Vue.filter('run', code => eval(code))   // DANGER | ||||
| ``` | ||||
| --- | ||||
| 
 | ||||
| ## Outras Vulnerabilidades Comuns em Projetos Vue | ||||
| 
 | ||||
| ### Poluição de protótipo em plugins | ||||
| Helpers de deep-merge em alguns plugins (por exemplo, **vue-i18n**) permitiram que atacantes escrevessem em `Object.prototype`. | ||||
| ```js | ||||
| import merge from 'deepmerge' | ||||
| merge({}, JSON.parse('{ "__proto__": { "polluted": true } }')) | ||||
| ``` | ||||
| ### Redirecionamentos abertos com vue-router | ||||
| Passar URLs de usuários não verificadas para `router.push` ou `<router-link>` pode redirecionar para URIs `javascript:` ou domínios de phishing. | ||||
| ```js | ||||
| this.$router.push(this.$route.query.next) // DANGER | ||||
| ``` | ||||
| ### CSRF em Axios / fetch | ||||
| SPAs ainda precisam de tokens CSRF do lado do servidor; cookies SameSite sozinhos não podem bloquear POSTs cross-origin enviados automaticamente. | ||||
| ```js | ||||
| axios.post('/api/transfer', data, { | ||||
| headers: { 'X-CSRF-TOKEN': token } | ||||
| }) | ||||
| ``` | ||||
| ### Click-jacking | ||||
| Aplicativos Vue são passíveis de serem emoldurados, a menos que você envie tanto `X-Frame-Options: DENY` quanto `Content-Security-Policy: frame-ancestors 'none'`. | ||||
| ```http | ||||
| X-Frame-Options: DENY | ||||
| Content-Security-Policy: frame-ancestors 'none'; | ||||
| ``` | ||||
| ### Content-Security-Policy armadilhas | ||||
| A construção completa do Vue precisa de `unsafe-eval`; mude para a construção em tempo de execução ou templates pré-compilados para que você possa eliminar essa fonte perigosa. | ||||
| ```http | ||||
| Content-Security-Policy: default-src 'self'; script-src 'self'; | ||||
| ``` | ||||
| ### Ataques à cadeia de suprimentos (node-ipc – Março de 2022) | ||||
| O sabotagem do **node-ipc**—retirado pelo Vue CLI—mostrou como uma dependência transitiva pode executar código arbitrário em máquinas de desenvolvimento. Fixe versões e audite com frequência. | ||||
| ```shell | ||||
| npm ci --ignore-scripts   # safer install | ||||
| ``` | ||||
| --- | ||||
| 
 | ||||
| ## Lista de Verificação de Fortalecimento | ||||
| 
 | ||||
| 1. **Sanitizar** cada string antes de chegar a `v-html` (DOMPurify). | ||||
| 2. **Lista branca** de esquemas, atributos, componentes e eventos permitidos. | ||||
| 3. **Evitar `eval`** e templates dinâmicos completamente. | ||||
| 4. **Corrigir dependências semanalmente** e monitorar avisos. | ||||
| 5. **Enviar cabeçalhos HTTP fortes** (CSP, HSTS, XFO, CSRF). | ||||
| 6. **Trancar sua cadeia de suprimentos** com auditorias, arquivos de bloqueio e commits assinados. | ||||
| 
 | ||||
| ## Referências | ||||
| 
 | ||||
| - [https://www.stackhawk.com/blog/vue-xss-guide-examples-and-prevention/](https://www.stackhawk.com/blog/vue-xss-guide-examples-and-prevention/) | ||||
| - [https://medium.com/@isaacwangethi30/vue-js-security-6e246a7613da](https://medium.com/@isaacwangethi30/vue-js-security-6e246a7613da) | ||||
| - [https://vuejs.org/guide/best-practices/security](https://vuejs.org/guide/best-practices/security) | ||||
| 
 | ||||
| {{#include ../../banners/hacktricks-training.md}} | ||||
							
								
								
									
										403
									
								
								theme/ai.js
									
									
									
									
									
								
							
							
						
						
									
										403
									
								
								theme/ai.js
									
									
									
									
									
								
							| @ -1,108 +1,259 @@ | ||||
| /** | ||||
|  * HackTricks AI Chat Widget v1.14 – animated typing indicator | ||||
|  * HackTricks AI Chat Widget v1.15 – Markdown rendering + sanitised | ||||
|  * ------------------------------------------------------------------------ | ||||
|  * • Replaces the static “…” placeholder with a three‑dot **bouncing** loader | ||||
|  *   while waiting for the assistant’s response. | ||||
|  * • Replaces the static “…” placeholder with a three-dot **bouncing** loader | ||||
|  * • Renders assistant replies as Markdown while purging any unsafe HTML | ||||
|  *   (XSS-safe via DOMPurify) | ||||
|  * ------------------------------------------------------------------------ | ||||
|  */ | ||||
| (function () { | ||||
|     const LOG = "[HackTricks-AI]"; | ||||
|    | ||||
|     /* ---------------- User‑tunable constants ---------------- */ | ||||
|     const MAX_CONTEXT  = 3000;     // highlighted‑text char limit
 | ||||
|     const MAX_QUESTION = 500;      // question char limit
 | ||||
|     const TOOLTIP_TEXT = | ||||
|       "💡 Highlight any text on the page,\nthen click to ask HackTricks AI about it"; | ||||
|    | ||||
|     const API_BASE   = "https://www.hacktricks.ai/api/assistants/threads"; | ||||
|     const BRAND_RED  = "#b31328"; // HackTricks brand
 | ||||
|    | ||||
|     /* ------------------------------ State ------------------------------ */ | ||||
|     let threadId  = null; | ||||
|     let isRunning = false; | ||||
|    | ||||
|     const $ = (sel, ctx = document) => ctx.querySelector(sel); | ||||
|     if (document.getElementById("ht-ai-btn")) { console.warn(`${LOG} Widget already injected.`); return; } | ||||
|     (document.readyState === "loading" ? document.addEventListener("DOMContentLoaded", init) : init()); | ||||
|    | ||||
|     /* ==================================================================== */ | ||||
|     async function init() { | ||||
|       console.log(`${LOG} Injecting widget… v1.14`); | ||||
|       await ensureThreadId(); | ||||
|       injectStyles(); | ||||
|    | ||||
|       const btn      = createFloatingButton(); | ||||
|       createTooltip(btn); | ||||
|       const panel    = createSidebar(); | ||||
|       const chatLog  = $("#ht-ai-chat"); | ||||
|       const sendBtn  = $("#ht-ai-send"); | ||||
|       const inputBox = $("#ht-ai-question"); | ||||
|       const resetBtn = $("#ht-ai-reset"); | ||||
|       const closeBtn = $("#ht-ai-close"); | ||||
|    | ||||
|       /* ------------------- Selection snapshot ------------------- */ | ||||
|       let savedSelection = ""; | ||||
|       btn.addEventListener("pointerdown", () => { savedSelection = window.getSelection().toString().trim(); }); | ||||
|    | ||||
|       /* ------------------- Helpers ------------------------------ */ | ||||
|       function addMsg(text, cls) { | ||||
|         const b = document.createElement("div"); | ||||
|         b.className = `ht-msg ${cls}`; | ||||
|         b.textContent = text; | ||||
|         chatLog.appendChild(b); | ||||
|         chatLog.scrollTop = chatLog.scrollHeight; | ||||
|         return b; | ||||
|       } | ||||
|       const LOADER_HTML = '<span class="ht-loading"><span></span><span></span><span></span></span>'; | ||||
|    | ||||
|       function setInputDisabled(d) { inputBox.disabled = d; sendBtn.disabled = d; } | ||||
|       function clearThreadCookie() { document.cookie = "threadId=; Path=/; Max-Age=0"; threadId = null; } | ||||
|       function resetConversation() { chatLog.innerHTML=""; clearThreadCookie(); panel.classList.remove("open"); } | ||||
|    | ||||
|       /* ------------------- Panel open / close ------------------- */ | ||||
|       btn.addEventListener("click", () => { | ||||
|         if (!savedSelection) { alert("Please highlight some text first to then ask Hacktricks AI about it."); return; } | ||||
|         if (savedSelection.length > MAX_CONTEXT) { alert(`Highlighted text is too long (${savedSelection.length} chars). Max allowed: ${MAX_CONTEXT}.`); return; } | ||||
|         chatLog.innerHTML=""; addMsg(savedSelection, "ht-context"); panel.classList.add("open"); inputBox.focus(); | ||||
|       }); | ||||
|       closeBtn.addEventListener("click", resetConversation); | ||||
|       resetBtn.addEventListener("click", resetConversation); | ||||
|    | ||||
|       /* --------------------------- Messaging --------------------------- */ | ||||
|       async function sendMessage(question, context=null) { | ||||
|         if (!threadId) await ensureThreadId(); | ||||
|         if (isRunning) { addMsg("Please wait until the current operation completes.", "ht-ai"); return; } | ||||
|    | ||||
|         isRunning = true; setInputDisabled(true); | ||||
|         const loadingBubble = addMsg("", "ht-ai"); | ||||
|         loadingBubble.innerHTML = LOADER_HTML; | ||||
|    | ||||
|         const content = context ? `### Context:\n${context}\n\n### Question to answer:\n${question}` : question; | ||||
|         try { | ||||
|           const res = await fetch(`${API_BASE}/${threadId}/messages`, { method:"POST", credentials:"include", headers:{"Content-Type":"application/json"}, body:JSON.stringify({content}) }); | ||||
|           if (!res.ok) { | ||||
|             let err=`Unknown error: ${res.status}`; | ||||
|             try { const e=await res.json(); if(e.error) err=`Error: ${e.error}`; else if(res.status===429) err="Rate limit exceeded. Please try again later."; } catch(_){} | ||||
|             loadingBubble.textContent = err; return; } | ||||
|           const data = await res.json(); | ||||
|           loadingBubble.remove(); | ||||
|           if (Array.isArray(data.response)) data.response.forEach(p=>{ addMsg( p.type==="text"&&p.text&&p.text.value ? p.text.value : JSON.stringify(p), "ht-ai"); }); | ||||
|           else if (typeof data.response === "string") addMsg(data.response, "ht-ai"); | ||||
|           else addMsg(JSON.stringify(data,null,2), "ht-ai"); | ||||
|         } catch (e) { console.error("Error sending message:",e); loadingBubble.textContent="An unexpected error occurred."; } | ||||
|         finally { isRunning=false; setInputDisabled(false); chatLog.scrollTop=chatLog.scrollHeight; } | ||||
|       } | ||||
|    | ||||
|       async function handleSend(){ const q=inputBox.value.trim(); if(!q)return; if(q.length>MAX_QUESTION){alert(`Your question is too long (${q.length} chars). Max allowed: ${MAX_QUESTION}.`); return;} inputBox.value=""; addMsg(q,"ht-user"); await sendMessage(q,savedSelection||null);}     | ||||
|       sendBtn.addEventListener("click", handleSend); | ||||
|       inputBox.addEventListener("keydown", e=>{ if(e.key==="Enter"&&!e.shiftKey){ e.preventDefault(); handleSend(); } }); | ||||
|   const LOG = "[HackTricks-AI]"; | ||||
| 
 | ||||
|   /* ---------------- User-tunable constants ---------------- */ | ||||
|   const MAX_CONTEXT  = 3000;     // highlighted-text char limit
 | ||||
|   const MAX_QUESTION = 500;      // question char limit
 | ||||
|   const TOOLTIP_TEXT = | ||||
|     "💡 Highlight any text on the page,\nthen click to ask HackTricks AI about it"; | ||||
| 
 | ||||
|   const API_BASE  = "https://www.hacktricks.ai/api/assistants/threads"; | ||||
|   const BRAND_RED = "#b31328"; // HackTricks brand
 | ||||
| 
 | ||||
|   /* ------------------------------ State ------------------------------ */ | ||||
|   let threadId  = null; | ||||
|   let isRunning = false; | ||||
| 
 | ||||
|   const $ = (sel, ctx = document) => ctx.querySelector(sel); | ||||
|   if (document.getElementById("ht-ai-btn")) { | ||||
|     console.warn(`${LOG} Widget already injected.`); | ||||
|     return; | ||||
|   } | ||||
|   (document.readyState === "loading" | ||||
|     ? document.addEventListener("DOMContentLoaded", init) | ||||
|     : init()); | ||||
| 
 | ||||
|   /* ==================================================================== */ | ||||
|   /*  🔗 1. 3rd-party libs → Markdown & sanitiser                         */ | ||||
|   /* ==================================================================== */ | ||||
|   function loadScript(src) { | ||||
|     return new Promise((resolve, reject) => { | ||||
|       const s = document.createElement("script"); | ||||
|       s.src = src; | ||||
|       s.onload = resolve; | ||||
|       s.onerror = () => reject(new Error(`Failed to load ${src}`)); | ||||
|       document.head.appendChild(s); | ||||
|     }); | ||||
|   } | ||||
| 
 | ||||
|   async function ensureDeps() { | ||||
|     const deps = []; | ||||
|     if (typeof marked === "undefined") { | ||||
|       deps.push(loadScript("https://cdn.jsdelivr.net/npm/marked/marked.min.js")); | ||||
|     } | ||||
|    | ||||
|     /* ==================================================================== */ | ||||
|     async function ensureThreadId(){ const m=document.cookie.match(/threadId=([^;]+)/); if(m&&m[1]){threadId=m[1];return;} try{ const r=await fetch(API_BASE,{method:"POST",credentials:"include"}); const d=await r.json(); if(!r.ok||!d.threadId) throw new Error(`${r.status} ${r.statusText}`); threadId=d.threadId; document.cookie=`threadId=${threadId}; Path=/; Secure; SameSite=Strict; Max-Age=7200`; }catch(e){ console.error("Error creating threadId:",e); alert("Failed to initialise the conversation. Please refresh and try again."); throw e; }} | ||||
|    | ||||
|     /* ==================================================================== */ | ||||
|     function injectStyles(){ const css=` | ||||
|     if (typeof DOMPurify === "undefined") { | ||||
|       deps.push( | ||||
|         loadScript( | ||||
|           "https://cdnjs.cloudflare.com/ajax/libs/dompurify/3.2.5/purify.min.js" | ||||
|         ) | ||||
|       ); | ||||
|     } | ||||
|     if (deps.length) await Promise.all(deps); | ||||
|   } | ||||
| 
 | ||||
|   function mdToSafeHTML(md) { | ||||
|     // 1️⃣ Markdown → raw HTML
 | ||||
|     const raw = marked.parse(md, { mangle: false, headerIds: false }); | ||||
|     // 2️⃣ Purify
 | ||||
|     return DOMPurify.sanitize(raw, { USE_PROFILES: { html: true } }); | ||||
|   } | ||||
| 
 | ||||
|   /* ==================================================================== */ | ||||
|   async function init() { | ||||
|     /* ----- make sure marked & DOMPurify are ready before anything else */ | ||||
|     try { | ||||
|       await ensureDeps(); | ||||
|     } catch (e) { | ||||
|       console.error(`${LOG} Could not load dependencies`, e); | ||||
|       return; | ||||
|     } | ||||
| 
 | ||||
|     console.log(`${LOG} Injecting widget… v1.15`); | ||||
| 
 | ||||
|     await ensureThreadId(); | ||||
|     injectStyles(); | ||||
| 
 | ||||
|     const btn      = createFloatingButton(); | ||||
|     createTooltip(btn); | ||||
|     const panel    = createSidebar(); | ||||
|     const chatLog  = $("#ht-ai-chat"); | ||||
|     const sendBtn  = $("#ht-ai-send"); | ||||
|     const inputBox = $("#ht-ai-question"); | ||||
|     const resetBtn = $("#ht-ai-reset"); | ||||
|     const closeBtn = $("#ht-ai-close"); | ||||
| 
 | ||||
|     /* ------------------- Selection snapshot ------------------- */ | ||||
|     let savedSelection = ""; | ||||
|     btn.addEventListener("pointerdown", () => { | ||||
|       savedSelection = window.getSelection().toString().trim(); | ||||
|     }); | ||||
| 
 | ||||
|     /* ------------------- Helpers ------------------------------ */ | ||||
|     function addMsg(text, cls) { | ||||
|       const b = document.createElement("div"); | ||||
|       b.className = `ht-msg ${cls}`; | ||||
| 
 | ||||
|       // ✨ assistant replies rendered as Markdown + sanitised
 | ||||
|       if (cls === "ht-ai") { | ||||
|         b.innerHTML = mdToSafeHTML(text); | ||||
|       } else { | ||||
|         // user / context bubbles stay plain-text
 | ||||
|         b.textContent = text; | ||||
|       } | ||||
| 
 | ||||
|       chatLog.appendChild(b); | ||||
|       chatLog.scrollTop = chatLog.scrollHeight; | ||||
|       return b; | ||||
|     } | ||||
|     const LOADER_HTML = | ||||
|       '<span class="ht-loading"><span></span><span></span><span></span></span>'; | ||||
| 
 | ||||
|     function setInputDisabled(d) { | ||||
|       inputBox.disabled = d; | ||||
|       sendBtn.disabled  = d; | ||||
|     } | ||||
|     function clearThreadCookie() { | ||||
|       document.cookie = "threadId=; Path=/; Max-Age=0"; | ||||
|       threadId        = null; | ||||
|     } | ||||
|     function resetConversation() { | ||||
|       chatLog.innerHTML = ""; | ||||
|       clearThreadCookie(); | ||||
|       panel.classList.remove("open"); | ||||
|     } | ||||
| 
 | ||||
|     /* ------------------- Panel open / close ------------------- */ | ||||
|     btn.addEventListener("click", () => { | ||||
|       if (!savedSelection) { | ||||
|         alert("Please highlight some text first to then ask HackTricks AI about it."); | ||||
|         return; | ||||
|       } | ||||
|       if (savedSelection.length > MAX_CONTEXT) { | ||||
|         alert( | ||||
|           `Highlighted text is too long (${savedSelection.length} chars). Max allowed: ${MAX_CONTEXT}.` | ||||
|         ); | ||||
|         return; | ||||
|       } | ||||
|       chatLog.innerHTML = ""; | ||||
|       addMsg(savedSelection, "ht-context"); | ||||
|       panel.classList.add("open"); | ||||
|       inputBox.focus(); | ||||
|     }); | ||||
|     closeBtn.addEventListener("click", resetConversation); | ||||
|     resetBtn.addEventListener("click", resetConversation); | ||||
| 
 | ||||
|     /* --------------------------- Messaging --------------------------- */ | ||||
|     async function sendMessage(question, context = null) { | ||||
|       if (!threadId) await ensureThreadId(); | ||||
|       if (isRunning) { | ||||
|         addMsg("Please wait until the current operation completes.", "ht-ai"); | ||||
|         return; | ||||
|       } | ||||
| 
 | ||||
|       isRunning = true; | ||||
|       setInputDisabled(true); | ||||
|       const loadingBubble = addMsg("", "ht-ai"); | ||||
|       loadingBubble.innerHTML = LOADER_HTML; | ||||
| 
 | ||||
|       const content = context | ||||
|         ? `### Context:\n${context}\n\n### Question to answer:\n${question}` | ||||
|         : question; | ||||
|       try { | ||||
|         const res = await fetch(`${API_BASE}/${threadId}/messages`, { | ||||
|           method: "POST", | ||||
|           credentials: "include", | ||||
|           headers: { "Content-Type": "application/json" }, | ||||
|           body: JSON.stringify({ content }) | ||||
|         }); | ||||
|         if (!res.ok) { | ||||
|           let err = `Unknown error: ${res.status}`; | ||||
|           try { | ||||
|             const e = await res.json(); | ||||
|             if (e.error) err = `Error: ${e.error}`; | ||||
|             else if (res.status === 429) | ||||
|               err = "Rate limit exceeded. Please try again later."; | ||||
|           } catch (_) {} | ||||
|           loadingBubble.textContent = err; | ||||
|           return; | ||||
|         } | ||||
|         const data = await res.json(); | ||||
|         loadingBubble.remove(); | ||||
|         if (Array.isArray(data.response)) | ||||
|           data.response.forEach((p) => { | ||||
|             addMsg( | ||||
|               p.type === "text" && p.text && p.text.value | ||||
|                 ? p.text.value | ||||
|                 : JSON.stringify(p), | ||||
|               "ht-ai" | ||||
|             ); | ||||
|           }); | ||||
|         else if (typeof data.response === "string") | ||||
|           addMsg(data.response, "ht-ai"); | ||||
|         else addMsg(JSON.stringify(data, null, 2), "ht-ai"); | ||||
|       } catch (e) { | ||||
|         console.error("Error sending message:", e); | ||||
|         loadingBubble.textContent = "An unexpected error occurred."; | ||||
|       } finally { | ||||
|         isRunning = false; | ||||
|         setInputDisabled(false); | ||||
|         chatLog.scrollTop = chatLog.scrollHeight; | ||||
|       } | ||||
|     } | ||||
| 
 | ||||
|     async function handleSend() { | ||||
|       const q = inputBox.value.trim(); | ||||
|       if (!q) return; | ||||
|       if (q.length > MAX_QUESTION) { | ||||
|         alert( | ||||
|           `Your question is too long (${q.length} chars). Max allowed: ${MAX_QUESTION}.` | ||||
|         ); | ||||
|         return; | ||||
|       } | ||||
|       inputBox.value = ""; | ||||
|       addMsg(q, "ht-user"); | ||||
|       await sendMessage(q, savedSelection || null); | ||||
|     } | ||||
|     sendBtn.addEventListener("click", handleSend); | ||||
|     inputBox.addEventListener("keydown", (e) => { | ||||
|       if (e.key === "Enter" && !e.shiftKey) { | ||||
|         e.preventDefault(); | ||||
|         handleSend(); | ||||
|       } | ||||
|     }); | ||||
|   } | ||||
| 
 | ||||
|   /* ==================================================================== */ | ||||
|   async function ensureThreadId() { | ||||
|     const m = document.cookie.match(/threadId=([^;]+)/); | ||||
|     if (m && m[1]) { | ||||
|       threadId = m[1]; | ||||
|       return; | ||||
|     } | ||||
|     try { | ||||
|       const r = await fetch(API_BASE, { method: "POST", credentials: "include" }); | ||||
|       const d = await r.json(); | ||||
|       if (!r.ok || !d.threadId) throw new Error(`${r.status} ${r.statusText}`); | ||||
|       threadId       = d.threadId; | ||||
|       document.cookie = | ||||
|         `threadId=${threadId}; Path=/; Secure; SameSite=Strict; Max-Age=7200`; | ||||
|     } catch (e) { | ||||
|       console.error("Error creating threadId:", e); | ||||
|       alert("Failed to initialise the conversation. Please refresh and try again."); | ||||
|       throw e; | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   /* ==================================================================== */ | ||||
|   function injectStyles() { | ||||
|     const css = ` | ||||
|         #ht-ai-btn{position:fixed;bottom:20px;left:50%;transform:translateX(-50%);width:60px;height:60px;border-radius:50%;background:#1e1e1e;color:#fff;font-size:28px;display:flex;align-items:center;justify-content:center;cursor:pointer;z-index:99999;box-shadow:0 2px 8px rgba(0,0,0,.4);transition:opacity .2s} | ||||
|         #ht-ai-btn:hover{opacity:.85} | ||||
|         @media(max-width:768px){#ht-ai-btn{display:none}} | ||||
| @ -132,10 +283,50 @@ | ||||
|         @keyframes ht-bounce{0%,80%,100%{transform:scale(0);}40%{transform:scale(1);} } | ||||
|         ::selection{background:#ffeb3b;color:#000} | ||||
|         ::-moz-selection{background:#ffeb3b;color:#000}`;
 | ||||
|       const s=document.createElement("style"); s.id="ht-ai-style"; s.textContent=css; document.head.appendChild(s);}   | ||||
|    | ||||
|     function createFloatingButton(){ const d=document.createElement("div"); d.id="ht-ai-btn"; d.textContent="🤖"; document.body.appendChild(d); return d; } | ||||
|     function createTooltip(btn){ const t=document.createElement("div"); t.id="ht-ai-tooltip"; t.textContent=TOOLTIP_TEXT; document.body.appendChild(t); btn.addEventListener("mouseenter",()=>{const r=btn.getBoundingClientRect(); t.style.left=`${r.left+r.width/2}px`; t.style.top=`${r.top}px`; t.classList.add("show");}); btn.addEventListener("mouseleave",()=>t.classList.remove("show")); } | ||||
|     function createSidebar(){ const p=document.createElement("div"); p.id="ht-ai-panel"; p.innerHTML=`<div id="ht-ai-header"><strong>HackTricksAI Chat</strong><div class="ht-actions"><button id="ht-ai-reset" title="Reset">↺</button><span id="ht-ai-close" title="Close">✖</span></div></div><div id="ht-ai-chat"></div><div id="ht-ai-input"><textarea id="ht-ai-question" placeholder="Type your question…"></textarea><button id="ht-ai-send">Send</button></div>`; document.body.appendChild(p); return p; } | ||||
|   })(); | ||||
|    | ||||
|     const s = document.createElement("style"); | ||||
|     s.id = "ht-ai-style"; | ||||
|     s.textContent = css; | ||||
|     document.head.appendChild(s); | ||||
|   } | ||||
| 
 | ||||
|   function createFloatingButton() { | ||||
|     const d = document.createElement("div"); | ||||
|     d.id = "ht-ai-btn"; | ||||
|     d.textContent = "🤖"; | ||||
|     document.body.appendChild(d); | ||||
|     return d; | ||||
|   } | ||||
| 
 | ||||
|   function createTooltip(btn) { | ||||
|     const t = document.createElement("div"); | ||||
|     t.id = "ht-ai-tooltip"; | ||||
|     t.textContent = TOOLTIP_TEXT; | ||||
|     document.body.appendChild(t); | ||||
|     btn.addEventListener("mouseenter", () => { | ||||
|       const r = btn.getBoundingClientRect(); | ||||
|       t.style.left = `${r.left + r.width / 2}px`; | ||||
|       t.style.top  = `${r.top}px`; | ||||
|       t.classList.add("show"); | ||||
|     }); | ||||
|     btn.addEventListener("mouseleave", () => t.classList.remove("show")); | ||||
|   } | ||||
| 
 | ||||
|   function createSidebar() { | ||||
|     const p = document.createElement("div"); | ||||
|     p.id = "ht-ai-panel"; | ||||
|     p.innerHTML = ` | ||||
|       <div id="ht-ai-header"><strong>HackTricks AI Chat</strong> | ||||
|         <div class="ht-actions"> | ||||
|           <button id="ht-ai-reset" title="Reset">↺</button> | ||||
|           <span id="ht-ai-close" title="Close">✖</span> | ||||
|         </div> | ||||
|       </div> | ||||
|       <div id="ht-ai-chat"></div> | ||||
|       <div id="ht-ai-input"> | ||||
|         <textarea id="ht-ai-question" placeholder="Type your question…"></textarea> | ||||
|         <button id="ht-ai-send">Send</button> | ||||
|       </div>`; | ||||
|     document.body.appendChild(p); | ||||
|     return p; | ||||
|   } | ||||
| })(); | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user