fullcourtdefense-cli 1.7.20 → 1.7.21
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/commands/agentCi.js +1 -1
- package/dist/commands/local-scan.js +761 -761
- package/dist/version.json +1 -1
- package/package.json +44 -44
|
@@ -1080,769 +1080,769 @@ function localMcpServerOptions() {
|
|
|
1080
1080
|
function compactScanHtml(args) {
|
|
1081
1081
|
const auth = currentShieldConfig(args);
|
|
1082
1082
|
const isConfigured = Boolean(auth.shieldId && auth.shieldKey);
|
|
1083
|
-
return `<!doctype html>
|
|
1084
|
-
<html lang="en">
|
|
1085
|
-
<head>
|
|
1086
|
-
<meta charset="utf-8" />
|
|
1087
|
-
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
1088
|
-
<title>Full Court Defense Local Scanner</title>
|
|
1089
|
-
<style>
|
|
1090
|
-
:root { color-scheme: light; font-family: Inter, ui-sans-serif, system-ui, -apple-system, Segoe UI, sans-serif; }
|
|
1091
|
-
body { margin: 0; background: #f8fafc; color: #0f172a; }
|
|
1092
|
-
.wrap { max-width: 1100px; margin: 0 auto; padding: 28px 18px 48px; }
|
|
1093
|
-
.hero { border: 1px solid #bfdbfe; background: linear-gradient(135deg, #eff6ff, #ecfdf5); border-radius: 24px; padding: 22px; box-shadow: 0 10px 30px rgba(15,23,42,.06); }
|
|
1094
|
-
.auth { margin-top: 18px; border: 1px solid ${isConfigured ? '#bbf7d0' : '#fed7aa'}; background: ${isConfigured ? '#f0fdf4' : '#fff7ed'}; border-radius: 20px; padding: 16px; }
|
|
1095
|
-
.eyebrow { color: #1d4ed8; font-size: 12px; font-weight: 900; letter-spacing: .08em; text-transform: uppercase; }
|
|
1096
|
-
h1 { margin: 6px 0 8px; font-size: 26px; line-height: 1.15; }
|
|
1097
|
-
p { color: #475569; line-height: 1.55; }
|
|
1098
|
-
.grid { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 12px; }
|
|
1099
|
-
.menuShell { display: grid; grid-template-columns: minmax(0, 1.15fr) minmax(0, 1.15fr) minmax(180px, .7fr); gap: 14px; margin-top: 18px; align-items: stretch; }
|
|
1100
|
-
.menuGroup { border: 1px solid #dbeafe; background: rgba(255,255,255,.7); border-radius: 20px; padding: 12px; }
|
|
1101
|
-
.menuGroup.run { border-color: #bbf7d0; background: #f0fdf4; }
|
|
1102
|
-
.menuLabel { color: #475569; font-size: 11px; font-weight: 900; letter-spacing: .08em; text-transform: uppercase; margin: 0 0 9px; }
|
|
1103
|
-
.steps { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 10px; margin-top: 18px; }
|
|
1104
|
-
.step { border: 1px solid #e2e8f0; background: white; color: #64748b; border-radius: 16px; padding: 12px; font-size: 12px; font-weight: 900; }
|
|
1105
|
-
.step.active { border-color: #2563eb; background: #eff6ff; color: #1d4ed8; }
|
|
1106
|
-
.step.done { border-color: #bbf7d0; background: #f0fdf4; color: #166534; }
|
|
1107
|
-
.sectionTitle { display: flex; align-items: center; justify-content: space-between; gap: 12px; margin-bottom: 12px; }
|
|
1108
|
-
.sectionTitle h2 { margin: 0; font-size: 18px; }
|
|
1109
|
-
.muted { color: #64748b; font-size: 13px; }
|
|
1110
|
-
.card { cursor: pointer; border: 2px solid #e2e8f0; background: white; border-radius: 18px; padding: 16px; text-align: left; transition: .15s ease; }
|
|
1111
|
-
.workflowCard { min-height: 118px; }
|
|
1112
|
-
.workflowCard b { font-size: 18px; }
|
|
1113
|
-
.workflowCard span { font-size: 13px; }
|
|
1114
|
-
.landingScanGrid { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 10px; }
|
|
1115
|
-
.scanTypeGrid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 10px; margin: 12px 0; }
|
|
1116
|
-
.advancedScanGrid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 10px; margin: 8px 0 12px; }
|
|
1117
|
-
.card:hover, .card.active { border-color: #2563eb; background: #eff6ff; transform: translateY(-1px); }
|
|
1118
|
-
.card b { display: block; font-size: 14px; margin-bottom: 4px; }
|
|
1119
|
-
.card span { color: #64748b; font-size: 12px; line-height: 1.35; }
|
|
1120
|
-
.panel { margin-top: 18px; border: 1px solid #e2e8f0; border-radius: 22px; background: white; padding: 18px; }
|
|
1121
|
-
label { display: block; font-size: 12px; font-weight: 800; color: #475569; margin: 12px 0 6px; }
|
|
1122
|
-
input, select, textarea { box-sizing: border-box; width: 100%; border: 1px solid #cbd5e1; border-radius: 12px; padding: 11px 12px; font: inherit; font-size: 14px; }
|
|
1123
|
-
textarea { min-height: 92px; font-family: ui-monospace, SFMono-Regular, Consolas, monospace; }
|
|
1124
|
-
.two { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; }
|
|
1125
|
-
.actions { display: flex; gap: 10px; align-items: center; margin-top: 16px; flex-wrap: wrap; }
|
|
1126
|
-
button.primary { border: 0; background: #16a34a; color: white; border-radius: 13px; padding: 12px 18px; font-weight: 900; cursor: pointer; box-shadow: 0 12px 24px rgba(22,163,74,.18); }
|
|
1127
|
-
button.primary:disabled { background: #cbd5e1; cursor: not-allowed; box-shadow: none; }
|
|
1128
|
-
button.secondary { border: 1px solid #cbd5e1; background: white; color: #334155; border-radius: 13px; padding: 11px 16px; font-weight: 800; cursor: pointer; }
|
|
1129
|
-
.note { margin-top: 14px; border: 1px solid #bbf7d0; background: #f0fdf4; color: #166534; border-radius: 16px; padding: 12px; font-size: 13px; }
|
|
1130
|
-
.console { margin-top: 18px; overflow: hidden; border-radius: 22px; border: 1px solid #1e293b; background: #020617; box-shadow: 0 18px 40px rgba(15,23,42,.18); }
|
|
1131
|
-
.consoleHead { display: flex; justify-content: space-between; align-items: center; gap: 12px; padding: 12px 14px; border-bottom: 1px solid #1e293b; background: #0f172a; color: #e2e8f0; }
|
|
1132
|
-
.consoleHead b { font-size: 13px; }
|
|
1133
|
-
.pill { border: 1px solid #334155; background: #111827; color: #93c5fd; border-radius: 999px; padding: 4px 9px; font-size: 11px; font-weight: 800; }
|
|
1134
|
-
pre { margin: 0; white-space: pre-wrap; overflow: auto; background: #020617; color: #d1fae5; padding: 14px; height: 380px; font: 12px/1.55 ui-monospace, SFMono-Regular, Consolas, monospace; }
|
|
1135
|
-
.resultCard { margin-top: 14px; border: 1px solid #dbeafe; background: #f8fbff; border-radius: 16px; padding: 14px; }
|
|
1136
|
-
.resultHead { display: flex; justify-content: space-between; align-items: center; gap: 10px; margin-bottom: 10px; }
|
|
1137
|
-
.resultTitle { font-size: 14px; font-weight: 900; color: #0f172a; }
|
|
1138
|
-
.statusTag { border: 1px solid #cbd5e1; border-radius: 999px; padding: 4px 10px; font-size: 12px; font-weight: 900; }
|
|
1139
|
-
.statusTag.pass { border-color: #bbf7d0; color: #166534; background: #f0fdf4; }
|
|
1140
|
-
.statusTag.fail { border-color: #fecaca; color: #991b1b; background: #fef2f2; }
|
|
1141
|
-
.resultGrid { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 10px; }
|
|
1142
|
-
.resultMetric { border: 1px solid #e2e8f0; background: white; border-radius: 12px; padding: 10px; }
|
|
1143
|
-
.resultMetric b { display: block; font-size: 11px; color: #64748b; text-transform: uppercase; letter-spacing: .05em; margin-bottom: 4px; }
|
|
1144
|
-
.resultMetric span { font-size: 16px; font-weight: 800; color: #0f172a; }
|
|
1145
|
-
.resultMeta { margin-top: 10px; color: #334155; font-size: 12px; line-height: 1.5; }
|
|
1146
|
-
.resultMeta a { color: #1d4ed8; }
|
|
1147
|
-
.mcpBanner { margin-top: 10px; border-radius: 14px; padding: 12px; font-size: 13px; line-height: 1.45; }
|
|
1148
|
-
.mcpBanner.ok { border: 1px solid #bbf7d0; background: #f0fdf4; color: #166534; }
|
|
1149
|
-
.mcpBanner.blocked { border: 1px solid #fecaca; background: #fef2f2; color: #991b1b; }
|
|
1150
|
-
.mcpCards { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 10px; margin-top: 10px; }
|
|
1151
|
-
.mcpCard { border: 1px solid #e2e8f0; background: white; border-radius: 12px; padding: 10px; }
|
|
1152
|
-
.mcpCard b { display: block; color: #64748b; font-size: 11px; text-transform: uppercase; letter-spacing: .05em; margin-bottom: 4px; }
|
|
1153
|
-
.mcpCard span { color: #0f172a; font-size: 13px; font-weight: 750; overflow-wrap: anywhere; }
|
|
1154
|
-
.localTable { width: 100%; border-collapse: collapse; margin-top: 10px; font-size: 12px; overflow: hidden; border-radius: 12px; }
|
|
1155
|
-
.localTable th, .localTable td { border-bottom: 1px solid #e2e8f0; padding: 8px; text-align: left; vertical-align: top; }
|
|
1156
|
-
.localTable th { color: #475569; background: #f8fafc; font-size: 11px; text-transform: uppercase; letter-spacing: .05em; }
|
|
1157
|
-
.copyBox { margin-top: 10px; }
|
|
1158
|
-
.hidden { display: none; }
|
|
1159
|
-
@media (max-width: 800px) { .grid, .two, .menuShell { grid-template-columns: 1fr; } }
|
|
1160
|
-
</style>
|
|
1161
|
-
</head>
|
|
1162
|
-
<body>
|
|
1163
|
-
<main class="wrap">
|
|
1164
|
-
<section class="hero">
|
|
1165
|
-
<div class="eyebrow">Local Web Console</div>
|
|
1166
|
-
<h1>Choose a local workflow</h1>
|
|
1167
|
-
<p>Pick one clear path. Run MCP Tool is a one-off tool call. Security Scan is a red-team scan against an internal endpoint, RAG target, or MCP server.</p>
|
|
1168
|
-
<div class="auth">
|
|
1169
|
-
<div class="eyebrow">${isConfigured ? 'Authenticated' : 'Authentication Required'}</div>
|
|
1083
|
+
return `<!doctype html>
|
|
1084
|
+
<html lang="en">
|
|
1085
|
+
<head>
|
|
1086
|
+
<meta charset="utf-8" />
|
|
1087
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
1088
|
+
<title>Full Court Defense Local Scanner</title>
|
|
1089
|
+
<style>
|
|
1090
|
+
:root { color-scheme: light; font-family: Inter, ui-sans-serif, system-ui, -apple-system, Segoe UI, sans-serif; }
|
|
1091
|
+
body { margin: 0; background: #f8fafc; color: #0f172a; }
|
|
1092
|
+
.wrap { max-width: 1100px; margin: 0 auto; padding: 28px 18px 48px; }
|
|
1093
|
+
.hero { border: 1px solid #bfdbfe; background: linear-gradient(135deg, #eff6ff, #ecfdf5); border-radius: 24px; padding: 22px; box-shadow: 0 10px 30px rgba(15,23,42,.06); }
|
|
1094
|
+
.auth { margin-top: 18px; border: 1px solid ${isConfigured ? '#bbf7d0' : '#fed7aa'}; background: ${isConfigured ? '#f0fdf4' : '#fff7ed'}; border-radius: 20px; padding: 16px; }
|
|
1095
|
+
.eyebrow { color: #1d4ed8; font-size: 12px; font-weight: 900; letter-spacing: .08em; text-transform: uppercase; }
|
|
1096
|
+
h1 { margin: 6px 0 8px; font-size: 26px; line-height: 1.15; }
|
|
1097
|
+
p { color: #475569; line-height: 1.55; }
|
|
1098
|
+
.grid { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 12px; }
|
|
1099
|
+
.menuShell { display: grid; grid-template-columns: minmax(0, 1.15fr) minmax(0, 1.15fr) minmax(180px, .7fr); gap: 14px; margin-top: 18px; align-items: stretch; }
|
|
1100
|
+
.menuGroup { border: 1px solid #dbeafe; background: rgba(255,255,255,.7); border-radius: 20px; padding: 12px; }
|
|
1101
|
+
.menuGroup.run { border-color: #bbf7d0; background: #f0fdf4; }
|
|
1102
|
+
.menuLabel { color: #475569; font-size: 11px; font-weight: 900; letter-spacing: .08em; text-transform: uppercase; margin: 0 0 9px; }
|
|
1103
|
+
.steps { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 10px; margin-top: 18px; }
|
|
1104
|
+
.step { border: 1px solid #e2e8f0; background: white; color: #64748b; border-radius: 16px; padding: 12px; font-size: 12px; font-weight: 900; }
|
|
1105
|
+
.step.active { border-color: #2563eb; background: #eff6ff; color: #1d4ed8; }
|
|
1106
|
+
.step.done { border-color: #bbf7d0; background: #f0fdf4; color: #166534; }
|
|
1107
|
+
.sectionTitle { display: flex; align-items: center; justify-content: space-between; gap: 12px; margin-bottom: 12px; }
|
|
1108
|
+
.sectionTitle h2 { margin: 0; font-size: 18px; }
|
|
1109
|
+
.muted { color: #64748b; font-size: 13px; }
|
|
1110
|
+
.card { cursor: pointer; border: 2px solid #e2e8f0; background: white; border-radius: 18px; padding: 16px; text-align: left; transition: .15s ease; }
|
|
1111
|
+
.workflowCard { min-height: 118px; }
|
|
1112
|
+
.workflowCard b { font-size: 18px; }
|
|
1113
|
+
.workflowCard span { font-size: 13px; }
|
|
1114
|
+
.landingScanGrid { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 10px; }
|
|
1115
|
+
.scanTypeGrid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 10px; margin: 12px 0; }
|
|
1116
|
+
.advancedScanGrid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 10px; margin: 8px 0 12px; }
|
|
1117
|
+
.card:hover, .card.active { border-color: #2563eb; background: #eff6ff; transform: translateY(-1px); }
|
|
1118
|
+
.card b { display: block; font-size: 14px; margin-bottom: 4px; }
|
|
1119
|
+
.card span { color: #64748b; font-size: 12px; line-height: 1.35; }
|
|
1120
|
+
.panel { margin-top: 18px; border: 1px solid #e2e8f0; border-radius: 22px; background: white; padding: 18px; }
|
|
1121
|
+
label { display: block; font-size: 12px; font-weight: 800; color: #475569; margin: 12px 0 6px; }
|
|
1122
|
+
input, select, textarea { box-sizing: border-box; width: 100%; border: 1px solid #cbd5e1; border-radius: 12px; padding: 11px 12px; font: inherit; font-size: 14px; }
|
|
1123
|
+
textarea { min-height: 92px; font-family: ui-monospace, SFMono-Regular, Consolas, monospace; }
|
|
1124
|
+
.two { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; }
|
|
1125
|
+
.actions { display: flex; gap: 10px; align-items: center; margin-top: 16px; flex-wrap: wrap; }
|
|
1126
|
+
button.primary { border: 0; background: #16a34a; color: white; border-radius: 13px; padding: 12px 18px; font-weight: 900; cursor: pointer; box-shadow: 0 12px 24px rgba(22,163,74,.18); }
|
|
1127
|
+
button.primary:disabled { background: #cbd5e1; cursor: not-allowed; box-shadow: none; }
|
|
1128
|
+
button.secondary { border: 1px solid #cbd5e1; background: white; color: #334155; border-radius: 13px; padding: 11px 16px; font-weight: 800; cursor: pointer; }
|
|
1129
|
+
.note { margin-top: 14px; border: 1px solid #bbf7d0; background: #f0fdf4; color: #166534; border-radius: 16px; padding: 12px; font-size: 13px; }
|
|
1130
|
+
.console { margin-top: 18px; overflow: hidden; border-radius: 22px; border: 1px solid #1e293b; background: #020617; box-shadow: 0 18px 40px rgba(15,23,42,.18); }
|
|
1131
|
+
.consoleHead { display: flex; justify-content: space-between; align-items: center; gap: 12px; padding: 12px 14px; border-bottom: 1px solid #1e293b; background: #0f172a; color: #e2e8f0; }
|
|
1132
|
+
.consoleHead b { font-size: 13px; }
|
|
1133
|
+
.pill { border: 1px solid #334155; background: #111827; color: #93c5fd; border-radius: 999px; padding: 4px 9px; font-size: 11px; font-weight: 800; }
|
|
1134
|
+
pre { margin: 0; white-space: pre-wrap; overflow: auto; background: #020617; color: #d1fae5; padding: 14px; height: 380px; font: 12px/1.55 ui-monospace, SFMono-Regular, Consolas, monospace; }
|
|
1135
|
+
.resultCard { margin-top: 14px; border: 1px solid #dbeafe; background: #f8fbff; border-radius: 16px; padding: 14px; }
|
|
1136
|
+
.resultHead { display: flex; justify-content: space-between; align-items: center; gap: 10px; margin-bottom: 10px; }
|
|
1137
|
+
.resultTitle { font-size: 14px; font-weight: 900; color: #0f172a; }
|
|
1138
|
+
.statusTag { border: 1px solid #cbd5e1; border-radius: 999px; padding: 4px 10px; font-size: 12px; font-weight: 900; }
|
|
1139
|
+
.statusTag.pass { border-color: #bbf7d0; color: #166534; background: #f0fdf4; }
|
|
1140
|
+
.statusTag.fail { border-color: #fecaca; color: #991b1b; background: #fef2f2; }
|
|
1141
|
+
.resultGrid { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 10px; }
|
|
1142
|
+
.resultMetric { border: 1px solid #e2e8f0; background: white; border-radius: 12px; padding: 10px; }
|
|
1143
|
+
.resultMetric b { display: block; font-size: 11px; color: #64748b; text-transform: uppercase; letter-spacing: .05em; margin-bottom: 4px; }
|
|
1144
|
+
.resultMetric span { font-size: 16px; font-weight: 800; color: #0f172a; }
|
|
1145
|
+
.resultMeta { margin-top: 10px; color: #334155; font-size: 12px; line-height: 1.5; }
|
|
1146
|
+
.resultMeta a { color: #1d4ed8; }
|
|
1147
|
+
.mcpBanner { margin-top: 10px; border-radius: 14px; padding: 12px; font-size: 13px; line-height: 1.45; }
|
|
1148
|
+
.mcpBanner.ok { border: 1px solid #bbf7d0; background: #f0fdf4; color: #166534; }
|
|
1149
|
+
.mcpBanner.blocked { border: 1px solid #fecaca; background: #fef2f2; color: #991b1b; }
|
|
1150
|
+
.mcpCards { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 10px; margin-top: 10px; }
|
|
1151
|
+
.mcpCard { border: 1px solid #e2e8f0; background: white; border-radius: 12px; padding: 10px; }
|
|
1152
|
+
.mcpCard b { display: block; color: #64748b; font-size: 11px; text-transform: uppercase; letter-spacing: .05em; margin-bottom: 4px; }
|
|
1153
|
+
.mcpCard span { color: #0f172a; font-size: 13px; font-weight: 750; overflow-wrap: anywhere; }
|
|
1154
|
+
.localTable { width: 100%; border-collapse: collapse; margin-top: 10px; font-size: 12px; overflow: hidden; border-radius: 12px; }
|
|
1155
|
+
.localTable th, .localTable td { border-bottom: 1px solid #e2e8f0; padding: 8px; text-align: left; vertical-align: top; }
|
|
1156
|
+
.localTable th { color: #475569; background: #f8fafc; font-size: 11px; text-transform: uppercase; letter-spacing: .05em; }
|
|
1157
|
+
.copyBox { margin-top: 10px; }
|
|
1158
|
+
.hidden { display: none; }
|
|
1159
|
+
@media (max-width: 800px) { .grid, .two, .menuShell { grid-template-columns: 1fr; } }
|
|
1160
|
+
</style>
|
|
1161
|
+
</head>
|
|
1162
|
+
<body>
|
|
1163
|
+
<main class="wrap">
|
|
1164
|
+
<section class="hero">
|
|
1165
|
+
<div class="eyebrow">Local Web Console</div>
|
|
1166
|
+
<h1>Choose a local workflow</h1>
|
|
1167
|
+
<p>Pick one clear path. Run MCP Tool is a one-off tool call. Security Scan is a red-team scan against an internal endpoint, RAG target, or MCP server.</p>
|
|
1168
|
+
<div class="auth">
|
|
1169
|
+
<div class="eyebrow">${isConfigured ? 'Authenticated' : 'Authentication Required'}</div>
|
|
1170
1170
|
<p id="authStatus">${isConfigured
|
|
1171
1171
|
? `Connected to Shield ${htmlEscape(maskSecret(auth.shieldId))}. Reports will save to Full Court Defense.`
|
|
1172
|
-
: 'Configure CLI once with fullcourtdefense configure, then run local scans. UI uses saved local config automatically.'}</p>
|
|
1173
|
-
<div class="actions">
|
|
1174
|
-
<button class="secondary" id="toggleSetup">Change Shield settings</button>
|
|
1175
|
-
<button class="secondary" id="checkBackend">Check outbound backend</button>
|
|
1176
|
-
</div>
|
|
1177
|
-
<div id="setupPanel" class="hidden">
|
|
1178
|
-
<div class="two">
|
|
1179
|
-
<div><label>Shield ID</label><input id="shieldId" value="${htmlEscape(auth.shieldId || '')}" placeholder="sh_..." /></div>
|
|
1180
|
-
<div><label>Shield key</label><input id="shieldKey" type="password" value="${htmlEscape(auth.shieldKey || '')}" placeholder="shsk_..." /></div>
|
|
1181
|
-
</div>
|
|
1182
|
-
<label>Backend API URL</label>
|
|
1183
|
-
<input id="apiUrl" value="${htmlEscape(auth.apiUrl || DEFAULT_API_URL)}" placeholder="https://api.fullcourtdefense.ai" />
|
|
1184
|
-
<div class="actions">
|
|
1185
|
-
<button class="secondary" id="saveSetup">Save Shield settings</button>
|
|
1186
|
-
</div>
|
|
1187
|
-
</div>
|
|
1188
|
-
<div class="note" id="backendStatus">Backend status: not checked yet.</div>
|
|
1189
|
-
</div>
|
|
1190
|
-
<div class="steps" id="scanSteps">
|
|
1191
|
-
<div class="step active" id="step-dot-1">1. Connect target</div>
|
|
1192
|
-
<div class="step" id="step-dot-2">2. Select scan type</div>
|
|
1193
|
-
<div class="step" id="step-dot-3">3. Run red-team scan</div>
|
|
1194
|
-
</div>
|
|
1195
|
-
<div class="menuShell" id="cards">
|
|
1196
|
-
<div class="menuGroup run">
|
|
1197
|
-
<div class="menuLabel">Run, not a scan</div>
|
|
1198
|
-
<button class="card workflowCard active" data-workflow="runner"><b>Run one MCP tool</b><span>Call exactly one local MCP tool through a direct server or protected gateway. Best for demos and policy checks.</span></button>
|
|
1199
|
-
</div>
|
|
1200
|
-
<div class="menuGroup">
|
|
1201
|
-
<div class="menuLabel">Security scans</div>
|
|
1202
|
-
<div class="landingScanGrid">
|
|
1203
|
-
<button class="card workflowCard" data-workflow="bot"><b>Bot Scan</b><span>Red-team an internal bot, agent, or chat API.</span></button>
|
|
1204
|
-
<button class="card workflowCard" data-workflow="posture"><b>Security Posture Scan</b><span>Run web-style endpoint security checkup.</span></button>
|
|
1205
|
-
<button class="card workflowCard" data-workflow="mcp"><b>MCP Scan</b><span>Scan an MCP server or protected gateway for risky tool paths.</span></button>
|
|
1206
|
-
<button class="card workflowCard" data-workflow="mcp-posture"><b>MCP Posture Scan</b><span>Inventory exposed MCP tools without invoking them.</span></button>
|
|
1207
|
-
</div>
|
|
1208
|
-
</div>
|
|
1209
|
-
<div class="menuGroup">
|
|
1210
|
-
<div class="menuLabel">Help</div>
|
|
1211
|
-
<button class="card workflowCard" data-workflow="help"><b>Options</b><span>Modes, formats, and when to use each local workflow.</span></button>
|
|
1212
|
-
</div>
|
|
1213
|
-
</div>
|
|
1214
|
-
</section>
|
|
1215
|
-
|
|
1216
|
-
<section class="panel hidden" id="workflowPanel">
|
|
1217
|
-
<div id="step-1" class="stepSection">
|
|
1218
|
-
<div class="sectionTitle">
|
|
1219
|
-
<div>
|
|
1220
|
-
<h2 id="connectTitle">Run MCP Tool</h2>
|
|
1221
|
-
<div class="muted" id="connectSubtitle">Call one MCP tool from this machine. This is not a scan.</div>
|
|
1222
|
-
</div>
|
|
1223
|
-
</div>
|
|
1224
|
-
<div id="runner-fields">
|
|
1225
|
-
<div class="note">This is not a scan. It runs one MCP tool call from this machine. Use a protected proxy/gateway command to show policy decisions, or a direct local MCP server for raw tool testing.</div>
|
|
1226
|
-
<label>Saved/protected MCP server</label>
|
|
1227
|
-
<select id="runnerServer">
|
|
1228
|
-
<option value="">Manual connection...</option>
|
|
1229
|
-
</select>
|
|
1230
|
-
<div class="two">
|
|
1231
|
-
<div>
|
|
1232
|
-
<label>Protected MCP HTTP/SSE URL</label>
|
|
1233
|
-
<input id="runnerMcpUrl" placeholder="Optional: http://127.0.0.1:5066/mcp" />
|
|
1234
|
-
</div>
|
|
1235
|
-
<div>
|
|
1236
|
-
<label>Tool name</label>
|
|
1237
|
-
<input id="runnerTool" placeholder="Example: lookup_customer_profile" />
|
|
1238
|
-
</div>
|
|
1239
|
-
</div>
|
|
1240
|
-
<div class="two">
|
|
1241
|
-
<div>
|
|
1242
|
-
<label>Operation/action override</label>
|
|
1243
|
-
<input id="runnerOperation" placeholder="optional, e.g. read / write / delete / payment:write" />
|
|
1244
|
-
</div>
|
|
1245
|
-
<div>
|
|
1246
|
-
<label>Why use this?</label>
|
|
1247
|
-
<input value="Use when one tool supports multiple actions" readonly />
|
|
1248
|
-
</div>
|
|
1249
|
-
</div>
|
|
1250
|
-
<div class="two">
|
|
1251
|
-
<div><label>Or stdio command</label><input id="runnerMcpCommand" placeholder="node" /></div>
|
|
1252
|
-
<div><label>Command args</label><input id="runnerMcpArgs" placeholder="C:\\path\\to\\mcp-server.js" /></div>
|
|
1253
|
-
</div>
|
|
1254
|
-
<label>Tool arguments JSON</label>
|
|
1255
|
-
<textarea id="runnerToolArgs" spellcheck="false">{
|
|
1256
|
-
"customerId": "RO-PB-10492"
|
|
1257
|
-
}</textarea>
|
|
1258
|
-
<div class="actions">
|
|
1259
|
-
<button class="primary" id="runMcpTool">Run MCP tool</button>
|
|
1260
|
-
<button class="secondary" id="copyMcpResult">Copy result</button>
|
|
1261
|
-
</div>
|
|
1262
|
-
<div id="mcpResultCard" class="resultCard hidden">
|
|
1263
|
-
<div class="resultHead">
|
|
1264
|
-
<div class="resultTitle">MCP tool result</div>
|
|
1265
|
-
<span id="mcpResultStatus" class="statusTag">READY</span>
|
|
1266
|
-
</div>
|
|
1267
|
-
<div id="mcpResultTable"></div>
|
|
1268
|
-
<div class="copyBox">
|
|
1269
|
-
<label>Copyable JSON</label>
|
|
1270
|
-
<textarea id="mcpResultJson" spellcheck="false" readonly></textarea>
|
|
1271
|
-
</div>
|
|
1272
|
-
</div>
|
|
1273
|
-
</div>
|
|
1274
|
-
|
|
1275
|
-
<div id="scan-picker" class="hidden">
|
|
1276
|
-
<div class="note">Security scans run red-team templates locally from this machine. First choose what you want to test.</div>
|
|
1277
|
-
<div class="scanTypeGrid">
|
|
1278
|
-
<button class="card scanTypeCard active" data-target="endpoint"><b>Bot Scan</b><span>Red-team an internal bot, agent, or chat API with prompt-injection templates.</span></button>
|
|
1279
|
-
<button class="card scanTypeCard" data-target="posture"><b>Security Posture Scan</b><span>Run the web-style checkup for HTTPS, headers, auth exposure, and endpoint posture.</span></button>
|
|
1280
|
-
</div>
|
|
1281
|
-
<div class="menuLabel">Advanced local targets</div>
|
|
1282
|
-
<div class="advancedScanGrid">
|
|
1283
|
-
<button class="card scanTypeCard" data-target="rag"><b>RAG Scan</b><span>Test retrieval URL or local corpus path.</span></button>
|
|
1284
|
-
<button class="card scanTypeCard" data-target="mcp"><b>MCP Scan</b><span>Scan tool responses and tool-abuse paths.</span></button>
|
|
1285
|
-
<button class="card scanTypeCard" data-target="mcp-posture"><b>MCP Posture Scan</b><span>List tools and flag risky exposed capabilities.</span></button>
|
|
1286
|
-
</div>
|
|
1287
|
-
</div>
|
|
1288
|
-
|
|
1289
|
-
<div id="endpoint-fields" class="hidden">
|
|
1290
|
-
<label>Internal endpoint URL</label>
|
|
1291
|
-
<input id="endpoint" placeholder="http://127.0.0.1:3000/chat" />
|
|
1292
|
-
<div class="two">
|
|
1293
|
-
<div><label>Request field</label><input id="inputField" value="message" /></div>
|
|
1294
|
-
<div><label>Output field</label><input id="outputField" value="response" /></div>
|
|
1295
|
-
</div>
|
|
1296
|
-
</div>
|
|
1297
|
-
|
|
1298
|
-
<div id="posture-fields" class="hidden">
|
|
1299
|
-
<div class="note">Security Posture Scan checks HTTP endpoint configuration, auth exposure, headers, transport, and operational security signals. To test MCP tools, choose MCP Scan from the Security Scans cards.</div>
|
|
1300
|
-
<div class="two">
|
|
1301
|
-
<div>
|
|
1302
|
-
<label>AI endpoint URL</label>
|
|
1303
|
-
<input id="postureUrl" placeholder="https://your-ai-endpoint.com/api/chat" />
|
|
1304
|
-
</div>
|
|
1305
|
-
<div>
|
|
1306
|
-
<label>HTTP method</label>
|
|
1307
|
-
<select id="postureMethod"><option value="POST" selected>POST</option><option value="GET">GET</option></select>
|
|
1308
|
-
</div>
|
|
1309
|
-
</div>
|
|
1310
|
-
<div class="two">
|
|
1311
|
-
<div>
|
|
1312
|
-
<label>Auth type</label>
|
|
1313
|
-
<select id="postureAuthType"><option value="none" selected>No Auth</option><option value="bearer">Bearer Token</option><option value="api-key">API Key</option></select>
|
|
1314
|
-
</div>
|
|
1315
|
-
<div>
|
|
1316
|
-
<label>Token / API key value</label>
|
|
1317
|
-
<input id="postureToken" type="password" placeholder="Optional" />
|
|
1318
|
-
</div>
|
|
1319
|
-
</div>
|
|
1320
|
-
</div>
|
|
1321
|
-
|
|
1322
|
-
<div id="rag-fields" class="hidden">
|
|
1323
|
-
<label>RAG retrieval URL</label>
|
|
1324
|
-
<input id="ragUrl" placeholder="http://127.0.0.1:8787/rag/retrieve" />
|
|
1325
|
-
<div class="two">
|
|
1326
|
-
<div><label>RAG request field</label><input id="ragInputField" value="query" /></div>
|
|
1327
|
-
<div><label>RAG output field</label><input id="ragOutputField" value="chunks" /></div>
|
|
1328
|
-
</div>
|
|
1329
|
-
<label>Or local corpus path</label>
|
|
1330
|
-
<input id="ragPath" placeholder="C:\\company\\docs" />
|
|
1331
|
-
</div>
|
|
1332
|
-
|
|
1333
|
-
<div id="mcp-fields" class="hidden">
|
|
1334
|
-
<div class="note" id="mcpModeNote">MCP Scan runs red-team templates against MCP tool behavior. MCP Posture Scan lists exposed tools and flags risky capabilities without invoking them.</div>
|
|
1335
|
-
<div class="two">
|
|
1336
|
-
<div><label>MCP HTTP/SSE URL</label><input id="mcpUrl" placeholder="http://127.0.0.1:5066/mcp" /></div>
|
|
1337
|
-
<div><label>MCP tool</label><input id="mcpTool" value="all" /></div>
|
|
1338
|
-
</div>
|
|
1339
|
-
<div class="two">
|
|
1340
|
-
<div><label>Or stdio command</label><input id="mcpCommand" placeholder="node" /></div>
|
|
1341
|
-
<div><label>Command args</label><input id="mcpArgs" placeholder=".\\server.js" /></div>
|
|
1342
|
-
</div>
|
|
1343
|
-
</div>
|
|
1344
|
-
|
|
1345
|
-
<div id="help-fields" class="hidden">
|
|
1346
|
-
<div class="note">Use Run MCP Tool for one live tool call. Use Endpoint, RAG, or MCP Scan for red-team testing. Scan reports save back to the normal web report history when a Shield key is configured.</div>
|
|
1347
|
-
</div>
|
|
1348
|
-
<div class="actions" id="scanActions">
|
|
1349
|
-
<button class="secondary" id="command">Show CLI command</button>
|
|
1350
|
-
<button class="primary" id="connectNext">Continue to scan type</button>
|
|
1351
|
-
</div>
|
|
1352
|
-
</div>
|
|
1353
|
-
|
|
1354
|
-
<div id="step-2" class="stepSection hidden">
|
|
1355
|
-
<div class="sectionTitle">
|
|
1356
|
-
<div>
|
|
1357
|
-
<h2>Select scan type</h2>
|
|
1358
|
-
<div class="muted">Same scan modes as web red-team scanning.</div>
|
|
1359
|
-
</div>
|
|
1360
|
-
</div>
|
|
1361
|
-
<div class="two">
|
|
1362
|
-
<div>
|
|
1363
|
-
<label>Scan mode</label>
|
|
1364
|
-
<select id="mode"><option value="quick" selected>Quick</option><option value="full">Full</option><option value="targeted">Targeted</option><option value="deep">Deep</option></select>
|
|
1365
|
-
</div>
|
|
1366
|
-
<div>
|
|
1367
|
-
<label>Output format</label>
|
|
1368
|
-
<select id="format"><option value="summary">Summary</option><option value="report" selected>Report</option><option value="full-report">Full report</option><option value="json">JSON</option></select>
|
|
1369
|
-
</div>
|
|
1370
|
-
</div>
|
|
1371
|
-
<div class="note">Quick runs 20 critical/high templates. Full runs the full bundled web attack corpus. Targeted adds your target context.</div>
|
|
1372
|
-
<div class="actions">
|
|
1373
|
-
<button class="secondary" id="backToConnect">Back</button>
|
|
1374
|
-
<button class="primary" id="scanTypeNext">Continue to scan</button>
|
|
1375
|
-
</div>
|
|
1376
|
-
</div>
|
|
1377
|
-
|
|
1378
|
-
<div id="step-3" class="stepSection hidden">
|
|
1379
|
-
<div class="sectionTitle">
|
|
1380
|
-
<div>
|
|
1381
|
-
<h2>Run red-team scan</h2>
|
|
1382
|
-
<div class="muted">The scan executes locally and saves a report through our backend.</div>
|
|
1383
|
-
</div>
|
|
1384
|
-
</div>
|
|
1385
|
-
<div class="actions">
|
|
1386
|
-
<button class="primary" id="run" ${isConfigured ? '' : 'disabled'}>Run local scan</button>
|
|
1387
|
-
<button class="secondary" id="newScan">New scan</button>
|
|
1388
|
-
</div>
|
|
1389
|
-
<div class="note">This page only listens on 127.0.0.1. It can reach localhost/VPN targets because it runs from your machine.</div>
|
|
1390
|
-
<div id="resultCard" class="resultCard hidden">
|
|
1391
|
-
<div class="resultHead">
|
|
1392
|
-
<div class="resultTitle">Scan result</div>
|
|
1393
|
-
<span id="resultStatus" class="statusTag">READY</span>
|
|
1394
|
-
</div>
|
|
1395
|
-
<div class="resultGrid">
|
|
1396
|
-
<div class="resultMetric"><b>Score</b><span id="resultScore">-</span></div>
|
|
1397
|
-
<div class="resultMetric"><b>Passed</b><span id="resultPassed">-</span></div>
|
|
1398
|
-
<div class="resultMetric"><b>Failed</b><span id="resultFailed">-</span></div>
|
|
1399
|
-
<div class="resultMetric"><b>Violations</b><span id="resultViolations">-</span></div>
|
|
1400
|
-
<div class="resultMetric"><b>Session</b><span id="resultSession">-</span></div>
|
|
1401
|
-
<div class="resultMetric"><b>Target</b><span id="resultTarget">-</span></div>
|
|
1402
|
-
</div>
|
|
1403
|
-
<div class="resultMeta" id="resultMeta"></div>
|
|
1404
|
-
</div>
|
|
1405
|
-
<div class="console">
|
|
1406
|
-
<div class="consoleHead">
|
|
1407
|
-
<b>Red-team console</b>
|
|
1408
|
-
<span class="pill" id="consoleStatus">Ready</span>
|
|
1409
|
-
</div>
|
|
1410
|
-
<pre id="out">Full Court Defense Local Web Console ready.
|
|
1411
|
-
|
|
1412
|
-
Choose Run MCP Tool for one live tool call.
|
|
1413
|
-
Choose Endpoint/RAG/MCP Scan for a local red-team scan.
|
|
1414
|
-
Local targets stay on this machine; saved scan reports go to Full Court Defense.</pre>
|
|
1415
|
-
</div>
|
|
1416
|
-
</div>
|
|
1417
|
-
</section>
|
|
1418
|
-
</main>
|
|
1419
|
-
<script>
|
|
1420
|
-
const scanTargets = ['endpoint','posture','rag','mcp','mcp-posture'];
|
|
1421
|
-
let target = 'home';
|
|
1422
|
-
let step = 1;
|
|
1423
|
-
const qs = new URLSearchParams(location.search);
|
|
1424
|
-
if (['runner','endpoint','posture','rag','mcp','mcp-posture','help'].includes(qs.get('target'))) target = qs.get('target');
|
|
1425
|
-
const byId = id => document.getElementById(id);
|
|
1426
|
-
const workflow = () => target === 'endpoint' ? 'bot' : target === 'posture' ? 'posture' : target === 'home' ? '' : target;
|
|
1427
|
-
const sync = () => {
|
|
1428
|
-
document.querySelectorAll('.workflowCard').forEach(c => c.classList.toggle('active', c.dataset.workflow === workflow()));
|
|
1429
|
-
document.querySelectorAll('.scanTypeCard').forEach(c => c.classList.toggle('active', c.dataset.target === target));
|
|
1430
|
-
byId('workflowPanel').classList.toggle('hidden', target === 'home');
|
|
1431
|
-
['runner','endpoint','posture','rag','mcp','help'].forEach(id => {
|
|
1432
|
-
const show = id === 'mcp' ? ['mcp','mcp-posture'].includes(target) : id === target;
|
|
1433
|
-
byId(id + '-fields').classList.toggle('hidden', !show);
|
|
1434
|
-
});
|
|
1435
|
-
const scanTarget = scanTargets.includes(target);
|
|
1436
|
-
byId('mcpModeNote').textContent = target === 'mcp-posture'
|
|
1437
|
-
? 'MCP Posture Scan lists exposed tools, checks whether the server appears protected, and flags risky capabilities without invoking any tool.'
|
|
1438
|
-
: 'MCP Scan runs red-team templates against MCP tool behavior. Use MCP Posture Scan for inventory-only posture.';
|
|
1439
|
-
byId('scan-picker').classList.toggle('hidden', !scanTarget);
|
|
1440
|
-
byId('scanSteps').classList.toggle('hidden', !scanTarget);
|
|
1441
|
-
byId('scanActions').classList.toggle('hidden', !scanTarget);
|
|
1442
|
-
byId('connectTitle').textContent = target === 'runner'
|
|
1443
|
-
? 'Run MCP Tool'
|
|
1444
|
-
: target === 'help'
|
|
1445
|
-
? 'Local workflow options'
|
|
1446
|
-
: target === 'mcp-posture'
|
|
1447
|
-
? 'MCP posture scan'
|
|
1448
|
-
: 'Choose scan target';
|
|
1449
|
-
byId('connectSubtitle').textContent = target === 'runner'
|
|
1450
|
-
? 'Call one MCP tool from this machine. This is not a scan.'
|
|
1451
|
-
: target === 'help'
|
|
1452
|
-
? 'Choose one-off tool run or local red-team scan.'
|
|
1453
|
-
: target === 'mcp-posture'
|
|
1454
|
-
? 'Connect an MCP server or protected gateway. No tools are invoked.'
|
|
1455
|
-
: 'Select Endpoint, RAG, or MCP, then connect the internal target.';
|
|
1456
|
-
if (!scanTarget || target === 'home') setStep(1);
|
|
1457
|
-
};
|
|
1458
|
-
const setStep = next => {
|
|
1459
|
-
step = next;
|
|
1460
|
-
[1,2,3].forEach(n => {
|
|
1461
|
-
byId('step-' + n).classList.toggle('hidden', n !== step);
|
|
1462
|
-
byId('step-dot-' + n).classList.toggle('active', n === step);
|
|
1463
|
-
byId('step-dot-' + n).classList.toggle('done', n < step);
|
|
1464
|
-
});
|
|
1465
|
-
};
|
|
1466
|
-
document.querySelectorAll('.workflowCard').forEach(c => c.onclick = () => {
|
|
1467
|
-
target = c.dataset.workflow === 'bot' ? 'endpoint' : c.dataset.workflow;
|
|
1468
|
-
sync();
|
|
1469
|
-
byId('workflowPanel').scrollIntoView({ behavior: 'smooth', block: 'start' });
|
|
1470
|
-
});
|
|
1471
|
-
document.querySelectorAll('.scanTypeCard').forEach(c => c.onclick = () => {
|
|
1472
|
-
target = c.dataset.target;
|
|
1473
|
-
sync();
|
|
1474
|
-
});
|
|
1475
|
-
sync();
|
|
1476
|
-
byId('ragUrl').value = qs.get('ragUrl') || '';
|
|
1477
|
-
byId('ragPath').value = qs.get('ragPath') || '';
|
|
1478
|
-
byId('endpoint').value = qs.get('endpoint') || '';
|
|
1479
|
-
byId('mcpUrl').value = qs.get('mcpUrl') || '';
|
|
1480
|
-
byId('mcpCommand').value = qs.get('mcpCommand') || '';
|
|
1481
|
-
byId('mcpArgs').value = qs.get('mcpArgs') || '';
|
|
1482
|
-
byId('runnerMcpUrl').value = qs.get('mcpUrl') || '';
|
|
1483
|
-
byId('runnerMcpCommand').value = qs.get('mcpCommand') || '';
|
|
1484
|
-
byId('runnerMcpArgs').value = qs.get('mcpArgs') || '';
|
|
1485
|
-
byId('runnerTool').value = qs.get('mcpTool') || '';
|
|
1486
|
-
byId('runnerOperation').value = qs.get('operation') || '';
|
|
1487
|
-
const payload = () => ({
|
|
1488
|
-
target, mode: byId('mode').value, format: byId('format').value,
|
|
1489
|
-
endpoint: byId('endpoint').value, inputField: byId('inputField').value, outputField: byId('outputField').value,
|
|
1490
|
-
postureUrl: byId('postureUrl').value, postureMethod: byId('postureMethod').value, postureAuthType: byId('postureAuthType').value, postureToken: byId('postureToken').value,
|
|
1491
|
-
ragUrl: byId('ragUrl').value, ragPath: byId('ragPath').value, ragInputField: byId('ragInputField').value, ragOutputField: byId('ragOutputField').value,
|
|
1492
|
-
mcpUrl: byId('mcpUrl').value, mcpCommand: byId('mcpCommand').value, mcpArgs: byId('mcpArgs').value, mcpTool: byId('mcpTool').value
|
|
1493
|
-
});
|
|
1494
|
-
const mcpRunnerPayload = () => ({
|
|
1495
|
-
mcpUrl: byId('runnerMcpUrl').value,
|
|
1496
|
-
mcpCommand: byId('runnerMcpCommand').value,
|
|
1497
|
-
mcpArgs: byId('runnerMcpArgs').value,
|
|
1498
|
-
toolName: byId('runnerTool').value,
|
|
1499
|
-
operation: byId('runnerOperation').value,
|
|
1500
|
-
toolArgs: byId('runnerToolArgs').value
|
|
1501
|
-
});
|
|
1502
|
-
async function loadMcpServers() {
|
|
1503
|
-
try {
|
|
1504
|
-
const res = await fetch('/mcp/servers');
|
|
1505
|
-
const data = await res.json();
|
|
1506
|
-
const select = byId('runnerServer');
|
|
1507
|
-
const options = data.servers || [];
|
|
1508
|
-
select.innerHTML = '<option value="">Manual connection...</option>' + options.map((server, index) =>
|
|
1509
|
-
'<option value="' + index + '">' + htmlEscape((server.protected ? 'Protected: ' : 'Direct: ') + server.name + ' - ' + server.source + (server.agentName ? ' - ' + server.agentName : '')) + '</option>'
|
|
1510
|
-
).join('');
|
|
1511
|
-
select.onchange = () => {
|
|
1512
|
-
const server = options[Number(select.value)];
|
|
1513
|
-
if (!server) return;
|
|
1514
|
-
byId('runnerMcpUrl').value = '';
|
|
1515
|
-
byId('runnerMcpCommand').value = server.command || '';
|
|
1516
|
-
byId('runnerMcpArgs').value = Array.isArray(server.args) ? server.args.map(part => /\\s/.test(part) ? JSON.stringify(part) : part).join(' ') : '';
|
|
1517
|
-
setConsole('Loaded saved MCP server: ' + server.name + '\\n' + (server.protected ? 'This entry is already routed through FullCourtDefense gateway.' : 'Direct MCP entry. Wrap it with protect-all for policy enforcement.'), 'Server loaded');
|
|
1518
|
-
};
|
|
1519
|
-
} catch {
|
|
1520
|
-
// Local server picker is convenience only; manual fields still work.
|
|
1521
|
-
}
|
|
1522
|
-
}
|
|
1523
|
-
const renderCommand = p => {
|
|
1524
|
-
const parts = ['fullcourtdefense scan --local --type', p.target, '--mode', p.mode, '--format', p.format, '--no-open'];
|
|
1525
|
-
if (p.target === 'endpoint') parts.push('--endpoint', JSON.stringify(p.endpoint), '--method POST --request-format custom --input-field', p.inputField || 'message', '--output-field', p.outputField || 'response');
|
|
1526
|
-
if (p.target === 'posture') return 'Security Posture Scan runs through this local web UI and calls the Full Court Defense posture checkup API for ' + (p.postureUrl || '<endpoint-url>');
|
|
1527
|
-
if (p.target === 'rag') { if (p.ragUrl) parts.push('--rag-url', JSON.stringify(p.ragUrl), '--method POST --request-format custom --input-field', p.ragInputField || 'query', '--output-field', p.ragOutputField || 'chunks'); else parts.push('--rag-path', JSON.stringify(p.ragPath || '.\\\\docs')); }
|
|
1528
|
-
if (p.target === 'mcp-posture') return 'MCP Posture Scan runs through this local web UI and inventories exposed tools for ' + (p.mcpUrl || ((p.mcpCommand || 'node') + ' ' + (p.mcpArgs || '.\\\\server.js')));
|
|
1529
|
-
if (p.target === 'mcp') { if (p.mcpUrl) parts.push('--mcp-url', JSON.stringify(p.mcpUrl)); else parts.push('--mcp-command', p.mcpCommand || 'node', '--mcp-args', JSON.stringify(p.mcpArgs || '.\\\\server.js')); parts.push('--mcp-tool', p.mcpTool || 'all'); }
|
|
1530
|
-
return parts.join(' ');
|
|
1531
|
-
};
|
|
1532
|
-
const setConsole = (value, status) => {
|
|
1533
|
-
byId('out').innerHTML = htmlEscape(value).replace(/(https?:\\/\\/[^\\s)]+)/g, '<a style="color:#93c5fd" href="$1" target="_blank">$1</a>');
|
|
1534
|
-
byId('out').scrollTop = byId('out').scrollHeight;
|
|
1535
|
-
if (status) byId('consoleStatus').textContent = status;
|
|
1536
|
-
};
|
|
1537
|
-
const parseScanSummary = (text) => {
|
|
1538
|
-
const match = (regex) => (text.match(regex)?.[1] || '').trim();
|
|
1539
|
-
const status = match(/Status:\\s*(PASS|FAIL)/i).toUpperCase();
|
|
1540
|
-
const score = match(/Score:\\s*(\\d+\\/?100?)/i) || match(/Score:\\s*(\\d+)/i);
|
|
1541
|
-
const passed = match(/Passed:\\s*(\\d+)/i);
|
|
1542
|
-
const failed = match(/Failed:\\s*(\\d+)/i);
|
|
1543
|
-
const violations = match(/Violations:\\s*(\\d+)/i);
|
|
1544
|
-
const session = match(/Session:\\s*([^\\n]+)/i);
|
|
1545
|
-
const targetText = match(/Target:\\s*([^\\n]+)/i);
|
|
1546
|
-
const reportUrl = match(/Web report:\\s*(https?:\\/\\/[^\\s]+)/i) || match(/Saved web report [^\\(]*\\((https?:\\/\\/[^\\s)]+)\\)/i);
|
|
1547
|
-
if (!status && !score && !passed && !failed) return null;
|
|
1548
|
-
return { status, score, passed, failed, violations, session, targetText, reportUrl };
|
|
1549
|
-
};
|
|
1550
|
-
const clearScanSummary = () => {
|
|
1551
|
-
byId('resultCard').classList.add('hidden');
|
|
1552
|
-
byId('resultStatus').textContent = 'READY';
|
|
1553
|
-
byId('resultStatus').className = 'statusTag';
|
|
1554
|
-
byId('resultScore').textContent = '-';
|
|
1555
|
-
byId('resultPassed').textContent = '-';
|
|
1556
|
-
byId('resultFailed').textContent = '-';
|
|
1557
|
-
byId('resultViolations').textContent = '-';
|
|
1558
|
-
byId('resultSession').textContent = '-';
|
|
1559
|
-
byId('resultTarget').textContent = '-';
|
|
1560
|
-
byId('resultMeta').innerHTML = '';
|
|
1561
|
-
};
|
|
1562
|
-
const setScanSummary = (summary) => {
|
|
1563
|
-
if (!summary) return;
|
|
1564
|
-
byId('resultCard').classList.remove('hidden');
|
|
1565
|
-
const statusClass = summary.status === 'PASS' ? 'statusTag pass' : summary.status === 'FAIL' ? 'statusTag fail' : 'statusTag';
|
|
1566
|
-
byId('resultStatus').className = statusClass;
|
|
1567
|
-
byId('resultStatus').textContent = summary.status || 'COMPLETE';
|
|
1568
|
-
byId('resultScore').textContent = summary.score || '-';
|
|
1569
|
-
byId('resultPassed').textContent = summary.passed || '-';
|
|
1570
|
-
byId('resultFailed').textContent = summary.failed || '-';
|
|
1571
|
-
byId('resultViolations').textContent = summary.violations || '-';
|
|
1572
|
-
byId('resultSession').textContent = summary.session || '-';
|
|
1573
|
-
byId('resultTarget').textContent = summary.targetText ? summary.targetText.slice(0, 48) + (summary.targetText.length > 48 ? '…' : '') : '-';
|
|
1574
|
-
byId('resultMeta').innerHTML = summary.reportUrl
|
|
1575
|
-
? 'Saved report: <a target="_blank" href="' + htmlEscape(summary.reportUrl) + '">' + htmlEscape(summary.reportUrl) + '</a>'
|
|
1576
|
-
: 'No saved web report URL found in this output.';
|
|
1577
|
-
};
|
|
1578
|
-
const resetForNewScan = () => {
|
|
1579
|
-
target = 'runner';
|
|
1580
|
-
sync();
|
|
1581
|
-
setStep(1);
|
|
1582
|
-
byId('endpoint').value = '';
|
|
1583
|
-
byId('postureUrl').value = '';
|
|
1584
|
-
byId('postureMethod').value = 'POST';
|
|
1585
|
-
byId('postureAuthType').value = 'none';
|
|
1586
|
-
byId('postureToken').value = '';
|
|
1587
|
-
byId('inputField').value = 'message';
|
|
1588
|
-
byId('outputField').value = 'response';
|
|
1589
|
-
byId('ragUrl').value = '';
|
|
1590
|
-
byId('ragPath').value = '';
|
|
1591
|
-
byId('ragInputField').value = 'query';
|
|
1592
|
-
byId('ragOutputField').value = 'chunks';
|
|
1593
|
-
byId('mcpUrl').value = '';
|
|
1594
|
-
byId('mcpCommand').value = '';
|
|
1595
|
-
byId('mcpArgs').value = '';
|
|
1596
|
-
byId('mcpTool').value = 'all';
|
|
1597
|
-
byId('runnerMcpUrl').value = '';
|
|
1598
|
-
byId('runnerMcpCommand').value = '';
|
|
1599
|
-
byId('runnerMcpArgs').value = '';
|
|
1600
|
-
byId('runnerTool').value = '';
|
|
1601
|
-
byId('runnerOperation').value = '';
|
|
1602
|
-
byId('runnerToolArgs').value = '{\\n "customerId": "RO-PB-10492"\\n}';
|
|
1603
|
-
byId('mcpResultCard').classList.add('hidden');
|
|
1604
|
-
byId('mcpResultJson').value = '';
|
|
1605
|
-
byId('mcpResultTable').innerHTML = '';
|
|
1606
|
-
byId('mode').value = 'quick';
|
|
1607
|
-
byId('format').value = 'report';
|
|
1608
|
-
clearScanSummary();
|
|
1609
|
-
setConsole('Full Court Defense Local Web Console ready.\\n\\nChoose Run MCP Tool for one live tool call.\\nChoose Endpoint/RAG/MCP Scan for a local red-team scan.', 'Ready');
|
|
1610
|
-
};
|
|
1611
|
-
byId('command').onclick = () => { setConsole(renderCommand(payload()), 'Command'); };
|
|
1612
|
-
byId('connectNext').onclick = () => setStep(2);
|
|
1613
|
-
byId('backToConnect').onclick = () => setStep(1);
|
|
1614
|
-
byId('scanTypeNext').onclick = () => setStep(3);
|
|
1615
|
-
byId('newScan').onclick = () => resetForNewScan();
|
|
1616
|
-
byId('toggleSetup').onclick = () => {
|
|
1617
|
-
const panel = byId('setupPanel');
|
|
1618
|
-
const hidden = panel.classList.contains('hidden');
|
|
1619
|
-
panel.classList.toggle('hidden', !hidden);
|
|
1620
|
-
byId('toggleSetup').textContent = hidden ? 'Hide Shield settings' : 'Change Shield settings';
|
|
1621
|
-
};
|
|
1622
|
-
byId('saveSetup').onclick = async () => {
|
|
1623
|
-
byId('authStatus').textContent = 'Saving Shield settings...';
|
|
1624
|
-
const res = await fetch('/auth/save', {
|
|
1625
|
-
method: 'POST',
|
|
1626
|
-
headers: { 'content-type': 'application/json' },
|
|
1627
|
-
body: JSON.stringify({
|
|
1628
|
-
shieldId: byId('shieldId').value,
|
|
1629
|
-
shieldKey: byId('shieldKey').value,
|
|
1630
|
-
apiUrl: byId('apiUrl').value
|
|
1631
|
-
})
|
|
1632
|
-
});
|
|
1633
|
-
const data = await res.json();
|
|
1634
|
-
if (data.success) {
|
|
1635
|
-
byId('authStatus').textContent = 'Connected to Shield ' + data.shieldId + '. Reports will save to Full Court Defense.';
|
|
1636
|
-
byId('run').disabled = false;
|
|
1637
|
-
} else {
|
|
1638
|
-
byId('authStatus').textContent = data.error || 'Could not save Shield settings.';
|
|
1639
|
-
}
|
|
1640
|
-
};
|
|
1641
|
-
const checkBackend = async () => {
|
|
1642
|
-
byId('backendStatus').textContent = 'Checking outbound backend...';
|
|
1643
|
-
const res = await fetch('/backend/check', {
|
|
1644
|
-
method: 'POST',
|
|
1645
|
-
headers: { 'content-type': 'application/json' },
|
|
1646
|
-
body: JSON.stringify({
|
|
1647
|
-
shieldId: byId('shieldId').value,
|
|
1648
|
-
shieldKey: byId('shieldKey').value,
|
|
1649
|
-
apiUrl: byId('apiUrl').value
|
|
1650
|
-
})
|
|
1651
|
-
});
|
|
1652
|
-
const data = await res.json();
|
|
1653
|
-
if (data.ok) {
|
|
1654
|
-
byId('backendStatus').textContent = 'Backend status: connected. Outbound scan/report traffic can reach Full Court Defense.';
|
|
1655
|
-
byId('backendStatus').style.borderColor = '#bbf7d0';
|
|
1656
|
-
byId('backendStatus').style.background = '#f0fdf4';
|
|
1657
|
-
byId('backendStatus').style.color = '#166534';
|
|
1658
|
-
} else {
|
|
1659
|
-
byId('backendStatus').textContent = 'Backend status: not reachable. ' + (data.error || 'Check network/proxy/firewall/API URL.');
|
|
1660
|
-
byId('backendStatus').style.borderColor = '#fecaca';
|
|
1661
|
-
byId('backendStatus').style.background = '#fef2f2';
|
|
1662
|
-
byId('backendStatus').style.color = '#991b1b';
|
|
1663
|
-
}
|
|
1664
|
-
return data.ok;
|
|
1665
|
-
};
|
|
1666
|
-
byId('checkBackend').onclick = checkBackend;
|
|
1667
|
-
loadMcpServers();
|
|
1668
|
-
checkBackend().catch(() => {});
|
|
1669
|
-
function mcpRows(value) {
|
|
1670
|
-
if (Array.isArray(value)) return value;
|
|
1671
|
-
const content = value && value.content;
|
|
1672
|
-
if (Array.isArray(content)) {
|
|
1673
|
-
const textItem = content.find(item => item && typeof item.text === 'string');
|
|
1674
|
-
if (textItem) {
|
|
1675
|
-
try {
|
|
1676
|
-
const parsed = JSON.parse(textItem.text);
|
|
1677
|
-
if (Array.isArray(parsed)) return parsed;
|
|
1678
|
-
if (parsed && Array.isArray(parsed.data)) return parsed.data;
|
|
1679
|
-
if (parsed && Array.isArray(parsed.results)) return parsed.results;
|
|
1680
|
-
} catch {}
|
|
1681
|
-
}
|
|
1682
|
-
}
|
|
1683
|
-
if (value && Array.isArray(value.data)) return value.data;
|
|
1684
|
-
if (value && Array.isArray(value.results)) return value.results;
|
|
1685
|
-
return null;
|
|
1686
|
-
}
|
|
1687
|
-
function mcpText(value) {
|
|
1688
|
-
if (value == null) return '';
|
|
1689
|
-
if (typeof value === 'string') return value;
|
|
1690
|
-
if (typeof value === 'number' || typeof value === 'boolean') return String(value);
|
|
1691
|
-
const content = value.content;
|
|
1692
|
-
if (Array.isArray(content)) {
|
|
1693
|
-
return content
|
|
1694
|
-
.map(item => item && typeof item.text === 'string' ? item.text : '')
|
|
1695
|
-
.filter(Boolean)
|
|
1696
|
-
.join('\\n');
|
|
1697
|
-
}
|
|
1698
|
-
return '';
|
|
1699
|
-
}
|
|
1700
|
-
function mcpObject(value) {
|
|
1701
|
-
if (!value || typeof value !== 'object' || Array.isArray(value)) return null;
|
|
1702
|
-
const contentText = mcpText(value);
|
|
1703
|
-
if (contentText) {
|
|
1704
|
-
try {
|
|
1705
|
-
const parsed = JSON.parse(contentText);
|
|
1706
|
-
if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) return parsed;
|
|
1707
|
-
} catch {}
|
|
1708
|
-
}
|
|
1709
|
-
if (value.data && typeof value.data === 'object' && !Array.isArray(value.data)) return value.data;
|
|
1710
|
-
if (value.result && typeof value.result === 'object' && !Array.isArray(value.result)) return value.result;
|
|
1711
|
-
return value;
|
|
1712
|
-
}
|
|
1713
|
-
function cellValue(value) {
|
|
1714
|
-
if (value == null) return '';
|
|
1715
|
-
if (typeof value === 'object') return JSON.stringify(value);
|
|
1716
|
-
return String(value);
|
|
1717
|
-
}
|
|
1718
|
-
function renderObjectCards(obj) {
|
|
1719
|
-
const preferred = ['id', 'customerId', 'name', 'title', 'status', 'plan', 'email', 'decision', 'matchedPolicy', 'reason', 'ok'];
|
|
1720
|
-
const keys = Array.from(new Set([...preferred.filter(k => Object.prototype.hasOwnProperty.call(obj, k)), ...Object.keys(obj)])).slice(0, 12);
|
|
1721
|
-
return '<div class="mcpCards">' + keys.map(k => '<div class="mcpCard"><b>' + htmlEscape(k) + '</b><span>' + htmlEscape(cellValue(obj[k])) + '</span></div>').join('') + '</div>';
|
|
1722
|
-
}
|
|
1723
|
-
function renderMcpTable(rows) {
|
|
1724
|
-
const preferred = ['id', 'customerId', 'name', 'title', 'status', 'plan', 'email', 'createdAt', 'updatedAt'];
|
|
1725
|
-
const keys = Array.from(new Set([...preferred.filter(k => rows.some(row => row && Object.prototype.hasOwnProperty.call(row, k))), ...rows.flatMap(row => Object.keys(row || {}))])).slice(0, 10);
|
|
1726
|
-
return '<table class="localTable"><thead><tr>' + keys.map(k => '<th>' + htmlEscape(k) + '</th>').join('') + '</tr></thead><tbody>' +
|
|
1727
|
-
rows.slice(0, 50).map(row => '<tr>' + keys.map(k => '<td>' + htmlEscape(cellValue(row[k])) + '</td>').join('') + '</tr>').join('') +
|
|
1728
|
-
'</tbody></table>' +
|
|
1729
|
-
(rows.length > 50 ? '<div class="resultMeta">Showing first 50 rows. Full result is in raw JSON below.</div>' : '');
|
|
1730
|
-
}
|
|
1731
|
-
function renderMcpResult(data) {
|
|
1732
|
-
byId('mcpResultCard').classList.remove('hidden');
|
|
1733
|
-
byId('mcpResultStatus').textContent = data.blocked ? 'BLOCKED' : data.success ? 'ALLOW / RESULT' : 'ERROR';
|
|
1734
|
-
byId('mcpResultStatus').className = data.success ? 'statusTag pass' : 'statusTag fail';
|
|
1735
|
-
byId('mcpResultJson').value = JSON.stringify(data, null, 2);
|
|
1736
|
-
const rows = data.success ? mcpRows(data.result) : null;
|
|
1737
|
-
if (rows && rows.length && rows.every(row => row && typeof row === 'object' && !Array.isArray(row))) {
|
|
1738
|
-
byId('mcpResultTable').innerHTML = '<div class="mcpBanner ok"><b>Allowed.</b> Tool returned ' + rows.length + ' row(s).</div>' + renderMcpTable(rows);
|
|
1739
|
-
} else if (data.success) {
|
|
1740
|
-
const obj = mcpObject(data.result);
|
|
1741
|
-
const text = mcpText(data.result);
|
|
1742
|
-
byId('mcpResultTable').innerHTML = '<div class="mcpBanner ok"><b>Allowed.</b> Tool call completed through the configured MCP server/proxy.</div>' +
|
|
1743
|
-
(obj ? renderObjectCards(obj) : '<div class="resultMeta">' + htmlEscape(text || 'Result is shown as raw JSON below.') + '</div>');
|
|
1744
|
-
} else {
|
|
1745
|
-
const blockedCopy = data.blocked
|
|
1746
|
-
? 'Blocked because a FullCourtDefense policy or local safety rule is defined for this tool/action.'
|
|
1747
|
-
: 'The MCP call failed before a successful tool result was returned.';
|
|
1748
|
-
byId('mcpResultTable').innerHTML = '<div class="mcpBanner blocked"><b>' + (data.blocked ? 'Blocked by policy.' : 'MCP error.') + '</b> ' + htmlEscape(blockedCopy) + '<br/><span>' + htmlEscape(data.reason || data.error || 'No reason returned.') + '</span></div>';
|
|
1749
|
-
}
|
|
1750
|
-
}
|
|
1751
|
-
byId('copyMcpResult').onclick = async () => {
|
|
1752
|
-
await navigator.clipboard?.writeText(byId('mcpResultJson').value || '').catch(() => undefined);
|
|
1753
|
-
byId('copyMcpResult').textContent = 'Copied';
|
|
1754
|
-
setTimeout(() => { byId('copyMcpResult').textContent = 'Copy result'; }, 1200);
|
|
1755
|
-
};
|
|
1756
|
-
byId('runMcpTool').onclick = async () => {
|
|
1757
|
-
byId('runMcpTool').disabled = true;
|
|
1758
|
-
byId('runMcpTool').textContent = 'Running...';
|
|
1759
|
-
byId('mcpResultCard').classList.add('hidden');
|
|
1760
|
-
setConsole('Calling MCP tool locally through configured proxy/server...\\n', 'Running MCP');
|
|
1761
|
-
try {
|
|
1762
|
-
const res = await fetch('/mcp/run', {
|
|
1763
|
-
method: 'POST',
|
|
1764
|
-
headers: { 'content-type': 'application/json' },
|
|
1765
|
-
body: JSON.stringify(mcpRunnerPayload())
|
|
1766
|
-
});
|
|
1767
|
-
const data = await res.json();
|
|
1768
|
-
renderMcpResult(data);
|
|
1769
|
-
setConsole(JSON.stringify(data, null, 2), data.success ? 'MCP complete' : 'MCP blocked/error');
|
|
1770
|
-
} catch (error) {
|
|
1771
|
-
const data = { success: false, error: error && error.message ? error.message : String(error) };
|
|
1772
|
-
renderMcpResult(data);
|
|
1773
|
-
setConsole(JSON.stringify(data, null, 2), 'MCP error');
|
|
1774
|
-
} finally {
|
|
1775
|
-
byId('runMcpTool').disabled = false;
|
|
1776
|
-
byId('runMcpTool').textContent = 'Run MCP tool';
|
|
1777
|
-
}
|
|
1778
|
-
};
|
|
1779
|
-
byId('run').onclick = async () => {
|
|
1780
|
-
if (byId('run').disabled) return;
|
|
1781
|
-
byId('run').disabled = true;
|
|
1782
|
-
byId('run').textContent = 'Running...';
|
|
1783
|
-
clearScanSummary();
|
|
1784
|
-
if (target === 'mcp-posture') {
|
|
1785
|
-
try {
|
|
1786
|
-
setConsole('Starting MCP Posture Scan...\\nNo MCP tools will be invoked; this only lists exposed tools.\\n', 'Running MCP posture');
|
|
1787
|
-
const postureRes = await fetch('/mcp/posture', { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify(payload()) });
|
|
1788
|
-
const postureData = await postureRes.json();
|
|
1789
|
-
setConsole(JSON.stringify(postureData, null, 2), postureData.ok ? 'MCP posture complete' : 'MCP posture failed');
|
|
1790
|
-
} catch (error) {
|
|
1791
|
-
setConsole('MCP Posture Scan failed: ' + (error && error.message ? error.message : String(error)), 'MCP posture failed');
|
|
1792
|
-
} finally {
|
|
1793
|
-
byId('run').disabled = false;
|
|
1794
|
-
byId('run').textContent = 'Run local scan';
|
|
1795
|
-
}
|
|
1796
|
-
return;
|
|
1797
|
-
}
|
|
1798
|
-
const backendOk = await checkBackend();
|
|
1799
|
-
if (!backendOk) {
|
|
1800
|
-
setConsole('Outbound backend check failed. Fix backend connectivity before scanning so verdicts and reports can be generated.', 'Blocked');
|
|
1801
|
-
byId('run').disabled = false;
|
|
1802
|
-
byId('run').textContent = 'Run local scan';
|
|
1803
|
-
return;
|
|
1804
|
-
}
|
|
1805
|
-
if (target === 'posture') {
|
|
1806
|
-
try {
|
|
1807
|
-
setConsole('Starting Security Posture Scan...\\n', 'Running posture');
|
|
1808
|
-
const postureRes = await fetch('/posture/run', { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify(payload()) });
|
|
1809
|
-
const postureData = await postureRes.json();
|
|
1810
|
-
setConsole(JSON.stringify(postureData, null, 2), postureData.ok ? 'Posture complete' : 'Posture failed');
|
|
1811
|
-
} catch (error) {
|
|
1812
|
-
setConsole('Security Posture Scan failed: ' + (error && error.message ? error.message : String(error)), 'Posture failed');
|
|
1813
|
-
} finally {
|
|
1814
|
-
byId('run').disabled = false;
|
|
1815
|
-
byId('run').textContent = 'Run local scan';
|
|
1816
|
-
}
|
|
1817
|
-
return;
|
|
1818
|
-
}
|
|
1819
|
-
setConsole('Starting local red-team scan...\\n', 'Running');
|
|
1820
|
-
const res = await fetch('/run', { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify(payload()) });
|
|
1821
|
-
if (!res.body) {
|
|
1822
|
-
const finalText = await res.text();
|
|
1823
|
-
setConsole(finalText, 'Complete');
|
|
1824
|
-
setScanSummary(parseScanSummary(finalText));
|
|
1825
|
-
byId('run').disabled = false;
|
|
1826
|
-
byId('run').textContent = 'Run local scan';
|
|
1827
|
-
return;
|
|
1828
|
-
}
|
|
1829
|
-
const reader = res.body.getReader();
|
|
1830
|
-
const decoder = new TextDecoder();
|
|
1831
|
-
let text = '';
|
|
1832
|
-
while (true) {
|
|
1833
|
-
const next = await reader.read();
|
|
1834
|
-
if (next.done) break;
|
|
1835
|
-
text += decoder.decode(next.value, { stream: true });
|
|
1836
|
-
setConsole(text, 'Running');
|
|
1837
|
-
}
|
|
1838
|
-
byId('consoleStatus').textContent = 'Complete';
|
|
1839
|
-
setScanSummary(parseScanSummary(text));
|
|
1840
|
-
byId('run').disabled = false;
|
|
1841
|
-
byId('run').textContent = 'Run local scan';
|
|
1842
|
-
};
|
|
1843
|
-
function htmlEscape(s) { return s.replace(/[&<>"]/g, ch => ({'&':'&','<':'<','>':'>','"':'"'}[ch])); }
|
|
1844
|
-
</script>
|
|
1845
|
-
</body>
|
|
1172
|
+
: 'Configure CLI once with fullcourtdefense configure, then run local scans. UI uses saved local config automatically.'}</p>
|
|
1173
|
+
<div class="actions">
|
|
1174
|
+
<button class="secondary" id="toggleSetup">Change Shield settings</button>
|
|
1175
|
+
<button class="secondary" id="checkBackend">Check outbound backend</button>
|
|
1176
|
+
</div>
|
|
1177
|
+
<div id="setupPanel" class="hidden">
|
|
1178
|
+
<div class="two">
|
|
1179
|
+
<div><label>Shield ID</label><input id="shieldId" value="${htmlEscape(auth.shieldId || '')}" placeholder="sh_..." /></div>
|
|
1180
|
+
<div><label>Shield key</label><input id="shieldKey" type="password" value="${htmlEscape(auth.shieldKey || '')}" placeholder="shsk_..." /></div>
|
|
1181
|
+
</div>
|
|
1182
|
+
<label>Backend API URL</label>
|
|
1183
|
+
<input id="apiUrl" value="${htmlEscape(auth.apiUrl || DEFAULT_API_URL)}" placeholder="https://api.fullcourtdefense.ai" />
|
|
1184
|
+
<div class="actions">
|
|
1185
|
+
<button class="secondary" id="saveSetup">Save Shield settings</button>
|
|
1186
|
+
</div>
|
|
1187
|
+
</div>
|
|
1188
|
+
<div class="note" id="backendStatus">Backend status: not checked yet.</div>
|
|
1189
|
+
</div>
|
|
1190
|
+
<div class="steps" id="scanSteps">
|
|
1191
|
+
<div class="step active" id="step-dot-1">1. Connect target</div>
|
|
1192
|
+
<div class="step" id="step-dot-2">2. Select scan type</div>
|
|
1193
|
+
<div class="step" id="step-dot-3">3. Run red-team scan</div>
|
|
1194
|
+
</div>
|
|
1195
|
+
<div class="menuShell" id="cards">
|
|
1196
|
+
<div class="menuGroup run">
|
|
1197
|
+
<div class="menuLabel">Run, not a scan</div>
|
|
1198
|
+
<button class="card workflowCard active" data-workflow="runner"><b>Run one MCP tool</b><span>Call exactly one local MCP tool through a direct server or protected gateway. Best for demos and policy checks.</span></button>
|
|
1199
|
+
</div>
|
|
1200
|
+
<div class="menuGroup">
|
|
1201
|
+
<div class="menuLabel">Security scans</div>
|
|
1202
|
+
<div class="landingScanGrid">
|
|
1203
|
+
<button class="card workflowCard" data-workflow="bot"><b>Bot Scan</b><span>Red-team an internal bot, agent, or chat API.</span></button>
|
|
1204
|
+
<button class="card workflowCard" data-workflow="posture"><b>Security Posture Scan</b><span>Run web-style endpoint security checkup.</span></button>
|
|
1205
|
+
<button class="card workflowCard" data-workflow="mcp"><b>MCP Scan</b><span>Scan an MCP server or protected gateway for risky tool paths.</span></button>
|
|
1206
|
+
<button class="card workflowCard" data-workflow="mcp-posture"><b>MCP Posture Scan</b><span>Inventory exposed MCP tools without invoking them.</span></button>
|
|
1207
|
+
</div>
|
|
1208
|
+
</div>
|
|
1209
|
+
<div class="menuGroup">
|
|
1210
|
+
<div class="menuLabel">Help</div>
|
|
1211
|
+
<button class="card workflowCard" data-workflow="help"><b>Options</b><span>Modes, formats, and when to use each local workflow.</span></button>
|
|
1212
|
+
</div>
|
|
1213
|
+
</div>
|
|
1214
|
+
</section>
|
|
1215
|
+
|
|
1216
|
+
<section class="panel hidden" id="workflowPanel">
|
|
1217
|
+
<div id="step-1" class="stepSection">
|
|
1218
|
+
<div class="sectionTitle">
|
|
1219
|
+
<div>
|
|
1220
|
+
<h2 id="connectTitle">Run MCP Tool</h2>
|
|
1221
|
+
<div class="muted" id="connectSubtitle">Call one MCP tool from this machine. This is not a scan.</div>
|
|
1222
|
+
</div>
|
|
1223
|
+
</div>
|
|
1224
|
+
<div id="runner-fields">
|
|
1225
|
+
<div class="note">This is not a scan. It runs one MCP tool call from this machine. Use a protected proxy/gateway command to show policy decisions, or a direct local MCP server for raw tool testing.</div>
|
|
1226
|
+
<label>Saved/protected MCP server</label>
|
|
1227
|
+
<select id="runnerServer">
|
|
1228
|
+
<option value="">Manual connection...</option>
|
|
1229
|
+
</select>
|
|
1230
|
+
<div class="two">
|
|
1231
|
+
<div>
|
|
1232
|
+
<label>Protected MCP HTTP/SSE URL</label>
|
|
1233
|
+
<input id="runnerMcpUrl" placeholder="Optional: http://127.0.0.1:5066/mcp" />
|
|
1234
|
+
</div>
|
|
1235
|
+
<div>
|
|
1236
|
+
<label>Tool name</label>
|
|
1237
|
+
<input id="runnerTool" placeholder="Example: lookup_customer_profile" />
|
|
1238
|
+
</div>
|
|
1239
|
+
</div>
|
|
1240
|
+
<div class="two">
|
|
1241
|
+
<div>
|
|
1242
|
+
<label>Operation/action override</label>
|
|
1243
|
+
<input id="runnerOperation" placeholder="optional, e.g. read / write / delete / payment:write" />
|
|
1244
|
+
</div>
|
|
1245
|
+
<div>
|
|
1246
|
+
<label>Why use this?</label>
|
|
1247
|
+
<input value="Use when one tool supports multiple actions" readonly />
|
|
1248
|
+
</div>
|
|
1249
|
+
</div>
|
|
1250
|
+
<div class="two">
|
|
1251
|
+
<div><label>Or stdio command</label><input id="runnerMcpCommand" placeholder="node" /></div>
|
|
1252
|
+
<div><label>Command args</label><input id="runnerMcpArgs" placeholder="C:\\path\\to\\mcp-server.js" /></div>
|
|
1253
|
+
</div>
|
|
1254
|
+
<label>Tool arguments JSON</label>
|
|
1255
|
+
<textarea id="runnerToolArgs" spellcheck="false">{
|
|
1256
|
+
"customerId": "RO-PB-10492"
|
|
1257
|
+
}</textarea>
|
|
1258
|
+
<div class="actions">
|
|
1259
|
+
<button class="primary" id="runMcpTool">Run MCP tool</button>
|
|
1260
|
+
<button class="secondary" id="copyMcpResult">Copy result</button>
|
|
1261
|
+
</div>
|
|
1262
|
+
<div id="mcpResultCard" class="resultCard hidden">
|
|
1263
|
+
<div class="resultHead">
|
|
1264
|
+
<div class="resultTitle">MCP tool result</div>
|
|
1265
|
+
<span id="mcpResultStatus" class="statusTag">READY</span>
|
|
1266
|
+
</div>
|
|
1267
|
+
<div id="mcpResultTable"></div>
|
|
1268
|
+
<div class="copyBox">
|
|
1269
|
+
<label>Copyable JSON</label>
|
|
1270
|
+
<textarea id="mcpResultJson" spellcheck="false" readonly></textarea>
|
|
1271
|
+
</div>
|
|
1272
|
+
</div>
|
|
1273
|
+
</div>
|
|
1274
|
+
|
|
1275
|
+
<div id="scan-picker" class="hidden">
|
|
1276
|
+
<div class="note">Security scans run red-team templates locally from this machine. First choose what you want to test.</div>
|
|
1277
|
+
<div class="scanTypeGrid">
|
|
1278
|
+
<button class="card scanTypeCard active" data-target="endpoint"><b>Bot Scan</b><span>Red-team an internal bot, agent, or chat API with prompt-injection templates.</span></button>
|
|
1279
|
+
<button class="card scanTypeCard" data-target="posture"><b>Security Posture Scan</b><span>Run the web-style checkup for HTTPS, headers, auth exposure, and endpoint posture.</span></button>
|
|
1280
|
+
</div>
|
|
1281
|
+
<div class="menuLabel">Advanced local targets</div>
|
|
1282
|
+
<div class="advancedScanGrid">
|
|
1283
|
+
<button class="card scanTypeCard" data-target="rag"><b>RAG Scan</b><span>Test retrieval URL or local corpus path.</span></button>
|
|
1284
|
+
<button class="card scanTypeCard" data-target="mcp"><b>MCP Scan</b><span>Scan tool responses and tool-abuse paths.</span></button>
|
|
1285
|
+
<button class="card scanTypeCard" data-target="mcp-posture"><b>MCP Posture Scan</b><span>List tools and flag risky exposed capabilities.</span></button>
|
|
1286
|
+
</div>
|
|
1287
|
+
</div>
|
|
1288
|
+
|
|
1289
|
+
<div id="endpoint-fields" class="hidden">
|
|
1290
|
+
<label>Internal endpoint URL</label>
|
|
1291
|
+
<input id="endpoint" placeholder="http://127.0.0.1:3000/chat" />
|
|
1292
|
+
<div class="two">
|
|
1293
|
+
<div><label>Request field</label><input id="inputField" value="message" /></div>
|
|
1294
|
+
<div><label>Output field</label><input id="outputField" value="response" /></div>
|
|
1295
|
+
</div>
|
|
1296
|
+
</div>
|
|
1297
|
+
|
|
1298
|
+
<div id="posture-fields" class="hidden">
|
|
1299
|
+
<div class="note">Security Posture Scan checks HTTP endpoint configuration, auth exposure, headers, transport, and operational security signals. To test MCP tools, choose MCP Scan from the Security Scans cards.</div>
|
|
1300
|
+
<div class="two">
|
|
1301
|
+
<div>
|
|
1302
|
+
<label>AI endpoint URL</label>
|
|
1303
|
+
<input id="postureUrl" placeholder="https://your-ai-endpoint.com/api/chat" />
|
|
1304
|
+
</div>
|
|
1305
|
+
<div>
|
|
1306
|
+
<label>HTTP method</label>
|
|
1307
|
+
<select id="postureMethod"><option value="POST" selected>POST</option><option value="GET">GET</option></select>
|
|
1308
|
+
</div>
|
|
1309
|
+
</div>
|
|
1310
|
+
<div class="two">
|
|
1311
|
+
<div>
|
|
1312
|
+
<label>Auth type</label>
|
|
1313
|
+
<select id="postureAuthType"><option value="none" selected>No Auth</option><option value="bearer">Bearer Token</option><option value="api-key">API Key</option></select>
|
|
1314
|
+
</div>
|
|
1315
|
+
<div>
|
|
1316
|
+
<label>Token / API key value</label>
|
|
1317
|
+
<input id="postureToken" type="password" placeholder="Optional" />
|
|
1318
|
+
</div>
|
|
1319
|
+
</div>
|
|
1320
|
+
</div>
|
|
1321
|
+
|
|
1322
|
+
<div id="rag-fields" class="hidden">
|
|
1323
|
+
<label>RAG retrieval URL</label>
|
|
1324
|
+
<input id="ragUrl" placeholder="http://127.0.0.1:8787/rag/retrieve" />
|
|
1325
|
+
<div class="two">
|
|
1326
|
+
<div><label>RAG request field</label><input id="ragInputField" value="query" /></div>
|
|
1327
|
+
<div><label>RAG output field</label><input id="ragOutputField" value="chunks" /></div>
|
|
1328
|
+
</div>
|
|
1329
|
+
<label>Or local corpus path</label>
|
|
1330
|
+
<input id="ragPath" placeholder="C:\\company\\docs" />
|
|
1331
|
+
</div>
|
|
1332
|
+
|
|
1333
|
+
<div id="mcp-fields" class="hidden">
|
|
1334
|
+
<div class="note" id="mcpModeNote">MCP Scan runs red-team templates against MCP tool behavior. MCP Posture Scan lists exposed tools and flags risky capabilities without invoking them.</div>
|
|
1335
|
+
<div class="two">
|
|
1336
|
+
<div><label>MCP HTTP/SSE URL</label><input id="mcpUrl" placeholder="http://127.0.0.1:5066/mcp" /></div>
|
|
1337
|
+
<div><label>MCP tool</label><input id="mcpTool" value="all" /></div>
|
|
1338
|
+
</div>
|
|
1339
|
+
<div class="two">
|
|
1340
|
+
<div><label>Or stdio command</label><input id="mcpCommand" placeholder="node" /></div>
|
|
1341
|
+
<div><label>Command args</label><input id="mcpArgs" placeholder=".\\server.js" /></div>
|
|
1342
|
+
</div>
|
|
1343
|
+
</div>
|
|
1344
|
+
|
|
1345
|
+
<div id="help-fields" class="hidden">
|
|
1346
|
+
<div class="note">Use Run MCP Tool for one live tool call. Use Endpoint, RAG, or MCP Scan for red-team testing. Scan reports save back to the normal web report history when a Shield key is configured.</div>
|
|
1347
|
+
</div>
|
|
1348
|
+
<div class="actions" id="scanActions">
|
|
1349
|
+
<button class="secondary" id="command">Show CLI command</button>
|
|
1350
|
+
<button class="primary" id="connectNext">Continue to scan type</button>
|
|
1351
|
+
</div>
|
|
1352
|
+
</div>
|
|
1353
|
+
|
|
1354
|
+
<div id="step-2" class="stepSection hidden">
|
|
1355
|
+
<div class="sectionTitle">
|
|
1356
|
+
<div>
|
|
1357
|
+
<h2>Select scan type</h2>
|
|
1358
|
+
<div class="muted">Same scan modes as web red-team scanning.</div>
|
|
1359
|
+
</div>
|
|
1360
|
+
</div>
|
|
1361
|
+
<div class="two">
|
|
1362
|
+
<div>
|
|
1363
|
+
<label>Scan mode</label>
|
|
1364
|
+
<select id="mode"><option value="quick" selected>Quick</option><option value="full">Full</option><option value="targeted">Targeted</option><option value="deep">Deep</option></select>
|
|
1365
|
+
</div>
|
|
1366
|
+
<div>
|
|
1367
|
+
<label>Output format</label>
|
|
1368
|
+
<select id="format"><option value="summary">Summary</option><option value="report" selected>Report</option><option value="full-report">Full report</option><option value="json">JSON</option></select>
|
|
1369
|
+
</div>
|
|
1370
|
+
</div>
|
|
1371
|
+
<div class="note">Quick runs 20 critical/high templates. Full runs the full bundled web attack corpus. Targeted adds your target context.</div>
|
|
1372
|
+
<div class="actions">
|
|
1373
|
+
<button class="secondary" id="backToConnect">Back</button>
|
|
1374
|
+
<button class="primary" id="scanTypeNext">Continue to scan</button>
|
|
1375
|
+
</div>
|
|
1376
|
+
</div>
|
|
1377
|
+
|
|
1378
|
+
<div id="step-3" class="stepSection hidden">
|
|
1379
|
+
<div class="sectionTitle">
|
|
1380
|
+
<div>
|
|
1381
|
+
<h2>Run red-team scan</h2>
|
|
1382
|
+
<div class="muted">The scan executes locally and saves a report through our backend.</div>
|
|
1383
|
+
</div>
|
|
1384
|
+
</div>
|
|
1385
|
+
<div class="actions">
|
|
1386
|
+
<button class="primary" id="run" ${isConfigured ? '' : 'disabled'}>Run local scan</button>
|
|
1387
|
+
<button class="secondary" id="newScan">New scan</button>
|
|
1388
|
+
</div>
|
|
1389
|
+
<div class="note">This page only listens on 127.0.0.1. It can reach localhost/VPN targets because it runs from your machine.</div>
|
|
1390
|
+
<div id="resultCard" class="resultCard hidden">
|
|
1391
|
+
<div class="resultHead">
|
|
1392
|
+
<div class="resultTitle">Scan result</div>
|
|
1393
|
+
<span id="resultStatus" class="statusTag">READY</span>
|
|
1394
|
+
</div>
|
|
1395
|
+
<div class="resultGrid">
|
|
1396
|
+
<div class="resultMetric"><b>Score</b><span id="resultScore">-</span></div>
|
|
1397
|
+
<div class="resultMetric"><b>Passed</b><span id="resultPassed">-</span></div>
|
|
1398
|
+
<div class="resultMetric"><b>Failed</b><span id="resultFailed">-</span></div>
|
|
1399
|
+
<div class="resultMetric"><b>Violations</b><span id="resultViolations">-</span></div>
|
|
1400
|
+
<div class="resultMetric"><b>Session</b><span id="resultSession">-</span></div>
|
|
1401
|
+
<div class="resultMetric"><b>Target</b><span id="resultTarget">-</span></div>
|
|
1402
|
+
</div>
|
|
1403
|
+
<div class="resultMeta" id="resultMeta"></div>
|
|
1404
|
+
</div>
|
|
1405
|
+
<div class="console">
|
|
1406
|
+
<div class="consoleHead">
|
|
1407
|
+
<b>Red-team console</b>
|
|
1408
|
+
<span class="pill" id="consoleStatus">Ready</span>
|
|
1409
|
+
</div>
|
|
1410
|
+
<pre id="out">Full Court Defense Local Web Console ready.
|
|
1411
|
+
|
|
1412
|
+
Choose Run MCP Tool for one live tool call.
|
|
1413
|
+
Choose Endpoint/RAG/MCP Scan for a local red-team scan.
|
|
1414
|
+
Local targets stay on this machine; saved scan reports go to Full Court Defense.</pre>
|
|
1415
|
+
</div>
|
|
1416
|
+
</div>
|
|
1417
|
+
</section>
|
|
1418
|
+
</main>
|
|
1419
|
+
<script>
|
|
1420
|
+
const scanTargets = ['endpoint','posture','rag','mcp','mcp-posture'];
|
|
1421
|
+
let target = 'home';
|
|
1422
|
+
let step = 1;
|
|
1423
|
+
const qs = new URLSearchParams(location.search);
|
|
1424
|
+
if (['runner','endpoint','posture','rag','mcp','mcp-posture','help'].includes(qs.get('target'))) target = qs.get('target');
|
|
1425
|
+
const byId = id => document.getElementById(id);
|
|
1426
|
+
const workflow = () => target === 'endpoint' ? 'bot' : target === 'posture' ? 'posture' : target === 'home' ? '' : target;
|
|
1427
|
+
const sync = () => {
|
|
1428
|
+
document.querySelectorAll('.workflowCard').forEach(c => c.classList.toggle('active', c.dataset.workflow === workflow()));
|
|
1429
|
+
document.querySelectorAll('.scanTypeCard').forEach(c => c.classList.toggle('active', c.dataset.target === target));
|
|
1430
|
+
byId('workflowPanel').classList.toggle('hidden', target === 'home');
|
|
1431
|
+
['runner','endpoint','posture','rag','mcp','help'].forEach(id => {
|
|
1432
|
+
const show = id === 'mcp' ? ['mcp','mcp-posture'].includes(target) : id === target;
|
|
1433
|
+
byId(id + '-fields').classList.toggle('hidden', !show);
|
|
1434
|
+
});
|
|
1435
|
+
const scanTarget = scanTargets.includes(target);
|
|
1436
|
+
byId('mcpModeNote').textContent = target === 'mcp-posture'
|
|
1437
|
+
? 'MCP Posture Scan lists exposed tools, checks whether the server appears protected, and flags risky capabilities without invoking any tool.'
|
|
1438
|
+
: 'MCP Scan runs red-team templates against MCP tool behavior. Use MCP Posture Scan for inventory-only posture.';
|
|
1439
|
+
byId('scan-picker').classList.toggle('hidden', !scanTarget);
|
|
1440
|
+
byId('scanSteps').classList.toggle('hidden', !scanTarget);
|
|
1441
|
+
byId('scanActions').classList.toggle('hidden', !scanTarget);
|
|
1442
|
+
byId('connectTitle').textContent = target === 'runner'
|
|
1443
|
+
? 'Run MCP Tool'
|
|
1444
|
+
: target === 'help'
|
|
1445
|
+
? 'Local workflow options'
|
|
1446
|
+
: target === 'mcp-posture'
|
|
1447
|
+
? 'MCP posture scan'
|
|
1448
|
+
: 'Choose scan target';
|
|
1449
|
+
byId('connectSubtitle').textContent = target === 'runner'
|
|
1450
|
+
? 'Call one MCP tool from this machine. This is not a scan.'
|
|
1451
|
+
: target === 'help'
|
|
1452
|
+
? 'Choose one-off tool run or local red-team scan.'
|
|
1453
|
+
: target === 'mcp-posture'
|
|
1454
|
+
? 'Connect an MCP server or protected gateway. No tools are invoked.'
|
|
1455
|
+
: 'Select Endpoint, RAG, or MCP, then connect the internal target.';
|
|
1456
|
+
if (!scanTarget || target === 'home') setStep(1);
|
|
1457
|
+
};
|
|
1458
|
+
const setStep = next => {
|
|
1459
|
+
step = next;
|
|
1460
|
+
[1,2,3].forEach(n => {
|
|
1461
|
+
byId('step-' + n).classList.toggle('hidden', n !== step);
|
|
1462
|
+
byId('step-dot-' + n).classList.toggle('active', n === step);
|
|
1463
|
+
byId('step-dot-' + n).classList.toggle('done', n < step);
|
|
1464
|
+
});
|
|
1465
|
+
};
|
|
1466
|
+
document.querySelectorAll('.workflowCard').forEach(c => c.onclick = () => {
|
|
1467
|
+
target = c.dataset.workflow === 'bot' ? 'endpoint' : c.dataset.workflow;
|
|
1468
|
+
sync();
|
|
1469
|
+
byId('workflowPanel').scrollIntoView({ behavior: 'smooth', block: 'start' });
|
|
1470
|
+
});
|
|
1471
|
+
document.querySelectorAll('.scanTypeCard').forEach(c => c.onclick = () => {
|
|
1472
|
+
target = c.dataset.target;
|
|
1473
|
+
sync();
|
|
1474
|
+
});
|
|
1475
|
+
sync();
|
|
1476
|
+
byId('ragUrl').value = qs.get('ragUrl') || '';
|
|
1477
|
+
byId('ragPath').value = qs.get('ragPath') || '';
|
|
1478
|
+
byId('endpoint').value = qs.get('endpoint') || '';
|
|
1479
|
+
byId('mcpUrl').value = qs.get('mcpUrl') || '';
|
|
1480
|
+
byId('mcpCommand').value = qs.get('mcpCommand') || '';
|
|
1481
|
+
byId('mcpArgs').value = qs.get('mcpArgs') || '';
|
|
1482
|
+
byId('runnerMcpUrl').value = qs.get('mcpUrl') || '';
|
|
1483
|
+
byId('runnerMcpCommand').value = qs.get('mcpCommand') || '';
|
|
1484
|
+
byId('runnerMcpArgs').value = qs.get('mcpArgs') || '';
|
|
1485
|
+
byId('runnerTool').value = qs.get('mcpTool') || '';
|
|
1486
|
+
byId('runnerOperation').value = qs.get('operation') || '';
|
|
1487
|
+
const payload = () => ({
|
|
1488
|
+
target, mode: byId('mode').value, format: byId('format').value,
|
|
1489
|
+
endpoint: byId('endpoint').value, inputField: byId('inputField').value, outputField: byId('outputField').value,
|
|
1490
|
+
postureUrl: byId('postureUrl').value, postureMethod: byId('postureMethod').value, postureAuthType: byId('postureAuthType').value, postureToken: byId('postureToken').value,
|
|
1491
|
+
ragUrl: byId('ragUrl').value, ragPath: byId('ragPath').value, ragInputField: byId('ragInputField').value, ragOutputField: byId('ragOutputField').value,
|
|
1492
|
+
mcpUrl: byId('mcpUrl').value, mcpCommand: byId('mcpCommand').value, mcpArgs: byId('mcpArgs').value, mcpTool: byId('mcpTool').value
|
|
1493
|
+
});
|
|
1494
|
+
const mcpRunnerPayload = () => ({
|
|
1495
|
+
mcpUrl: byId('runnerMcpUrl').value,
|
|
1496
|
+
mcpCommand: byId('runnerMcpCommand').value,
|
|
1497
|
+
mcpArgs: byId('runnerMcpArgs').value,
|
|
1498
|
+
toolName: byId('runnerTool').value,
|
|
1499
|
+
operation: byId('runnerOperation').value,
|
|
1500
|
+
toolArgs: byId('runnerToolArgs').value
|
|
1501
|
+
});
|
|
1502
|
+
async function loadMcpServers() {
|
|
1503
|
+
try {
|
|
1504
|
+
const res = await fetch('/mcp/servers');
|
|
1505
|
+
const data = await res.json();
|
|
1506
|
+
const select = byId('runnerServer');
|
|
1507
|
+
const options = data.servers || [];
|
|
1508
|
+
select.innerHTML = '<option value="">Manual connection...</option>' + options.map((server, index) =>
|
|
1509
|
+
'<option value="' + index + '">' + htmlEscape((server.protected ? 'Protected: ' : 'Direct: ') + server.name + ' - ' + server.source + (server.agentName ? ' - ' + server.agentName : '')) + '</option>'
|
|
1510
|
+
).join('');
|
|
1511
|
+
select.onchange = () => {
|
|
1512
|
+
const server = options[Number(select.value)];
|
|
1513
|
+
if (!server) return;
|
|
1514
|
+
byId('runnerMcpUrl').value = '';
|
|
1515
|
+
byId('runnerMcpCommand').value = server.command || '';
|
|
1516
|
+
byId('runnerMcpArgs').value = Array.isArray(server.args) ? server.args.map(part => /\\s/.test(part) ? JSON.stringify(part) : part).join(' ') : '';
|
|
1517
|
+
setConsole('Loaded saved MCP server: ' + server.name + '\\n' + (server.protected ? 'This entry is already routed through FullCourtDefense gateway.' : 'Direct MCP entry. Wrap it with protect-all for policy enforcement.'), 'Server loaded');
|
|
1518
|
+
};
|
|
1519
|
+
} catch {
|
|
1520
|
+
// Local server picker is convenience only; manual fields still work.
|
|
1521
|
+
}
|
|
1522
|
+
}
|
|
1523
|
+
const renderCommand = p => {
|
|
1524
|
+
const parts = ['fullcourtdefense scan --local --type', p.target, '--mode', p.mode, '--format', p.format, '--no-open'];
|
|
1525
|
+
if (p.target === 'endpoint') parts.push('--endpoint', JSON.stringify(p.endpoint), '--method POST --request-format custom --input-field', p.inputField || 'message', '--output-field', p.outputField || 'response');
|
|
1526
|
+
if (p.target === 'posture') return 'Security Posture Scan runs through this local web UI and calls the Full Court Defense posture checkup API for ' + (p.postureUrl || '<endpoint-url>');
|
|
1527
|
+
if (p.target === 'rag') { if (p.ragUrl) parts.push('--rag-url', JSON.stringify(p.ragUrl), '--method POST --request-format custom --input-field', p.ragInputField || 'query', '--output-field', p.ragOutputField || 'chunks'); else parts.push('--rag-path', JSON.stringify(p.ragPath || '.\\\\docs')); }
|
|
1528
|
+
if (p.target === 'mcp-posture') return 'MCP Posture Scan runs through this local web UI and inventories exposed tools for ' + (p.mcpUrl || ((p.mcpCommand || 'node') + ' ' + (p.mcpArgs || '.\\\\server.js')));
|
|
1529
|
+
if (p.target === 'mcp') { if (p.mcpUrl) parts.push('--mcp-url', JSON.stringify(p.mcpUrl)); else parts.push('--mcp-command', p.mcpCommand || 'node', '--mcp-args', JSON.stringify(p.mcpArgs || '.\\\\server.js')); parts.push('--mcp-tool', p.mcpTool || 'all'); }
|
|
1530
|
+
return parts.join(' ');
|
|
1531
|
+
};
|
|
1532
|
+
const setConsole = (value, status) => {
|
|
1533
|
+
byId('out').innerHTML = htmlEscape(value).replace(/(https?:\\/\\/[^\\s)]+)/g, '<a style="color:#93c5fd" href="$1" target="_blank">$1</a>');
|
|
1534
|
+
byId('out').scrollTop = byId('out').scrollHeight;
|
|
1535
|
+
if (status) byId('consoleStatus').textContent = status;
|
|
1536
|
+
};
|
|
1537
|
+
const parseScanSummary = (text) => {
|
|
1538
|
+
const match = (regex) => (text.match(regex)?.[1] || '').trim();
|
|
1539
|
+
const status = match(/Status:\\s*(PASS|FAIL)/i).toUpperCase();
|
|
1540
|
+
const score = match(/Score:\\s*(\\d+\\/?100?)/i) || match(/Score:\\s*(\\d+)/i);
|
|
1541
|
+
const passed = match(/Passed:\\s*(\\d+)/i);
|
|
1542
|
+
const failed = match(/Failed:\\s*(\\d+)/i);
|
|
1543
|
+
const violations = match(/Violations:\\s*(\\d+)/i);
|
|
1544
|
+
const session = match(/Session:\\s*([^\\n]+)/i);
|
|
1545
|
+
const targetText = match(/Target:\\s*([^\\n]+)/i);
|
|
1546
|
+
const reportUrl = match(/Web report:\\s*(https?:\\/\\/[^\\s]+)/i) || match(/Saved web report [^\\(]*\\((https?:\\/\\/[^\\s)]+)\\)/i);
|
|
1547
|
+
if (!status && !score && !passed && !failed) return null;
|
|
1548
|
+
return { status, score, passed, failed, violations, session, targetText, reportUrl };
|
|
1549
|
+
};
|
|
1550
|
+
const clearScanSummary = () => {
|
|
1551
|
+
byId('resultCard').classList.add('hidden');
|
|
1552
|
+
byId('resultStatus').textContent = 'READY';
|
|
1553
|
+
byId('resultStatus').className = 'statusTag';
|
|
1554
|
+
byId('resultScore').textContent = '-';
|
|
1555
|
+
byId('resultPassed').textContent = '-';
|
|
1556
|
+
byId('resultFailed').textContent = '-';
|
|
1557
|
+
byId('resultViolations').textContent = '-';
|
|
1558
|
+
byId('resultSession').textContent = '-';
|
|
1559
|
+
byId('resultTarget').textContent = '-';
|
|
1560
|
+
byId('resultMeta').innerHTML = '';
|
|
1561
|
+
};
|
|
1562
|
+
const setScanSummary = (summary) => {
|
|
1563
|
+
if (!summary) return;
|
|
1564
|
+
byId('resultCard').classList.remove('hidden');
|
|
1565
|
+
const statusClass = summary.status === 'PASS' ? 'statusTag pass' : summary.status === 'FAIL' ? 'statusTag fail' : 'statusTag';
|
|
1566
|
+
byId('resultStatus').className = statusClass;
|
|
1567
|
+
byId('resultStatus').textContent = summary.status || 'COMPLETE';
|
|
1568
|
+
byId('resultScore').textContent = summary.score || '-';
|
|
1569
|
+
byId('resultPassed').textContent = summary.passed || '-';
|
|
1570
|
+
byId('resultFailed').textContent = summary.failed || '-';
|
|
1571
|
+
byId('resultViolations').textContent = summary.violations || '-';
|
|
1572
|
+
byId('resultSession').textContent = summary.session || '-';
|
|
1573
|
+
byId('resultTarget').textContent = summary.targetText ? summary.targetText.slice(0, 48) + (summary.targetText.length > 48 ? '…' : '') : '-';
|
|
1574
|
+
byId('resultMeta').innerHTML = summary.reportUrl
|
|
1575
|
+
? 'Saved report: <a target="_blank" href="' + htmlEscape(summary.reportUrl) + '">' + htmlEscape(summary.reportUrl) + '</a>'
|
|
1576
|
+
: 'No saved web report URL found in this output.';
|
|
1577
|
+
};
|
|
1578
|
+
const resetForNewScan = () => {
|
|
1579
|
+
target = 'runner';
|
|
1580
|
+
sync();
|
|
1581
|
+
setStep(1);
|
|
1582
|
+
byId('endpoint').value = '';
|
|
1583
|
+
byId('postureUrl').value = '';
|
|
1584
|
+
byId('postureMethod').value = 'POST';
|
|
1585
|
+
byId('postureAuthType').value = 'none';
|
|
1586
|
+
byId('postureToken').value = '';
|
|
1587
|
+
byId('inputField').value = 'message';
|
|
1588
|
+
byId('outputField').value = 'response';
|
|
1589
|
+
byId('ragUrl').value = '';
|
|
1590
|
+
byId('ragPath').value = '';
|
|
1591
|
+
byId('ragInputField').value = 'query';
|
|
1592
|
+
byId('ragOutputField').value = 'chunks';
|
|
1593
|
+
byId('mcpUrl').value = '';
|
|
1594
|
+
byId('mcpCommand').value = '';
|
|
1595
|
+
byId('mcpArgs').value = '';
|
|
1596
|
+
byId('mcpTool').value = 'all';
|
|
1597
|
+
byId('runnerMcpUrl').value = '';
|
|
1598
|
+
byId('runnerMcpCommand').value = '';
|
|
1599
|
+
byId('runnerMcpArgs').value = '';
|
|
1600
|
+
byId('runnerTool').value = '';
|
|
1601
|
+
byId('runnerOperation').value = '';
|
|
1602
|
+
byId('runnerToolArgs').value = '{\\n "customerId": "RO-PB-10492"\\n}';
|
|
1603
|
+
byId('mcpResultCard').classList.add('hidden');
|
|
1604
|
+
byId('mcpResultJson').value = '';
|
|
1605
|
+
byId('mcpResultTable').innerHTML = '';
|
|
1606
|
+
byId('mode').value = 'quick';
|
|
1607
|
+
byId('format').value = 'report';
|
|
1608
|
+
clearScanSummary();
|
|
1609
|
+
setConsole('Full Court Defense Local Web Console ready.\\n\\nChoose Run MCP Tool for one live tool call.\\nChoose Endpoint/RAG/MCP Scan for a local red-team scan.', 'Ready');
|
|
1610
|
+
};
|
|
1611
|
+
byId('command').onclick = () => { setConsole(renderCommand(payload()), 'Command'); };
|
|
1612
|
+
byId('connectNext').onclick = () => setStep(2);
|
|
1613
|
+
byId('backToConnect').onclick = () => setStep(1);
|
|
1614
|
+
byId('scanTypeNext').onclick = () => setStep(3);
|
|
1615
|
+
byId('newScan').onclick = () => resetForNewScan();
|
|
1616
|
+
byId('toggleSetup').onclick = () => {
|
|
1617
|
+
const panel = byId('setupPanel');
|
|
1618
|
+
const hidden = panel.classList.contains('hidden');
|
|
1619
|
+
panel.classList.toggle('hidden', !hidden);
|
|
1620
|
+
byId('toggleSetup').textContent = hidden ? 'Hide Shield settings' : 'Change Shield settings';
|
|
1621
|
+
};
|
|
1622
|
+
byId('saveSetup').onclick = async () => {
|
|
1623
|
+
byId('authStatus').textContent = 'Saving Shield settings...';
|
|
1624
|
+
const res = await fetch('/auth/save', {
|
|
1625
|
+
method: 'POST',
|
|
1626
|
+
headers: { 'content-type': 'application/json' },
|
|
1627
|
+
body: JSON.stringify({
|
|
1628
|
+
shieldId: byId('shieldId').value,
|
|
1629
|
+
shieldKey: byId('shieldKey').value,
|
|
1630
|
+
apiUrl: byId('apiUrl').value
|
|
1631
|
+
})
|
|
1632
|
+
});
|
|
1633
|
+
const data = await res.json();
|
|
1634
|
+
if (data.success) {
|
|
1635
|
+
byId('authStatus').textContent = 'Connected to Shield ' + data.shieldId + '. Reports will save to Full Court Defense.';
|
|
1636
|
+
byId('run').disabled = false;
|
|
1637
|
+
} else {
|
|
1638
|
+
byId('authStatus').textContent = data.error || 'Could not save Shield settings.';
|
|
1639
|
+
}
|
|
1640
|
+
};
|
|
1641
|
+
const checkBackend = async () => {
|
|
1642
|
+
byId('backendStatus').textContent = 'Checking outbound backend...';
|
|
1643
|
+
const res = await fetch('/backend/check', {
|
|
1644
|
+
method: 'POST',
|
|
1645
|
+
headers: { 'content-type': 'application/json' },
|
|
1646
|
+
body: JSON.stringify({
|
|
1647
|
+
shieldId: byId('shieldId').value,
|
|
1648
|
+
shieldKey: byId('shieldKey').value,
|
|
1649
|
+
apiUrl: byId('apiUrl').value
|
|
1650
|
+
})
|
|
1651
|
+
});
|
|
1652
|
+
const data = await res.json();
|
|
1653
|
+
if (data.ok) {
|
|
1654
|
+
byId('backendStatus').textContent = 'Backend status: connected. Outbound scan/report traffic can reach Full Court Defense.';
|
|
1655
|
+
byId('backendStatus').style.borderColor = '#bbf7d0';
|
|
1656
|
+
byId('backendStatus').style.background = '#f0fdf4';
|
|
1657
|
+
byId('backendStatus').style.color = '#166534';
|
|
1658
|
+
} else {
|
|
1659
|
+
byId('backendStatus').textContent = 'Backend status: not reachable. ' + (data.error || 'Check network/proxy/firewall/API URL.');
|
|
1660
|
+
byId('backendStatus').style.borderColor = '#fecaca';
|
|
1661
|
+
byId('backendStatus').style.background = '#fef2f2';
|
|
1662
|
+
byId('backendStatus').style.color = '#991b1b';
|
|
1663
|
+
}
|
|
1664
|
+
return data.ok;
|
|
1665
|
+
};
|
|
1666
|
+
byId('checkBackend').onclick = checkBackend;
|
|
1667
|
+
loadMcpServers();
|
|
1668
|
+
checkBackend().catch(() => {});
|
|
1669
|
+
function mcpRows(value) {
|
|
1670
|
+
if (Array.isArray(value)) return value;
|
|
1671
|
+
const content = value && value.content;
|
|
1672
|
+
if (Array.isArray(content)) {
|
|
1673
|
+
const textItem = content.find(item => item && typeof item.text === 'string');
|
|
1674
|
+
if (textItem) {
|
|
1675
|
+
try {
|
|
1676
|
+
const parsed = JSON.parse(textItem.text);
|
|
1677
|
+
if (Array.isArray(parsed)) return parsed;
|
|
1678
|
+
if (parsed && Array.isArray(parsed.data)) return parsed.data;
|
|
1679
|
+
if (parsed && Array.isArray(parsed.results)) return parsed.results;
|
|
1680
|
+
} catch {}
|
|
1681
|
+
}
|
|
1682
|
+
}
|
|
1683
|
+
if (value && Array.isArray(value.data)) return value.data;
|
|
1684
|
+
if (value && Array.isArray(value.results)) return value.results;
|
|
1685
|
+
return null;
|
|
1686
|
+
}
|
|
1687
|
+
function mcpText(value) {
|
|
1688
|
+
if (value == null) return '';
|
|
1689
|
+
if (typeof value === 'string') return value;
|
|
1690
|
+
if (typeof value === 'number' || typeof value === 'boolean') return String(value);
|
|
1691
|
+
const content = value.content;
|
|
1692
|
+
if (Array.isArray(content)) {
|
|
1693
|
+
return content
|
|
1694
|
+
.map(item => item && typeof item.text === 'string' ? item.text : '')
|
|
1695
|
+
.filter(Boolean)
|
|
1696
|
+
.join('\\n');
|
|
1697
|
+
}
|
|
1698
|
+
return '';
|
|
1699
|
+
}
|
|
1700
|
+
function mcpObject(value) {
|
|
1701
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) return null;
|
|
1702
|
+
const contentText = mcpText(value);
|
|
1703
|
+
if (contentText) {
|
|
1704
|
+
try {
|
|
1705
|
+
const parsed = JSON.parse(contentText);
|
|
1706
|
+
if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) return parsed;
|
|
1707
|
+
} catch {}
|
|
1708
|
+
}
|
|
1709
|
+
if (value.data && typeof value.data === 'object' && !Array.isArray(value.data)) return value.data;
|
|
1710
|
+
if (value.result && typeof value.result === 'object' && !Array.isArray(value.result)) return value.result;
|
|
1711
|
+
return value;
|
|
1712
|
+
}
|
|
1713
|
+
function cellValue(value) {
|
|
1714
|
+
if (value == null) return '';
|
|
1715
|
+
if (typeof value === 'object') return JSON.stringify(value);
|
|
1716
|
+
return String(value);
|
|
1717
|
+
}
|
|
1718
|
+
function renderObjectCards(obj) {
|
|
1719
|
+
const preferred = ['id', 'customerId', 'name', 'title', 'status', 'plan', 'email', 'decision', 'matchedPolicy', 'reason', 'ok'];
|
|
1720
|
+
const keys = Array.from(new Set([...preferred.filter(k => Object.prototype.hasOwnProperty.call(obj, k)), ...Object.keys(obj)])).slice(0, 12);
|
|
1721
|
+
return '<div class="mcpCards">' + keys.map(k => '<div class="mcpCard"><b>' + htmlEscape(k) + '</b><span>' + htmlEscape(cellValue(obj[k])) + '</span></div>').join('') + '</div>';
|
|
1722
|
+
}
|
|
1723
|
+
function renderMcpTable(rows) {
|
|
1724
|
+
const preferred = ['id', 'customerId', 'name', 'title', 'status', 'plan', 'email', 'createdAt', 'updatedAt'];
|
|
1725
|
+
const keys = Array.from(new Set([...preferred.filter(k => rows.some(row => row && Object.prototype.hasOwnProperty.call(row, k))), ...rows.flatMap(row => Object.keys(row || {}))])).slice(0, 10);
|
|
1726
|
+
return '<table class="localTable"><thead><tr>' + keys.map(k => '<th>' + htmlEscape(k) + '</th>').join('') + '</tr></thead><tbody>' +
|
|
1727
|
+
rows.slice(0, 50).map(row => '<tr>' + keys.map(k => '<td>' + htmlEscape(cellValue(row[k])) + '</td>').join('') + '</tr>').join('') +
|
|
1728
|
+
'</tbody></table>' +
|
|
1729
|
+
(rows.length > 50 ? '<div class="resultMeta">Showing first 50 rows. Full result is in raw JSON below.</div>' : '');
|
|
1730
|
+
}
|
|
1731
|
+
function renderMcpResult(data) {
|
|
1732
|
+
byId('mcpResultCard').classList.remove('hidden');
|
|
1733
|
+
byId('mcpResultStatus').textContent = data.blocked ? 'BLOCKED' : data.success ? 'ALLOW / RESULT' : 'ERROR';
|
|
1734
|
+
byId('mcpResultStatus').className = data.success ? 'statusTag pass' : 'statusTag fail';
|
|
1735
|
+
byId('mcpResultJson').value = JSON.stringify(data, null, 2);
|
|
1736
|
+
const rows = data.success ? mcpRows(data.result) : null;
|
|
1737
|
+
if (rows && rows.length && rows.every(row => row && typeof row === 'object' && !Array.isArray(row))) {
|
|
1738
|
+
byId('mcpResultTable').innerHTML = '<div class="mcpBanner ok"><b>Allowed.</b> Tool returned ' + rows.length + ' row(s).</div>' + renderMcpTable(rows);
|
|
1739
|
+
} else if (data.success) {
|
|
1740
|
+
const obj = mcpObject(data.result);
|
|
1741
|
+
const text = mcpText(data.result);
|
|
1742
|
+
byId('mcpResultTable').innerHTML = '<div class="mcpBanner ok"><b>Allowed.</b> Tool call completed through the configured MCP server/proxy.</div>' +
|
|
1743
|
+
(obj ? renderObjectCards(obj) : '<div class="resultMeta">' + htmlEscape(text || 'Result is shown as raw JSON below.') + '</div>');
|
|
1744
|
+
} else {
|
|
1745
|
+
const blockedCopy = data.blocked
|
|
1746
|
+
? 'Blocked because a FullCourtDefense policy or local safety rule is defined for this tool/action.'
|
|
1747
|
+
: 'The MCP call failed before a successful tool result was returned.';
|
|
1748
|
+
byId('mcpResultTable').innerHTML = '<div class="mcpBanner blocked"><b>' + (data.blocked ? 'Blocked by policy.' : 'MCP error.') + '</b> ' + htmlEscape(blockedCopy) + '<br/><span>' + htmlEscape(data.reason || data.error || 'No reason returned.') + '</span></div>';
|
|
1749
|
+
}
|
|
1750
|
+
}
|
|
1751
|
+
byId('copyMcpResult').onclick = async () => {
|
|
1752
|
+
await navigator.clipboard?.writeText(byId('mcpResultJson').value || '').catch(() => undefined);
|
|
1753
|
+
byId('copyMcpResult').textContent = 'Copied';
|
|
1754
|
+
setTimeout(() => { byId('copyMcpResult').textContent = 'Copy result'; }, 1200);
|
|
1755
|
+
};
|
|
1756
|
+
byId('runMcpTool').onclick = async () => {
|
|
1757
|
+
byId('runMcpTool').disabled = true;
|
|
1758
|
+
byId('runMcpTool').textContent = 'Running...';
|
|
1759
|
+
byId('mcpResultCard').classList.add('hidden');
|
|
1760
|
+
setConsole('Calling MCP tool locally through configured proxy/server...\\n', 'Running MCP');
|
|
1761
|
+
try {
|
|
1762
|
+
const res = await fetch('/mcp/run', {
|
|
1763
|
+
method: 'POST',
|
|
1764
|
+
headers: { 'content-type': 'application/json' },
|
|
1765
|
+
body: JSON.stringify(mcpRunnerPayload())
|
|
1766
|
+
});
|
|
1767
|
+
const data = await res.json();
|
|
1768
|
+
renderMcpResult(data);
|
|
1769
|
+
setConsole(JSON.stringify(data, null, 2), data.success ? 'MCP complete' : 'MCP blocked/error');
|
|
1770
|
+
} catch (error) {
|
|
1771
|
+
const data = { success: false, error: error && error.message ? error.message : String(error) };
|
|
1772
|
+
renderMcpResult(data);
|
|
1773
|
+
setConsole(JSON.stringify(data, null, 2), 'MCP error');
|
|
1774
|
+
} finally {
|
|
1775
|
+
byId('runMcpTool').disabled = false;
|
|
1776
|
+
byId('runMcpTool').textContent = 'Run MCP tool';
|
|
1777
|
+
}
|
|
1778
|
+
};
|
|
1779
|
+
byId('run').onclick = async () => {
|
|
1780
|
+
if (byId('run').disabled) return;
|
|
1781
|
+
byId('run').disabled = true;
|
|
1782
|
+
byId('run').textContent = 'Running...';
|
|
1783
|
+
clearScanSummary();
|
|
1784
|
+
if (target === 'mcp-posture') {
|
|
1785
|
+
try {
|
|
1786
|
+
setConsole('Starting MCP Posture Scan...\\nNo MCP tools will be invoked; this only lists exposed tools.\\n', 'Running MCP posture');
|
|
1787
|
+
const postureRes = await fetch('/mcp/posture', { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify(payload()) });
|
|
1788
|
+
const postureData = await postureRes.json();
|
|
1789
|
+
setConsole(JSON.stringify(postureData, null, 2), postureData.ok ? 'MCP posture complete' : 'MCP posture failed');
|
|
1790
|
+
} catch (error) {
|
|
1791
|
+
setConsole('MCP Posture Scan failed: ' + (error && error.message ? error.message : String(error)), 'MCP posture failed');
|
|
1792
|
+
} finally {
|
|
1793
|
+
byId('run').disabled = false;
|
|
1794
|
+
byId('run').textContent = 'Run local scan';
|
|
1795
|
+
}
|
|
1796
|
+
return;
|
|
1797
|
+
}
|
|
1798
|
+
const backendOk = await checkBackend();
|
|
1799
|
+
if (!backendOk) {
|
|
1800
|
+
setConsole('Outbound backend check failed. Fix backend connectivity before scanning so verdicts and reports can be generated.', 'Blocked');
|
|
1801
|
+
byId('run').disabled = false;
|
|
1802
|
+
byId('run').textContent = 'Run local scan';
|
|
1803
|
+
return;
|
|
1804
|
+
}
|
|
1805
|
+
if (target === 'posture') {
|
|
1806
|
+
try {
|
|
1807
|
+
setConsole('Starting Security Posture Scan...\\n', 'Running posture');
|
|
1808
|
+
const postureRes = await fetch('/posture/run', { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify(payload()) });
|
|
1809
|
+
const postureData = await postureRes.json();
|
|
1810
|
+
setConsole(JSON.stringify(postureData, null, 2), postureData.ok ? 'Posture complete' : 'Posture failed');
|
|
1811
|
+
} catch (error) {
|
|
1812
|
+
setConsole('Security Posture Scan failed: ' + (error && error.message ? error.message : String(error)), 'Posture failed');
|
|
1813
|
+
} finally {
|
|
1814
|
+
byId('run').disabled = false;
|
|
1815
|
+
byId('run').textContent = 'Run local scan';
|
|
1816
|
+
}
|
|
1817
|
+
return;
|
|
1818
|
+
}
|
|
1819
|
+
setConsole('Starting local red-team scan...\\n', 'Running');
|
|
1820
|
+
const res = await fetch('/run', { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify(payload()) });
|
|
1821
|
+
if (!res.body) {
|
|
1822
|
+
const finalText = await res.text();
|
|
1823
|
+
setConsole(finalText, 'Complete');
|
|
1824
|
+
setScanSummary(parseScanSummary(finalText));
|
|
1825
|
+
byId('run').disabled = false;
|
|
1826
|
+
byId('run').textContent = 'Run local scan';
|
|
1827
|
+
return;
|
|
1828
|
+
}
|
|
1829
|
+
const reader = res.body.getReader();
|
|
1830
|
+
const decoder = new TextDecoder();
|
|
1831
|
+
let text = '';
|
|
1832
|
+
while (true) {
|
|
1833
|
+
const next = await reader.read();
|
|
1834
|
+
if (next.done) break;
|
|
1835
|
+
text += decoder.decode(next.value, { stream: true });
|
|
1836
|
+
setConsole(text, 'Running');
|
|
1837
|
+
}
|
|
1838
|
+
byId('consoleStatus').textContent = 'Complete';
|
|
1839
|
+
setScanSummary(parseScanSummary(text));
|
|
1840
|
+
byId('run').disabled = false;
|
|
1841
|
+
byId('run').textContent = 'Run local scan';
|
|
1842
|
+
};
|
|
1843
|
+
function htmlEscape(s) { return s.replace(/[&<>"]/g, ch => ({'&':'&','<':'<','>':'>','"':'"'}[ch])); }
|
|
1844
|
+
</script>
|
|
1845
|
+
</body>
|
|
1846
1846
|
</html>`;
|
|
1847
1847
|
}
|
|
1848
1848
|
async function readRequestBody(req) {
|