fullcourtdefense-cli 1.7.12 → 1.7.13

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.
@@ -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(180px, 1fr) minmax(0, 3fr) minmax(160px, .85fr); gap: 14px; margin-top: 18px; align-items: stretch; }
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,11 @@ 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
+ .scanTypeGrid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 10px; margin: 12px 0; }
1115
+ .advancedScanGrid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 10px; margin: 8px 0 12px; }
1111
1116
  .card:hover, .card.active { border-color: #2563eb; background: #eff6ff; transform: translateY(-1px); }
1112
1117
  .card b { display: block; font-size: 14px; margin-bottom: 4px; }
1113
1118
  .card span { color: #64748b; font-size: 12px; line-height: 1.35; }
@@ -1157,8 +1162,8 @@ function compactScanHtml(args) {
1157
1162
  <main class="wrap">
1158
1163
  <section class="hero">
1159
1164
  <div class="eyebrow">Local Web Console</div>
1160
- <h1>Run local MCP tools or scan internal AI targets</h1>
1161
- <p>This compact browser UI is served by the CLI on 127.0.0.1. One-off MCP tool runs stay local in this page. Red-team scans run locally too, while Shield verdicts and saved reports go outbound to Full Court Defense.</p>
1165
+ <h1>Choose a local workflow</h1>
1166
+ <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
1167
  <div class="auth">
1163
1168
  <div class="eyebrow">${isConfigured ? 'Authenticated' : 'Authentication Required'}</div>
1164
1169
  <p id="authStatus">${isConfigured
@@ -1189,19 +1194,15 @@ function compactScanHtml(args) {
1189
1194
  <div class="menuShell" id="cards">
1190
1195
  <div class="menuGroup run">
1191
1196
  <div class="menuLabel">Run, not a scan</div>
1192
- <button class="card active" data-target="runner"><b>Run MCP Tool</b><span>Call one tool through a local MCP server or protected gateway.</span></button>
1197
+ <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
1198
  </div>
1194
1199
  <div class="menuGroup">
1195
1200
  <div class="menuLabel">Security scans</div>
1196
- <div class="grid">
1197
- <button class="card" data-target="endpoint"><b>Endpoint Scan</b><span>Red-team an internal agent/chat API.</span></button>
1198
- <button class="card" data-target="rag"><b>RAG Scan</b><span>Test retrieval URL or local corpus path.</span></button>
1199
- <button class="card" data-target="mcp"><b>MCP Scan</b><span>Scan tool responses and tool-abuse paths.</span></button>
1200
- </div>
1201
+ <button class="card workflowCard" data-workflow="scan"><b>Start security scan</b><span>Run quick or full red-team tests against Endpoint, RAG, or MCP targets. Reports save to Full Court Defense.</span></button>
1201
1202
  </div>
1202
1203
  <div class="menuGroup">
1203
1204
  <div class="menuLabel">Help</div>
1204
- <button class="card" data-target="help"><b>Options</b><span>Modes, formats, and when to use each local workflow.</span></button>
1205
+ <button class="card workflowCard" data-workflow="help"><b>Options</b><span>Modes, formats, and when to use each local workflow.</span></button>
1205
1206
  </div>
1206
1207
  </div>
1207
1208
  </section>
@@ -1265,6 +1266,19 @@ function compactScanHtml(args) {
1265
1266
  </div>
1266
1267
  </div>
1267
1268
 
1269
+ <div id="scan-picker" class="hidden">
1270
+ <div class="note">Security scans run red-team templates locally from this machine. First choose what you want to test.</div>
1271
+ <div class="scanTypeGrid">
1272
+ <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>
1273
+ <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>
1274
+ </div>
1275
+ <div class="menuLabel">Advanced local targets</div>
1276
+ <div class="advancedScanGrid">
1277
+ <button class="card scanTypeCard" data-target="rag"><b>RAG Scan</b><span>Test retrieval URL or local corpus path.</span></button>
1278
+ <button class="card scanTypeCard" data-target="mcp"><b>MCP Scan</b><span>Scan tool responses and tool-abuse paths.</span></button>
1279
+ </div>
1280
+ </div>
1281
+
1268
1282
  <div id="endpoint-fields" class="hidden">
1269
1283
  <label>Internal endpoint URL</label>
1270
1284
  <input id="endpoint" placeholder="http://127.0.0.1:3000/chat" />
@@ -1274,6 +1288,30 @@ function compactScanHtml(args) {
1274
1288
  </div>
1275
1289
  </div>
1276
1290
 
1291
+ <div id="posture-fields" class="hidden">
1292
+ <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>
1293
+ <div class="two">
1294
+ <div>
1295
+ <label>AI endpoint URL</label>
1296
+ <input id="postureUrl" placeholder="https://your-ai-endpoint.com/api/chat" />
1297
+ </div>
1298
+ <div>
1299
+ <label>HTTP method</label>
1300
+ <select id="postureMethod"><option value="POST" selected>POST</option><option value="GET">GET</option></select>
1301
+ </div>
1302
+ </div>
1303
+ <div class="two">
1304
+ <div>
1305
+ <label>Auth type</label>
1306
+ <select id="postureAuthType"><option value="none" selected>No Auth</option><option value="bearer">Bearer Token</option><option value="api-key">API Key</option></select>
1307
+ </div>
1308
+ <div>
1309
+ <label>Token / API key value</label>
1310
+ <input id="postureToken" type="password" placeholder="Optional" />
1311
+ </div>
1312
+ </div>
1313
+ </div>
1314
+
1277
1315
  <div id="rag-fields" class="hidden">
1278
1316
  <label>RAG retrieval URL</label>
1279
1317
  <input id="ragUrl" placeholder="http://127.0.0.1:8787/rag/retrieve" />
@@ -1371,27 +1409,31 @@ Local targets stay on this machine; saved scan reports go to Full Court Defense.
1371
1409
  </section>
1372
1410
  </main>
1373
1411
  <script>
1412
+ const scanTargets = ['endpoint','posture','rag','mcp'];
1374
1413
  let target = 'runner';
1375
1414
  let step = 1;
1376
1415
  const qs = new URLSearchParams(location.search);
1377
- if (['runner','endpoint','rag','mcp'].includes(qs.get('target'))) target = qs.get('target');
1416
+ if (['runner','endpoint','posture','rag','mcp','help'].includes(qs.get('target'))) target = qs.get('target');
1378
1417
  const byId = id => document.getElementById(id);
1418
+ const workflow = () => scanTargets.includes(target) ? 'scan' : target;
1379
1419
  const sync = () => {
1380
- document.querySelectorAll('.card').forEach(c => c.classList.toggle('active', c.dataset.target === target));
1381
- ['runner','endpoint','rag','mcp','help'].forEach(id => byId(id + '-fields').classList.toggle('hidden', id !== target));
1382
- const scanTarget = ['endpoint','rag','mcp'].includes(target);
1420
+ document.querySelectorAll('.workflowCard').forEach(c => c.classList.toggle('active', c.dataset.workflow === workflow()));
1421
+ document.querySelectorAll('.scanTypeCard').forEach(c => c.classList.toggle('active', c.dataset.target === target));
1422
+ ['runner','endpoint','posture','rag','mcp','help'].forEach(id => byId(id + '-fields').classList.toggle('hidden', id !== target));
1423
+ const scanTarget = scanTargets.includes(target);
1424
+ byId('scan-picker').classList.toggle('hidden', !scanTarget);
1383
1425
  byId('scanSteps').classList.toggle('hidden', !scanTarget);
1384
1426
  byId('scanActions').classList.toggle('hidden', !scanTarget);
1385
1427
  byId('connectTitle').textContent = target === 'runner'
1386
1428
  ? 'Run MCP Tool'
1387
1429
  : target === 'help'
1388
1430
  ? 'Local workflow options'
1389
- : 'Connect scan target';
1431
+ : 'Choose scan target';
1390
1432
  byId('connectSubtitle').textContent = target === 'runner'
1391
1433
  ? 'Call one MCP tool from this machine. This is not a scan.'
1392
1434
  : target === 'help'
1393
1435
  ? 'Choose one-off tool run or local red-team scan.'
1394
- : 'Choose the internal target to red-team from this machine.';
1436
+ : 'Select Endpoint, RAG, or MCP, then connect the internal target.';
1395
1437
  if (!scanTarget) setStep(1);
1396
1438
  };
1397
1439
  const setStep = next => {
@@ -1402,7 +1444,14 @@ Local targets stay on this machine; saved scan reports go to Full Court Defense.
1402
1444
  byId('step-dot-' + n).classList.toggle('done', n < step);
1403
1445
  });
1404
1446
  };
1405
- document.querySelectorAll('.card').forEach(c => c.onclick = () => { target = c.dataset.target; sync(); });
1447
+ document.querySelectorAll('.workflowCard').forEach(c => c.onclick = () => {
1448
+ target = c.dataset.workflow === 'scan' ? 'endpoint' : c.dataset.workflow;
1449
+ sync();
1450
+ });
1451
+ document.querySelectorAll('.scanTypeCard').forEach(c => c.onclick = () => {
1452
+ target = c.dataset.target;
1453
+ sync();
1454
+ });
1406
1455
  sync();
1407
1456
  byId('ragUrl').value = qs.get('ragUrl') || '';
1408
1457
  byId('ragPath').value = qs.get('ragPath') || '';
@@ -1418,6 +1467,7 @@ Local targets stay on this machine; saved scan reports go to Full Court Defense.
1418
1467
  const payload = () => ({
1419
1468
  target, mode: byId('mode').value, format: byId('format').value,
1420
1469
  endpoint: byId('endpoint').value, inputField: byId('inputField').value, outputField: byId('outputField').value,
1470
+ postureUrl: byId('postureUrl').value, postureMethod: byId('postureMethod').value, postureAuthType: byId('postureAuthType').value, postureToken: byId('postureToken').value,
1421
1471
  ragUrl: byId('ragUrl').value, ragPath: byId('ragPath').value, ragInputField: byId('ragInputField').value, ragOutputField: byId('ragOutputField').value,
1422
1472
  mcpUrl: byId('mcpUrl').value, mcpCommand: byId('mcpCommand').value, mcpArgs: byId('mcpArgs').value, mcpTool: byId('mcpTool').value
1423
1473
  });
@@ -1453,6 +1503,7 @@ Local targets stay on this machine; saved scan reports go to Full Court Defense.
1453
1503
  const renderCommand = p => {
1454
1504
  const parts = ['fullcourtdefense scan --local --type', p.target, '--mode', p.mode, '--format', p.format, '--no-open'];
1455
1505
  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');
1506
+ 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
1507
  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
1508
  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
1509
  return parts.join(' ');
@@ -1508,6 +1559,10 @@ Local targets stay on this machine; saved scan reports go to Full Court Defense.
1508
1559
  sync();
1509
1560
  setStep(1);
1510
1561
  byId('endpoint').value = '';
1562
+ byId('postureUrl').value = '';
1563
+ byId('postureMethod').value = 'POST';
1564
+ byId('postureAuthType').value = 'none';
1565
+ byId('postureToken').value = '';
1511
1566
  byId('inputField').value = 'message';
1512
1567
  byId('outputField').value = 'response';
1513
1568
  byId('ragUrl').value = '';
@@ -1712,6 +1767,20 @@ Local targets stay on this machine; saved scan reports go to Full Court Defense.
1712
1767
  byId('run').textContent = 'Run local scan';
1713
1768
  return;
1714
1769
  }
1770
+ if (target === 'posture') {
1771
+ try {
1772
+ setConsole('Starting Security Posture Scan...\\n', 'Running posture');
1773
+ const postureRes = await fetch('/posture/run', { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify(payload()) });
1774
+ const postureData = await postureRes.json();
1775
+ setConsole(JSON.stringify(postureData, null, 2), postureData.ok ? 'Posture complete' : 'Posture failed');
1776
+ } catch (error) {
1777
+ setConsole('Security Posture Scan failed: ' + (error && error.message ? error.message : String(error)), 'Posture failed');
1778
+ } finally {
1779
+ byId('run').disabled = false;
1780
+ byId('run').textContent = 'Run local scan';
1781
+ }
1782
+ return;
1783
+ }
1715
1784
  setConsole('Starting local red-team scan...\\n', 'Running');
1716
1785
  const res = await fetch('/run', { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify(payload()) });
1717
1786
  if (!res.body) {
@@ -1824,6 +1893,40 @@ async function runCliFromBrowser(body, onChunk) {
1824
1893
  });
1825
1894
  });
1826
1895
  }
1896
+ async function runPostureScanFromBrowser(args, body) {
1897
+ const auth = currentShieldConfig(args);
1898
+ const apiUrl = (auth.apiUrl || DEFAULT_API_URL).replace(/\/$/, '');
1899
+ const endpoint = typeof body.postureUrl === 'string' ? body.postureUrl.trim() : '';
1900
+ const method = body.postureMethod === 'GET' ? 'GET' : 'POST';
1901
+ const authType = typeof body.postureAuthType === 'string' ? body.postureAuthType : 'none';
1902
+ const token = typeof body.postureToken === 'string' ? body.postureToken.trim() : '';
1903
+ if (!endpoint)
1904
+ throw new Error('AI endpoint URL is required for Security Posture Scan.');
1905
+ const headers = {};
1906
+ if (authType === 'bearer' && token)
1907
+ headers.Authorization = `Bearer ${token}`;
1908
+ if (authType === 'api-key' && token)
1909
+ headers['X-API-Key'] = token;
1910
+ const response = await fetch(`${apiUrl}/api/posture/scan/public`, {
1911
+ method: 'POST',
1912
+ headers: { 'content-type': 'application/json' },
1913
+ body: JSON.stringify({
1914
+ endpoint,
1915
+ method,
1916
+ scanMode: 'passive',
1917
+ ...(Object.keys(headers).length ? { headers } : {}),
1918
+ }),
1919
+ signal: AbortSignal.timeout(60000),
1920
+ });
1921
+ const text = await response.text();
1922
+ return {
1923
+ ok: response.ok,
1924
+ status: response.status,
1925
+ endpoint,
1926
+ scanType: 'security_posture',
1927
+ result: text,
1928
+ };
1929
+ }
1827
1930
  function createMcpClient(options) {
1828
1931
  if (options.transport === 'sse')
1829
1932
  return new SseMcpClient(options);
@@ -1990,6 +2093,13 @@ function startCompactWebServer(args) {
1990
2093
  res.end(JSON.stringify(result));
1991
2094
  return;
1992
2095
  }
2096
+ if (req.method === 'POST' && url.pathname === '/posture/run') {
2097
+ const body = await readRequestBody(req);
2098
+ const result = await runPostureScanFromBrowser(args, body);
2099
+ res.writeHead(result.ok ? 200 : 400, { 'content-type': 'application/json; charset=utf-8' });
2100
+ res.end(JSON.stringify(result));
2101
+ return;
2102
+ }
1993
2103
  if (req.method === 'POST' && url.pathname === '/run') {
1994
2104
  const body = await readRequestBody(req);
1995
2105
  res.writeHead(200, {
package/dist/version.json CHANGED
@@ -1,3 +1,3 @@
1
1
  {
2
- "version": "1.7.12"
2
+ "version": "1.7.13"
3
3
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fullcourtdefense-cli",
3
- "version": "1.7.12",
3
+ "version": "1.7.13",
4
4
  "description": "Full Court Defense CLI — security scanning for AI agents from your terminal",
5
5
  "main": "dist/index.js",
6
6
  "bin": {