fullcourtdefense-cli 1.6.3 → 1.6.4

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.
@@ -135,7 +135,21 @@ function buildToolCall(event, p) {
135
135
  }
136
136
  case 'mcp': {
137
137
  const toolName = str(p.tool_name) || str(p.toolName) || str(p.name) || 'mcp';
138
- const rawInput = p.tool_input ?? p.arguments ?? p.input ?? p.params;
138
+ let rawInput = p.tool_input ?? p.arguments ?? p.input ?? p.params;
139
+ // Cursor sends `tool_input` as a JSON-ENCODED STRING (e.g. '{"command":"DROP ..."}').
140
+ // Parse it so the real args (command/query/sql/...) are visible to the policy engine.
141
+ // Without this, args collapse to { value: "<json string>" }, no operation verb is
142
+ // inferred, and the operation falls back to the tool NAME — so a tool called
143
+ // `drop_production_database` would match a DROP rule for EVERY command (SELECT included).
144
+ if (typeof rawInput === 'string') {
145
+ const trimmed = rawInput.trim();
146
+ if (trimmed.startsWith('{') || trimmed.startsWith('[')) {
147
+ try {
148
+ rawInput = JSON.parse(trimmed);
149
+ }
150
+ catch { /* keep the raw string */ }
151
+ }
152
+ }
139
153
  const toolArgs = rawInput && typeof rawInput === 'object' ? rawInput : { value: rawInput };
140
154
  return { toolName, toolArgs };
141
155
  }
@@ -177,6 +191,9 @@ function safeJson(v) {
177
191
  return String(v);
178
192
  }
179
193
  }
194
+ function degradedAllowMessage(event, detail) {
195
+ return `FullCourtDefense policy gate unavailable; allowed ${event} in degraded mode. ${detail}`.trim();
196
+ }
180
197
  /** Per-event attribution that maps onto the existing Shield runtime headers (prompt path). */
181
198
  function attributionFor(event, p) {
182
199
  const h = {};
@@ -232,7 +249,7 @@ async function hookCommand(args, config) {
232
249
  const shadow = args.shadow === 'true';
233
250
  const failClosed = args.failClosed === 'true';
234
251
  const timeoutMs = Number(args.timeout) > 0 ? Number(args.timeout) : 8000;
235
- const approvalMode = args.approvalMode === 'block' ? 'block' : 'wait';
252
+ const approvalMode = args.approvalMode === 'wait' ? 'wait' : 'block';
236
253
  const approvalTimeoutMs = Number(args.approvalTimeoutMs) > 0 ? Number(args.approvalTimeoutMs) : 300000;
237
254
  const approvalPollMs = Math.max(1000, Number(args.approvalPollMs) > 0 ? Number(args.approvalPollMs) : 3000);
238
255
  const creds = (0, config_1.resolveCliCredentials)(config, {
@@ -316,7 +333,7 @@ async function hookCommand(args, config) {
316
333
  respond(false);
317
334
  }
318
335
  async function enforceActionPolicy(ctx) {
319
- const { event, payload, apiUrl, shieldId, shieldKey, shadow, failClosed, timeoutMs, respond } = ctx;
336
+ const { event, payload, apiUrl, shieldId, shieldKey, shadow, timeoutMs, respond } = ctx;
320
337
  const call = buildToolCall(event, payload);
321
338
  if (!call) {
322
339
  respond(false);
@@ -347,20 +364,20 @@ async function enforceActionPolicy(ctx) {
347
364
  clearTimeout(timer);
348
365
  if (!resp.ok) {
349
366
  dbg({ phase: 'policy_http_error', event, status: resp.status });
350
- respond(failClosed, failClosed ? `FullCourtDefense policy gate unavailable (HTTP ${resp.status}) — blocked by fail-closed policy.` : undefined, `FullCourtDefense policy gate returned HTTP ${resp.status}.`);
367
+ respond(false, undefined, degradedAllowMessage(event, `Backend returned HTTP ${resp.status}.`));
351
368
  return;
352
369
  }
353
370
  const body = await resp.json().catch(() => ({}));
354
371
  if (!body.success || !body.data) {
355
372
  dbg({ phase: 'policy_no_data', event, error: body.error });
356
- respond(failClosed, failClosed ? 'FullCourtDefense policy gate error blocked by fail-closed policy.' : undefined, body.error);
373
+ respond(false, undefined, degradedAllowMessage(event, body.error ? `Backend error: ${body.error}` : 'Backend returned no policy data.'));
357
374
  return;
358
375
  }
359
376
  result = body.data;
360
377
  }
361
378
  catch (err) {
362
379
  dbg({ phase: 'policy_exception', event, error: err instanceof Error ? err.message : String(err) });
363
- respond(failClosed, failClosed ? 'FullCourtDefense policy gate unreachable — blocked by fail-closed policy.' : undefined, `FullCourtDefense hook error: ${err instanceof Error ? err.message : String(err)}.`);
380
+ respond(false, undefined, degradedAllowMessage(event, `Hook error: ${err instanceof Error ? err.message : String(err)}.`));
364
381
  return;
365
382
  }
366
383
  const reason = result.actionPolicy?.reason
@@ -453,7 +470,8 @@ async function enforceShieldText(ctx) {
453
470
  });
454
471
  clearTimeout(timer);
455
472
  if (!resp.ok) {
456
- respond(failClosed, failClosed ? `FullCourtDefense gate unavailable (HTTP ${resp.status}) — blocked by fail-closed policy.` : undefined, `FullCourtDefense gate returned HTTP ${resp.status}.`);
473
+ respond(false, undefined, degradedAllowMessage(event, `Backend returned HTTP ${resp.status}.`));
474
+ return;
457
475
  }
458
476
  const data = await resp.json().catch(() => ({}));
459
477
  const sh = (data._shield || {});
@@ -468,6 +486,6 @@ async function enforceShieldText(ctx) {
468
486
  respond(true, `Blocked by FullCourtDefense — ${reason}.`, `FullCourtDefense blocked this ${event} as "${reason}". Do not retry; revise to remove the flagged content.`);
469
487
  }
470
488
  catch (err) {
471
- respond(failClosed, failClosed ? 'FullCourtDefense gate unreachable — blocked by fail-closed policy.' : undefined, `FullCourtDefense hook error: ${err instanceof Error ? err.message : String(err)}.`);
489
+ respond(false, undefined, degradedAllowMessage(event, `Hook error: ${err instanceof Error ? err.message : String(err)}.`));
472
490
  }
473
491
  }
package/dist/version.json CHANGED
@@ -1,3 +1,3 @@
1
1
  {
2
- "version": "1.6.3"
2
+ "version": "1.6.4"
3
3
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fullcourtdefense-cli",
3
- "version": "1.6.3",
3
+ "version": "1.6.4",
4
4
  "description": "Full Court Defense CLI — security scanning for AI agents from your terminal",
5
5
  "main": "dist/index.js",
6
6
  "bin": {