awguard 1.1.1 → 1.4.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 +18 -0
- package/README.md +68 -3
- package/action.yml +4 -4
- package/docs/awguard-badge.json +7 -0
- package/docs/launch-plan.md +24 -0
- package/docs/market-analysis.md +69 -0
- package/examples/.github/copilot-instructions.md +4 -0
- package/examples/.mcp.json +15 -0
- package/examples/README.md +6 -0
- package/package.json +5 -3
- package/src/cli.js +8 -3
- package/src/graph.js +18 -4
- package/src/migration.js +60 -6
- package/src/presets.js +6 -3
- package/src/remediation.js +78 -11
- package/src/reporters.js +15 -6
- package/src/scanner.js +601 -8
- package/src/score.js +125 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,23 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.4.0
|
|
4
|
+
|
|
5
|
+
- Add `AWG013` for project MCP configs that start mutable packages, unpinned containers, or shell wrappers.
|
|
6
|
+
- Add `AWG014` for MCP configs that hardcode tokens, API keys, passwords, or authorization headers.
|
|
7
|
+
- Scan `.mcp.json`, `.vscode/mcp.json`, `.cursor/mcp.json`, Windsurf, Cline, Roo, and related MCP config files without executing configured servers.
|
|
8
|
+
|
|
9
|
+
## 1.3.0
|
|
10
|
+
|
|
11
|
+
- Add `AWG012` for risky persistent agent instruction files.
|
|
12
|
+
- Scan `AGENTS.md`, `CLAUDE.md`, `CODEX.md`, `GEMINI.md`, Copilot instructions, Cursor rules, and related instruction files.
|
|
13
|
+
- Flag instructions that bypass approvals, treat untrusted GitHub text as commands, or expose secrets.
|
|
14
|
+
|
|
15
|
+
## 1.2.0
|
|
16
|
+
|
|
17
|
+
- Add `--format score` for an Agentic Workflow Injection scorecard.
|
|
18
|
+
- Add `--format badge` for Shields.io endpoint badge JSON.
|
|
19
|
+
- Add a checked-in AWI risk badge for the project README.
|
|
20
|
+
|
|
3
21
|
## 1.1.1
|
|
4
22
|
|
|
5
23
|
- Add npm package metadata for the public `awguard` package.
|
package/README.md
CHANGED
|
@@ -3,11 +3,13 @@
|
|
|
3
3
|
[](https://github.com/Mughal-Baig/agentic-workflow-guard/actions/workflows/test.yml)
|
|
4
4
|
[](https://github.com/Mughal-Baig/agentic-workflow-guard/actions/workflows/code-scanning.yml)
|
|
5
5
|
[](https://github.com/Mughal-Baig/agentic-workflow-guard/releases)
|
|
6
|
+
[](https://www.npmjs.com/package/awguard)
|
|
7
|
+
[](docs/awguard-badge.json)
|
|
6
8
|
[](LICENSE)
|
|
7
9
|
|
|
8
|
-
`agentic-workflow-guard` is a small, zero-dependency scanner for GitHub Actions workflows
|
|
10
|
+
`agentic-workflow-guard` is a small, zero-dependency scanner for GitHub Actions workflows, persistent agent instruction files, and MCP configs used by AI coding agents, LLMs, or automated review bots.
|
|
9
11
|
|
|
10
|
-
It looks for a new class of CI/CD risk: untrusted issue, pull request, comment, or branch text flowing into an AI agent prompt, then into write-capable tools, secrets, or
|
|
12
|
+
It looks for a new class of CI/CD risk: untrusted issue, pull request, comment, or branch text flowing into an AI agent prompt, then into write-capable tools, secrets, shell scripts, persistent instructions that weaken review boundaries, or MCP servers that expand agent authority.
|
|
11
13
|
|
|
12
14
|
Its unique output is an **Agentic Workflow Injection attack graph**:
|
|
13
15
|
|
|
@@ -111,7 +113,7 @@ jobs:
|
|
|
111
113
|
## CLI
|
|
112
114
|
|
|
113
115
|
```bash
|
|
114
|
-
awguard [path] [--config file] [--preset name] [--format text|json|markdown|github|sarif|graph|html|migration] [--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] [--output file] [--baseline file] [--write-baseline file] [--fix-dry-run] [--fail-on none|low|medium|high|critical]
|
|
115
117
|
```
|
|
116
118
|
|
|
117
119
|
Examples:
|
|
@@ -122,6 +124,8 @@ node ./bin/awguard.js . --config awguard.config.json
|
|
|
122
124
|
node ./bin/awguard.js . --preset strict --format graph
|
|
123
125
|
node ./bin/awguard.js . --format html --output awguard-report.html
|
|
124
126
|
node ./bin/awguard.js . --format migration --output awguard-migration.md
|
|
127
|
+
node ./bin/awguard.js . --format score
|
|
128
|
+
node ./bin/awguard.js . --format badge --output awguard-badge.json
|
|
125
129
|
node ./bin/awguard.js . --fix-dry-run
|
|
126
130
|
node ./bin/awguard.js . --format markdown --fail-on medium
|
|
127
131
|
node ./bin/awguard.js . --format sarif --output awguard.sarif --fail-on none
|
|
@@ -215,6 +219,61 @@ untrusted GitHub event text
|
|
|
215
219
|
-> safe outputs or approved apply job
|
|
216
220
|
```
|
|
217
221
|
|
|
222
|
+
## AWI Score And Badge
|
|
223
|
+
|
|
224
|
+
Generate a shareable Agentic Workflow Injection scorecard:
|
|
225
|
+
|
|
226
|
+
```bash
|
|
227
|
+
node ./bin/awguard.js . --format score
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
Generate a Shields.io endpoint badge JSON:
|
|
231
|
+
|
|
232
|
+
```bash
|
|
233
|
+
node ./bin/awguard.js . --format badge --output docs/awguard-badge.json
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
Then add a badge to your README:
|
|
237
|
+
|
|
238
|
+
```markdown
|
|
239
|
+
[](docs/awguard-badge.json)
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
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
|
+
## Agent Context Guard
|
|
245
|
+
|
|
246
|
+
AWGuard also scans persistent agent instruction files:
|
|
247
|
+
|
|
248
|
+
- `AGENTS.md`
|
|
249
|
+
- `CLAUDE.md`
|
|
250
|
+
- `CODEX.md`
|
|
251
|
+
- `GEMINI.md`
|
|
252
|
+
- `.github/copilot-instructions.md`
|
|
253
|
+
- `.github/instructions/*.instructions.md`
|
|
254
|
+
- `.cursor/rules/*.{md,mdc,txt}`
|
|
255
|
+
- `.cursorrules`, `.windsurfrules`, and `.clinerules`
|
|
256
|
+
|
|
257
|
+
It flags instruction files that tell agents to bypass approvals, skip permission prompts, obey issue or PR text as commands, or expose secrets.
|
|
258
|
+
|
|
259
|
+
## MCP Trust Boundary Guard
|
|
260
|
+
|
|
261
|
+
AWGuard also scans project-scoped MCP config files without starting the configured servers:
|
|
262
|
+
|
|
263
|
+
- `.mcp.json`
|
|
264
|
+
- `mcp.json`
|
|
265
|
+
- `.vscode/mcp.json`
|
|
266
|
+
- `.cursor/mcp.json`
|
|
267
|
+
- `.windsurf/mcp_config.json`
|
|
268
|
+
- `.codeium/windsurf/mcp_config.json`
|
|
269
|
+
- `cline_mcp_settings.json`
|
|
270
|
+
- `.cline/mcp_settings.json`
|
|
271
|
+
- `.roo/mcp.json`
|
|
272
|
+
- `.kilocode/mcp.json`
|
|
273
|
+
- `claude_desktop_config.json`
|
|
274
|
+
|
|
275
|
+
It flags MCP configs that start mutable packages such as `npx package`, `uvx package@latest`, or unpinned Docker images, and configs that commit tokens, API keys, passwords, or authorization headers.
|
|
276
|
+
|
|
218
277
|
## Fix Dry Run
|
|
219
278
|
|
|
220
279
|
Print remediation guidance without editing files:
|
|
@@ -251,6 +310,9 @@ If you omit rule ids, the suppression applies to all findings on the target line
|
|
|
251
310
|
| AWG009 | Medium | `workflow_run` consuming artifacts before scripts |
|
|
252
311
|
| AWG010 | Low | Third-party actions in agent workflows not pinned to a SHA |
|
|
253
312
|
| AWG011 | Medium | Invalid suppression comments |
|
|
313
|
+
| AWG012 | High/Critical | Agent instruction files that weaken approval, permission, or secret boundaries |
|
|
314
|
+
| AWG013 | High | MCP configs that start mutable packages, unpinned containers, or shell wrappers |
|
|
315
|
+
| AWG014 | Critical | MCP configs that hardcode secrets, tokens, passwords, or auth headers |
|
|
254
316
|
|
|
255
317
|
## Example Finding
|
|
256
318
|
|
|
@@ -265,6 +327,9 @@ If you omit rule ids, the suppression applies to all findings on the target line
|
|
|
265
327
|
|
|
266
328
|
- Safe autofix for low-risk permission changes.
|
|
267
329
|
- Safe-output migration patch previews for common triage and review bots.
|
|
330
|
+
- Hosted AWI score API for dynamic cross-repository badges.
|
|
331
|
+
- Agent instruction file rule packs for Copilot, Claude Code, Codex, Gemini, Cursor, and Windsurf.
|
|
332
|
+
- MCP config rule packs for Claude Code, Copilot, VS Code, Cursor, Windsurf, Cline, and Roo.
|
|
268
333
|
- GitHub App integration for always-on repository monitoring.
|
|
269
334
|
- Rule packs for Claude Code, Codex, Gemini, Copilot, Aider, and custom agents.
|
|
270
335
|
- Public vulnerable workflow lab with attack and fix walkthroughs.
|
package/action.yml
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
name: Agentic Workflow Guard
|
|
2
|
-
description: Scan GitHub Actions workflows for AI-agent injection and unsafe
|
|
2
|
+
description: Scan GitHub Actions workflows, agent instruction files, and MCP configs for AI-agent injection and unsafe tool boundaries.
|
|
3
3
|
author: agentic-workflow-guard
|
|
4
4
|
inputs:
|
|
5
5
|
path:
|
|
6
|
-
description: Repository path or
|
|
6
|
+
description: Repository path, workflow file, agent instruction file, or MCP config file to scan.
|
|
7
7
|
required: false
|
|
8
8
|
default: .
|
|
9
9
|
format:
|
|
10
|
-
description: Output format: github, text, json, markdown, sarif, graph, html, or
|
|
10
|
+
description: Output format: github, text, json, markdown, sarif, graph, html, migration, score, or badge.
|
|
11
11
|
required: false
|
|
12
12
|
default: github
|
|
13
13
|
fail-on:
|
|
@@ -15,7 +15,7 @@ inputs:
|
|
|
15
15
|
required: false
|
|
16
16
|
default: high
|
|
17
17
|
output:
|
|
18
|
-
description: Optional file path for json, markdown, sarif, graph, html, or
|
|
18
|
+
description: Optional file path for json, markdown, sarif, graph, html, migration, score, or badge output.
|
|
19
19
|
required: false
|
|
20
20
|
default: ''
|
|
21
21
|
baseline:
|
package/docs/launch-plan.md
CHANGED
|
@@ -32,6 +32,27 @@ Short pitch:
|
|
|
32
32
|
```
|
|
33
33
|
|
|
34
34
|
7. Show the migration from unsafe agent job to read-only proposal job plus safe outputs or an approved apply job.
|
|
35
|
+
8. Run:
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
node ./bin/awguard.js . --format score
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
9. Show the README badge and say: "Add an AWI risk badge to your repo before adding AI agents to CI."
|
|
42
|
+
10. Show an unsafe `AGENTS.md` or `.github/copilot-instructions.md` line and run:
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
node ./bin/awguard.js . --format text
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
11. Explain that AWGuard scans both the workflow and the persistent agent instructions that shape agent behavior.
|
|
49
|
+
12. Show an unsafe `.mcp.json` with `npx @modelcontextprotocol/server-github` and a committed token, then run:
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
node ./bin/awguard.js examples/.mcp.json --format text
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
13. Explain the new hook: "This scanner checks repo-provided MCP tool wiring without executing the MCP server."
|
|
35
56
|
|
|
36
57
|
## Release Checklist
|
|
37
58
|
|
|
@@ -42,6 +63,9 @@ Short pitch:
|
|
|
42
63
|
- Post with the headline: "I built a scanner that maps and migrates Agentic Workflow Injection in GitHub Actions."
|
|
43
64
|
- Include the AWI attack chain screenshot in social posts.
|
|
44
65
|
- Include the migration report screenshot after the graph screenshot.
|
|
66
|
+
- Include the AWI risk badge as the final screenshot because it is the easiest artifact for other maintainers to copy.
|
|
67
|
+
- Include a short "workflow looked safe, AGENTS.md made it unsafe" example because it is the most surprising hook.
|
|
68
|
+
- Include a short "MCP config looked like developer tooling, but it gave the agent a mutable tool server and a token" example because MCP is the hottest adjacent security topic.
|
|
45
69
|
|
|
46
70
|
## Distribution Targets
|
|
47
71
|
|
package/docs/market-analysis.md
CHANGED
|
@@ -139,6 +139,64 @@ The unscoped npm package name `agentic-workflow-guard` is already published by a
|
|
|
139
139
|
|
|
140
140
|
The v1.1 package target is now `awguard`, matching the existing CLI binary and leaving the GitHub Action name unchanged.
|
|
141
141
|
|
|
142
|
+
## Deep Research Refresh: Badge And Scorecard Hook
|
|
143
|
+
|
|
144
|
+
The next reach improvement is a shareable AWI score and README badge. OpenSSF Scorecard has more than 5,000 GitHub stars and explicitly uses badges as a way for maintainers to show security posture. Shields.io is the common README-badge layer across GitHub projects. That pattern matters because a scanner hidden in CI logs does not travel; a badge travels with every repository that adopts it.
|
|
145
|
+
|
|
146
|
+
Recent GitHub and web research suggests this gap is still open:
|
|
147
|
+
|
|
148
|
+
- General GitHub Actions tools such as `zizmorcore/zizmor` and `rhysd/actionlint` are established, but they are not focused on Agentic Workflow Injection scoring.
|
|
149
|
+
- Broad AI-agent scanners such as AgentShield and agentic-radar cover MCP, skills, and agent configuration, but they do not own a GitHub Actions AWI score badge.
|
|
150
|
+
- GitHub search for `agentic workflow injection` shows only small early projects, which means the term is still young enough for a focused tool to become the reference implementation.
|
|
151
|
+
- Shields.io endpoint badges are easy to adopt because they only need a small JSON document.
|
|
152
|
+
|
|
153
|
+
Agentic Workflow Guard now supports:
|
|
154
|
+
|
|
155
|
+
- `--format score` for a Markdown AWI scorecard.
|
|
156
|
+
- `--format badge` for Shields.io endpoint JSON.
|
|
157
|
+
- A checked-in project badge at `docs/awguard-badge.json`.
|
|
158
|
+
|
|
159
|
+
The scorecard is intentionally simple: start at 100, subtract weighted penalties for critical, high, medium, and low findings, then show an A-F grade. This gives maintainers a quick public signal while keeping SARIF, attack graphs, and migration reports available for detailed review.
|
|
160
|
+
|
|
161
|
+
## Deep Research Refresh: Agent Context Guard
|
|
162
|
+
|
|
163
|
+
The next uniqueness gap is persistent agent instruction files. GitHub documents repository custom instructions through `.github/copilot-instructions.md`, OpenAI Codex documents `AGENTS.md`, and the public `agents.md` project positions `AGENTS.md` as a predictable place for coding-agent guidance. Claude Code also documents permission modes and warns that bypassing permissions is a special, dangerous mode.
|
|
164
|
+
|
|
165
|
+
That means agent safety is no longer only in workflow YAML. A repository can have conservative GitHub Actions permissions while `AGENTS.md`, `CLAUDE.md`, `GEMINI.md`, Cursor rules, or Copilot instructions tell the agent to skip approval, obey issue comments, or expose credentials. General GitHub Actions linters do not inspect these files, and broad AI security scanners do not own the GitHub Actions plus persistent-instructions intersection.
|
|
166
|
+
|
|
167
|
+
Agentic Workflow Guard now supports `AWG012` to scan common instruction files for risky persistent guidance:
|
|
168
|
+
|
|
169
|
+
- bypassing approvals, confirmations, or permission prompts;
|
|
170
|
+
- treating issue, PR, comment, branch, or artifact text as commands;
|
|
171
|
+
- allowing secrets, tokens, API keys, or credentials to be printed, returned, or sent.
|
|
172
|
+
|
|
173
|
+
This strengthens the project's position as an Agentic Workflow Injection guardrail instead of only another YAML scanner.
|
|
174
|
+
|
|
175
|
+
## Deep Research Refresh: MCP Trust Boundary Guard
|
|
176
|
+
|
|
177
|
+
The next gap is project-scoped MCP configuration. Claude Code documents `.mcp.json` as a project-root file designed to be checked into version control, VS Code documents workspace MCP config at `.vscode/mcp.json`, and GitHub Copilot documents MCP servers as additional tools for CLI agents. That means a repository can now ship the tool wiring an agent will trust, not only the workflow that starts the agent.
|
|
178
|
+
|
|
179
|
+
GitHub and web research show that MCP security is popular but crowded at the live-server/tool-description layer:
|
|
180
|
+
|
|
181
|
+
- `snyk/agent-scan` has roughly 2.5k GitHub stars and scans MCP servers, tools, resources, and skills, but its README warns that scanning MCP configurations executes the configured commands.
|
|
182
|
+
- `cisco-ai-defense/mcp-scanner` has roughly 900+ GitHub stars and focuses on MCP server threats.
|
|
183
|
+
- Other GitHub search results for `mcp scanner` are smaller server scanners, while searches for `agentic workflow injection` still show only tiny early projects.
|
|
184
|
+
|
|
185
|
+
The opening for Agentic Workflow Guard is a zero-execution repository scan:
|
|
186
|
+
|
|
187
|
+
```text
|
|
188
|
+
checked-in MCP config -> mutable package or shell startup -> agent gains unexpected tools
|
|
189
|
+
checked-in MCP config -> committed token/header -> agent gains credentialed external access
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
Agentic Workflow Guard now supports:
|
|
193
|
+
|
|
194
|
+
- `AWG013` for project MCP configs that start mutable packages, `@latest` specs, unpinned containers, or shell wrappers.
|
|
195
|
+
- `AWG014` for committed tokens, API keys, passwords, bearer headers, and other MCP auth material.
|
|
196
|
+
- Discovery for `.mcp.json`, `.vscode/mcp.json`, `.cursor/mcp.json`, Windsurf, Cline, Roo, and related MCP config files.
|
|
197
|
+
|
|
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
|
+
|
|
142
200
|
## Distribution Plan
|
|
143
201
|
|
|
144
202
|
1. Publish the repo with a short demo GIF or screenshot.
|
|
@@ -155,6 +213,17 @@ The v1.1 package target is now `awguard`, matching the existing CLI binary and l
|
|
|
155
213
|
- GitHub SARIF upload docs: https://docs.github.com/en/code-security/how-tos/find-and-fix-code-vulnerabilities/integrate-with-existing-tools/uploading-a-sarif-file-to-github
|
|
156
214
|
- GitHub Actions secure use reference: https://docs.github.com/en/enterprise-cloud@latest/actions/reference/security/secure-use
|
|
157
215
|
- GitHub Agentic Workflows security architecture: https://github.github.com/gh-aw/
|
|
216
|
+
- GitHub Copilot repository custom instructions: https://docs.github.com/en/copilot/how-tos/custom-instructions/adding-repository-custom-instructions-for-github-copilot
|
|
217
|
+
- GitHub Copilot CLI MCP command reference: https://docs.github.com/en/copilot/reference/copilot-cli-reference/cli-command-reference#mcp-server-configuration
|
|
218
|
+
- GitHub Copilot MCP in VS Code: https://docs.github.com/en/copilot/how-tos/provide-context/use-mcp-in-your-ide/extend-copilot-chat-with-mcp
|
|
219
|
+
- VS Code MCP configuration reference: https://code.visualstudio.com/docs/copilot/reference/mcp-configuration
|
|
220
|
+
- Claude Code MCP documentation: https://code.claude.com/docs/en/mcp
|
|
221
|
+
- Snyk Agent Scan / MCP Scan: https://github.com/snyk/agent-scan
|
|
222
|
+
- Cisco AI Defense MCP Scanner: https://github.com/cisco-ai-defense/mcp-scanner
|
|
223
|
+
- MCP threat modeling and tool poisoning research: https://arxiv.org/abs/2603.22489
|
|
224
|
+
- OpenAI Codex AGENTS.md documentation: https://github.com/openai/codex/blob/main/docs/agents_md.md
|
|
225
|
+
- AGENTS.md project: https://github.com/openai/agents.md
|
|
226
|
+
- Claude Code permission modes: https://code.claude.com/docs/en/permission-modes
|
|
158
227
|
- OpenSSF Scorecard dangerous workflow check: https://github.com/ossf/scorecard/blob/main/docs/checks.md#dangerous-workflow
|
|
159
228
|
- Semgrep inline ignore docs: https://semgrep.dev/docs/ignoring-files-folders-code
|
|
160
229
|
- ESLint configuration comment descriptions: https://eslint.org/docs/latest/use/configure/rules#configuration-comment-descriptions
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
{
|
|
2
|
+
"mcpServers": {
|
|
3
|
+
"github": {
|
|
4
|
+
"command": "npx",
|
|
5
|
+
"args": ["-y", "@modelcontextprotocol/server-github"],
|
|
6
|
+
"env": {
|
|
7
|
+
"GITHUB_TOKEN": "ghp_exampletokenexampletokenexampletoken"
|
|
8
|
+
}
|
|
9
|
+
},
|
|
10
|
+
"browser": {
|
|
11
|
+
"command": "docker",
|
|
12
|
+
"args": ["run", "--rm", "example/mcp-browser:latest"]
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
}
|
package/examples/README.md
CHANGED
|
@@ -4,6 +4,8 @@
|
|
|
4
4
|
- `safe-agent.yml`: quieter workflow with read-only permissions and bounded prompt file.
|
|
5
5
|
- `suppressed-agent.yml`: demonstrates audited inline suppressions.
|
|
6
6
|
- `pull-request-target.yml`: demonstrates privileged PR checkout risk.
|
|
7
|
+
- `.github/copilot-instructions.md`: demonstrates risky persistent agent instruction guidance.
|
|
8
|
+
- `.mcp.json`: demonstrates mutable MCP server packages and committed MCP credentials.
|
|
7
9
|
- `awguard.config.example.json`: sample config with a strict preset and overrides.
|
|
8
10
|
|
|
9
11
|
Try:
|
|
@@ -12,5 +14,9 @@ Try:
|
|
|
12
14
|
node ../bin/awguard.js unsafe-agent.yml --format graph
|
|
13
15
|
node ../bin/awguard.js unsafe-agent.yml --format html --output awguard-report.html
|
|
14
16
|
node ../bin/awguard.js unsafe-agent.yml --format migration
|
|
17
|
+
node ../bin/awguard.js unsafe-agent.yml --format score
|
|
18
|
+
node ../bin/awguard.js safe-agent.yml --format badge
|
|
19
|
+
node ../bin/awguard.js .mcp.json --format text
|
|
20
|
+
node ../bin/awguard.js . --format text
|
|
15
21
|
node ../bin/awguard.js unsafe-agent.yml --fix-dry-run
|
|
16
22
|
```
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "awguard",
|
|
3
|
-
"version": "1.
|
|
4
|
-
"description": "Scan GitHub Actions workflows for AI-agent injection
|
|
3
|
+
"version": "1.4.0",
|
|
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",
|
|
7
7
|
"bugs": {
|
|
@@ -27,7 +27,9 @@
|
|
|
27
27
|
"devsecops",
|
|
28
28
|
"llm",
|
|
29
29
|
"agentic-workflow-injection",
|
|
30
|
-
"safe-outputs"
|
|
30
|
+
"safe-outputs",
|
|
31
|
+
"mcp",
|
|
32
|
+
"model-context-protocol"
|
|
31
33
|
],
|
|
32
34
|
"license": "MIT",
|
|
33
35
|
"engines": {
|
package/src/cli.js
CHANGED
|
@@ -5,20 +5,23 @@ 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 { renderGithubAnnotations, renderGraph, renderHtml, renderJson, renderMarkdown, renderMigration, renderSarif, renderText } from './reporters.js';
|
|
8
|
+
import { renderBadge, renderGithubAnnotations, renderGraph, renderHtml, renderJson, renderMarkdown, renderMigration, renderSarif, renderScore, renderText } from './reporters.js';
|
|
9
9
|
|
|
10
10
|
const HELP = `Agentic Workflow Guard
|
|
11
11
|
|
|
12
12
|
Usage:
|
|
13
|
-
awguard [path] [--config file] [--preset name] [--format text|json|markdown|github|sarif|graph|html|migration] [--output file] [--baseline file] [--write-baseline file] [--fix-dry-run] [--fail-on none|low|medium|high|critical]
|
|
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]
|
|
14
14
|
|
|
15
15
|
Examples:
|
|
16
16
|
awguard .
|
|
17
|
+
awguard .mcp.json
|
|
17
18
|
awguard . --config awguard.config.json
|
|
18
19
|
awguard . --preset strict --format graph
|
|
19
20
|
awguard .github/workflows/agent.yml --format markdown --fail-on high
|
|
20
21
|
awguard . --format html --output awguard-report.html
|
|
21
22
|
awguard . --format migration --output awguard-migration.md
|
|
23
|
+
awguard . --format score
|
|
24
|
+
awguard . --format badge --output awguard-badge.json
|
|
22
25
|
awguard . --fix-dry-run
|
|
23
26
|
awguard . --format sarif --output awguard.sarif --fail-on none
|
|
24
27
|
awguard . --write-baseline awguard.baseline.json
|
|
@@ -118,7 +121,7 @@ export function parseArgs(args, env = {}) {
|
|
|
118
121
|
}
|
|
119
122
|
}
|
|
120
123
|
|
|
121
|
-
validateEnum('format', options.format, ['text', 'json', 'markdown', 'github', 'sarif', 'graph', 'html', 'migration']);
|
|
124
|
+
validateEnum('format', options.format, ['text', 'json', 'markdown', 'github', 'sarif', 'graph', 'html', 'migration', 'score', 'badge']);
|
|
122
125
|
validateEnum('fail-on', options.failOn, ['none', 'low', 'medium', 'high', 'critical']);
|
|
123
126
|
|
|
124
127
|
return options;
|
|
@@ -136,6 +139,8 @@ function render(result, format) {
|
|
|
136
139
|
if (format === 'graph') return renderGraph(result);
|
|
137
140
|
if (format === 'html') return renderHtml(result);
|
|
138
141
|
if (format === 'migration') return renderMigration(result);
|
|
142
|
+
if (format === 'score') return renderScore(result);
|
|
143
|
+
if (format === 'badge') return renderBadge(result);
|
|
139
144
|
if (format === 'github') return renderGithubAnnotations(result);
|
|
140
145
|
return renderText(result);
|
|
141
146
|
}
|
package/src/graph.js
CHANGED
|
@@ -11,7 +11,10 @@ const impactByRule = {
|
|
|
11
11
|
AWG008: 'Default token permissions may be broader than intended',
|
|
12
12
|
AWG009: 'Untrusted artifacts can influence privileged jobs',
|
|
13
13
|
AWG010: 'Mutable third-party action can change behavior',
|
|
14
|
-
AWG011: 'Suppression policy can hide real risk'
|
|
14
|
+
AWG011: 'Suppression policy can hide real risk',
|
|
15
|
+
AWG012: 'Persistent agent instructions can weaken CI guardrails',
|
|
16
|
+
AWG013: 'Mutable MCP tool server can change agent capabilities',
|
|
17
|
+
AWG014: 'Committed MCP credential can expose external tools or data'
|
|
15
18
|
};
|
|
16
19
|
|
|
17
20
|
export function buildAttackGraphs(result) {
|
|
@@ -42,7 +45,7 @@ export function renderGraphMarkdown(result) {
|
|
|
42
45
|
const lines = [
|
|
43
46
|
'# Agentic Workflow Guard Attack Graph',
|
|
44
47
|
'',
|
|
45
|
-
`Scanned
|
|
48
|
+
`Scanned files: **${result.scannedFiles.length}**`,
|
|
46
49
|
`Findings: **${result.summary.total}**`,
|
|
47
50
|
`Attack chains: **${attackGraph.summary.chains}**`,
|
|
48
51
|
''
|
|
@@ -159,11 +162,11 @@ export function renderHtmlReport(result) {
|
|
|
159
162
|
<body>
|
|
160
163
|
<header>
|
|
161
164
|
<h1>Agentic Workflow Guard</h1>
|
|
162
|
-
<p class="subtitle">Attack graph report for AI-agent
|
|
165
|
+
<p class="subtitle">Attack graph report for AI-agent workflows, instruction files, and MCP configs. It maps untrusted context and tool wiring to agent capabilities, permissions, and possible impact.</p>
|
|
163
166
|
</header>
|
|
164
167
|
<main>
|
|
165
168
|
<section class="metrics">
|
|
166
|
-
<div class="metric"><span>
|
|
169
|
+
<div class="metric"><span>Scanned files</span><strong>${result.scannedFiles.length}</strong></div>
|
|
167
170
|
<div class="metric"><span>Findings</span><strong>${result.summary.total}</strong></div>
|
|
168
171
|
<div class="metric"><span>Highest severity</span><strong>${escapeHtml(result.summary.highest)}</strong></div>
|
|
169
172
|
<div class="metric"><span>Attack chains</span><strong>${attackGraph.summary.chains}</strong></div>
|
|
@@ -212,6 +215,9 @@ function inferSource(finding) {
|
|
|
212
215
|
if (match) return `GitHub event field: ${match[1] || match[2] || 'github context'}`;
|
|
213
216
|
if (finding.ruleId === 'AWG009') return 'workflow_run artifact';
|
|
214
217
|
if (finding.ruleId === 'AWG010') return 'third-party action ref';
|
|
218
|
+
if (finding.ruleId === 'AWG012') return 'persistent agent instruction file';
|
|
219
|
+
if (finding.ruleId === 'AWG013') return 'project-scoped MCP server config';
|
|
220
|
+
if (finding.ruleId === 'AWG014') return 'committed MCP credential material';
|
|
215
221
|
return 'workflow configuration';
|
|
216
222
|
}
|
|
217
223
|
|
|
@@ -220,6 +226,8 @@ function inferBoundary(finding) {
|
|
|
220
226
|
if (finding.ruleId === 'AWG002') return 'run script interpolation';
|
|
221
227
|
if (finding.ruleId === 'AWG003') return 'checkout of untrusted PR code';
|
|
222
228
|
if (finding.ruleId === 'AWG007') return 'command execution sink';
|
|
229
|
+
if (finding.ruleId === 'AWG012') return 'agent instruction context';
|
|
230
|
+
if (finding.ruleId === 'AWG013' || finding.ruleId === 'AWG014') return 'MCP tool boundary';
|
|
223
231
|
return 'workflow execution';
|
|
224
232
|
}
|
|
225
233
|
|
|
@@ -227,6 +235,9 @@ function inferCapability(finding) {
|
|
|
227
235
|
if (finding.ruleId === 'AWG006') return 'autonomous agent tool use';
|
|
228
236
|
if (finding.ruleId === 'AWG007' || finding.ruleId === 'AWG002') return 'shell command execution';
|
|
229
237
|
if (finding.ruleId === 'AWG010') return 'third-party action execution';
|
|
238
|
+
if (finding.ruleId === 'AWG012') return 'persistent prompt steering';
|
|
239
|
+
if (finding.ruleId === 'AWG013') return 'MCP server startup';
|
|
240
|
+
if (finding.ruleId === 'AWG014') return 'credentialed MCP tool access';
|
|
230
241
|
return 'CI runner and agent tools';
|
|
231
242
|
}
|
|
232
243
|
|
|
@@ -234,6 +245,9 @@ function inferAuthority(finding) {
|
|
|
234
245
|
if (finding.ruleId === 'AWG004') return 'write-capable token';
|
|
235
246
|
if (finding.ruleId === 'AWG005') return 'secret environment values';
|
|
236
247
|
if (finding.ruleId === 'AWG008') return 'implicit token permissions';
|
|
248
|
+
if (finding.ruleId === 'AWG012') return 'agent policy context';
|
|
249
|
+
if (finding.ruleId === 'AWG013') return 'developer machine or CI tool process';
|
|
250
|
+
if (finding.ruleId === 'AWG014') return 'MCP server secrets';
|
|
237
251
|
return 'workflow permissions';
|
|
238
252
|
}
|
|
239
253
|
|
package/src/migration.js
CHANGED
|
@@ -48,6 +48,21 @@ const ruleActions = {
|
|
|
48
48
|
'Pin third-party actions to full commit SHAs in agent workflows.',
|
|
49
49
|
'Review action updates before changing pins.',
|
|
50
50
|
'Prefer official or internally reviewed actions for privileged jobs.'
|
|
51
|
+
],
|
|
52
|
+
AWG012: [
|
|
53
|
+
'Remove persistent instructions that tell agents to bypass approvals, confirmations, or permission checks.',
|
|
54
|
+
'Tell agents to treat issue, PR, comment, branch, and artifact text as untrusted data instead of commands.',
|
|
55
|
+
'Keep AGENTS.md, CLAUDE.md, GEMINI.md, Copilot instructions, and Cursor rules aligned with the workflow permission model.'
|
|
56
|
+
],
|
|
57
|
+
AWG013: [
|
|
58
|
+
'Pin project-scoped MCP server packages to exact versions or container digests.',
|
|
59
|
+
'Replace shell-wrapper MCP startup commands with direct executable and argument arrays.',
|
|
60
|
+
'Review MCP server packages before letting agents use them in CI or shared developer workspaces.'
|
|
61
|
+
],
|
|
62
|
+
AWG014: [
|
|
63
|
+
'Remove committed MCP tokens, API keys, passwords, and auth headers.',
|
|
64
|
+
'Use prompted inputs, environment variables, or managed secrets for MCP credentials.',
|
|
65
|
+
'Rotate credentials that were present in repository history.'
|
|
51
66
|
]
|
|
52
67
|
};
|
|
53
68
|
|
|
@@ -80,9 +95,9 @@ export function renderMigrationPlan(result) {
|
|
|
80
95
|
const lines = [
|
|
81
96
|
'# Agentic Workflow Guard Migration Plan',
|
|
82
97
|
'',
|
|
83
|
-
`Scanned
|
|
98
|
+
`Scanned files: **${plan.summary.scannedFiles}**`,
|
|
84
99
|
`Findings to migrate: **${plan.summary.findings}**`,
|
|
85
|
-
`Affected
|
|
100
|
+
`Affected files: **${plan.summary.files}**`,
|
|
86
101
|
`Highest severity: **${plan.summary.highest}**`,
|
|
87
102
|
'',
|
|
88
103
|
'Goal: move from agent jobs that can read untrusted GitHub text and directly act, to a two-stage pattern where the agent proposes structured output and a trusted layer validates what can happen next.',
|
|
@@ -134,8 +149,9 @@ export function renderMigrationPlan(result) {
|
|
|
134
149
|
lines.push('');
|
|
135
150
|
lines.push('Reference pattern:');
|
|
136
151
|
lines.push('');
|
|
137
|
-
|
|
138
|
-
lines.push(
|
|
152
|
+
const referencePattern = renderReferencePattern(filePlan);
|
|
153
|
+
lines.push(`\`\`\`${referencePattern.language}`);
|
|
154
|
+
lines.push(referencePattern.text);
|
|
139
155
|
lines.push('```');
|
|
140
156
|
lines.push('');
|
|
141
157
|
}
|
|
@@ -161,6 +177,9 @@ function riskShapeFor(findings) {
|
|
|
161
177
|
if ([...rules].some((rule) => writeRules.has(rule))) pieces.push('privileged write path exists');
|
|
162
178
|
if (rules.has('AWG005')) pieces.push('secrets are in scope');
|
|
163
179
|
if (rules.has('AWG010')) pieces.push('agent workflow depends on mutable third-party code');
|
|
180
|
+
if (rules.has('AWG012')) pieces.push('persistent agent instructions weaken review or permission boundaries');
|
|
181
|
+
if (rules.has('AWG013')) pieces.push('project MCP config can change agent tool capabilities through mutable startup');
|
|
182
|
+
if (rules.has('AWG014')) pieces.push('project MCP config contains committed credentials');
|
|
164
183
|
|
|
165
184
|
return pieces.length > 0 ? pieces.join('; ') : 'workflow hardening issue';
|
|
166
185
|
}
|
|
@@ -199,15 +218,49 @@ function allowedOperationsFor(findings) {
|
|
|
199
218
|
operations.add('metadata-only pull request updates after maintainer approval');
|
|
200
219
|
}
|
|
201
220
|
|
|
221
|
+
if (rules.has('AWG012')) {
|
|
222
|
+
operations.add('instruction-file update that explicitly treats GitHub event text as untrusted data');
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
if (rules.has('AWG013')) {
|
|
226
|
+
operations.add('MCP server startup only from pinned packages, reviewed local paths, or container digests');
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
if (rules.has('AWG014')) {
|
|
230
|
+
operations.add('MCP credentials supplied by prompt input, environment variable, or secret manager only');
|
|
231
|
+
}
|
|
232
|
+
|
|
202
233
|
operations.add('noop or missing-data report when validation fails');
|
|
203
234
|
return [...operations];
|
|
204
235
|
}
|
|
205
236
|
|
|
206
237
|
function renderReferencePattern(filePlan) {
|
|
238
|
+
if (filePlan.findings.every((finding) => ['AWG013', 'AWG014'].includes(finding.ruleId))) {
|
|
239
|
+
return {
|
|
240
|
+
language: 'json',
|
|
241
|
+
text: `{
|
|
242
|
+
"inputs": [{ "type": "promptString", "id": "github-token", "password": true }],
|
|
243
|
+
"mcpServers": {
|
|
244
|
+
"github": {
|
|
245
|
+
"command": "npx",
|
|
246
|
+
"args": ["-y", "@modelcontextprotocol/server-github@1.2.3"],
|
|
247
|
+
"env": { "GITHUB_TOKEN": "\${input:github-token}" }
|
|
248
|
+
},
|
|
249
|
+
"browser": {
|
|
250
|
+
"command": "docker",
|
|
251
|
+
"args": ["run", "--rm", "example/mcp-browser@sha256:..."]
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
}`
|
|
255
|
+
};
|
|
256
|
+
}
|
|
257
|
+
|
|
207
258
|
const needsApproval = filePlan.findings.some((finding) => writeRules.has(finding.ruleId));
|
|
208
259
|
const applyGate = needsApproval ? "if: github.event_name == 'workflow_dispatch'" : 'if: always()';
|
|
209
260
|
|
|
210
|
-
return
|
|
261
|
+
return {
|
|
262
|
+
language: 'yaml',
|
|
263
|
+
text: `permissions:
|
|
211
264
|
contents: read
|
|
212
265
|
|
|
213
266
|
jobs:
|
|
@@ -239,7 +292,8 @@ jobs:
|
|
|
239
292
|
- name: Validate structured proposal before applying
|
|
240
293
|
run: |
|
|
241
294
|
./scripts/validate-agent-proposal.js proposal.json
|
|
242
|
-
./scripts/apply-allowed-github-operation.js proposal.json
|
|
295
|
+
./scripts/apply-allowed-github-operation.js proposal.json`
|
|
296
|
+
};
|
|
243
297
|
}
|
|
244
298
|
|
|
245
299
|
function groupBy(values, keyFn) {
|
package/src/presets.js
CHANGED
|
@@ -7,7 +7,8 @@ export const presetCatalog = {
|
|
|
7
7
|
AWG005: 'critical',
|
|
8
8
|
AWG006: 'critical',
|
|
9
9
|
AWG008: 'high',
|
|
10
|
-
AWG010: 'medium'
|
|
10
|
+
AWG010: 'medium',
|
|
11
|
+
AWG013: 'critical'
|
|
11
12
|
},
|
|
12
13
|
suppressions: {
|
|
13
14
|
minimumReasonLength: 25
|
|
@@ -16,7 +17,8 @@ export const presetCatalog = {
|
|
|
16
17
|
'claude-code': {
|
|
17
18
|
rules: {
|
|
18
19
|
AWG001: 'critical',
|
|
19
|
-
AWG006: 'critical'
|
|
20
|
+
AWG006: 'critical',
|
|
21
|
+
AWG013: 'high'
|
|
20
22
|
},
|
|
21
23
|
suppressions: {
|
|
22
24
|
allowedRules: ['AWG001', 'AWG002', 'AWG008'],
|
|
@@ -27,7 +29,8 @@ export const presetCatalog = {
|
|
|
27
29
|
rules: {
|
|
28
30
|
AWG001: 'critical',
|
|
29
31
|
AWG002: 'critical',
|
|
30
|
-
AWG006: 'high'
|
|
32
|
+
AWG006: 'high',
|
|
33
|
+
AWG013: 'high'
|
|
31
34
|
},
|
|
32
35
|
suppressions: {
|
|
33
36
|
allowedRules: ['AWG001', 'AWG002', 'AWG008'],
|