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
|
+
}
|
package/dist/commands/hook.js
CHANGED
|
@@ -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
|
|
140
|
+
command: buildHookCommand(flag, { shadow, failClosed }),
|
|
141
141
|
timeout: timeoutSec,
|
|
142
142
|
};
|
|
143
143
|
if (failClosed)
|
package/dist/version.json
CHANGED