awguard 1.4.0 → 1.5.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 +6 -0
- package/README.md +19 -1
- package/action.yml +1 -1
- package/docs/launch-plan.md +11 -4
- package/docs/market-analysis.md +18 -0
- package/docs/roadmap.md +71 -0
- package/examples/README.md +1 -0
- package/package.json +1 -1
- package/src/cli.js +29 -3
- package/src/inventory.js +148 -0
- package/src/reporters.js +6 -1
- package/src/scanner.js +28 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.5.0
|
|
4
|
+
|
|
5
|
+
- Add `--format inventory` to map agentic repository surfaces by workflows, agent context files, and MCP configs.
|
|
6
|
+
- Scan GitHub Copilot custom agents, reusable prompts, and repository skills under `.github/agents`, `.github/prompts`, and `.github/skills`.
|
|
7
|
+
- Add a scope expansion roadmap for policy mode, agent capability SBOMs, trend reports, and adoption tooling.
|
|
8
|
+
|
|
3
9
|
## 1.4.0
|
|
4
10
|
|
|
5
11
|
- Add `AWG013` for project MCP configs that start mutable packages, unpinned containers, or shell wrappers.
|
package/README.md
CHANGED
|
@@ -113,7 +113,7 @@ jobs:
|
|
|
113
113
|
## CLI
|
|
114
114
|
|
|
115
115
|
```bash
|
|
116
|
-
awguard [path] [--config file] [--preset name] [--format text|json|markdown|github|sarif|graph|html|migration|score|badge] [--output file] [--baseline file] [--write-baseline file] [--fix-dry-run] [--fail-on none|low|medium|high|critical]
|
|
116
|
+
awguard [path] [--config file] [--preset name] [--format text|json|markdown|github|sarif|graph|html|migration|score|badge|inventory] [--output file] [--baseline file] [--write-baseline file] [--fix-dry-run] [--fail-on none|low|medium|high|critical]
|
|
117
117
|
```
|
|
118
118
|
|
|
119
119
|
Examples:
|
|
@@ -124,6 +124,7 @@ node ./bin/awguard.js . --config awguard.config.json
|
|
|
124
124
|
node ./bin/awguard.js . --preset strict --format graph
|
|
125
125
|
node ./bin/awguard.js . --format html --output awguard-report.html
|
|
126
126
|
node ./bin/awguard.js . --format migration --output awguard-migration.md
|
|
127
|
+
node ./bin/awguard.js . --format inventory
|
|
127
128
|
node ./bin/awguard.js . --format score
|
|
128
129
|
node ./bin/awguard.js . --format badge --output awguard-badge.json
|
|
129
130
|
node ./bin/awguard.js . --fix-dry-run
|
|
@@ -241,6 +242,16 @@ Then add a badge to your README:
|
|
|
241
242
|
|
|
242
243
|
The score starts at 100 and subtracts risk for critical, high, medium, and low findings. This makes AWGuard easy to show in a README without hiding the detailed SARIF, graph, and migration reports.
|
|
243
244
|
|
|
245
|
+
## Agentic Surface Inventory
|
|
246
|
+
|
|
247
|
+
Generate a repository map of agent-related surfaces:
|
|
248
|
+
|
|
249
|
+
```bash
|
|
250
|
+
node ./bin/awguard.js . --format inventory
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
The inventory groups scanned files into GitHub Actions workflows, persistent agent context files, and MCP configs. It shows which surfaces exist, which rules fired, and what to review next. This is useful before a team enables new coding agents because it answers: "Where can agents read instructions, get tools, or act in CI?"
|
|
254
|
+
|
|
244
255
|
## Agent Context Guard
|
|
245
256
|
|
|
246
257
|
AWGuard also scans persistent agent instruction files:
|
|
@@ -251,6 +262,9 @@ AWGuard also scans persistent agent instruction files:
|
|
|
251
262
|
- `GEMINI.md`
|
|
252
263
|
- `.github/copilot-instructions.md`
|
|
253
264
|
- `.github/instructions/*.instructions.md`
|
|
265
|
+
- `.github/agents/*.md`
|
|
266
|
+
- `.github/prompts/*.prompt.md`
|
|
267
|
+
- `.github/skills/**/SKILL.md`
|
|
254
268
|
- `.cursor/rules/*.{md,mdc,txt}`
|
|
255
269
|
- `.cursorrules`, `.windsurfrules`, and `.clinerules`
|
|
256
270
|
|
|
@@ -330,6 +344,9 @@ If you omit rule ids, the suppression applies to all findings on the target line
|
|
|
330
344
|
- Hosted AWI score API for dynamic cross-repository badges.
|
|
331
345
|
- Agent instruction file rule packs for Copilot, Claude Code, Codex, Gemini, Cursor, and Windsurf.
|
|
332
346
|
- MCP config rule packs for Claude Code, Copilot, VS Code, Cursor, Windsurf, Cline, and Roo.
|
|
347
|
+
- Policy mode for approved MCP packages, actions, token scopes, and agent context files.
|
|
348
|
+
- Agent capability SBOM for prompts, tools, MCP servers, permissions, and write paths.
|
|
349
|
+
- Trend reports that show newly added agent surfaces and newly introduced findings.
|
|
333
350
|
- GitHub App integration for always-on repository monitoring.
|
|
334
351
|
- Rule packs for Claude Code, Codex, Gemini, Copilot, Aider, and custom agents.
|
|
335
352
|
- Public vulnerable workflow lab with attack and fix walkthroughs.
|
|
@@ -337,3 +354,4 @@ If you omit rule ids, the suppression applies to all findings on the target line
|
|
|
337
354
|
## Research Backing
|
|
338
355
|
|
|
339
356
|
See [docs/market-analysis.md](docs/market-analysis.md) for the demand analysis, gap, audience, and launch plan.
|
|
357
|
+
See [docs/roadmap.md](docs/roadmap.md) for the scope expansion roadmap.
|
package/action.yml
CHANGED
|
@@ -7,7 +7,7 @@ inputs:
|
|
|
7
7
|
required: false
|
|
8
8
|
default: .
|
|
9
9
|
format:
|
|
10
|
-
description: Output format: github, text, json, markdown, sarif, graph, html, migration, score, or
|
|
10
|
+
description: Output format: github, text, json, markdown, sarif, graph, html, migration, score, badge, or inventory.
|
|
11
11
|
required: false
|
|
12
12
|
default: github
|
|
13
13
|
fail-on:
|
package/docs/launch-plan.md
CHANGED
|
@@ -39,20 +39,27 @@ Short pitch:
|
|
|
39
39
|
```
|
|
40
40
|
|
|
41
41
|
9. Show the README badge and say: "Add an AWI risk badge to your repo before adding AI agents to CI."
|
|
42
|
-
10.
|
|
42
|
+
10. Run:
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
node ./bin/awguard.js . --format inventory
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
11. Show the surface map and say: "Before you secure agent workflows, find every place the repository gives agents instructions or tools."
|
|
49
|
+
12. Show an unsafe `AGENTS.md` or `.github/copilot-instructions.md` line and run:
|
|
43
50
|
|
|
44
51
|
```bash
|
|
45
52
|
node ./bin/awguard.js . --format text
|
|
46
53
|
```
|
|
47
54
|
|
|
48
|
-
|
|
49
|
-
|
|
55
|
+
13. Explain that AWGuard scans both the workflow and the persistent agent instructions that shape agent behavior.
|
|
56
|
+
14. Show an unsafe `.mcp.json` with `npx @modelcontextprotocol/server-github` and a committed token, then run:
|
|
50
57
|
|
|
51
58
|
```bash
|
|
52
59
|
node ./bin/awguard.js examples/.mcp.json --format text
|
|
53
60
|
```
|
|
54
61
|
|
|
55
|
-
|
|
62
|
+
15. Explain the new hook: "This scanner checks repo-provided MCP tool wiring without executing the MCP server."
|
|
56
63
|
|
|
57
64
|
## Release Checklist
|
|
58
65
|
|
package/docs/market-analysis.md
CHANGED
|
@@ -197,6 +197,24 @@ Agentic Workflow Guard now supports:
|
|
|
197
197
|
|
|
198
198
|
This keeps the project focused: AWGuard does not try to replace MCP runtime scanners. It gives maintainers a GitHub-native, zero-dependency first check before an agent or scanner executes repo-provided MCP server commands.
|
|
199
199
|
|
|
200
|
+
## Deep Research Refresh: Agentic Surface Inventory
|
|
201
|
+
|
|
202
|
+
The next scope expansion is an inventory view. GitHub now documents repository-level custom agents in `.github/agents`, Copilot MCP configuration, and custom instructions. VS Code documents workspace MCP config. This means the repository itself can contain multiple agent-control surfaces before a single workflow runs.
|
|
203
|
+
|
|
204
|
+
The useful maintainer question is no longer only "is this workflow unsafe?" It is:
|
|
205
|
+
|
|
206
|
+
```text
|
|
207
|
+
what agent surfaces does this repository expose, and which ones changed?
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
Agentic Workflow Guard now supports:
|
|
211
|
+
|
|
212
|
+
- `--format inventory` for a surface map grouped by workflows, agent context files, and MCP configs.
|
|
213
|
+
- scanning `.github/agents/*.md`, `.github/prompts/*.prompt.md`, and `.github/skills/**/SKILL.md` as persistent agent context.
|
|
214
|
+
- a roadmap that moves toward policy mode, agent capability SBOMs, trend reports, and adoption generators.
|
|
215
|
+
|
|
216
|
+
This widens the project while preserving its niche: AWGuard remains a zero-execution repository scanner for agentic risk, not a broad runtime agent firewall.
|
|
217
|
+
|
|
200
218
|
## Distribution Plan
|
|
201
219
|
|
|
202
220
|
1. Publish the repo with a short demo GIF or screenshot.
|
package/docs/roadmap.md
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
# Scope Expansion Roadmap
|
|
2
|
+
|
|
3
|
+
## Research Signal
|
|
4
|
+
|
|
5
|
+
AWGuard should widen from "workflow scanner" into an agentic repository safety map while staying small and zero-dependency.
|
|
6
|
+
|
|
7
|
+
Current research points:
|
|
8
|
+
|
|
9
|
+
- GitHub Copilot now documents repository-level custom agents in `.github/agents`, repository MCP configuration, and repo-scoped agent behavior.
|
|
10
|
+
- VS Code and Copilot document MCP configuration in repository or workspace files such as `.vscode/mcp.json`.
|
|
11
|
+
- GitHub Actions security docs still warn that attacker-controlled GitHub context must be treated as untrusted input.
|
|
12
|
+
- OpenSSF Scorecard shows that security tools travel further when they produce a simple public score, badge, and clear adoption path.
|
|
13
|
+
- Existing MCP scanners focus on live server/tool inspection; AWGuard's lane is zero-execution repository scanning before those tools start.
|
|
14
|
+
|
|
15
|
+
## Feature List
|
|
16
|
+
|
|
17
|
+
1. Agentic Surface Inventory
|
|
18
|
+
- Add `--format inventory`.
|
|
19
|
+
- Group scanned files into GitHub Actions workflows, agent context files, and MCP configs.
|
|
20
|
+
- Show findings, highest severity, and recommended next steps per surface.
|
|
21
|
+
|
|
22
|
+
2. Wider Agent Context Coverage
|
|
23
|
+
- Scan `.github/agents/*.md` for Copilot custom agents.
|
|
24
|
+
- Scan `.github/prompts/*.prompt.md` for reusable prompts.
|
|
25
|
+
- Scan `.github/skills/**/SKILL.md` for repository skills.
|
|
26
|
+
- Keep using `AWG012` for risky persistent instructions.
|
|
27
|
+
|
|
28
|
+
3. Policy Mode
|
|
29
|
+
- Add an `awguard.policy.json` format for explicit allowlists.
|
|
30
|
+
- Allow approved MCP commands, package pins, Docker digests, action owners, and workflow write scopes.
|
|
31
|
+
- Report drift when the repository adds a new agent surface without policy.
|
|
32
|
+
|
|
33
|
+
4. Setup And Adoption Generator
|
|
34
|
+
- Add a command that prints a starter GitHub Action, strict config, baseline command, and badge snippet.
|
|
35
|
+
- Keep it as a print-only generator first so it remains safe.
|
|
36
|
+
|
|
37
|
+
5. Agent Capability SBOM
|
|
38
|
+
- Export a machine-readable inventory of agent prompts, tools, MCP servers, permissions, secrets exposure, and write capabilities.
|
|
39
|
+
- Make it useful for security reviews and audits.
|
|
40
|
+
|
|
41
|
+
6. Trend Reports
|
|
42
|
+
- Compare current scan output with a previous JSON report.
|
|
43
|
+
- Show newly added agent surfaces and newly introduced rules.
|
|
44
|
+
|
|
45
|
+
7. Vulnerable Lab
|
|
46
|
+
- Add a set of intentionally unsafe mini-repositories under examples or a separate demo repo.
|
|
47
|
+
- Each lab should include exploit explanation, AWGuard output, and fixed pattern.
|
|
48
|
+
|
|
49
|
+
8. GitHub App Or Scheduled Monitor
|
|
50
|
+
- Long-term: run continuously across repositories.
|
|
51
|
+
- Open issues when new agent surfaces appear or risk score drops.
|
|
52
|
+
|
|
53
|
+
## Work Plan
|
|
54
|
+
|
|
55
|
+
### Now
|
|
56
|
+
|
|
57
|
+
- Ship `--format inventory`.
|
|
58
|
+
- Expand `AWG012` coverage to Copilot custom agents, prompts, and skills.
|
|
59
|
+
- Document the widened project roadmap.
|
|
60
|
+
|
|
61
|
+
### Next
|
|
62
|
+
|
|
63
|
+
- Add policy mode for MCP server allowlists and approved agent context files.
|
|
64
|
+
- Add a setup generator for the GitHub Action, config, baseline, and README badge.
|
|
65
|
+
- Add JSON inventory output for downstream dashboards.
|
|
66
|
+
|
|
67
|
+
### Later
|
|
68
|
+
|
|
69
|
+
- Add trend reports for "new agent surface introduced" diffs.
|
|
70
|
+
- Build the vulnerable lab and screenshot-friendly walkthroughs.
|
|
71
|
+
- Explore a GitHub App after the CLI and Action adoption path is stable.
|
package/examples/README.md
CHANGED
|
@@ -14,6 +14,7 @@ Try:
|
|
|
14
14
|
node ../bin/awguard.js unsafe-agent.yml --format graph
|
|
15
15
|
node ../bin/awguard.js unsafe-agent.yml --format html --output awguard-report.html
|
|
16
16
|
node ../bin/awguard.js unsafe-agent.yml --format migration
|
|
17
|
+
node ../bin/awguard.js . --format inventory
|
|
17
18
|
node ../bin/awguard.js unsafe-agent.yml --format score
|
|
18
19
|
node ../bin/awguard.js safe-agent.yml --format badge
|
|
19
20
|
node ../bin/awguard.js .mcp.json --format text
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "awguard",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.5.0",
|
|
4
4
|
"description": "Scan GitHub Actions workflows, agent instructions, and MCP configs for AI-agent injection and unsafe tool boundaries.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"homepage": "https://github.com/Mughal-Baig/agentic-workflow-guard#readme",
|
package/src/cli.js
CHANGED
|
@@ -5,12 +5,24 @@ import { applyBaseline, createBaseline, loadBaseline, writeBaseline } from './ba
|
|
|
5
5
|
import { loadConfig } from './config.js';
|
|
6
6
|
import { renderFixDryRun } from './remediation.js';
|
|
7
7
|
import { scanWorkflows, severityRank } from './scanner.js';
|
|
8
|
-
import {
|
|
8
|
+
import {
|
|
9
|
+
renderBadge,
|
|
10
|
+
renderGithubAnnotations,
|
|
11
|
+
renderGraph,
|
|
12
|
+
renderHtml,
|
|
13
|
+
renderJson,
|
|
14
|
+
renderMarkdown,
|
|
15
|
+
renderMigration,
|
|
16
|
+
renderSarif,
|
|
17
|
+
renderScore,
|
|
18
|
+
renderSurfaceInventory,
|
|
19
|
+
renderText
|
|
20
|
+
} from './reporters.js';
|
|
9
21
|
|
|
10
22
|
const HELP = `Agentic Workflow Guard
|
|
11
23
|
|
|
12
24
|
Usage:
|
|
13
|
-
awguard [path] [--config file] [--preset name] [--format text|json|markdown|github|sarif|graph|html|migration|score|badge] [--output file] [--baseline file] [--write-baseline file] [--fix-dry-run] [--fail-on none|low|medium|high|critical]
|
|
25
|
+
awguard [path] [--config file] [--preset name] [--format text|json|markdown|github|sarif|graph|html|migration|score|badge|inventory] [--output file] [--baseline file] [--write-baseline file] [--fix-dry-run] [--fail-on none|low|medium|high|critical]
|
|
14
26
|
|
|
15
27
|
Examples:
|
|
16
28
|
awguard .
|
|
@@ -20,6 +32,7 @@ Examples:
|
|
|
20
32
|
awguard .github/workflows/agent.yml --format markdown --fail-on high
|
|
21
33
|
awguard . --format html --output awguard-report.html
|
|
22
34
|
awguard . --format migration --output awguard-migration.md
|
|
35
|
+
awguard . --format inventory
|
|
23
36
|
awguard . --format score
|
|
24
37
|
awguard . --format badge --output awguard-badge.json
|
|
25
38
|
awguard . --fix-dry-run
|
|
@@ -121,7 +134,19 @@ export function parseArgs(args, env = {}) {
|
|
|
121
134
|
}
|
|
122
135
|
}
|
|
123
136
|
|
|
124
|
-
validateEnum('format', options.format, [
|
|
137
|
+
validateEnum('format', options.format, [
|
|
138
|
+
'text',
|
|
139
|
+
'json',
|
|
140
|
+
'markdown',
|
|
141
|
+
'github',
|
|
142
|
+
'sarif',
|
|
143
|
+
'graph',
|
|
144
|
+
'html',
|
|
145
|
+
'migration',
|
|
146
|
+
'score',
|
|
147
|
+
'badge',
|
|
148
|
+
'inventory'
|
|
149
|
+
]);
|
|
125
150
|
validateEnum('fail-on', options.failOn, ['none', 'low', 'medium', 'high', 'critical']);
|
|
126
151
|
|
|
127
152
|
return options;
|
|
@@ -141,6 +166,7 @@ function render(result, format) {
|
|
|
141
166
|
if (format === 'migration') return renderMigration(result);
|
|
142
167
|
if (format === 'score') return renderScore(result);
|
|
143
168
|
if (format === 'badge') return renderBadge(result);
|
|
169
|
+
if (format === 'inventory') return renderSurfaceInventory(result);
|
|
144
170
|
if (format === 'github') return renderGithubAnnotations(result);
|
|
145
171
|
return renderText(result);
|
|
146
172
|
}
|
package/src/inventory.js
ADDED
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import { classifyScanFile, severityRank } from './scanner.js';
|
|
3
|
+
|
|
4
|
+
const surfaceLabels = {
|
|
5
|
+
'github-workflow': 'GitHub Actions workflows',
|
|
6
|
+
'agent-context': 'Agent context files',
|
|
7
|
+
'mcp-config': 'MCP configs',
|
|
8
|
+
other: 'Other scanned files'
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
const surfaceOrder = ['github-workflow', 'agent-context', 'mcp-config', 'other'];
|
|
12
|
+
|
|
13
|
+
export function buildInventory(result) {
|
|
14
|
+
const fileRows = result.scannedFiles.map((file) => {
|
|
15
|
+
const relativeFile = path.relative(result.root, file) || file;
|
|
16
|
+
const surface = classifyScanFile(file, result.root);
|
|
17
|
+
const findings = result.findings.filter((finding) => finding.file === relativeFile);
|
|
18
|
+
|
|
19
|
+
return {
|
|
20
|
+
file: relativeFile,
|
|
21
|
+
surface,
|
|
22
|
+
label: surfaceLabels[surface] || surfaceLabels.other,
|
|
23
|
+
findings: findings.length,
|
|
24
|
+
highest: highestSeverity(findings),
|
|
25
|
+
rules: [...new Set(findings.map((finding) => finding.ruleId))]
|
|
26
|
+
};
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
const surfaces = surfaceOrder
|
|
30
|
+
.map((surface) => {
|
|
31
|
+
const files = fileRows.filter((file) => file.surface === surface);
|
|
32
|
+
const findings = result.findings.filter((finding) => files.some((file) => file.file === finding.file));
|
|
33
|
+
|
|
34
|
+
return {
|
|
35
|
+
surface,
|
|
36
|
+
label: surfaceLabels[surface],
|
|
37
|
+
files: files.length,
|
|
38
|
+
findings: findings.length,
|
|
39
|
+
highest: highestSeverity(findings),
|
|
40
|
+
rules: [...new Set(findings.map((finding) => finding.ruleId))]
|
|
41
|
+
};
|
|
42
|
+
})
|
|
43
|
+
.filter((surface) => surface.files > 0);
|
|
44
|
+
|
|
45
|
+
return {
|
|
46
|
+
summary: {
|
|
47
|
+
scannedFiles: result.scannedFiles.length,
|
|
48
|
+
surfaces: surfaces.length,
|
|
49
|
+
findings: result.findings.length,
|
|
50
|
+
highest: result.summary.highest
|
|
51
|
+
},
|
|
52
|
+
surfaces,
|
|
53
|
+
files: fileRows,
|
|
54
|
+
recommendations: recommendationsFor(surfaces, result.findings)
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export function renderInventory(result) {
|
|
59
|
+
const inventory = buildInventory(result);
|
|
60
|
+
const lines = [
|
|
61
|
+
'# Agentic Surface Inventory',
|
|
62
|
+
'',
|
|
63
|
+
`Scanned files: **${inventory.summary.scannedFiles}**`,
|
|
64
|
+
`Agentic surfaces: **${inventory.summary.surfaces}**`,
|
|
65
|
+
`Findings: **${inventory.summary.findings}**`,
|
|
66
|
+
`Highest severity: **${inventory.summary.highest}**`,
|
|
67
|
+
'',
|
|
68
|
+
'## Surface Summary',
|
|
69
|
+
'',
|
|
70
|
+
'| Surface | Files | Findings | Highest | Rules |',
|
|
71
|
+
'| --- | ---: | ---: | --- | --- |'
|
|
72
|
+
];
|
|
73
|
+
|
|
74
|
+
if (inventory.surfaces.length === 0) {
|
|
75
|
+
lines.push('| None found | 0 | 0 | none | |');
|
|
76
|
+
} else {
|
|
77
|
+
for (const surface of inventory.surfaces) {
|
|
78
|
+
lines.push(
|
|
79
|
+
`| ${surface.label} | ${surface.files} | ${surface.findings} | ${surface.highest} | ${
|
|
80
|
+
surface.rules.length > 0 ? surface.rules.join(', ') : ''
|
|
81
|
+
} |`
|
|
82
|
+
);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
lines.push('', '## Files', '', '| Surface | File | Findings | Highest | Rules |', '| --- | --- | ---: | --- | --- |');
|
|
87
|
+
|
|
88
|
+
if (inventory.files.length === 0) {
|
|
89
|
+
lines.push('| None found | | 0 | none | |');
|
|
90
|
+
} else {
|
|
91
|
+
for (const file of inventory.files) {
|
|
92
|
+
lines.push(
|
|
93
|
+
`| ${file.label} | \`${escapeMarkdown(file.file)}\` | ${file.findings} | ${file.highest} | ${
|
|
94
|
+
file.rules.length > 0 ? file.rules.join(', ') : ''
|
|
95
|
+
} |`
|
|
96
|
+
);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
lines.push('', '## Recommended Next Steps', '');
|
|
101
|
+
for (const recommendation of inventory.recommendations) {
|
|
102
|
+
lines.push(`- ${recommendation}`);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
return lines.join('\n');
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function recommendationsFor(surfaces, findings) {
|
|
109
|
+
const surfaceNames = new Set(surfaces.map((surface) => surface.surface));
|
|
110
|
+
const rules = new Set(findings.map((finding) => finding.ruleId));
|
|
111
|
+
const recommendations = [];
|
|
112
|
+
|
|
113
|
+
if (rules.has('AWG014')) {
|
|
114
|
+
recommendations.push('Remove and rotate committed MCP credentials before widening agent access.');
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
if (rules.has('AWG013')) {
|
|
118
|
+
recommendations.push('Pin MCP server packages, container images, and startup commands before enabling repository-scoped tools.');
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
if (rules.has('AWG012')) {
|
|
122
|
+
recommendations.push('Review persistent agent context files before relying on workflow permission boundaries.');
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
if (!surfaceNames.has('agent-context')) {
|
|
126
|
+
recommendations.push('Add an explicit `AGENTS.md` or `.github/copilot-instructions.md` with conservative safety rules before introducing agents.');
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
if (!surfaceNames.has('mcp-config')) {
|
|
130
|
+
recommendations.push('Keep MCP configs absent until there is a reviewed tool allowlist and credential handling plan.');
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
if (recommendations.length === 0) {
|
|
134
|
+
recommendations.push('Keep this inventory in CI so new agent surfaces are visible during review.');
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
return recommendations;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function highestSeverity(findings) {
|
|
141
|
+
return findings.reduce((current, finding) => {
|
|
142
|
+
return severityRank[finding.severity] > severityRank[current] ? finding.severity : current;
|
|
143
|
+
}, 'none');
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function escapeMarkdown(value) {
|
|
147
|
+
return String(value).replaceAll('|', '\\|');
|
|
148
|
+
}
|
package/src/reporters.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import path from 'node:path';
|
|
2
2
|
import { findingFingerprint } from './fingerprints.js';
|
|
3
3
|
import { renderGraphMarkdown, renderHtmlReport } from './graph.js';
|
|
4
|
+
import { renderInventory } from './inventory.js';
|
|
4
5
|
import { renderMigrationPlan } from './migration.js';
|
|
5
6
|
import { renderBadgeJson, renderScorecard } from './score.js';
|
|
6
7
|
import { ruleCatalog } from './scanner.js';
|
|
@@ -83,7 +84,7 @@ export function renderSarif(result) {
|
|
|
83
84
|
driver: {
|
|
84
85
|
name: 'Agentic Workflow Guard',
|
|
85
86
|
informationUri: 'https://github.com/Mughal-Baig/agentic-workflow-guard',
|
|
86
|
-
semanticVersion: '1.
|
|
87
|
+
semanticVersion: '1.5.0',
|
|
87
88
|
rules: Object.entries(ruleCatalog).map(([id, rule]) => ({
|
|
88
89
|
id,
|
|
89
90
|
name: id,
|
|
@@ -205,6 +206,10 @@ export function renderBadge(result) {
|
|
|
205
206
|
return renderBadgeJson(result);
|
|
206
207
|
}
|
|
207
208
|
|
|
209
|
+
export function renderSurfaceInventory(result) {
|
|
210
|
+
return renderInventory(result);
|
|
211
|
+
}
|
|
212
|
+
|
|
208
213
|
export function renderGithubAnnotations(result) {
|
|
209
214
|
if (result.findings.length === 0) {
|
|
210
215
|
return 'Agentic Workflow Guard: no findings.';
|
package/src/scanner.js
CHANGED
|
@@ -306,6 +306,13 @@ export function scanMcpConfigText(text, file = '.mcp.json', root = process.cwd()
|
|
|
306
306
|
return context.findings;
|
|
307
307
|
}
|
|
308
308
|
|
|
309
|
+
export function classifyScanFile(file, root = process.cwd()) {
|
|
310
|
+
if (isMcpConfigFile(file, root)) return 'mcp-config';
|
|
311
|
+
if (isAgentInstructionFile(file, root)) return 'agent-context';
|
|
312
|
+
if (workflowExtensions.has(path.extname(file))) return 'github-workflow';
|
|
313
|
+
return 'other';
|
|
314
|
+
}
|
|
315
|
+
|
|
309
316
|
function scanFile(file, root, config) {
|
|
310
317
|
const text = fs.readFileSync(file, 'utf8');
|
|
311
318
|
if (isAgentInstructionFile(file, root)) {
|
|
@@ -831,6 +838,21 @@ function discoverAgentInstructionFiles(root) {
|
|
|
831
838
|
files.push(...walk(githubInstructionsDir).filter((file) => file.endsWith('.instructions.md')));
|
|
832
839
|
}
|
|
833
840
|
|
|
841
|
+
const githubAgentsDir = path.join(root, '.github', 'agents');
|
|
842
|
+
if (fs.existsSync(githubAgentsDir)) {
|
|
843
|
+
files.push(...walk(githubAgentsDir).filter((file) => file.endsWith('.md')));
|
|
844
|
+
}
|
|
845
|
+
|
|
846
|
+
const githubPromptsDir = path.join(root, '.github', 'prompts');
|
|
847
|
+
if (fs.existsSync(githubPromptsDir)) {
|
|
848
|
+
files.push(...walk(githubPromptsDir).filter((file) => file.endsWith('.prompt.md')));
|
|
849
|
+
}
|
|
850
|
+
|
|
851
|
+
const githubSkillsDir = path.join(root, '.github', 'skills');
|
|
852
|
+
if (fs.existsSync(githubSkillsDir)) {
|
|
853
|
+
files.push(...walk(githubSkillsDir).filter((file) => path.basename(file).toLowerCase() === 'skill.md'));
|
|
854
|
+
}
|
|
855
|
+
|
|
834
856
|
const cursorRulesDir = path.join(root, '.cursor', 'rules');
|
|
835
857
|
if (fs.existsSync(cursorRulesDir)) {
|
|
836
858
|
files.push(...walk(cursorRulesDir).filter((file) => ['.md', '.mdc', '.txt'].includes(path.extname(file))));
|
|
@@ -859,6 +881,12 @@ function isAgentInstructionFile(file, root) {
|
|
|
859
881
|
/\/\.github\/copilot-instructions\.md$/i.test(normalizedFile) ||
|
|
860
882
|
/^\.github\/instructions\/.+\.instructions\.md$/i.test(relativeFile) ||
|
|
861
883
|
/\/\.github\/instructions\/.+\.instructions\.md$/i.test(normalizedFile) ||
|
|
884
|
+
/^\.github\/agents\/.+\.md$/i.test(relativeFile) ||
|
|
885
|
+
/\/\.github\/agents\/.+\.md$/i.test(normalizedFile) ||
|
|
886
|
+
/^\.github\/prompts\/.+\.prompt\.md$/i.test(relativeFile) ||
|
|
887
|
+
/\/\.github\/prompts\/.+\.prompt\.md$/i.test(normalizedFile) ||
|
|
888
|
+
/^\.github\/skills\/.+\/skill\.md$/i.test(relativeFile) ||
|
|
889
|
+
/\/\.github\/skills\/.+\/skill\.md$/i.test(normalizedFile) ||
|
|
862
890
|
/^\.cursor\/rules\/.+\.(?:md|mdc|txt)$/i.test(relativeFile) ||
|
|
863
891
|
/\/\.cursor\/rules\/.+\.(?:md|mdc|txt)$/i.test(normalizedFile)
|
|
864
892
|
);
|