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.
@@ -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(2, minmax(0, 1fr)); gap: 10px; }
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. This matches the web Security Checkup flow.</div>
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 => byId(id + '-fields').classList.toggle('hidden', id !== target));
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
- : 'Choose scan target';
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
- : 'Select Endpoint, RAG, or MCP, then connect the internal target.';
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
@@ -1,3 +1,3 @@
1
1
  {
2
- "version": "1.7.15"
2
+ "version": "1.7.17"
3
3
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fullcourtdefense-cli",
3
- "version": "1.7.15",
3
+ "version": "1.7.17",
4
4
  "description": "Full Court Defense CLI — security scanning for AI agents from your terminal",
5
5
  "main": "dist/index.js",
6
6
  "bin": {