fullcourtdefense-cli 1.7.21 → 1.7.22

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.
@@ -6,6 +6,8 @@ export interface AgentCiArgs {
6
6
  requireUpload?: string;
7
7
  requireGateway?: string;
8
8
  requireApprovalFor?: string;
9
+ changedOnly?: string;
10
+ changedFiles?: string;
9
11
  extraPath?: string;
10
12
  apiKey?: string;
11
13
  apiUrl?: string;
@@ -35,6 +35,7 @@ var __importStar = (this && this.__importStar) || (function () {
35
35
  Object.defineProperty(exports, "__esModule", { value: true });
36
36
  exports.agentCiCommand = agentCiCommand;
37
37
  const crypto = __importStar(require("crypto"));
38
+ const child_process_1 = require("child_process");
38
39
  const fs = __importStar(require("fs"));
39
40
  const path = __importStar(require("path"));
40
41
  const config_1 = require("../config");
@@ -159,11 +160,82 @@ function parseRequiredControls(value) {
159
160
  function finding(input) {
160
161
  return { ...input, id: stableId([input.category, input.title, input.filePath || '', input.detail]) };
161
162
  }
163
+ function normalizeRelativePath(cwd, filePath) {
164
+ if (!filePath)
165
+ return '';
166
+ const absolute = path.isAbsolute(filePath) ? filePath : path.resolve(cwd, filePath);
167
+ return path.relative(cwd, absolute).replace(/\\/g, '/').toLowerCase();
168
+ }
169
+ function parseChangedFilesInput(raw) {
170
+ return (raw || '')
171
+ .split(/\r?\n|,/)
172
+ .map(item => item.trim())
173
+ .filter(Boolean);
174
+ }
175
+ function resolveChangedFiles(cwd, args) {
176
+ if (args.changedOnly !== 'true')
177
+ return null;
178
+ let files = [];
179
+ if (args.changedFiles) {
180
+ const changedFilePath = path.resolve(cwd, args.changedFiles);
181
+ if (fs.existsSync(changedFilePath)) {
182
+ files = parseChangedFilesInput(fs.readFileSync(changedFilePath, 'utf8'));
183
+ }
184
+ else {
185
+ files = parseChangedFilesInput(args.changedFiles);
186
+ }
187
+ }
188
+ if (files.length === 0)
189
+ files = parseChangedFilesInput(process.env.AGENTGUARD_CHANGED_FILES || process.env.FCD_CHANGED_FILES);
190
+ if (files.length === 0 && process.env.GITHUB_BASE_REF) {
191
+ try {
192
+ files = (0, child_process_1.execFileSync)('git', ['diff', '--name-only', `origin/${process.env.GITHUB_BASE_REF}...HEAD`], { cwd, encoding: 'utf8' })
193
+ .split(/\r?\n/)
194
+ .filter(Boolean);
195
+ }
196
+ catch {
197
+ // The workflow should pass --changed-files; full scan is safer if diff context is unavailable.
198
+ }
199
+ }
200
+ if (files.length === 0)
201
+ return null;
202
+ return new Set(files.map(file => normalizeRelativePath(cwd, file)));
203
+ }
204
+ function isChangedFile(cwd, changedFiles, filePath) {
205
+ if (!changedFiles)
206
+ return true;
207
+ return changedFiles.has(normalizeRelativePath(cwd, filePath));
208
+ }
209
+ function agentFileRemediation(tags) {
210
+ const fixes = [];
211
+ if (tags.includes('allows_shell'))
212
+ fixes.push('remove blanket shell/terminal permission or require an explicit command allowlist plus human approval');
213
+ if (tags.includes('broad_filesystem'))
214
+ fixes.push('limit file access to approved project paths and deny secrets/home/system paths');
215
+ if (tags.includes('destructive_actions'))
216
+ fixes.push('require an AgentGuard Action Policy for delete, payment, transfer, email, Slack, or other side-effecting actions');
217
+ if (tags.includes('weak_guardrails'))
218
+ fixes.push('delete bypass language such as always allow, never block, disable security, or skip validation');
219
+ if (tags.includes('embedded_secret_ref'))
220
+ fixes.push('remove inline secret/password/token references and use a managed secret store');
221
+ if (tags.includes('jailbreak_hint'))
222
+ fixes.push('remove jailbreak/bypass instructions and add hierarchy language that system and security policies win');
223
+ if (tags.includes('no_runtime_hook'))
224
+ fixes.push('install the Full Court Defense hook/gateway so risky tool calls are checked before execution');
225
+ if (fixes.length === 0)
226
+ return 'Review the instruction/rule and add explicit least-privilege boundaries.';
227
+ return `Fix this file before merge: ${fixes.join('; ')}.`;
228
+ }
162
229
  function evaluateAgentGate(args) {
163
230
  const cwd = process.cwd();
164
- const mcpServers = discoverMcpServers(cwd, args.extraPath);
165
- const agentFiles = (0, discoverAgentFiles_1.scanAgentFiles)({ cwd });
231
+ const changedFiles = resolveChangedFiles(cwd, args);
232
+ const mcpServers = discoverMcpServers(cwd, args.extraPath)
233
+ .filter(server => isChangedFile(cwd, changedFiles, server.filePath));
234
+ const agentFiles = (0, discoverAgentFiles_1.scanAgentFiles)({ cwd })
235
+ .filter(file => isChangedFile(cwd, changedFiles, file.filePath));
166
236
  const secrets = (0, discoverSecrets_1.scanSecrets)({ cwd, maxEnvDepth: 3 });
237
+ const secretFindings = secrets.findings
238
+ .filter(item => isChangedFile(cwd, changedFiles, item.filePath));
167
239
  const requireGateway = args.requireGateway !== 'false';
168
240
  const requiredControls = parseRequiredControls(args.requireApprovalFor);
169
241
  const findings = [];
@@ -197,10 +269,10 @@ function evaluateAgentGate(args) {
197
269
  title: `Risky agent instruction: ${file.title}`,
198
270
  detail: `${file.filePath} contains agent risk tags: ${file.riskTags.join(', ')}.`,
199
271
  filePath: file.filePath,
200
- remediation: 'Review the instruction/rule and add explicit least-privilege boundaries.',
272
+ remediation: agentFileRemediation(file.riskTags),
201
273
  }));
202
274
  }
203
- for (const secret of secrets.findings.filter(item => item.severity === 'critical' || item.severity === 'high')) {
275
+ for (const secret of secretFindings.filter(item => item.severity === 'critical' || item.severity === 'high')) {
204
276
  findings.push(finding({
205
277
  severity: secret.severity === 'critical' ? 'critical' : 'high',
206
278
  category: 'secret',
package/dist/index.js CHANGED
@@ -265,6 +265,8 @@ function printHelp() {
265
265
  --require-gateway agent-ci requires risky MCP servers to use FCD gateway
266
266
  --require-approval-for agent-ci risky controls list, e.g. payment,delete,shell,secrets
267
267
  --require-upload agent-ci fails if evidence cannot upload to AgentGuard UI
268
+ --changed-only agent-ci limits findings to changed files
269
+ --changed-files <path> file or comma/newline list of changed files for agent-ci
268
270
  --method <GET|POST> Local endpoint method
269
271
  --request-format <fmt> Local endpoint request format: custom or openai
270
272
  --auth-type <type> none, bearer, basic, api-key
@@ -506,6 +508,8 @@ async function main() {
506
508
  requireUpload: flags['require-upload'],
507
509
  requireGateway: flags['require-gateway'],
508
510
  requireApprovalFor: flags['require-approval-for'],
511
+ changedOnly: flags['changed-only'],
512
+ changedFiles: flags['changed-files'],
509
513
  extraPath: flags.path,
510
514
  apiKey: flags['api-key'],
511
515
  apiUrl: flags['api-url'],
package/dist/version.json CHANGED
@@ -1,3 +1,3 @@
1
1
  {
2
- "version": "1.7.21"
2
+ "version": "1.7.22"
3
3
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fullcourtdefense-cli",
3
- "version": "1.7.21",
3
+ "version": "1.7.22",
4
4
  "description": "Full Court Defense CLI — security scanning for AI agents from your terminal",
5
5
  "main": "dist/index.js",
6
6
  "bin": {