fullcourtdefense-cli 1.6.7 → 1.6.9

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.
@@ -8,3 +8,4 @@ export interface DeterministicFinding {
8
8
  }
9
9
  export declare function scanDeterministicToolCall(toolName: string, toolArgs: Record<string, unknown>): DeterministicFinding | undefined;
10
10
  export declare function scanDeterministicTextResponse(text: string): DeterministicFinding | undefined;
11
+ export declare function scanDeterministicPrompt(text: string): DeterministicFinding | undefined;
@@ -2,6 +2,7 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.scanDeterministicToolCall = scanDeterministicToolCall;
4
4
  exports.scanDeterministicTextResponse = scanDeterministicTextResponse;
5
+ exports.scanDeterministicPrompt = scanDeterministicPrompt;
5
6
  const SECRET_PATTERNS = [
6
7
  { label: 'private key material', re: /-----BEGIN (?:RSA |EC |OPENSSH |DSA )?PRIVATE KEY-----/i },
7
8
  { label: 'AWS access key', re: /\bAKIA[0-9A-Z]{16}\b/ },
@@ -317,3 +318,89 @@ function scanDeterministicTextResponse(text) {
317
318
  evidence: evidence(secret.value),
318
319
  };
319
320
  }
321
+ function looksLikeQuestion(value) {
322
+ return /^(?:what|why|how|explain|describe|is|are|can you explain)\b/i.test(value.trim());
323
+ }
324
+ function bareOrActionIntent(value, action) {
325
+ const trimmed = value.trim();
326
+ if (looksLikeQuestion(trimmed))
327
+ return false;
328
+ return action.test(trimmed) || /^[^\n]{1,240}$/.test(trimmed);
329
+ }
330
+ function scanDeterministicPrompt(text) {
331
+ const trimmed = text.trim();
332
+ if (!trimmed || looksLikeQuestion(trimmed))
333
+ return undefined;
334
+ const secret = findSecret(trimmed);
335
+ if (secret && OUTBOUND_TOOL_HINT.test(trimmed)) {
336
+ return {
337
+ blocked: true,
338
+ ruleId: 'local-prompt-secret-exfiltration',
339
+ category: 'secret_exfiltration',
340
+ reason: `Blocked prompt asking to send ${secret.label}.`,
341
+ evidence: evidence(secret.value),
342
+ };
343
+ }
344
+ const sensitivePath = containsSensitiveCredentialPath(trimmed);
345
+ if (sensitivePath && bareOrActionIntent(trimmed, /\b(?:read|open|cat|show|print|copy|upload|send|exfiltrate)\b/i)) {
346
+ return {
347
+ blocked: true,
348
+ ruleId: 'local-prompt-sensitive-credential-path',
349
+ category: 'sensitive_file',
350
+ reason: `Blocked prompt targeting ${sensitivePath}.`,
351
+ evidence: evidence(trimmed),
352
+ };
353
+ }
354
+ if (bareOrActionIntent(trimmed, /\b(?:run|execute|launch|start)\b/i)) {
355
+ const reverseShell = reverseShellReason(trimmed);
356
+ if (reverseShell) {
357
+ return {
358
+ blocked: true,
359
+ ruleId: 'local-prompt-reverse-shell',
360
+ category: 'reverse_shell',
361
+ reason: `Blocked prompt containing ${reverseShell}.`,
362
+ evidence: evidence(trimmed),
363
+ };
364
+ }
365
+ const destructive = destructiveCommandReason(trimmed);
366
+ if (destructive) {
367
+ return {
368
+ blocked: true,
369
+ ruleId: 'local-prompt-destructive-command',
370
+ category: 'destructive_command',
371
+ reason: `Blocked prompt containing ${destructive}.`,
372
+ evidence: evidence(trimmed),
373
+ };
374
+ }
375
+ const infraDestroy = infraDestroyReason(trimmed);
376
+ if (infraDestroy) {
377
+ return {
378
+ blocked: true,
379
+ ruleId: 'local-prompt-infra-destroy',
380
+ category: 'infra_destroy',
381
+ reason: `Blocked prompt containing ${infraDestroy}.`,
382
+ evidence: evidence(trimmed),
383
+ };
384
+ }
385
+ }
386
+ const destructiveSql = destructiveSqlReason(trimmed);
387
+ if (destructiveSql && bareOrActionIntent(trimmed, /\b(?:run|execute|query|apply)\b/i)) {
388
+ return {
389
+ blocked: true,
390
+ ruleId: 'local-prompt-destructive-sql',
391
+ category: 'destructive_sql',
392
+ reason: `Blocked prompt containing destructive SQL command: ${destructiveSql}.`,
393
+ evidence: evidence(trimmed),
394
+ };
395
+ }
396
+ if (containsMetadataEndpoint(trimmed) && /\b(?:fetch|request|curl|open|connect|call)\b/i.test(trimmed)) {
397
+ return {
398
+ blocked: true,
399
+ ruleId: 'local-prompt-cloud-metadata-ssrf',
400
+ category: 'metadata_ssrf',
401
+ reason: 'Blocked prompt targeting cloud metadata endpoint.',
402
+ evidence: evidence(trimmed),
403
+ };
404
+ }
405
+ return undefined;
406
+ }
@@ -358,6 +358,16 @@ async function hookCommand(args, config) {
358
358
  const text = extractPromptText(payload).trim();
359
359
  if (!text)
360
360
  respond(false);
361
+ const localBlock = (0, deterministicGuard_1.scanDeterministicPrompt)(text);
362
+ if (localBlock) {
363
+ dbg({ phase: 'local_deterministic_prompt_block', event, ruleId: localBlock.ruleId, category: localBlock.category });
364
+ if (shadow) {
365
+ respond(false, undefined, `[FullCourtDefense shadow] would block prompt: ${localBlock.reason}`);
366
+ return;
367
+ }
368
+ respond(true, `Blocked by FullCourtDefense local guard — ${localBlock.reason}`, `FullCourtDefense blocked this prompt locally (${localBlock.ruleId}). Do not retry.`);
369
+ return;
370
+ }
361
371
  await enforceShieldText({
362
372
  event, text, payload, apiUrl: apiUrl, shieldId: shieldId, shieldKey,
363
373
  shadow, failClosed, timeoutMs, respond,
@@ -137,7 +137,7 @@ async function installCursorHookCommand(args, config) {
137
137
  const { hookKey, flag, failClosedDefault, timeoutSec } = EVENT_MAP[e];
138
138
  const failClosed = e === 'prompt' ? false : (args.failClosed !== undefined ? args.failClosed === 'true' : failClosedDefault);
139
139
  const entry = {
140
- command: buildHookCommand(flag, { shadow: e === 'prompt' ? true : shadow, failClosed }),
140
+ command: buildHookCommand(flag, { shadow, failClosed }),
141
141
  timeout: timeoutSec,
142
142
  };
143
143
  if (failClosed)
package/dist/version.json CHANGED
@@ -1,3 +1,3 @@
1
1
  {
2
- "version": "1.6.7"
2
+ "version": "1.6.9"
3
3
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fullcourtdefense-cli",
3
- "version": "1.6.7",
3
+ "version": "1.6.9",
4
4
  "description": "Full Court Defense CLI — security scanning for AI agents from your terminal",
5
5
  "main": "dist/index.js",
6
6
  "bin": {