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.
- package/dist/commands/agentCi.d.ts +2 -0
- package/dist/commands/agentCi.js +76 -4
- package/dist/index.js +4 -0
- package/dist/version.json +1 -1
- package/package.json +1 -1
package/dist/commands/agentCi.js
CHANGED
|
@@ -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
|
|
165
|
-
const
|
|
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:
|
|
272
|
+
remediation: agentFileRemediation(file.riskTags),
|
|
201
273
|
}));
|
|
202
274
|
}
|
|
203
|
-
for (const secret of
|
|
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