No vulnerabilities logged yet.
';
} else {
// Sort by severity weight
const severityWeight = { "Critical": 5, "High": 4, "Medium": 3, "Low": 2, "Gas": 1, "Info": 0 };
const sortedFindings = [...findings].sort((a, b) => severityWeight[b.severity] - severityWeight[a.severity]);
sortedFindings.forEach(f => {
const card = document.createElement('div');
card.className = 'sca-finding-card';
card.innerHTML = `
Status: ${f.status}
Description:
${f.desc}
Recommendation:
${f.rec}
`;
findingsListDiv.appendChild(card);
});
}
// 5. Update Manage Table
renderManageTable();
}
function renderManageTable() {
scaManageBody = document.getElementById('sca-manage-body'); // re-grab to be safe
scaManageBody.innerHTML = "";
findings.forEach((f, index) => {
const tr = document.createElement('tr');
tr.innerHTML = `
${f.id} |
${f.severity} |
${f.title} |
|
`;
scaManageBody.appendChild(tr);
});
}
// Add global delete function
window.deleteFinding = function(index) {
if(confirm("Delete this finding?")) {
findings.splice(index, 1);
updateDashboard();
}
}
function addFinding() {
const title = inpTitle.value.trim();
const severity = inpSev.value;
const status = inpStatus.value;
const desc = inpDesc.value.trim();
const rec = inpRec.value.trim();
let id = inpId.value.trim();
if (!title || !desc) {
alert("Please enter a Title and Description.");
return;
}
// Auto-gen ID if empty based on Severity char + number
if (!id) {
const prefix = severity.charAt(0).toUpperCase();
// Count existing of this letter
const count = findings.filter(f => f.id.startsWith(prefix)).length + 1;
id = `${prefix}-0${count}`;
}
findings.push({ id, title, severity, status, desc, rec });
// Reset form
inpTitle.value = "";
inpDesc.value = "";
inpRec.value = "";
inpId.value = "";
// Show success
addMsg.style.display = "block";
setTimeout(() => { addMsg.style.display = "none"; }, 2000);
updateDashboard();
}
function loadSampleData() {
projectInfo.name = "EtherVault Protocol";
projectInfo.commit = "e7f8a9b...";
findings = [
{
id: "C-01",
title: "Reentrancy in withdraw() function",
severity: "Critical",
status: "Fixed",
desc: "The `withdraw` function performs an external call to send ETH to the user before updating the user's balance state variable. A malicious contract can recursively call `withdraw` before the balance is updated, draining the contract.",
rec: "Implement the Checks-Effects-Interactions pattern. Update the state variable `balances[msg.sender] = 0` before making the external call. Alternatively, use OpenZeppelin's `ReentrancyGuard` modifier."
},
{
id: "H-01",
title: "Unchecked Return Value on Transfer",
severity: "High",
status: "Open",
desc: "The contract uses `ERC20.transfer` without checking the boolean return value. Some tokens return false on failure rather than reverting. This could lead to the contract believing a transfer succeeded when it failed.",
rec: "Use OpenZeppelin's `SafeERC20` library and `safeTransfer` to handle non-standard ERC20 implementations automatically."
},
{
id: "M-01",
title: "Centralization Risk: Owner can pause indefinitely",
severity: "Medium",
status: "Acknowledged",
desc: "The `pause()` function allows the owner to freeze all assets in the protocol without a timelock. If the private key is compromised, user funds are stuck.",
rec: "Implement a TimelockController or a multi-sig wallet for sensitive administrative functions."
},
{
id: "G-01",
title: "Use of storage instead of memory in loop",
severity: "Gas",
status: "Fixed",
desc: "In `updatePools()`, the code reads `pools[i]` from storage in every iteration. This consumes excessive gas.",
rec: "Cache the array length and read struct data into `memory` variables inside the loop."
}
];
// Update config inputs to match
confProject.value = projectInfo.name;
confCommit.value = projectInfo.commit;
updateDashboard();
alert("Sample vulnerability data loaded.");
openTab(0);
}
// --- Tab Logic ---
function openTab(index) {
tabPanes.forEach(p => p.classList.remove('sca-active'));
tabLinks.forEach(l => l.classList.remove('sca-active'));
tabPanes[index].classList.add('sca-active');
tabLinks[index].classList.add('sca-active');
}
tabLinks.forEach((link, index) => {
link.addEventListener('click', () => openTab(index));
});
// --- Event Listeners ---
addBtn.addEventListener('click', addFinding);
updateConfigBtn.addEventListener('click', () => {
projectInfo.name = confProject.value || "Project";
projectInfo.commit = confCommit.value || "N/A";
updateDashboard();
alert("Project details updated.");
});
loadSampleBtn.addEventListener('click', loadSampleData);
resetBtn.addEventListener('click', () => {
if(confirm("Clear all findings?")) {
findings = [];
confProject.value = "";
confCommit.value = "";
projectInfo = { name: "[Project Name]", commit: "--" };
updateDashboard();
}
});
// PDF Generation
downloadPdfBtn.addEventListener('click', async () => {
const element = document.getElementById('sca-pdf-export-area');
const btn = downloadPdfBtn;
btn.textContent = "Generating...";
btn.disabled = true;
try {
const canvas = await html2canvas(element, { scale: 2, useCORS: true, backgroundColor: '#ffffff' });
const imgData = canvas.toDataURL('image/png');
const { jsPDF } = window.jspdf;
const pdf = new jsPDF('p', 'mm', 'a4');
const pdfWidth = pdf.internal.pageSize.getWidth();
const pdfHeight = pdf.internal.pageSize.getHeight();
const margin = 10;
const printWidth = pdfWidth - (margin * 2);
const imgHeight = (canvas.height * printWidth) / canvas.width;
let heightLeft = imgHeight;
let position = margin;
// First page
pdf.addImage(imgData, 'PNG', margin, position, printWidth, imgHeight);
heightLeft -= (pdfHeight - margin*2);
// Multi-page loop
while (heightLeft > 0) {
position = heightLeft - imgHeight + margin;
pdf.addPage();
pdf.addImage(imgData, 'PNG', margin, position - margin, printWidth, imgHeight);
heightLeft -= (pdfHeight - margin*2);
}
pdf.save('Smart_Contract_Audit_Report.pdf');
} catch (err) {
console.error(err);
alert("Error generating PDF.");
} finally {
btn.textContent = "Download Audit Report (PDF)";
btn.disabled = false;
}
});
// Initialize View
updateDashboard();
});