fullcourtdefense-cli 1.7.16 → 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(3, 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); }
@@ -1203,6 +1203,7 @@ function compactScanHtml(args) {
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
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>
1206
1207
  </div>
1207
1208
  </div>
1208
1209
  <div class="menuGroup">
@@ -1281,6 +1282,7 @@ function compactScanHtml(args) {
1281
1282
  <div class="advancedScanGrid">
1282
1283
  <button class="card scanTypeCard" data-target="rag"><b>RAG Scan</b><span>Test retrieval URL or local corpus path.</span></button>
1283
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>
1284
1286
  </div>
1285
1287
  </div>
1286
1288
 
@@ -1329,6 +1331,7 @@ function compactScanHtml(args) {
1329
1331
  </div>
1330
1332
 
1331
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>
1332
1335
  <div class="two">
1333
1336
  <div><label>MCP HTTP/SSE URL</label><input id="mcpUrl" placeholder="http://127.0.0.1:5066/mcp" /></div>
1334
1337
  <div><label>MCP tool</label><input id="mcpTool" value="all" /></div>
@@ -1414,19 +1417,25 @@ Local targets stay on this machine; saved scan reports go to Full Court Defense.
1414
1417
  </section>
1415
1418
  </main>
1416
1419
  <script>
1417
- const scanTargets = ['endpoint','posture','rag','mcp'];
1420
+ const scanTargets = ['endpoint','posture','rag','mcp','mcp-posture'];
1418
1421
  let target = 'home';
1419
1422
  let step = 1;
1420
1423
  const qs = new URLSearchParams(location.search);
1421
- 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');
1422
1425
  const byId = id => document.getElementById(id);
1423
1426
  const workflow = () => target === 'endpoint' ? 'bot' : target === 'posture' ? 'posture' : target === 'home' ? '' : target;
1424
1427
  const sync = () => {
1425
1428
  document.querySelectorAll('.workflowCard').forEach(c => c.classList.toggle('active', c.dataset.workflow === workflow()));
1426
1429
  document.querySelectorAll('.scanTypeCard').forEach(c => c.classList.toggle('active', c.dataset.target === target));
1427
1430
  byId('workflowPanel').classList.toggle('hidden', target === 'home');
1428
- ['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
+ });
1429
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.';
1430
1439
  byId('scan-picker').classList.toggle('hidden', !scanTarget);
1431
1440
  byId('scanSteps').classList.toggle('hidden', !scanTarget);
1432
1441
  byId('scanActions').classList.toggle('hidden', !scanTarget);
@@ -1434,12 +1443,16 @@ Local targets stay on this machine; saved scan reports go to Full Court Defense.
1434
1443
  ? 'Run MCP Tool'
1435
1444
  : target === 'help'
1436
1445
  ? 'Local workflow options'
1437
- : 'Choose scan target';
1446
+ : target === 'mcp-posture'
1447
+ ? 'MCP posture scan'
1448
+ : 'Choose scan target';
1438
1449
  byId('connectSubtitle').textContent = target === 'runner'
1439
1450
  ? 'Call one MCP tool from this machine. This is not a scan.'
1440
1451
  : target === 'help'
1441
1452
  ? 'Choose one-off tool run or local red-team scan.'
1442
- : '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.';
1443
1456
  if (!scanTarget || target === 'home') setStep(1);
1444
1457
  };
1445
1458
  const setStep = next => {
@@ -1512,6 +1525,7 @@ Local targets stay on this machine; saved scan reports go to Full Court Defense.
1512
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');
1513
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>');
1514
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')));
1515
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'); }
1516
1530
  return parts.join(' ');
1517
1531
  };
@@ -1767,6 +1781,20 @@ Local targets stay on this machine; saved scan reports go to Full Court Defense.
1767
1781
  byId('run').disabled = true;
1768
1782
  byId('run').textContent = 'Running...';
1769
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
+ }
1770
1798
  const backendOk = await checkBackend();
1771
1799
  if (!backendOk) {
1772
1800
  setConsole('Outbound backend check failed. Fix backend connectivity before scanning so verdicts and reports can be generated.', 'Blocked');
@@ -2039,6 +2067,84 @@ async function runMcpToolFromBrowser(body) {
2039
2067
  client.close();
2040
2068
  }
2041
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
+ }
2042
2148
  function startCompactWebServer(args) {
2043
2149
  const server = http.createServer(async (req, res) => {
2044
2150
  try {
@@ -2100,6 +2206,13 @@ function startCompactWebServer(args) {
2100
2206
  res.end(JSON.stringify(result));
2101
2207
  return;
2102
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
+ }
2103
2216
  if (req.method === 'POST' && url.pathname === '/posture/run') {
2104
2217
  const body = await readRequestBody(req);
2105
2218
  const result = await runPostureScanFromBrowser(args, body);
package/dist/version.json CHANGED
@@ -1,3 +1,3 @@
1
1
  {
2
- "version": "1.7.16"
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.16",
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": {