Section 01

Report setup

Cover page details, logos and document control. These appear on the title page and header of every page.

Engagement

Logos

Click to upload your logo

Document control

Section 02

Executive summary

The non-technical overview for leadership. Severity counts and the overall risk rating are calculated automatically from your findings.

Overall risk rating

Summary narrative

Section 03

Scope & methodology

What was tested, how, and against which standards. Sensible defaults are pre-filled — edit as needed.

In-scope assets

Testing approach

Standards & frameworks

Tools used

Quick add:
Section 04

Findings

Add one entry per vulnerability. Findings are auto-numbered and sorted by severity in the report. Attach screenshots as evidence.

`; const blob=new Blob(['\ufeff'+doc],{type:'application/msword'}); dl(blob,(sanitize(state.clientName)||'VAPT')+'-Report.doc'); toast('Word file downloaded. Word may warn the format differs — that is expected; choose Yes to open.'); }catch(e){toast('Word export failed: '+(e&&e.message||e));} } function saveJSON(){ const clean={...state};clean.findings=state.findings.map(f=>{const{_open,...r}=f;return r;}); const blob=new Blob([JSON.stringify(clean,null,2)],{type:'application/json'}); dl(blob,(sanitize(state.clientName)||'VAPT')+'-draft.json'); toast('Draft saved as JSON — reload it anytime'); } function loadJSON(input){ const file=input.files[0];if(!file)return; const r=new FileReader(); if(state.findings.length&&!confirm('Load this draft? It will replace the report currently open.')){input.value='';return;} r.onerror=()=>{toast('Could not read that file');input.value='';}; r.onload=e=>{ try{ const d=JSON.parse(e.target.result); if(!adopt(d)){toast('That file is not a report draft');return;} save(); toast('Draft loaded'); }catch(err){ toast('Could not read that file — is it a .json draft?'); } finally{ input.value=''; } }; r.readAsText(file); } /* ---------------- helpers ---------------- */ function esc(t){return(t==null?'':String(t)).replace(/&/g,'&').replace(//g,'>');} /* Attribute context: quotes MUST go too. Without this, an imported draft could break out of src="…" or out of the JS string inside an inline handler. */ function escA(t){return esc(t).replace(/"/g,'"').replace(/'/g,''').replace(/`/g,'`');} /* Only ever render images we know are inline raster data. Blocks javascript:, data:text/html, and remote trackers arriving via an imported draft. */ const IMG_RE=/^data:image\/(png|jpe?g|gif|webp|svg\+xml);base64,[A-Za-z0-9+/=\s]+$/i; function safeImg(src){return typeof src==='string'&&IMG_RE.test(src.trim());} function sanitize(t){return(t||'').replace(/[^a-z0-9]+/gi,'-').replace(/^-|-$/g,'').slice(0,40);} function uid(){return'f'+Math.random().toString(36).slice(2,9);} function dl(blob,name){const a=document.createElement('a');a.href=URL.createObjectURL(blob);a.download=name;a.click();setTimeout(()=>URL.revokeObjectURL(a.href),1000);} let toastT;function toast(m){const t=document.getElementById('toast');t.textContent=m;t.classList.add('show');clearTimeout(toastT);toastT=setTimeout(()=>t.classList.remove('show'),2200);} function refreshAll(){ bindFields();labelFields();wireDropZones();renderChips('asset');renderChips('tool');renderStandards();renderFindings();renderNav();renderSumStrip(); renderLogo('clientLogo');renderLogo('vendorLogo'); document.querySelectorAll('[data-f]').forEach(el=>{const f=el.dataset.f;if(state[f]!==undefined)el.value=state[f];}); } /* ---------------- accessibility & drag-drop ---------------- The file had 36