awguard 1.5.0 → 1.6.0
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/CHANGELOG.md +8 -0
- package/Dockerfile +8 -0
- package/README.md +57 -1
- package/action.yml +2 -2
- package/docs/assets/terminal-demo.svg +19 -0
- package/docs/comparison.md +23 -0
- package/docs/launch-plan.md +23 -15
- package/docs/market-analysis.md +3 -1
- package/docs/marketplace-listing.md +40 -0
- package/docs/roadmap.md +10 -6
- package/docs/site/index.html +251 -0
- package/examples/.gitlab-ci.yml +6 -0
- package/examples/.vscode/tasks.json +17 -0
- package/examples/README.md +4 -0
- package/examples/awguard.config.example.json +6 -0
- package/examples/lab/README.md +27 -0
- package/examples/lab/fixed/.github/workflows/ai-triage.yml +20 -0
- package/examples/lab/fixed/.mcp.json +12 -0
- package/examples/lab/fixed/AGENTS.md +5 -0
- package/examples/lab/unsafe/.github/workflows/ai-triage.yml +16 -0
- package/examples/lab/unsafe/.mcp.json +11 -0
- package/examples/lab/unsafe/AGENTS.md +4 -0
- package/examples/pre-commit-config.yaml +8 -0
- package/package.json +2 -1
- package/src/cli.js +36 -2
- package/src/compare.js +110 -0
- package/src/config.js +29 -2
- package/src/graph.js +6 -1
- package/src/init.js +81 -0
- package/src/inventory.js +11 -0
- package/src/migration.js +10 -0
- package/src/presets.js +2 -1
- package/src/remediation.js +19 -0
- package/src/reporters.js +6 -2
- package/src/scanner.js +91 -5
- package/src/score.js +3 -0
package/src/scanner.js
CHANGED
|
@@ -175,6 +175,12 @@ export const ruleCatalog = {
|
|
|
175
175
|
severity: 'critical',
|
|
176
176
|
suggestion:
|
|
177
177
|
'Move MCP credentials into input prompts, environment variables, or a secret manager. Do not commit bearer tokens, API keys, passwords, or auth headers in MCP config files.'
|
|
178
|
+
},
|
|
179
|
+
AWG015: {
|
|
180
|
+
title: 'Agentic surface is not approved by policy',
|
|
181
|
+
severity: 'medium',
|
|
182
|
+
suggestion:
|
|
183
|
+
'Add the workflow, agent context file, MCP config, MCP server, package, or command to the policy allowlist only after review. Otherwise remove or harden it.'
|
|
178
184
|
}
|
|
179
185
|
};
|
|
180
186
|
|
|
@@ -315,13 +321,15 @@ export function classifyScanFile(file, root = process.cwd()) {
|
|
|
315
321
|
|
|
316
322
|
function scanFile(file, root, config) {
|
|
317
323
|
const text = fs.readFileSync(file, 'utf8');
|
|
324
|
+
let findings;
|
|
318
325
|
if (isAgentInstructionFile(file, root)) {
|
|
319
|
-
|
|
326
|
+
findings = scanAgentInstructionText(text, file, root, config);
|
|
327
|
+
} else if (isMcpConfigFile(file, root)) {
|
|
328
|
+
findings = scanMcpConfigText(text, file, root, config);
|
|
329
|
+
} else {
|
|
330
|
+
findings = scanWorkflowText(text, file, root, config);
|
|
320
331
|
}
|
|
321
|
-
|
|
322
|
-
return scanMcpConfigText(text, file, root, config);
|
|
323
|
-
}
|
|
324
|
-
return scanWorkflowText(text, file, root, config);
|
|
332
|
+
return [...findings, ...detectFilePolicy(file, root, config)];
|
|
325
333
|
}
|
|
326
334
|
|
|
327
335
|
function discoverScanFiles(root) {
|
|
@@ -553,6 +561,7 @@ function detectMcpConfigRisks(context, config) {
|
|
|
553
561
|
for (const server of servers) {
|
|
554
562
|
detectMutableMcpServer(context, server);
|
|
555
563
|
detectMcpSecretMaterial(context, server);
|
|
564
|
+
detectMcpPolicy(context, server);
|
|
556
565
|
}
|
|
557
566
|
}
|
|
558
567
|
|
|
@@ -610,6 +619,71 @@ function detectMcpSecretMaterial(context, server) {
|
|
|
610
619
|
}
|
|
611
620
|
}
|
|
612
621
|
|
|
622
|
+
function detectMcpPolicy(context, server) {
|
|
623
|
+
const policy = context.config.policy || {};
|
|
624
|
+
const command = normalizeCommand(stringValue(server.config.command));
|
|
625
|
+
const args = arrayOfStrings(server.config.args);
|
|
626
|
+
const packageSpec = findMcpPackageSpec(command, args);
|
|
627
|
+
|
|
628
|
+
if (policy.approvedMcpServers?.length > 0 && !policy.approvedMcpServers.includes(server.name)) {
|
|
629
|
+
addFinding(context, 'AWG015', locateMcpLine(context, server, server.name), {
|
|
630
|
+
evidence: `MCP server "${server.name}"`,
|
|
631
|
+
message: `MCP server "${server.name}" is not listed in policy.approvedMcpServers.`
|
|
632
|
+
});
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
if (policy.approvedMcpCommands?.length > 0 && command && !policy.approvedMcpCommands.includes(command)) {
|
|
636
|
+
addFinding(context, 'AWG015', locateMcpLine(context, server, command), {
|
|
637
|
+
evidence: `MCP server "${server.name}" command: ${command}`,
|
|
638
|
+
message: `MCP server "${server.name}" uses a command not listed in policy.approvedMcpCommands.`
|
|
639
|
+
});
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
if (policy.approvedMcpPackages?.length > 0 && packageSpec && !policy.approvedMcpPackages.includes(packageSpec)) {
|
|
643
|
+
addFinding(context, 'AWG015', locateMcpLine(context, server, packageSpec), {
|
|
644
|
+
evidence: `MCP server "${server.name}" package: ${packageSpec}`,
|
|
645
|
+
message: `MCP server "${server.name}" uses a package not listed in policy.approvedMcpPackages.`
|
|
646
|
+
});
|
|
647
|
+
}
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
function detectFilePolicy(file, root, config) {
|
|
651
|
+
const policy = config.policy || {};
|
|
652
|
+
if (!policy.approvedFiles || policy.approvedFiles.length === 0) return [];
|
|
653
|
+
|
|
654
|
+
const relativeFile = path.relative(root, file).split(path.sep).join('/') || path.basename(file);
|
|
655
|
+
if (matchesAnyPolicyPattern(relativeFile, policy.approvedFiles)) return [];
|
|
656
|
+
|
|
657
|
+
const context = createPolicyContext(file, root, config);
|
|
658
|
+
addFinding(context, 'AWG015', 1, {
|
|
659
|
+
evidence: relativeFile,
|
|
660
|
+
message: `${relativeFile} is an agentic surface that is not listed in policy.approvedFiles.`
|
|
661
|
+
});
|
|
662
|
+
return context.findings;
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
function createPolicyContext(file, root, config) {
|
|
666
|
+
return {
|
|
667
|
+
file,
|
|
668
|
+
relativeFile: path.isAbsolute(file) ? path.relative(root, file) || path.basename(file) : file,
|
|
669
|
+
lines: [],
|
|
670
|
+
runBlocks: new Set(),
|
|
671
|
+
triggers: new Set(),
|
|
672
|
+
hasAgent: true,
|
|
673
|
+
hasPromptBoundary: true,
|
|
674
|
+
hasUntrustedTrigger: false,
|
|
675
|
+
hasPermissionBlock: false,
|
|
676
|
+
hasBroadPermission: false,
|
|
677
|
+
hasSecret: false,
|
|
678
|
+
config,
|
|
679
|
+
suppressions: new Map(),
|
|
680
|
+
invalidSuppressions: [],
|
|
681
|
+
suppressedFindings: [],
|
|
682
|
+
findings: [],
|
|
683
|
+
seen: new Set()
|
|
684
|
+
};
|
|
685
|
+
}
|
|
686
|
+
|
|
613
687
|
function addFinding(context, ruleId, line, overrides = {}) {
|
|
614
688
|
const docs = ruleCatalog[ruleId];
|
|
615
689
|
const ruleConfig = context.config.rules?.[ruleId];
|
|
@@ -1207,6 +1281,18 @@ function isPlainObject(value) {
|
|
|
1207
1281
|
return Boolean(value) && typeof value === 'object' && !Array.isArray(value);
|
|
1208
1282
|
}
|
|
1209
1283
|
|
|
1284
|
+
function matchesAnyPolicyPattern(value, patterns) {
|
|
1285
|
+
return patterns.some((pattern) => wildcardToRegExp(pattern).test(value));
|
|
1286
|
+
}
|
|
1287
|
+
|
|
1288
|
+
function wildcardToRegExp(pattern) {
|
|
1289
|
+
const escaped = String(pattern)
|
|
1290
|
+
.split('*')
|
|
1291
|
+
.map((part) => escapeRegex(part))
|
|
1292
|
+
.join('.*');
|
|
1293
|
+
return new RegExp(`^${escaped}$`);
|
|
1294
|
+
}
|
|
1295
|
+
|
|
1210
1296
|
function windowAround(lines, index, radius) {
|
|
1211
1297
|
return lines.slice(Math.max(0, index - radius), Math.min(lines.length, index + radius + 1));
|
|
1212
1298
|
}
|
package/src/score.js
CHANGED
|
@@ -114,6 +114,9 @@ function actionFor(result, counts) {
|
|
|
114
114
|
}
|
|
115
115
|
|
|
116
116
|
if (counts.medium > 0) {
|
|
117
|
+
if (rules.has('AWG015')) {
|
|
118
|
+
return 'Review policy drift and approve only expected agentic surfaces.';
|
|
119
|
+
}
|
|
117
120
|
return 'Tighten explicit permissions, artifact boundaries, and suppression policy.';
|
|
118
121
|
}
|
|
119
122
|
|