@srhot/mcp-guard 0.1.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 +10 -0
- package/CONTRIBUTING.md +22 -0
- package/LICENSE +21 -0
- package/README.md +202 -0
- package/SECURITY.md +11 -0
- package/dist/cli.d.ts +7 -0
- package/dist/cli.js +73 -0
- package/dist/cli.js.map +1 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.js +5 -0
- package/dist/index.js.map +1 -0
- package/dist/policy/loadPolicy.d.ts +3 -0
- package/dist/policy/loadPolicy.js +67 -0
- package/dist/policy/loadPolicy.js.map +1 -0
- package/dist/policy/policyEngine.d.ts +3 -0
- package/dist/policy/policyEngine.js +30 -0
- package/dist/policy/policyEngine.js.map +1 -0
- package/dist/reporters/consoleReporter.d.ts +2 -0
- package/dist/reporters/consoleReporter.js +23 -0
- package/dist/reporters/consoleReporter.js.map +1 -0
- package/dist/reporters/jsonReporter.d.ts +2 -0
- package/dist/reporters/jsonReporter.js +4 -0
- package/dist/reporters/jsonReporter.js.map +1 -0
- package/dist/reporters/markdownReporter.d.ts +2 -0
- package/dist/reporters/markdownReporter.js +33 -0
- package/dist/reporters/markdownReporter.js.map +1 -0
- package/dist/rules/MCP01_SECRET_EXPOSURE.d.ts +2 -0
- package/dist/rules/MCP01_SECRET_EXPOSURE.js +48 -0
- package/dist/rules/MCP01_SECRET_EXPOSURE.js.map +1 -0
- package/dist/rules/MCP03_TOOL_POISONING.d.ts +2 -0
- package/dist/rules/MCP03_TOOL_POISONING.js +37 -0
- package/dist/rules/MCP03_TOOL_POISONING.js.map +1 -0
- package/dist/rules/MCP05_COMMAND_EXECUTION.d.ts +2 -0
- package/dist/rules/MCP05_COMMAND_EXECUTION.js +39 -0
- package/dist/rules/MCP05_COMMAND_EXECUTION.js.map +1 -0
- package/dist/rules/MCP07_INSUFFICIENT_AUTH.d.ts +2 -0
- package/dist/rules/MCP07_INSUFFICIENT_AUTH.js +63 -0
- package/dist/rules/MCP07_INSUFFICIENT_AUTH.js.map +1 -0
- package/dist/rules/MCP08_AUDIT_GAP.d.ts +2 -0
- package/dist/rules/MCP08_AUDIT_GAP.js +30 -0
- package/dist/rules/MCP08_AUDIT_GAP.js.map +1 -0
- package/dist/rules/MCP09_SHADOW_SERVER.d.ts +2 -0
- package/dist/rules/MCP09_SHADOW_SERVER.js +64 -0
- package/dist/rules/MCP09_SHADOW_SERVER.js.map +1 -0
- package/dist/rules/MCP10_CONTEXT_OVERSHARING.d.ts +2 -0
- package/dist/rules/MCP10_CONTEXT_OVERSHARING.js +39 -0
- package/dist/rules/MCP10_CONTEXT_OVERSHARING.js.map +1 -0
- package/dist/rules/index.d.ts +10 -0
- package/dist/rules/index.js +18 -0
- package/dist/rules/index.js.map +1 -0
- package/dist/scanner/fileLoader.d.ts +3 -0
- package/dist/scanner/fileLoader.js +48 -0
- package/dist/scanner/fileLoader.js.map +1 -0
- package/dist/scanner/scanTarget.d.ts +2 -0
- package/dist/scanner/scanTarget.js +21 -0
- package/dist/scanner/scanTarget.js.map +1 -0
- package/dist/types.d.ts +58 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/dist/utils/paths.d.ts +2 -0
- package/dist/utils/paths.js +8 -0
- package/dist/utils/paths.js.map +1 -0
- package/dist/utils/severity.d.ts +4 -0
- package/dist/utils/severity.js +20 -0
- package/dist/utils/severity.js.map +1 -0
- package/docs/architecture.md +11 -0
- package/docs/release.md +42 -0
- package/docs/roadmap.md +34 -0
- package/docs/rules.md +21 -0
- package/examples/bad-mcp-server/.env.example +4 -0
- package/examples/bad-mcp-server/mcp.json +11 -0
- package/examples/bad-mcp-server/server.ts +13 -0
- package/examples/bad-mcp-server/tools.json +17 -0
- package/examples/safe-mcp-server/mcp.json +13 -0
- package/examples/safe-mcp-server/server.ts +9 -0
- package/examples/safe-mcp-server/tools.json +17 -0
- package/mcpguard.config.yml +29 -0
- package/package.json +80 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 0.1.0
|
|
4
|
+
|
|
5
|
+
- Initial TypeScript CLI scanner.
|
|
6
|
+
- Added policy engine with `off`, `warn`, and `error` modes.
|
|
7
|
+
- Added seven initial MCP security rules.
|
|
8
|
+
- Added safe and bad local MCP examples.
|
|
9
|
+
- Added console, JSON, and Markdown reporters.
|
|
10
|
+
- Added Vitest coverage and GitHub Actions CI.
|
package/CONTRIBUTING.md
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# Contributing
|
|
2
|
+
|
|
3
|
+
Thanks for helping improve MCP-Guard.
|
|
4
|
+
|
|
5
|
+
## Development Setup
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install
|
|
9
|
+
npm test
|
|
10
|
+
npm run build
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Rule Contributions
|
|
14
|
+
|
|
15
|
+
Good v0.1 rules should be:
|
|
16
|
+
|
|
17
|
+
- Deterministic and locally testable.
|
|
18
|
+
- Focused on MCP security risks.
|
|
19
|
+
- Backed by safe synthetic examples.
|
|
20
|
+
- Configurable through the policy engine.
|
|
21
|
+
|
|
22
|
+
Avoid rules that call external services, require credentials, or need a live MCP server.
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 MCP-Guard contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
# MCP-Guard
|
|
2
|
+
|
|
3
|
+
Open-source security scanner and policy engine for Model Context Protocol server projects.
|
|
4
|
+
|
|
5
|
+
MCP-Guard helps detect tool poisoning, secret exposure, excessive permissions, risky command execution, missing audit controls, shadow MCP servers, and context over-sharing before an AI agent connects to them.
|
|
6
|
+
|
|
7
|
+
## Quick Start
|
|
8
|
+
|
|
9
|
+
Run MCP-Guard without installing it:
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
npx @srhot/mcp-guard scan .
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
Install it globally:
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
npm install -g @srhot/mcp-guard
|
|
19
|
+
mcp-guard scan .
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
Write a Markdown report:
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
npx @srhot/mcp-guard scan . --format markdown --output mcp-guard-report.md
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
Write a JSON report:
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
npx @srhot/mcp-guard scan . --format json --output mcp-guard-report.json
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
Expected behavior:
|
|
35
|
+
|
|
36
|
+
- Safe targets exit `0` and report no error-level findings.
|
|
37
|
+
- Bad targets exit `1` when error-level findings are present.
|
|
38
|
+
- Use `--no-fail` to print findings while exiting `0`, which is useful for demos and non-blocking CI checks.
|
|
39
|
+
|
|
40
|
+
MCP-Guard v0.1 is a static scanner and policy engine. It is not a runtime MCP firewall or proxy yet.
|
|
41
|
+
|
|
42
|
+
## What Can I Scan?
|
|
43
|
+
|
|
44
|
+
You can scan:
|
|
45
|
+
|
|
46
|
+
- MCP server repositories.
|
|
47
|
+
- Folders containing `mcp.json`.
|
|
48
|
+
- Folders containing `tools.json`.
|
|
49
|
+
- TypeScript or JavaScript MCP server implementations.
|
|
50
|
+
- Config folders used by MCP clients.
|
|
51
|
+
|
|
52
|
+
MCP-Guard v0.1 performs static scanning and policy checks. It does not yet inspect live MCP JSON-RPC traffic.
|
|
53
|
+
|
|
54
|
+
## Status
|
|
55
|
+
|
|
56
|
+
MCP-Guard v0.1 is a scanner, policy engine, and local test laboratory. It is not a runtime firewall or proxy yet.
|
|
57
|
+
|
|
58
|
+
## Why It Exists
|
|
59
|
+
|
|
60
|
+
MCP servers can expose tools, files, credentials, commands, and instructions to AI agents. That makes small configuration mistakes unusually powerful. MCP-Guard gives maintainers a fast local check for common MCP security risks before those servers are used by agents or committed to shared repositories.
|
|
61
|
+
|
|
62
|
+
## What It Detects
|
|
63
|
+
|
|
64
|
+
- Likely hardcoded secrets and private keys.
|
|
65
|
+
- Suspicious tool descriptions that look like hidden instructions.
|
|
66
|
+
- Risky command execution surfaces.
|
|
67
|
+
- Remote HTTP endpoints without clear authentication.
|
|
68
|
+
- Missing audit logging when policy requires it.
|
|
69
|
+
- MCP server names outside the allowlist.
|
|
70
|
+
- Broad filesystem and context exposure.
|
|
71
|
+
|
|
72
|
+
## Usage
|
|
73
|
+
|
|
74
|
+
```bash
|
|
75
|
+
mcp-guard scan <target>
|
|
76
|
+
mcp-guard scan <target> --format json
|
|
77
|
+
mcp-guard scan <target> --format markdown
|
|
78
|
+
mcp-guard scan <target> --output report.md
|
|
79
|
+
mcp-guard scan <target> --policy mcpguard.config.yml
|
|
80
|
+
mcp-guard scan <target> --no-fail
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
Exit behavior:
|
|
84
|
+
|
|
85
|
+
- Exit code `0` when there are no error-level findings.
|
|
86
|
+
- Exit code `1` when there are error-level findings.
|
|
87
|
+
- `--no-fail` always exits `0` while still printing findings.
|
|
88
|
+
|
|
89
|
+
## Example Output
|
|
90
|
+
|
|
91
|
+
```text
|
|
92
|
+
MCP-Guard scan FAILED
|
|
93
|
+
Target: /path/to/examples/bad-mcp-server
|
|
94
|
+
Findings: 30 total, 27 error-level
|
|
95
|
+
Severity: 1 critical, 26 high, 3 medium, 0 low, 0 info
|
|
96
|
+
|
|
97
|
+
[HIGH] MCP05_COMMAND_EXECUTION Risky command execution surface
|
|
98
|
+
Policy: error (fails scan)
|
|
99
|
+
File: examples/bad-mcp-server/server.ts:4
|
|
100
|
+
Evidence: exec(
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
## Policy Configuration
|
|
104
|
+
|
|
105
|
+
`mcpguard.config.yml` controls rule behavior and project expectations:
|
|
106
|
+
|
|
107
|
+
```yaml
|
|
108
|
+
rules:
|
|
109
|
+
MCP01_SECRET_EXPOSURE: error
|
|
110
|
+
MCP03_TOOL_POISONING: error
|
|
111
|
+
MCP05_COMMAND_EXECUTION: error
|
|
112
|
+
MCP07_INSUFFICIENT_AUTH: warn
|
|
113
|
+
MCP08_AUDIT_GAP: warn
|
|
114
|
+
MCP09_SHADOW_SERVER: warn
|
|
115
|
+
MCP10_CONTEXT_OVERSHARING: error
|
|
116
|
+
|
|
117
|
+
allowlist:
|
|
118
|
+
servers:
|
|
119
|
+
- safe-filesystem-server
|
|
120
|
+
- safe-github-server
|
|
121
|
+
|
|
122
|
+
require:
|
|
123
|
+
auditLogging: true
|
|
124
|
+
explicitToolConsent: true
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
Rule modes:
|
|
128
|
+
|
|
129
|
+
- `off` suppresses a rule.
|
|
130
|
+
- `warn` reports findings without failing the scan.
|
|
131
|
+
- `error` fails the scan when the rule finds an issue.
|
|
132
|
+
|
|
133
|
+
Without a policy override, high and critical findings fail the scan.
|
|
134
|
+
|
|
135
|
+
## Try the Test Laboratory
|
|
136
|
+
|
|
137
|
+
Clone the repository and run the local examples:
|
|
138
|
+
|
|
139
|
+
```bash
|
|
140
|
+
npm install
|
|
141
|
+
npm run scan:safe
|
|
142
|
+
npm run demo
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
`examples/safe-mcp-server` should pass. `examples/bad-mcp-server` intentionally contains risky patterns and should produce findings across the initial rule set.
|
|
146
|
+
|
|
147
|
+
## GitHub Action
|
|
148
|
+
|
|
149
|
+
Add MCP-Guard to another project with a workflow like this:
|
|
150
|
+
|
|
151
|
+
```yaml
|
|
152
|
+
name: MCP Guard
|
|
153
|
+
|
|
154
|
+
on: [push, pull_request]
|
|
155
|
+
|
|
156
|
+
jobs:
|
|
157
|
+
mcp-guard:
|
|
158
|
+
runs-on: ubuntu-latest
|
|
159
|
+
steps:
|
|
160
|
+
- uses: actions/checkout@v5
|
|
161
|
+
- uses: actions/setup-node@v5
|
|
162
|
+
with:
|
|
163
|
+
node-version: 24
|
|
164
|
+
package-manager-cache: false
|
|
165
|
+
- run: npx @srhot/mcp-guard scan .
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
## Development
|
|
169
|
+
|
|
170
|
+
```bash
|
|
171
|
+
npm install
|
|
172
|
+
npm run lint
|
|
173
|
+
npm test
|
|
174
|
+
npm run build
|
|
175
|
+
npm run format
|
|
176
|
+
npm run pack:check
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
## Local Package Smoke Test
|
|
180
|
+
|
|
181
|
+
```bash
|
|
182
|
+
npm pack
|
|
183
|
+
npm install -g ./srhot-mcp-guard-0.1.0.tgz
|
|
184
|
+
mcp-guard scan examples/safe-mcp-server
|
|
185
|
+
mcp-guard scan examples/bad-mcp-server --no-fail
|
|
186
|
+
npm uninstall -g @srhot/mcp-guard
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
## Roadmap
|
|
190
|
+
|
|
191
|
+
- v0.1: static scanner + policy engine + test lab.
|
|
192
|
+
- v0.2: SARIF output + stronger CI integration.
|
|
193
|
+
- v0.3: runtime MCP proxy/firewall.
|
|
194
|
+
- v0.4: dashboard and enterprise policy controls.
|
|
195
|
+
|
|
196
|
+
## Disclaimer
|
|
197
|
+
|
|
198
|
+
MCP-Guard is an early open-source security tool and does not replace a full security review.
|
|
199
|
+
|
|
200
|
+
## Contributing
|
|
201
|
+
|
|
202
|
+
Issues and pull requests are welcome. Please keep rules deterministic, locally testable, and free of external API requirements.
|
package/SECURITY.md
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# Security Policy
|
|
2
|
+
|
|
3
|
+
## Supported Versions
|
|
4
|
+
|
|
5
|
+
MCP-Guard is currently in v0.1. Security fixes target the latest release.
|
|
6
|
+
|
|
7
|
+
## Reporting a Vulnerability
|
|
8
|
+
|
|
9
|
+
Please report suspected vulnerabilities privately through the repository security advisory workflow when available. If that is not available, open a minimal issue that does not include exploitable details and ask the maintainers for a private contact path.
|
|
10
|
+
|
|
11
|
+
Do not include real secrets, credentials, or private MCP server configuration in public issues.
|
package/dist/cli.d.ts
ADDED
package/dist/cli.js
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import fs from "node:fs/promises";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { fileURLToPath } from "node:url";
|
|
5
|
+
import { Command, CommanderError } from "commander";
|
|
6
|
+
import { scanTarget } from "./scanner/scanTarget.js";
|
|
7
|
+
import { renderConsoleReport } from "./reporters/consoleReporter.js";
|
|
8
|
+
import { renderJsonReport } from "./reporters/jsonReporter.js";
|
|
9
|
+
import { renderMarkdownReport } from "./reporters/markdownReporter.js";
|
|
10
|
+
export async function runCli(argv = process.argv.slice(2), io = process) {
|
|
11
|
+
let exitCode = 0;
|
|
12
|
+
const program = new Command();
|
|
13
|
+
program
|
|
14
|
+
.name("mcp-guard")
|
|
15
|
+
.description("Security scanner and policy engine for MCP server projects")
|
|
16
|
+
.version("0.1.0")
|
|
17
|
+
.exitOverride();
|
|
18
|
+
program
|
|
19
|
+
.command("scan")
|
|
20
|
+
.argument("<target>", "MCP server project or config to scan")
|
|
21
|
+
.option("--format <format>", "report format: console, json, markdown", "console")
|
|
22
|
+
.option("--output <path>", "write report to a file")
|
|
23
|
+
.option("--policy <path>", "path to mcpguard.config.yml")
|
|
24
|
+
.option("--no-fail", "always exit 0 even when error-level findings are present")
|
|
25
|
+
.action(async (target, options) => {
|
|
26
|
+
const format = validateFormat(String(options.format ?? "console"));
|
|
27
|
+
const result = await scanTarget({
|
|
28
|
+
target,
|
|
29
|
+
policyPath: typeof options.policy === "string" ? options.policy : undefined
|
|
30
|
+
});
|
|
31
|
+
const report = renderReport(format, result);
|
|
32
|
+
if (typeof options.output === "string") {
|
|
33
|
+
await fs.mkdir(path.dirname(path.resolve(options.output)), { recursive: true });
|
|
34
|
+
await fs.writeFile(options.output, report, "utf8");
|
|
35
|
+
}
|
|
36
|
+
else {
|
|
37
|
+
io.stdout.write(report);
|
|
38
|
+
}
|
|
39
|
+
exitCode = result.shouldFail && options.fail !== false ? 1 : 0;
|
|
40
|
+
});
|
|
41
|
+
try {
|
|
42
|
+
await program.parseAsync(argv, { from: "user" });
|
|
43
|
+
}
|
|
44
|
+
catch (error) {
|
|
45
|
+
if (error instanceof CommanderError) {
|
|
46
|
+
return error.exitCode;
|
|
47
|
+
}
|
|
48
|
+
io.stderr.write(`${error instanceof Error ? error.message : String(error)}\n`);
|
|
49
|
+
return 1;
|
|
50
|
+
}
|
|
51
|
+
return exitCode;
|
|
52
|
+
}
|
|
53
|
+
function validateFormat(format) {
|
|
54
|
+
if (format === "console" || format === "json" || format === "markdown") {
|
|
55
|
+
return format;
|
|
56
|
+
}
|
|
57
|
+
throw new Error(`Unsupported format "${format}". Use console, json, or markdown.`);
|
|
58
|
+
}
|
|
59
|
+
function renderReport(format, result) {
|
|
60
|
+
if (format === "json") {
|
|
61
|
+
return renderJsonReport(result);
|
|
62
|
+
}
|
|
63
|
+
if (format === "markdown") {
|
|
64
|
+
return renderMarkdownReport(result);
|
|
65
|
+
}
|
|
66
|
+
return renderConsoleReport(result);
|
|
67
|
+
}
|
|
68
|
+
if (process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url)) {
|
|
69
|
+
runCli().then((code) => {
|
|
70
|
+
process.exitCode = code;
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
//# sourceMappingURL=cli.js.map
|
package/dist/cli.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export { scanTarget } from "./scanner/scanTarget.js";
|
|
2
|
+
export { loadPolicy } from "./policy/loadPolicy.js";
|
|
3
|
+
export { applyPolicy, summarizeFindings } from "./policy/policyEngine.js";
|
|
4
|
+
export { rules } from "./rules/index.js";
|
|
5
|
+
export type { Finding, LoadedFile, Policy, Rule, RuleContext, ScanOptions, ScanResult, Severity } from "./types.js";
|
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,yBAAyB,CAAC;AACrD,OAAO,EAAE,UAAU,EAAE,MAAM,wBAAwB,CAAC;AACpD,OAAO,EAAE,WAAW,EAAE,iBAAiB,EAAE,MAAM,0BAA0B,CAAC;AAC1E,OAAO,EAAE,KAAK,EAAE,MAAM,kBAAkB,CAAC"}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import fs from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { parse } from "yaml";
|
|
4
|
+
import { z } from "zod";
|
|
5
|
+
const ruleModeSchema = z.enum(["off", "warn", "error"]);
|
|
6
|
+
const policySchema = z
|
|
7
|
+
.object({
|
|
8
|
+
rules: z.record(ruleModeSchema).default({}),
|
|
9
|
+
allowlist: z
|
|
10
|
+
.object({
|
|
11
|
+
servers: z.array(z.string()).default([])
|
|
12
|
+
})
|
|
13
|
+
.default({ servers: [] }),
|
|
14
|
+
deny: z
|
|
15
|
+
.object({
|
|
16
|
+
filesystemRoots: z.array(z.string()).default([]),
|
|
17
|
+
commands: z.array(z.string()).default([])
|
|
18
|
+
})
|
|
19
|
+
.default({ filesystemRoots: [], commands: [] }),
|
|
20
|
+
require: z
|
|
21
|
+
.object({
|
|
22
|
+
auditLogging: z.boolean().default(false),
|
|
23
|
+
explicitToolConsent: z.boolean().default(false)
|
|
24
|
+
})
|
|
25
|
+
.default({ auditLogging: false, explicitToolConsent: false })
|
|
26
|
+
})
|
|
27
|
+
.default({});
|
|
28
|
+
export const defaultPolicy = {
|
|
29
|
+
rules: {},
|
|
30
|
+
allowlist: {
|
|
31
|
+
servers: []
|
|
32
|
+
},
|
|
33
|
+
deny: {
|
|
34
|
+
filesystemRoots: ["/", "/home", "C:\\Users"],
|
|
35
|
+
commands: ["rm -rf", "curl | sh", "powershell -enc", "wget", "scp"]
|
|
36
|
+
},
|
|
37
|
+
require: {
|
|
38
|
+
auditLogging: false,
|
|
39
|
+
explicitToolConsent: false
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
export async function loadPolicy(policyPath) {
|
|
43
|
+
if (!policyPath) {
|
|
44
|
+
const candidate = path.resolve(process.cwd(), "mcpguard.config.yml");
|
|
45
|
+
try {
|
|
46
|
+
await fs.access(candidate);
|
|
47
|
+
return loadPolicy(candidate);
|
|
48
|
+
}
|
|
49
|
+
catch {
|
|
50
|
+
return defaultPolicy;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
const raw = await fs.readFile(path.resolve(policyPath), "utf8");
|
|
54
|
+
const parsed = policySchema.parse(parse(raw) ?? {});
|
|
55
|
+
return {
|
|
56
|
+
rules: parsed.rules,
|
|
57
|
+
allowlist: parsed.allowlist,
|
|
58
|
+
deny: {
|
|
59
|
+
filesystemRoots: parsed.deny.filesystemRoots.length
|
|
60
|
+
? parsed.deny.filesystemRoots
|
|
61
|
+
: defaultPolicy.deny.filesystemRoots,
|
|
62
|
+
commands: parsed.deny.commands.length ? parsed.deny.commands : defaultPolicy.deny.commands
|
|
63
|
+
},
|
|
64
|
+
require: parsed.require
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
//# sourceMappingURL=loadPolicy.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"loadPolicy.js","sourceRoot":"","sources":["../../src/policy/loadPolicy.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { createSeverityCounts, shouldFailByDefault } from "../utils/severity.js";
|
|
2
|
+
export function applyPolicy(findings, policy) {
|
|
3
|
+
return findings.flatMap((finding) => {
|
|
4
|
+
const mode = policy.rules[finding.id];
|
|
5
|
+
if (mode === "off") {
|
|
6
|
+
return [];
|
|
7
|
+
}
|
|
8
|
+
const failsScan = mode === "error" ? true : mode === "warn" ? false : shouldFailByDefault(finding.severity);
|
|
9
|
+
const policyLevel = mode === "error" || (mode === undefined && failsScan) ? "error" : "warn";
|
|
10
|
+
return [
|
|
11
|
+
{
|
|
12
|
+
...finding,
|
|
13
|
+
policyLevel,
|
|
14
|
+
failsScan
|
|
15
|
+
}
|
|
16
|
+
];
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
export function summarizeFindings(findings) {
|
|
20
|
+
const bySeverity = createSeverityCounts();
|
|
21
|
+
for (const finding of findings) {
|
|
22
|
+
bySeverity[finding.severity] += 1;
|
|
23
|
+
}
|
|
24
|
+
return {
|
|
25
|
+
total: findings.length,
|
|
26
|
+
failCount: findings.filter((finding) => finding.failsScan).length,
|
|
27
|
+
bySeverity
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
//# sourceMappingURL=policyEngine.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"policyEngine.js","sourceRoot":"","sources":["../../src/policy/policyEngine.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,oBAAoB,EAAE,mBAAmB,EAAE,MAAM,sBAAsB,CAAC;AAEjF,MAAM,UAAU,WAAW,CAAC,QAAmB,EAAE,MAAc;IAC7D,OAAO,QAAQ,CAAC,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE;QAClC,MAAM,IAAI,GAAG,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;QAEtC,IAAI,IAAI,KAAK,KAAK,EAAE,CAAC;YACnB,OAAO,EAAE,CAAC;QACZ,CAAC;QAED,MAAM,SAAS,GACb,IAAI,KAAK,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,KAAK,MAAM,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,mBAAmB,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;QAC5F,MAAM,WAAW,GAAG,IAAI,KAAK,OAAO,IAAI,CAAC,IAAI,KAAK,SAAS,IAAI,SAAS,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC;QAE7F,OAAO;YACL;gBACE,GAAG,OAAO;gBACV,WAAW;gBACX,SAAS;aACV;SACF,CAAC;IACJ,CAAC,CAAC,CAAC;AACL,CAAC;AAED,MAAM,UAAU,iBAAiB,CAAC,QAAmB;IACnD,MAAM,UAAU,GAAG,oBAAoB,EAAE,CAAC;IAC1C,KAAK,MAAM,OAAO,IAAI,QAAQ,EAAE,CAAC;QAC/B,UAAU,CAAC,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC;IACpC,CAAC;IAED,OAAO;QACL,KAAK,EAAE,QAAQ,CAAC,MAAM;QACtB,SAAS,EAAE,QAAQ,CAAC,MAAM,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC,MAAM;QACjE,UAAU;KACX,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { displayPath } from "../utils/paths.js";
|
|
2
|
+
export function renderConsoleReport(result) {
|
|
3
|
+
const status = result.shouldFail ? "FAILED" : "PASSED";
|
|
4
|
+
const lines = [
|
|
5
|
+
`MCP-Guard scan ${status}`,
|
|
6
|
+
`Target: ${result.targetPath}`,
|
|
7
|
+
`Findings: ${result.summary.total} total, ${result.summary.failCount} error-level`,
|
|
8
|
+
`Severity: ${result.summary.bySeverity.CRITICAL} critical, ${result.summary.bySeverity.HIGH} high, ${result.summary.bySeverity.MEDIUM} medium, ${result.summary.bySeverity.LOW} low, ${result.summary.bySeverity.INFO} info`,
|
|
9
|
+
""
|
|
10
|
+
];
|
|
11
|
+
if (result.findings.length === 0) {
|
|
12
|
+
lines.push("No findings.");
|
|
13
|
+
return `${lines.join("\n")}\n`;
|
|
14
|
+
}
|
|
15
|
+
for (const finding of result.findings) {
|
|
16
|
+
const location = finding.lineNumber
|
|
17
|
+
? `${displayPath(finding.filePath)}:${finding.lineNumber}`
|
|
18
|
+
: displayPath(finding.filePath);
|
|
19
|
+
lines.push(`[${finding.severity}] ${finding.id} ${finding.title}`, ` Policy: ${finding.policyLevel ?? "warn"}${finding.failsScan ? " (fails scan)" : ""}`, ` File: ${location}`, ` Evidence: ${finding.evidence}`, ` Message: ${finding.message}`, ` Recommendation: ${finding.recommendation}`, "");
|
|
20
|
+
}
|
|
21
|
+
return `${lines.join("\n")}\n`;
|
|
22
|
+
}
|
|
23
|
+
//# sourceMappingURL=consoleReporter.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"consoleReporter.js","sourceRoot":"","sources":["../../src/reporters/consoleReporter.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAEhD,MAAM,UAAU,mBAAmB,CAAC,MAAkB;IACpD,MAAM,MAAM,GAAG,MAAM,CAAC,UAAU,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,QAAQ,CAAC;IACvD,MAAM,KAAK,GAAG;QACZ,kBAAkB,MAAM,EAAE;QAC1B,WAAW,MAAM,CAAC,UAAU,EAAE;QAC9B,aAAa,MAAM,CAAC,OAAO,CAAC,KAAK,WAAW,MAAM,CAAC,OAAO,CAAC,SAAS,cAAc;QAClF,aAAa,MAAM,CAAC,OAAO,CAAC,UAAU,CAAC,QAAQ,cAAc,MAAM,CAAC,OAAO,CAAC,UAAU,CAAC,IAAI,UAAU,MAAM,CAAC,OAAO,CAAC,UAAU,CAAC,MAAM,YAAY,MAAM,CAAC,OAAO,CAAC,UAAU,CAAC,GAAG,SAAS,MAAM,CAAC,OAAO,CAAC,UAAU,CAAC,IAAI,OAAO;QAC5N,EAAE;KACH,CAAC;IAEF,IAAI,MAAM,CAAC,QAAQ,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACjC,KAAK,CAAC,IAAI,CAAC,cAAc,CAAC,CAAC;QAC3B,OAAO,GAAG,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC;IACjC,CAAC;IAED,KAAK,MAAM,OAAO,IAAI,MAAM,CAAC,QAAQ,EAAE,CAAC;QACtC,MAAM,QAAQ,GAAG,OAAO,CAAC,UAAU;YACjC,CAAC,CAAC,GAAG,WAAW,CAAC,OAAO,CAAC,QAAQ,CAAC,IAAI,OAAO,CAAC,UAAU,EAAE;YAC1D,CAAC,CAAC,WAAW,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;QAElC,KAAK,CAAC,IAAI,CACR,IAAI,OAAO,CAAC,QAAQ,KAAK,OAAO,CAAC,EAAE,IAAI,OAAO,CAAC,KAAK,EAAE,EACtD,aAAa,OAAO,CAAC,WAAW,IAAI,MAAM,GAAG,OAAO,CAAC,SAAS,CAAC,CAAC,CAAC,eAAe,CAAC,CAAC,CAAC,EAAE,EAAE,EACvF,WAAW,QAAQ,EAAE,EACrB,eAAe,OAAO,CAAC,QAAQ,EAAE,EACjC,cAAc,OAAO,CAAC,OAAO,EAAE,EAC/B,qBAAqB,OAAO,CAAC,cAAc,EAAE,EAC7C,EAAE,CACH,CAAC;IACJ,CAAC;IAED,OAAO,GAAG,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC;AACjC,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"jsonReporter.js","sourceRoot":"","sources":["../../src/reporters/jsonReporter.ts"],"names":[],"mappings":"AAEA,MAAM,UAAU,gBAAgB,CAAC,MAAkB;IACjD,OAAO,GAAG,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC;AAChD,CAAC"}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { displayPath } from "../utils/paths.js";
|
|
2
|
+
export function renderMarkdownReport(result) {
|
|
3
|
+
const lines = [
|
|
4
|
+
"# MCP-Guard Scan Report",
|
|
5
|
+
"",
|
|
6
|
+
`Target: \`${result.targetPath}\``,
|
|
7
|
+
`Status: **${result.shouldFail ? "failed" : "passed"}**`,
|
|
8
|
+
"",
|
|
9
|
+
"## Summary",
|
|
10
|
+
"",
|
|
11
|
+
`- Total findings: ${result.summary.total}`,
|
|
12
|
+
`- Error-level findings: ${result.summary.failCount}`,
|
|
13
|
+
`- Critical: ${result.summary.bySeverity.CRITICAL}`,
|
|
14
|
+
`- High: ${result.summary.bySeverity.HIGH}`,
|
|
15
|
+
`- Medium: ${result.summary.bySeverity.MEDIUM}`,
|
|
16
|
+
`- Low: ${result.summary.bySeverity.LOW}`,
|
|
17
|
+
`- Info: ${result.summary.bySeverity.INFO}`,
|
|
18
|
+
""
|
|
19
|
+
];
|
|
20
|
+
if (result.findings.length === 0) {
|
|
21
|
+
lines.push("No findings.");
|
|
22
|
+
return `${lines.join("\n")}\n`;
|
|
23
|
+
}
|
|
24
|
+
lines.push("## Findings", "");
|
|
25
|
+
for (const finding of result.findings) {
|
|
26
|
+
const location = finding.lineNumber
|
|
27
|
+
? `${displayPath(finding.filePath)}:${finding.lineNumber}`
|
|
28
|
+
: displayPath(finding.filePath);
|
|
29
|
+
lines.push(`### ${finding.id}: ${finding.title}`, "", `- Severity: ${finding.severity}`, `- Policy: ${finding.policyLevel ?? "warn"}`, `- Location: \`${location}\``, `- Evidence: \`${finding.evidence}\``, `- Message: ${finding.message}`, `- Recommendation: ${finding.recommendation}`, "");
|
|
30
|
+
}
|
|
31
|
+
return `${lines.join("\n")}\n`;
|
|
32
|
+
}
|
|
33
|
+
//# sourceMappingURL=markdownReporter.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"markdownReporter.js","sourceRoot":"","sources":["../../src/reporters/markdownReporter.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAEhD,MAAM,UAAU,oBAAoB,CAAC,MAAkB;IACrD,MAAM,KAAK,GAAG;QACZ,yBAAyB;QACzB,EAAE;QACF,aAAa,MAAM,CAAC,UAAU,IAAI;QAClC,aAAa,MAAM,CAAC,UAAU,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,QAAQ,IAAI;QACxD,EAAE;QACF,YAAY;QACZ,EAAE;QACF,qBAAqB,MAAM,CAAC,OAAO,CAAC,KAAK,EAAE;QAC3C,2BAA2B,MAAM,CAAC,OAAO,CAAC,SAAS,EAAE;QACrD,eAAe,MAAM,CAAC,OAAO,CAAC,UAAU,CAAC,QAAQ,EAAE;QACnD,WAAW,MAAM,CAAC,OAAO,CAAC,UAAU,CAAC,IAAI,EAAE;QAC3C,aAAa,MAAM,CAAC,OAAO,CAAC,UAAU,CAAC,MAAM,EAAE;QAC/C,UAAU,MAAM,CAAC,OAAO,CAAC,UAAU,CAAC,GAAG,EAAE;QACzC,WAAW,MAAM,CAAC,OAAO,CAAC,UAAU,CAAC,IAAI,EAAE;QAC3C,EAAE;KACH,CAAC;IAEF,IAAI,MAAM,CAAC,QAAQ,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACjC,KAAK,CAAC,IAAI,CAAC,cAAc,CAAC,CAAC;QAC3B,OAAO,GAAG,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC;IACjC,CAAC;IAED,KAAK,CAAC,IAAI,CAAC,aAAa,EAAE,EAAE,CAAC,CAAC;IAE9B,KAAK,MAAM,OAAO,IAAI,MAAM,CAAC,QAAQ,EAAE,CAAC;QACtC,MAAM,QAAQ,GAAG,OAAO,CAAC,UAAU;YACjC,CAAC,CAAC,GAAG,WAAW,CAAC,OAAO,CAAC,QAAQ,CAAC,IAAI,OAAO,CAAC,UAAU,EAAE;YAC1D,CAAC,CAAC,WAAW,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;QAClC,KAAK,CAAC,IAAI,CACR,OAAO,OAAO,CAAC,EAAE,KAAK,OAAO,CAAC,KAAK,EAAE,EACrC,EAAE,EACF,eAAe,OAAO,CAAC,QAAQ,EAAE,EACjC,aAAa,OAAO,CAAC,WAAW,IAAI,MAAM,EAAE,EAC5C,iBAAiB,QAAQ,IAAI,EAC7B,iBAAiB,OAAO,CAAC,QAAQ,IAAI,EACrC,cAAc,OAAO,CAAC,OAAO,EAAE,EAC/B,qBAAqB,OAAO,CAAC,cAAc,EAAE,EAC7C,EAAE,CACH,CAAC;IACJ,CAAC;IAED,OAAO,GAAG,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC;AACjC,CAAC"}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { lineNumberForIndex } from "../scanner/fileLoader.js";
|
|
2
|
+
const secretPatterns = [
|
|
3
|
+
{
|
|
4
|
+
label: "named secret assignment",
|
|
5
|
+
regex: /\b(?:OPENAI_API_KEY|ANTHROPIC_API_KEY|GITHUB_TOKEN|API_KEY|SECRET|PASSWORD)\b\s*[:=]\s*["']?[A-Za-z0-9_./+=-]{12,}/gi
|
|
6
|
+
},
|
|
7
|
+
{
|
|
8
|
+
label: "bearer token",
|
|
9
|
+
regex: /\bBearer\s+[A-Za-z0-9._~+/=-]{12,}/gi,
|
|
10
|
+
critical: true
|
|
11
|
+
},
|
|
12
|
+
{
|
|
13
|
+
label: "private key block",
|
|
14
|
+
regex: /-----BEGIN\s+(?:RSA\s+|EC\s+|OPENSSH\s+)?PRIVATE KEY-----/gi,
|
|
15
|
+
critical: true
|
|
16
|
+
}
|
|
17
|
+
];
|
|
18
|
+
export const MCP01_SECRET_EXPOSURE = {
|
|
19
|
+
id: "MCP01_SECRET_EXPOSURE",
|
|
20
|
+
scan(context) {
|
|
21
|
+
const findings = [];
|
|
22
|
+
for (const file of context.files) {
|
|
23
|
+
for (const pattern of secretPatterns) {
|
|
24
|
+
for (const match of file.content.matchAll(pattern.regex)) {
|
|
25
|
+
findings.push({
|
|
26
|
+
id: "MCP01_SECRET_EXPOSURE",
|
|
27
|
+
title: "Possible secret exposure",
|
|
28
|
+
severity: pattern.critical ? "CRITICAL" : "HIGH",
|
|
29
|
+
category: "Secrets",
|
|
30
|
+
message: `Detected a likely ${pattern.label} in a scanned file.`,
|
|
31
|
+
filePath: file.filePath,
|
|
32
|
+
lineNumber: lineNumberForIndex(file.content, match.index ?? 0),
|
|
33
|
+
evidence: redactEvidence(match[0]),
|
|
34
|
+
recommendation: "Move secrets to a secure secret manager or environment variable and rotate any exposed credentials."
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
return findings;
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
function redactEvidence(value) {
|
|
43
|
+
if (value.length <= 16) {
|
|
44
|
+
return "[redacted]";
|
|
45
|
+
}
|
|
46
|
+
return `${value.slice(0, 12)}...[redacted]`;
|
|
47
|
+
}
|
|
48
|
+
//# sourceMappingURL=MCP01_SECRET_EXPOSURE.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"MCP01_SECRET_EXPOSURE.js","sourceRoot":"","sources":["../../src/rules/MCP01_SECRET_EXPOSURE.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,kBAAkB,EAAE,MAAM,0BAA0B,CAAC;AAE9D,MAAM,cAAc,GAAgE;IAClF;QACE,KAAK,EAAE,yBAAyB;QAChC,KAAK,EACH,sHAAsH;KACzH;IACD;QACE,KAAK,EAAE,cAAc;QACrB,KAAK,EAAE,sCAAsC;QAC7C,QAAQ,EAAE,IAAI;KACf;IACD;QACE,KAAK,EAAE,mBAAmB;QAC1B,KAAK,EAAE,6DAA6D;QACpE,QAAQ,EAAE,IAAI;KACf;CACF,CAAC;AAEF,MAAM,CAAC,MAAM,qBAAqB,GAAS;IACzC,EAAE,EAAE,uBAAuB;IAC3B,IAAI,CAAC,OAAO;QACV,MAAM,QAAQ,GAAc,EAAE,CAAC;QAE/B,KAAK,MAAM,IAAI,IAAI,OAAO,CAAC,KAAK,EAAE,CAAC;YACjC,KAAK,MAAM,OAAO,IAAI,cAAc,EAAE,CAAC;gBACrC,KAAK,MAAM,KAAK,IAAI,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;oBACzD,QAAQ,CAAC,IAAI,CAAC;wBACZ,EAAE,EAAE,uBAAuB;wBAC3B,KAAK,EAAE,0BAA0B;wBACjC,QAAQ,EAAE,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,MAAM;wBAChD,QAAQ,EAAE,SAAS;wBACnB,OAAO,EAAE,qBAAqB,OAAO,CAAC,KAAK,qBAAqB;wBAChE,QAAQ,EAAE,IAAI,CAAC,QAAQ;wBACvB,UAAU,EAAE,kBAAkB,CAAC,IAAI,CAAC,OAAO,EAAE,KAAK,CAAC,KAAK,IAAI,CAAC,CAAC;wBAC9D,QAAQ,EAAE,cAAc,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;wBAClC,cAAc,EACZ,qGAAqG;qBACxG,CAAC,CAAC;gBACL,CAAC;YACH,CAAC;QACH,CAAC;QAED,OAAO,QAAQ,CAAC;IAClB,CAAC;CACF,CAAC;AAEF,SAAS,cAAc,CAAC,KAAa;IACnC,IAAI,KAAK,CAAC,MAAM,IAAI,EAAE,EAAE,CAAC;QACvB,OAAO,YAAY,CAAC;IACtB,CAAC;IAED,OAAO,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,eAAe,CAAC;AAC9C,CAAC"}
|