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.
- package/dist/commands/agentCi.d.ts +2 -0
- package/dist/commands/agentCi.js +77 -5
- package/dist/commands/local-scan.js +761 -761
- package/dist/index.js +4 -0
- package/dist/version.json +1 -1
- package/package.json +44 -44
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',
|
|
@@ -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.
|
|
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
|