-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathapp.js
More file actions
81 lines (73 loc) · 2.93 KB
/
Copy pathapp.js
File metadata and controls
81 lines (73 loc) · 2.93 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
const targetInput = document.querySelector("#target");
const scanButton = document.querySelector("#scan");
const summaryEl = document.querySelector("#summary");
const findingsEl = document.querySelector("#findings");
const inventoryEl = document.querySelector("#inventory");
const downloadEl = document.querySelector("#download");
targetInput.value = "sample-repo";
function escapeHtml(value) {
return String(value ?? "").replace(/[&<>"']/g, (char) => ({
"&": "&",
"<": "<",
">": ">",
'"': """,
"'": "'"
})[char]);
}
function metric(label, value) {
return `<div class="metric"><span>${escapeHtml(label)}</span><strong>${escapeHtml(value)}</strong></div>`;
}
function render(report) {
if (report.error) {
summaryEl.innerHTML = metric("Error", report.error);
findingsEl.innerHTML = "";
inventoryEl.innerHTML = "";
return;
}
summaryEl.innerHTML = [
metric("Risk score", `${report.risk_score}/100`),
metric("Classification", report.classification),
metric("Workflows", report.workflow_count),
metric("Findings", report.finding_count)
].join("");
findingsEl.innerHTML = report.findings.length
? report.findings.map((item) => `
<article class="finding">
<span class="tag ${escapeHtml(item.severity)}">${escapeHtml(item.severity)}</span>
<h3>${escapeHtml(item.title)}</h3>
<p><strong>Workflow:</strong> <code>${escapeHtml(item.workflow)}</code></p>
<p><strong>Evidence:</strong> ${escapeHtml(item.evidence)}</p>
<p><strong>Impact:</strong> ${escapeHtml(item.impact)}</p>
<p><strong>Fix:</strong> ${escapeHtml(item.fix)}</p>
</article>
`).join("")
: "<p>No obvious blast-radius issues were detected by the current rule set.</p>";
inventoryEl.innerHTML = report.workflows.map((workflow) => `
<article class="workflow">
<h3><code>${escapeHtml(workflow.file)}</code></h3>
<p><strong>Events:</strong> ${escapeHtml(workflow.events.join(", ") || "not detected")}</p>
<p><strong>Permissions:</strong> <code>${escapeHtml(JSON.stringify(workflow.permissions))}</code></p>
<p><strong>Actions:</strong> ${escapeHtml(workflow.actions.join(", ") || "none detected")}</p>
<p><strong>Secrets:</strong> ${escapeHtml(workflow.secrets_referenced.join(", ") || "none detected")}</p>
</article>
`).join("");
downloadEl.href = `/api/report.md?target=${encodeURIComponent(targetInput.value)}`;
}
async function scan() {
scanButton.disabled = true;
scanButton.textContent = "Scanning...";
try {
const response = await fetch(`/api/scan?target=${encodeURIComponent(targetInput.value)}`);
render(await response.json());
} catch (error) {
render({ error: error.message });
} finally {
scanButton.disabled = false;
scanButton.textContent = "Scan";
}
}
scanButton.addEventListener("click", scan);
targetInput.addEventListener("keydown", (event) => {
if (event.key === "Enter") scan();
});
scan();