fullcourtdefense-cli 1.7.20 → 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',
@@ -272,7 +344,7 @@ function buildCiMetadata() {
272
344
  return {
273
345
  provider: process.env.GITHUB_ACTIONS === 'true' ? 'github_actions' : process.env.CI ? 'ci' : undefined,
274
346
  repository: process.env.GITHUB_REPOSITORY,
275
- ref: process.env.GITHUB_REF_NAME || process.env.GITHUB_HEAD_REF || process.env.GITHUB_REF,
347
+ ref: process.env.GITHUB_HEAD_REF || process.env.GITHUB_REF_NAME || process.env.GITHUB_REF,
276
348
  sha: process.env.GITHUB_SHA,
277
349
  runId: process.env.GITHUB_RUN_ID,
278
350
  runUrl: process.env.GITHUB_SERVER_URL && process.env.GITHUB_REPOSITORY && process.env.GITHUB_RUN_ID