fullcourtdefense-cli 1.7.15 → 1.7.17
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 +121 -7
- package/dist/version.json +1 -1
- package/package.json +1 -1
|
@@ -1111,7 +1111,7 @@ function compactScanHtml(args) {
|
|
|
1111
1111
|
.workflowCard { min-height: 118px; }
|
|
1112
1112
|
.workflowCard b { font-size: 18px; }
|
|
1113
1113
|
.workflowCard span { font-size: 13px; }
|
|
1114
|
-
.landingScanGrid { display: grid; grid-template-columns: repeat(
|
|
1114
|
+
.landingScanGrid { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 10px; }
|
|
1115
1115
|
.scanTypeGrid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 10px; margin: 12px 0; }
|
|
1116
1116
|
.advancedScanGrid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 10px; margin: 8px 0 12px; }
|
|
1117
1117
|
.card:hover, .card.active { border-color: #2563eb; background: #eff6ff; transform: translateY(-1px); }
|
|
@@ -1202,6 +1202,8 @@ function compactScanHtml(args) {
|
|
|
1202
1202
|
<div class="landingScanGrid">
|
|
1203
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
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>
|
|
1205
1207
|
</div>
|
|
1206
1208
|
</div>
|
|
1207
1209
|
<div class="menuGroup">
|
|
@@ -1280,6 +1282,7 @@ function compactScanHtml(args) {
|
|
|
1280
1282
|
<div class="advancedScanGrid">
|
|
1281
1283
|
<button class="card scanTypeCard" data-target="rag"><b>RAG Scan</b><span>Test retrieval URL or local corpus path.</span></button>
|
|
1282
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>
|
|
1283
1286
|
</div>
|
|
1284
1287
|
</div>
|
|
1285
1288
|
|
|
@@ -1293,7 +1296,7 @@ function compactScanHtml(args) {
|
|
|
1293
1296
|
</div>
|
|
1294
1297
|
|
|
1295
1298
|
<div id="posture-fields" class="hidden">
|
|
1296
|
-
<div class="note">Security Posture Scan checks endpoint configuration, auth exposure, headers, transport, and operational security signals.
|
|
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>
|
|
1297
1300
|
<div class="two">
|
|
1298
1301
|
<div>
|
|
1299
1302
|
<label>AI endpoint URL</label>
|
|
@@ -1328,6 +1331,7 @@ function compactScanHtml(args) {
|
|
|
1328
1331
|
</div>
|
|
1329
1332
|
|
|
1330
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>
|
|
1331
1335
|
<div class="two">
|
|
1332
1336
|
<div><label>MCP HTTP/SSE URL</label><input id="mcpUrl" placeholder="http://127.0.0.1:5066/mcp" /></div>
|
|
1333
1337
|
<div><label>MCP tool</label><input id="mcpTool" value="all" /></div>
|
|
@@ -1413,19 +1417,25 @@ Local targets stay on this machine; saved scan reports go to Full Court Defense.
|
|
|
1413
1417
|
</section>
|
|
1414
1418
|
</main>
|
|
1415
1419
|
<script>
|
|
1416
|
-
const scanTargets = ['endpoint','posture','rag','mcp'];
|
|
1420
|
+
const scanTargets = ['endpoint','posture','rag','mcp','mcp-posture'];
|
|
1417
1421
|
let target = 'home';
|
|
1418
1422
|
let step = 1;
|
|
1419
1423
|
const qs = new URLSearchParams(location.search);
|
|
1420
|
-
if (['runner','endpoint','posture','rag','mcp','help'].includes(qs.get('target'))) target = qs.get('target');
|
|
1424
|
+
if (['runner','endpoint','posture','rag','mcp','mcp-posture','help'].includes(qs.get('target'))) target = qs.get('target');
|
|
1421
1425
|
const byId = id => document.getElementById(id);
|
|
1422
1426
|
const workflow = () => target === 'endpoint' ? 'bot' : target === 'posture' ? 'posture' : target === 'home' ? '' : target;
|
|
1423
1427
|
const sync = () => {
|
|
1424
1428
|
document.querySelectorAll('.workflowCard').forEach(c => c.classList.toggle('active', c.dataset.workflow === workflow()));
|
|
1425
1429
|
document.querySelectorAll('.scanTypeCard').forEach(c => c.classList.toggle('active', c.dataset.target === target));
|
|
1426
1430
|
byId('workflowPanel').classList.toggle('hidden', target === 'home');
|
|
1427
|
-
['runner','endpoint','posture','rag','mcp','help'].forEach(id =>
|
|
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
|
+
});
|
|
1428
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.';
|
|
1429
1439
|
byId('scan-picker').classList.toggle('hidden', !scanTarget);
|
|
1430
1440
|
byId('scanSteps').classList.toggle('hidden', !scanTarget);
|
|
1431
1441
|
byId('scanActions').classList.toggle('hidden', !scanTarget);
|
|
@@ -1433,12 +1443,16 @@ Local targets stay on this machine; saved scan reports go to Full Court Defense.
|
|
|
1433
1443
|
? 'Run MCP Tool'
|
|
1434
1444
|
: target === 'help'
|
|
1435
1445
|
? 'Local workflow options'
|
|
1436
|
-
:
|
|
1446
|
+
: target === 'mcp-posture'
|
|
1447
|
+
? 'MCP posture scan'
|
|
1448
|
+
: 'Choose scan target';
|
|
1437
1449
|
byId('connectSubtitle').textContent = target === 'runner'
|
|
1438
1450
|
? 'Call one MCP tool from this machine. This is not a scan.'
|
|
1439
1451
|
: target === 'help'
|
|
1440
1452
|
? 'Choose one-off tool run or local red-team scan.'
|
|
1441
|
-
:
|
|
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.';
|
|
1442
1456
|
if (!scanTarget || target === 'home') setStep(1);
|
|
1443
1457
|
};
|
|
1444
1458
|
const setStep = next => {
|
|
@@ -1511,6 +1525,7 @@ Local targets stay on this machine; saved scan reports go to Full Court Defense.
|
|
|
1511
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');
|
|
1512
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>');
|
|
1513
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')));
|
|
1514
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'); }
|
|
1515
1530
|
return parts.join(' ');
|
|
1516
1531
|
};
|
|
@@ -1766,6 +1781,20 @@ Local targets stay on this machine; saved scan reports go to Full Court Defense.
|
|
|
1766
1781
|
byId('run').disabled = true;
|
|
1767
1782
|
byId('run').textContent = 'Running...';
|
|
1768
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
|
+
}
|
|
1769
1798
|
const backendOk = await checkBackend();
|
|
1770
1799
|
if (!backendOk) {
|
|
1771
1800
|
setConsole('Outbound backend check failed. Fix backend connectivity before scanning so verdicts and reports can be generated.', 'Blocked');
|
|
@@ -2038,6 +2067,84 @@ async function runMcpToolFromBrowser(body) {
|
|
|
2038
2067
|
client.close();
|
|
2039
2068
|
}
|
|
2040
2069
|
}
|
|
2070
|
+
function mcpPostureSignals(tool) {
|
|
2071
|
+
const text = `${tool.name || ''} ${tool.description || ''} ${JSON.stringify(tool.inputSchema || {})}`.toLowerCase();
|
|
2072
|
+
const checks = [
|
|
2073
|
+
[/\b(delete|remove|destroy|drop|wipe|purge|erase)\b/, 'destructive operation'],
|
|
2074
|
+
[/\b(write|update|create|insert|modify|patch|replace)\b/, 'write-capable operation'],
|
|
2075
|
+
[/\b(exec|shell|command|process|spawn|powershell|bash|terminal)\b/, 'shell/command execution'],
|
|
2076
|
+
[/\b(file|filesystem|path|readfile|writefile|directory|upload|download)\b/, 'file-system access'],
|
|
2077
|
+
[/\b(secret|token|key|password|credential|env|private)\b/, 'secret/credential access'],
|
|
2078
|
+
[/\b(payment|wire|transfer|invoice|billing|bank|account|iban)\b/, 'financial operation'],
|
|
2079
|
+
[/\b(http|request|fetch|webhook|url|network|browser)\b/, 'network/browser access'],
|
|
2080
|
+
[/\b(customer|user|email|phone|address|ssn|pii|record)\b/, 'customer/PII data access'],
|
|
2081
|
+
];
|
|
2082
|
+
return checks.filter(([pattern]) => pattern.test(text)).map(([, label]) => label);
|
|
2083
|
+
}
|
|
2084
|
+
async function runMcpPostureScanFromBrowser(body) {
|
|
2085
|
+
const rawUrl = typeof body.mcpUrl === 'string' ? body.mcpUrl.trim() : '';
|
|
2086
|
+
const rawCommand = typeof body.mcpCommand === 'string' ? body.mcpCommand.trim() : '';
|
|
2087
|
+
const rawArgs = typeof body.mcpArgs === 'string' ? body.mcpArgs.trim() : '';
|
|
2088
|
+
if (!rawUrl && !rawCommand)
|
|
2089
|
+
throw new Error('Provide either an MCP HTTP/SSE URL or stdio command.');
|
|
2090
|
+
const transport = rawUrl
|
|
2091
|
+
? (/\/sse\/?$/.test(new URL(rawUrl).pathname) ? 'sse' : 'http')
|
|
2092
|
+
: 'stdio';
|
|
2093
|
+
const options = {
|
|
2094
|
+
transport,
|
|
2095
|
+
url: rawUrl || undefined,
|
|
2096
|
+
command: rawCommand || undefined,
|
|
2097
|
+
args: parseArgsList(rawArgs),
|
|
2098
|
+
authType: 'none',
|
|
2099
|
+
toolName: 'all',
|
|
2100
|
+
toolArgs: {},
|
|
2101
|
+
};
|
|
2102
|
+
const destination = rawUrl || `${rawCommand} ${options.args?.join(' ') || ''}`.trim();
|
|
2103
|
+
const protectedGatewayDetected = /fullcourtdefense|agentguard|fcd|5066|gateway|proxy/i.test(destination);
|
|
2104
|
+
const client = createMcpClient(options);
|
|
2105
|
+
const startedAt = Date.now();
|
|
2106
|
+
try {
|
|
2107
|
+
await client.initialize();
|
|
2108
|
+
const tools = await client.listTools();
|
|
2109
|
+
const toolFindings = tools.map(tool => {
|
|
2110
|
+
const signals = mcpPostureSignals(tool);
|
|
2111
|
+
return {
|
|
2112
|
+
name: tool.name,
|
|
2113
|
+
description: tool.description || '',
|
|
2114
|
+
risk: signals.length >= 3 ? 'high' : signals.length >= 1 ? 'medium' : 'low',
|
|
2115
|
+
signals,
|
|
2116
|
+
};
|
|
2117
|
+
});
|
|
2118
|
+
const highRiskTools = toolFindings.filter(tool => tool.risk === 'high').length;
|
|
2119
|
+
const mediumRiskTools = toolFindings.filter(tool => tool.risk === 'medium').length;
|
|
2120
|
+
const findings = [
|
|
2121
|
+
...(protectedGatewayDetected ? [] : ['MCP target does not look like a protected Full Court Defense gateway/proxy.']),
|
|
2122
|
+
...(highRiskTools ? [`${highRiskTools} high-risk MCP tool(s) exposed.`] : []),
|
|
2123
|
+
...(mediumRiskTools ? [`${mediumRiskTools} medium-risk MCP tool(s) exposed.`] : []),
|
|
2124
|
+
...(tools.length > 20 ? [`Large MCP surface: ${tools.length} tools exposed.`] : []),
|
|
2125
|
+
];
|
|
2126
|
+
return {
|
|
2127
|
+
ok: true,
|
|
2128
|
+
scanType: 'mcp_posture',
|
|
2129
|
+
posture: findings.length === 0 ? 'pass' : highRiskTools || !protectedGatewayDetected ? 'fail' : 'warn',
|
|
2130
|
+
transport,
|
|
2131
|
+
destination,
|
|
2132
|
+
protectedGatewayDetected,
|
|
2133
|
+
elapsedMs: Date.now() - startedAt,
|
|
2134
|
+
summary: {
|
|
2135
|
+
totalTools: tools.length,
|
|
2136
|
+
highRiskTools,
|
|
2137
|
+
mediumRiskTools,
|
|
2138
|
+
lowRiskTools: toolFindings.filter(tool => tool.risk === 'low').length,
|
|
2139
|
+
},
|
|
2140
|
+
findings,
|
|
2141
|
+
tools: toolFindings,
|
|
2142
|
+
};
|
|
2143
|
+
}
|
|
2144
|
+
finally {
|
|
2145
|
+
client.close();
|
|
2146
|
+
}
|
|
2147
|
+
}
|
|
2041
2148
|
function startCompactWebServer(args) {
|
|
2042
2149
|
const server = http.createServer(async (req, res) => {
|
|
2043
2150
|
try {
|
|
@@ -2099,6 +2206,13 @@ function startCompactWebServer(args) {
|
|
|
2099
2206
|
res.end(JSON.stringify(result));
|
|
2100
2207
|
return;
|
|
2101
2208
|
}
|
|
2209
|
+
if (req.method === 'POST' && url.pathname === '/mcp/posture') {
|
|
2210
|
+
const body = await readRequestBody(req);
|
|
2211
|
+
const result = await runMcpPostureScanFromBrowser(body);
|
|
2212
|
+
res.writeHead(200, { 'content-type': 'application/json; charset=utf-8' });
|
|
2213
|
+
res.end(JSON.stringify(result));
|
|
2214
|
+
return;
|
|
2215
|
+
}
|
|
2102
2216
|
if (req.method === 'POST' && url.pathname === '/posture/run') {
|
|
2103
2217
|
const body = await readRequestBody(req);
|
|
2104
2218
|
const result = await runPostureScanFromBrowser(args, body);
|
package/dist/version.json
CHANGED