fullcourtdefense-cli 1.7.12 → 1.7.14
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/local-scan.js +130 -16
- package/dist/version.json +1 -1
- package/package.json +1 -1
|
@@ -1096,7 +1096,7 @@ function compactScanHtml(args) {
|
|
|
1096
1096
|
h1 { margin: 6px 0 8px; font-size: 26px; line-height: 1.15; }
|
|
1097
1097
|
p { color: #475569; line-height: 1.55; }
|
|
1098
1098
|
.grid { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 12px; }
|
|
1099
|
-
.menuShell { display: grid; grid-template-columns: minmax(
|
|
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
1100
|
.menuGroup { border: 1px solid #dbeafe; background: rgba(255,255,255,.7); border-radius: 20px; padding: 12px; }
|
|
1101
1101
|
.menuGroup.run { border-color: #bbf7d0; background: #f0fdf4; }
|
|
1102
1102
|
.menuLabel { color: #475569; font-size: 11px; font-weight: 900; letter-spacing: .08em; text-transform: uppercase; margin: 0 0 9px; }
|
|
@@ -1108,6 +1108,12 @@ function compactScanHtml(args) {
|
|
|
1108
1108
|
.sectionTitle h2 { margin: 0; font-size: 18px; }
|
|
1109
1109
|
.muted { color: #64748b; font-size: 13px; }
|
|
1110
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(2, 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; }
|
|
1111
1117
|
.card:hover, .card.active { border-color: #2563eb; background: #eff6ff; transform: translateY(-1px); }
|
|
1112
1118
|
.card b { display: block; font-size: 14px; margin-bottom: 4px; }
|
|
1113
1119
|
.card span { color: #64748b; font-size: 12px; line-height: 1.35; }
|
|
@@ -1157,8 +1163,8 @@ function compactScanHtml(args) {
|
|
|
1157
1163
|
<main class="wrap">
|
|
1158
1164
|
<section class="hero">
|
|
1159
1165
|
<div class="eyebrow">Local Web Console</div>
|
|
1160
|
-
<h1>
|
|
1161
|
-
<p>
|
|
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>
|
|
1162
1168
|
<div class="auth">
|
|
1163
1169
|
<div class="eyebrow">${isConfigured ? 'Authenticated' : 'Authentication Required'}</div>
|
|
1164
1170
|
<p id="authStatus">${isConfigured
|
|
@@ -1189,19 +1195,18 @@ function compactScanHtml(args) {
|
|
|
1189
1195
|
<div class="menuShell" id="cards">
|
|
1190
1196
|
<div class="menuGroup run">
|
|
1191
1197
|
<div class="menuLabel">Run, not a scan</div>
|
|
1192
|
-
<button class="card active" data-
|
|
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>
|
|
1193
1199
|
</div>
|
|
1194
1200
|
<div class="menuGroup">
|
|
1195
1201
|
<div class="menuLabel">Security scans</div>
|
|
1196
|
-
<div class="
|
|
1197
|
-
<button class="card" data-
|
|
1198
|
-
<button class="card" data-
|
|
1199
|
-
<button class="card" data-target="mcp"><b>MCP Scan</b><span>Scan tool responses and tool-abuse paths.</span></button>
|
|
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>
|
|
1200
1205
|
</div>
|
|
1201
1206
|
</div>
|
|
1202
1207
|
<div class="menuGroup">
|
|
1203
1208
|
<div class="menuLabel">Help</div>
|
|
1204
|
-
<button class="card" data-
|
|
1209
|
+
<button class="card workflowCard" data-workflow="help"><b>Options</b><span>Modes, formats, and when to use each local workflow.</span></button>
|
|
1205
1210
|
</div>
|
|
1206
1211
|
</div>
|
|
1207
1212
|
</section>
|
|
@@ -1265,6 +1270,19 @@ function compactScanHtml(args) {
|
|
|
1265
1270
|
</div>
|
|
1266
1271
|
</div>
|
|
1267
1272
|
|
|
1273
|
+
<div id="scan-picker" class="hidden">
|
|
1274
|
+
<div class="note">Security scans run red-team templates locally from this machine. First choose what you want to test.</div>
|
|
1275
|
+
<div class="scanTypeGrid">
|
|
1276
|
+
<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>
|
|
1277
|
+
<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>
|
|
1278
|
+
</div>
|
|
1279
|
+
<div class="menuLabel">Advanced local targets</div>
|
|
1280
|
+
<div class="advancedScanGrid">
|
|
1281
|
+
<button class="card scanTypeCard" data-target="rag"><b>RAG Scan</b><span>Test retrieval URL or local corpus path.</span></button>
|
|
1282
|
+
<button class="card scanTypeCard" data-target="mcp"><b>MCP Scan</b><span>Scan tool responses and tool-abuse paths.</span></button>
|
|
1283
|
+
</div>
|
|
1284
|
+
</div>
|
|
1285
|
+
|
|
1268
1286
|
<div id="endpoint-fields" class="hidden">
|
|
1269
1287
|
<label>Internal endpoint URL</label>
|
|
1270
1288
|
<input id="endpoint" placeholder="http://127.0.0.1:3000/chat" />
|
|
@@ -1274,6 +1292,30 @@ function compactScanHtml(args) {
|
|
|
1274
1292
|
</div>
|
|
1275
1293
|
</div>
|
|
1276
1294
|
|
|
1295
|
+
<div id="posture-fields" class="hidden">
|
|
1296
|
+
<div class="note">Security Posture Scan checks endpoint configuration, auth exposure, headers, transport, and operational security signals. This matches the web Security Checkup flow.</div>
|
|
1297
|
+
<div class="two">
|
|
1298
|
+
<div>
|
|
1299
|
+
<label>AI endpoint URL</label>
|
|
1300
|
+
<input id="postureUrl" placeholder="https://your-ai-endpoint.com/api/chat" />
|
|
1301
|
+
</div>
|
|
1302
|
+
<div>
|
|
1303
|
+
<label>HTTP method</label>
|
|
1304
|
+
<select id="postureMethod"><option value="POST" selected>POST</option><option value="GET">GET</option></select>
|
|
1305
|
+
</div>
|
|
1306
|
+
</div>
|
|
1307
|
+
<div class="two">
|
|
1308
|
+
<div>
|
|
1309
|
+
<label>Auth type</label>
|
|
1310
|
+
<select id="postureAuthType"><option value="none" selected>No Auth</option><option value="bearer">Bearer Token</option><option value="api-key">API Key</option></select>
|
|
1311
|
+
</div>
|
|
1312
|
+
<div>
|
|
1313
|
+
<label>Token / API key value</label>
|
|
1314
|
+
<input id="postureToken" type="password" placeholder="Optional" />
|
|
1315
|
+
</div>
|
|
1316
|
+
</div>
|
|
1317
|
+
</div>
|
|
1318
|
+
|
|
1277
1319
|
<div id="rag-fields" class="hidden">
|
|
1278
1320
|
<label>RAG retrieval URL</label>
|
|
1279
1321
|
<input id="ragUrl" placeholder="http://127.0.0.1:8787/rag/retrieve" />
|
|
@@ -1371,27 +1413,31 @@ Local targets stay on this machine; saved scan reports go to Full Court Defense.
|
|
|
1371
1413
|
</section>
|
|
1372
1414
|
</main>
|
|
1373
1415
|
<script>
|
|
1416
|
+
const scanTargets = ['endpoint','posture','rag','mcp'];
|
|
1374
1417
|
let target = 'runner';
|
|
1375
1418
|
let step = 1;
|
|
1376
1419
|
const qs = new URLSearchParams(location.search);
|
|
1377
|
-
if (['runner','endpoint','rag','mcp'].includes(qs.get('target'))) target = qs.get('target');
|
|
1420
|
+
if (['runner','endpoint','posture','rag','mcp','help'].includes(qs.get('target'))) target = qs.get('target');
|
|
1378
1421
|
const byId = id => document.getElementById(id);
|
|
1422
|
+
const workflow = () => target === 'endpoint' ? 'bot' : target === 'posture' ? 'posture' : target;
|
|
1379
1423
|
const sync = () => {
|
|
1380
|
-
document.querySelectorAll('.
|
|
1381
|
-
|
|
1382
|
-
|
|
1424
|
+
document.querySelectorAll('.workflowCard').forEach(c => c.classList.toggle('active', c.dataset.workflow === workflow()));
|
|
1425
|
+
document.querySelectorAll('.scanTypeCard').forEach(c => c.classList.toggle('active', c.dataset.target === target));
|
|
1426
|
+
['runner','endpoint','posture','rag','mcp','help'].forEach(id => byId(id + '-fields').classList.toggle('hidden', id !== target));
|
|
1427
|
+
const scanTarget = scanTargets.includes(target);
|
|
1428
|
+
byId('scan-picker').classList.toggle('hidden', !scanTarget);
|
|
1383
1429
|
byId('scanSteps').classList.toggle('hidden', !scanTarget);
|
|
1384
1430
|
byId('scanActions').classList.toggle('hidden', !scanTarget);
|
|
1385
1431
|
byId('connectTitle').textContent = target === 'runner'
|
|
1386
1432
|
? 'Run MCP Tool'
|
|
1387
1433
|
: target === 'help'
|
|
1388
1434
|
? 'Local workflow options'
|
|
1389
|
-
: '
|
|
1435
|
+
: 'Choose scan target';
|
|
1390
1436
|
byId('connectSubtitle').textContent = target === 'runner'
|
|
1391
1437
|
? 'Call one MCP tool from this machine. This is not a scan.'
|
|
1392
1438
|
: target === 'help'
|
|
1393
1439
|
? 'Choose one-off tool run or local red-team scan.'
|
|
1394
|
-
: '
|
|
1440
|
+
: 'Select Endpoint, RAG, or MCP, then connect the internal target.';
|
|
1395
1441
|
if (!scanTarget) setStep(1);
|
|
1396
1442
|
};
|
|
1397
1443
|
const setStep = next => {
|
|
@@ -1402,7 +1448,14 @@ Local targets stay on this machine; saved scan reports go to Full Court Defense.
|
|
|
1402
1448
|
byId('step-dot-' + n).classList.toggle('done', n < step);
|
|
1403
1449
|
});
|
|
1404
1450
|
};
|
|
1405
|
-
document.querySelectorAll('.
|
|
1451
|
+
document.querySelectorAll('.workflowCard').forEach(c => c.onclick = () => {
|
|
1452
|
+
target = c.dataset.workflow === 'bot' ? 'endpoint' : c.dataset.workflow;
|
|
1453
|
+
sync();
|
|
1454
|
+
});
|
|
1455
|
+
document.querySelectorAll('.scanTypeCard').forEach(c => c.onclick = () => {
|
|
1456
|
+
target = c.dataset.target;
|
|
1457
|
+
sync();
|
|
1458
|
+
});
|
|
1406
1459
|
sync();
|
|
1407
1460
|
byId('ragUrl').value = qs.get('ragUrl') || '';
|
|
1408
1461
|
byId('ragPath').value = qs.get('ragPath') || '';
|
|
@@ -1418,6 +1471,7 @@ Local targets stay on this machine; saved scan reports go to Full Court Defense.
|
|
|
1418
1471
|
const payload = () => ({
|
|
1419
1472
|
target, mode: byId('mode').value, format: byId('format').value,
|
|
1420
1473
|
endpoint: byId('endpoint').value, inputField: byId('inputField').value, outputField: byId('outputField').value,
|
|
1474
|
+
postureUrl: byId('postureUrl').value, postureMethod: byId('postureMethod').value, postureAuthType: byId('postureAuthType').value, postureToken: byId('postureToken').value,
|
|
1421
1475
|
ragUrl: byId('ragUrl').value, ragPath: byId('ragPath').value, ragInputField: byId('ragInputField').value, ragOutputField: byId('ragOutputField').value,
|
|
1422
1476
|
mcpUrl: byId('mcpUrl').value, mcpCommand: byId('mcpCommand').value, mcpArgs: byId('mcpArgs').value, mcpTool: byId('mcpTool').value
|
|
1423
1477
|
});
|
|
@@ -1453,6 +1507,7 @@ Local targets stay on this machine; saved scan reports go to Full Court Defense.
|
|
|
1453
1507
|
const renderCommand = p => {
|
|
1454
1508
|
const parts = ['fullcourtdefense scan --local --type', p.target, '--mode', p.mode, '--format', p.format, '--no-open'];
|
|
1455
1509
|
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');
|
|
1510
|
+
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>');
|
|
1456
1511
|
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')); }
|
|
1457
1512
|
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'); }
|
|
1458
1513
|
return parts.join(' ');
|
|
@@ -1508,6 +1563,10 @@ Local targets stay on this machine; saved scan reports go to Full Court Defense.
|
|
|
1508
1563
|
sync();
|
|
1509
1564
|
setStep(1);
|
|
1510
1565
|
byId('endpoint').value = '';
|
|
1566
|
+
byId('postureUrl').value = '';
|
|
1567
|
+
byId('postureMethod').value = 'POST';
|
|
1568
|
+
byId('postureAuthType').value = 'none';
|
|
1569
|
+
byId('postureToken').value = '';
|
|
1511
1570
|
byId('inputField').value = 'message';
|
|
1512
1571
|
byId('outputField').value = 'response';
|
|
1513
1572
|
byId('ragUrl').value = '';
|
|
@@ -1712,6 +1771,20 @@ Local targets stay on this machine; saved scan reports go to Full Court Defense.
|
|
|
1712
1771
|
byId('run').textContent = 'Run local scan';
|
|
1713
1772
|
return;
|
|
1714
1773
|
}
|
|
1774
|
+
if (target === 'posture') {
|
|
1775
|
+
try {
|
|
1776
|
+
setConsole('Starting Security Posture Scan...\\n', 'Running posture');
|
|
1777
|
+
const postureRes = await fetch('/posture/run', { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify(payload()) });
|
|
1778
|
+
const postureData = await postureRes.json();
|
|
1779
|
+
setConsole(JSON.stringify(postureData, null, 2), postureData.ok ? 'Posture complete' : 'Posture failed');
|
|
1780
|
+
} catch (error) {
|
|
1781
|
+
setConsole('Security Posture Scan failed: ' + (error && error.message ? error.message : String(error)), 'Posture failed');
|
|
1782
|
+
} finally {
|
|
1783
|
+
byId('run').disabled = false;
|
|
1784
|
+
byId('run').textContent = 'Run local scan';
|
|
1785
|
+
}
|
|
1786
|
+
return;
|
|
1787
|
+
}
|
|
1715
1788
|
setConsole('Starting local red-team scan...\\n', 'Running');
|
|
1716
1789
|
const res = await fetch('/run', { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify(payload()) });
|
|
1717
1790
|
if (!res.body) {
|
|
@@ -1824,6 +1897,40 @@ async function runCliFromBrowser(body, onChunk) {
|
|
|
1824
1897
|
});
|
|
1825
1898
|
});
|
|
1826
1899
|
}
|
|
1900
|
+
async function runPostureScanFromBrowser(args, body) {
|
|
1901
|
+
const auth = currentShieldConfig(args);
|
|
1902
|
+
const apiUrl = (auth.apiUrl || DEFAULT_API_URL).replace(/\/$/, '');
|
|
1903
|
+
const endpoint = typeof body.postureUrl === 'string' ? body.postureUrl.trim() : '';
|
|
1904
|
+
const method = body.postureMethod === 'GET' ? 'GET' : 'POST';
|
|
1905
|
+
const authType = typeof body.postureAuthType === 'string' ? body.postureAuthType : 'none';
|
|
1906
|
+
const token = typeof body.postureToken === 'string' ? body.postureToken.trim() : '';
|
|
1907
|
+
if (!endpoint)
|
|
1908
|
+
throw new Error('AI endpoint URL is required for Security Posture Scan.');
|
|
1909
|
+
const headers = {};
|
|
1910
|
+
if (authType === 'bearer' && token)
|
|
1911
|
+
headers.Authorization = `Bearer ${token}`;
|
|
1912
|
+
if (authType === 'api-key' && token)
|
|
1913
|
+
headers['X-API-Key'] = token;
|
|
1914
|
+
const response = await fetch(`${apiUrl}/api/posture/scan/public`, {
|
|
1915
|
+
method: 'POST',
|
|
1916
|
+
headers: { 'content-type': 'application/json' },
|
|
1917
|
+
body: JSON.stringify({
|
|
1918
|
+
endpoint,
|
|
1919
|
+
method,
|
|
1920
|
+
scanMode: 'passive',
|
|
1921
|
+
...(Object.keys(headers).length ? { headers } : {}),
|
|
1922
|
+
}),
|
|
1923
|
+
signal: AbortSignal.timeout(60000),
|
|
1924
|
+
});
|
|
1925
|
+
const text = await response.text();
|
|
1926
|
+
return {
|
|
1927
|
+
ok: response.ok,
|
|
1928
|
+
status: response.status,
|
|
1929
|
+
endpoint,
|
|
1930
|
+
scanType: 'security_posture',
|
|
1931
|
+
result: text,
|
|
1932
|
+
};
|
|
1933
|
+
}
|
|
1827
1934
|
function createMcpClient(options) {
|
|
1828
1935
|
if (options.transport === 'sse')
|
|
1829
1936
|
return new SseMcpClient(options);
|
|
@@ -1990,6 +2097,13 @@ function startCompactWebServer(args) {
|
|
|
1990
2097
|
res.end(JSON.stringify(result));
|
|
1991
2098
|
return;
|
|
1992
2099
|
}
|
|
2100
|
+
if (req.method === 'POST' && url.pathname === '/posture/run') {
|
|
2101
|
+
const body = await readRequestBody(req);
|
|
2102
|
+
const result = await runPostureScanFromBrowser(args, body);
|
|
2103
|
+
res.writeHead(result.ok ? 200 : 400, { 'content-type': 'application/json; charset=utf-8' });
|
|
2104
|
+
res.end(JSON.stringify(result));
|
|
2105
|
+
return;
|
|
2106
|
+
}
|
|
1993
2107
|
if (req.method === 'POST' && url.pathname === '/run') {
|
|
1994
2108
|
const body = await readRequestBody(req);
|
|
1995
2109
|
res.writeHead(200, {
|
package/dist/version.json
CHANGED