fullcourtdefense-cli 1.14.5 → 1.14.7

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.
@@ -41,16 +41,12 @@ const path = __importStar(require("path"));
41
41
  const MAX_FILES = 150;
42
42
  const MAX_FILE_BYTES = 512 * 1024;
43
43
  const EXCERPT_LEN = 180;
44
- const RISK_HEURISTICS = [
45
- { re: /\b(?:shell|bash|exec|subprocess|terminal|run_command)\b/i, tag: 'allows_shell' },
46
- { re: /\b(?:ignore (?:all )?(?:previous )?instructions|bypass|jailbreak|DAN mode)\b/i, tag: 'jailbreak_hint' },
47
- { re: /\b(?:always allow|never block|disable security|skip validation)\b/i, tag: 'weak_guardrails' },
48
- { re: /\b(?:read any file|full filesystem|entire filesystem|workspace trust|trusted workspace|\/etc\/passwd|sudo)\b/i, tag: 'broad_filesystem' },
49
- { re: /\b(?:auto[- ]?approve|without asking|unattended|non[- ]?interactive|yolo mode)\b/i, tag: 'auto_approval' },
50
- { re: /\b(?:curl|wget|webhook|exfiltrate|post to slack|send email|http request)\b/i, tag: 'network_exfil' },
51
- { re: /\b(?:send email|post to slack|transfer funds|delete all|publish package|npm publish|git push)\b/i, tag: 'destructive_actions' },
52
- { re: /\b(?:api[_-]?key|secret|password|token)\s*[:=]/i, tag: 'embedded_secret_ref' },
53
- ];
44
+ const NEGATED_RISK_LINE = /\b(?:do not|does not|don't|not an? (?:instruction|permission)|not permission|never|avoid|forbid|block|wrong|bad|dangerous|must not)\b/i;
45
+ const ACTIVE_AGENT_SUBJECT = /\b(?:agent|assistant|model|tool|mcp|skill|it)\b/i;
46
+ const EMBEDDED_SECRET_VALUE = /\b(?:sk-(?:proj-)?[A-Za-z0-9_-]{24,}|ghp_[A-Za-z0-9_]{24,}|github_pat_[A-Za-z0-9_]{24,}|glpat-[A-Za-z0-9_-]{20,}|npm_[A-Za-z0-9]{24,}|hf_[A-Za-z0-9]{24,}|gsk_[A-Za-z0-9]{24,}|AKIA[0-9A-Z]{16}|AIza[0-9A-Za-z_-]{28,}|xox[baprs]-[A-Za-z0-9-]{20,})\b/;
47
+ function stripCodeBlocks(content) {
48
+ return content.replace(/```[\s\S]*?```/g, '');
49
+ }
54
50
  function stableId(parts) {
55
51
  return crypto.createHash('sha256').update(parts.join('|')).digest('hex').slice(0, 16);
56
52
  }
@@ -66,11 +62,49 @@ function readAgentFile(filePath) {
66
62
  return null;
67
63
  }
68
64
  }
65
+ function lineHasRiskIntent(line, action, target) {
66
+ if (NEGATED_RISK_LINE.test(line))
67
+ return false;
68
+ if (!action.test(line))
69
+ return false;
70
+ return target ? target.test(line) : true;
71
+ }
72
+ function lineHasOrderedRiskIntent(line, pattern) {
73
+ return !NEGATED_RISK_LINE.test(line) && pattern.test(line);
74
+ }
75
+ function lineHasActiveAgentRisk(line, pattern) {
76
+ return !NEGATED_RISK_LINE.test(line) && ACTIVE_AGENT_SUBJECT.test(line) && pattern.test(line);
77
+ }
69
78
  function analyzeContent(content) {
70
79
  const tags = new Set();
71
- for (const rule of RISK_HEURISTICS) {
72
- if (rule.re.test(content))
73
- tags.add(rule.tag);
80
+ const text = stripCodeBlocks(content);
81
+ const lines = text.split(/\r?\n/).map(line => line.trim()).filter(Boolean);
82
+ const whole = lines.join('\n');
83
+ if (/\b(?:api[_-]?key|secret|password|token)\s*[:=]\s*/i.test(whole) && EMBEDDED_SECRET_VALUE.test(whole)) {
84
+ tags.add('embedded_secret_ref');
85
+ }
86
+ for (const line of lines) {
87
+ if (lineHasActiveAgentRisk(line, /\b(?:allow|can|may|must|should|use|execute|invoke|enable|grant)\b.{0,100}\b(?:shell|bash|exec|subprocess|run_command|command runner|terminal)\b/i)) {
88
+ tags.add('allows_shell');
89
+ }
90
+ if (lineHasActiveAgentRisk(line, /\b(?:allow|can|may|must|should|read|write|access|trust|grant)\b.{0,100}\b(?:read any file|full filesystem|entire filesystem|trusted workspace|workspace trust|\/etc\/passwd|sudo)\b/i)) {
91
+ tags.add('broad_filesystem');
92
+ }
93
+ if (lineHasOrderedRiskIntent(line, /\b(?:ignore (?:all )?(?:previous )?instructions|bypass safety|bypass guardrails|DAN mode)\b/i)) {
94
+ tags.add('jailbreak_hint');
95
+ }
96
+ if (lineHasOrderedRiskIntent(line, /\b(?:always allow|disable security|skip validation|bypass validation)\b/i)) {
97
+ tags.add('weak_guardrails');
98
+ }
99
+ if (lineHasRiskIntent(line, /\b(?:auto[- ]?approve|without asking|unattended|non[- ]?interactive|yolo mode)\b/i)) {
100
+ tags.add('auto_approval');
101
+ }
102
+ if (lineHasOrderedRiskIntent(line, /\b(?:exfiltrate\b|(?:send|post|upload|curl|wget)\b.{0,120}\b(?:secret|token|credential|api[_-]?key|env|customer|database|private|confidential)\b.{0,120}\b(?:webhook|https?:|external|slack|email|url)|(?:send|post|upload|curl|wget)\b.{0,120}\b(?:webhook|https?:|external|slack|email|url)\b.{0,120}\b(?:secret|token|credential|api[_-]?key|env|customer|database|private|confidential))\b/i)) {
103
+ tags.add('network_exfil');
104
+ }
105
+ if (lineHasOrderedRiskIntent(line, /\b(?:allow|can|may|must|should|run|execute|auto[- ]?approve)\b.{0,120}\b(?:transfer funds|delete all|publish package|npm publish|git push|drop table|remove directory)\b/i)) {
106
+ tags.add('destructive_actions');
107
+ }
74
108
  }
75
109
  return [...tags];
76
110
  }
@@ -127,8 +127,8 @@ function computeBlastRadius(input) {
127
127
  combos.push({
128
128
  id: 'permissive_rules_plus_secrets',
129
129
  severity: 'high',
130
- title: 'Permissive agent rules + local credentials',
131
- detail: 'Agent instructions allow broad tool use while local credentials are present on disk.',
130
+ title: 'Agent can reach local credentials',
131
+ detail: `${criticalSecrets.length} high-risk credential(s) are on this machine, and agent instructions allow broad actions such as shell, filesystem, auto-approval, or network sends. If the agent is hijacked, those credentials could be read or used.`,
132
132
  });
133
133
  }
134
134
  if (packagePublisherSecrets.length > 0 && riskyAgentFiles.some(f => f.riskTags.includes('destructive_actions')
@@ -137,8 +137,8 @@ function computeBlastRadius(input) {
137
137
  combos.push({
138
138
  id: 'supply_chain_token_plus_agent_actions',
139
139
  severity: 'high',
140
- title: 'Package publishing token + agent actions',
141
- detail: `${packagePublisherSecrets.length} package/repository credential(s) found alongside rules or tools that can run commands.`,
140
+ title: 'Publishing token exposed to agent actions',
141
+ detail: `${packagePublisherSecrets.length} package/repository token(s) are on this machine while agent rules allow command execution, auto-approval, or publishing actions. A hijacked agent could publish packages or push code with those credentials.`,
142
142
  });
143
143
  }
144
144
  if (agentFiles.length >= 5 && !clientCoverage.some(c => c.cursorHooksInstalled)) {
package/dist/version.json CHANGED
@@ -1,3 +1,3 @@
1
1
  {
2
- "version": "1.14.5"
2
+ "version": "1.14.7"
3
3
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fullcourtdefense-cli",
3
- "version": "1.14.5",
3
+ "version": "1.14.7",
4
4
  "description": "Full Court Defense CLI — security scanning for AI agents from your terminal",
5
5
  "main": "dist/index.js",
6
6
  "bin": {
@@ -21,6 +21,8 @@
21
21
  "test:cmd-guard": "npm run build && node scripts/test-cmd-guard.js",
22
22
  "test:posix-guard": "npm run build && node scripts/test-posix-guard.js",
23
23
  "test:secret-posture": "npm run build && node scripts/test-secret-posture-fixtures.js",
24
+ "test:agent-file-posture": "npm run build && node scripts/test-agent-file-posture.js",
25
+ "test:realworld-posture": "npm run build && node scripts/test-realworld-posture-fixtures.js",
24
26
  "test:posture-blast": "npm run build && node scripts/test-posture-blast-radius.js",
25
27
  "test:ping-e2e": "npm run build && node scripts/test-ping-e2e.js",
26
28
  "prepublishOnly": "npm run build"