agentfort 0.1.1__tar.gz
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.
- agentfort-0.1.1/LICENSE +21 -0
- agentfort-0.1.1/PKG-INFO +154 -0
- agentfort-0.1.1/README.md +125 -0
- agentfort-0.1.1/agentfort.egg-info/PKG-INFO +154 -0
- agentfort-0.1.1/agentfort.egg-info/SOURCES.txt +37 -0
- agentfort-0.1.1/agentfort.egg-info/dependency_links.txt +1 -0
- agentfort-0.1.1/agentfort.egg-info/entry_points.txt +2 -0
- agentfort-0.1.1/agentfort.egg-info/requires.txt +6 -0
- agentfort-0.1.1/agentfort.egg-info/top_level.txt +1 -0
- agentfort-0.1.1/agentsentry/__init__.py +0 -0
- agentfort-0.1.1/agentsentry/cli.py +173 -0
- agentfort-0.1.1/agentsentry/db/__init__.py +0 -0
- agentfort-0.1.1/agentsentry/db/advisory.py +51 -0
- agentfort-0.1.1/agentsentry/db/data/AGSA-001.yaml +29 -0
- agentfort-0.1.1/agentsentry/db/data/AGSA-002.yaml +24 -0
- agentfort-0.1.1/agentsentry/db/data/AGSA-003.yaml +23 -0
- agentfort-0.1.1/agentsentry/db/data/AGSA-004.yaml +32 -0
- agentfort-0.1.1/agentsentry/db/data/AGSA-005.yaml +31 -0
- agentfort-0.1.1/agentsentry/db/data/AGSA-006.yaml +23 -0
- agentfort-0.1.1/agentsentry/db/data/AGSA-007.yaml +25 -0
- agentfort-0.1.1/agentsentry/db/data/AGSA-008.yaml +23 -0
- agentfort-0.1.1/agentsentry/db/data/AGSA-009.yaml +24 -0
- agentfort-0.1.1/agentsentry/db/data/AGSA-010.yaml +30 -0
- agentfort-0.1.1/agentsentry/db/data/AGSA-012.yaml +26 -0
- agentfort-0.1.1/agentsentry/db/data/AGSA-013.yaml +29 -0
- agentfort-0.1.1/agentsentry/db/data/AGSA-014.yaml +24 -0
- agentfort-0.1.1/agentsentry/db/data/AGSA-015.yaml +30 -0
- agentfort-0.1.1/agentsentry/models.py +54 -0
- agentfort-0.1.1/agentsentry/report/__init__.py +0 -0
- agentfort-0.1.1/agentsentry/report/formatter.py +145 -0
- agentfort-0.1.1/agentsentry/scanner/__init__.py +0 -0
- agentfort-0.1.1/agentsentry/scanner/frameworks.py +168 -0
- agentfort-0.1.1/agentsentry/scanner/mcp.py +153 -0
- agentfort-0.1.1/agentsentry/scanner/osv.py +117 -0
- agentfort-0.1.1/agentsentry/scanner/patterns.py +129 -0
- agentfort-0.1.1/agentsentry/scanner/secrets.py +71 -0
- agentfort-0.1.1/pyproject.toml +51 -0
- agentfort-0.1.1/setup.cfg +4 -0
- agentfort-0.1.1/tests/test_scanners.py +199 -0
agentfort-0.1.1/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 NeuronKnight
|
|
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.
|
agentfort-0.1.1/PKG-INFO
ADDED
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: agentfort
|
|
3
|
+
Version: 0.1.1
|
|
4
|
+
Summary: CVE-style advisory database and static scanner for AI agent security risks
|
|
5
|
+
Author-email: NeuronKnight <dev@neuronknight.com>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/NeuronKnight/agentfort
|
|
8
|
+
Project-URL: Source, https://github.com/NeuronKnight/agentfort
|
|
9
|
+
Project-URL: Issues, https://github.com/NeuronKnight/agentfort/issues
|
|
10
|
+
Keywords: security,ai,agents,mcp,llm,scanner,langchain,crewai
|
|
11
|
+
Classifier: Development Status :: 3 - Alpha
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
18
|
+
Classifier: Topic :: Security
|
|
19
|
+
Classifier: Topic :: Software Development :: Quality Assurance
|
|
20
|
+
Requires-Python: >=3.9
|
|
21
|
+
Description-Content-Type: text/markdown
|
|
22
|
+
License-File: LICENSE
|
|
23
|
+
Requires-Dist: click>=8.0
|
|
24
|
+
Requires-Dist: rich>=13.0
|
|
25
|
+
Requires-Dist: pyyaml>=6.0
|
|
26
|
+
Provides-Extra: dev
|
|
27
|
+
Requires-Dist: pytest>=8.0; extra == "dev"
|
|
28
|
+
Dynamic: license-file
|
|
29
|
+
|
|
30
|
+
# AgentFort
|
|
31
|
+
|
|
32
|
+
Static security scanner for AI agent codebases. Analyzes repositories and MCP config files against a CVE-style advisory database — no live agent interaction required.
|
|
33
|
+
|
|
34
|
+
## What it does
|
|
35
|
+
|
|
36
|
+
- Scans Python repos for dangerous patterns: `eval()`, `exec()`, `shell=True`, hardcoded API keys
|
|
37
|
+
- Parses MCP config files (`claude_desktop_config.json`, `.mcp.json`) for shell execution tools, overly broad filesystem access, and unverified `npx`/`uvx` packages
|
|
38
|
+
- Detects agent framework imports (LangChain, CrewAI, AutoGen, OpenAI, Anthropic, etc.) and cross-references against known vulnerabilities
|
|
39
|
+
- **Queries OSV.dev live** for real CVEs against every detected package — findings stay current without any database updates
|
|
40
|
+
- Produces risk scores, terminal reports, Markdown, and JSON output
|
|
41
|
+
- Exits with code 1 on critical findings — CI pipeline friendly
|
|
42
|
+
|
|
43
|
+
## Install
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
# From repo root (inside a virtualenv)
|
|
47
|
+
pip install agentfort
|
|
48
|
+
|
|
49
|
+
# Or dev install from repo
|
|
50
|
+
pip install -e agentsentry/
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## Usage
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
# Scan a repo
|
|
57
|
+
agentfort scan --repo /path/to/your/agent-project
|
|
58
|
+
|
|
59
|
+
# Scan just an MCP config file
|
|
60
|
+
agentfort scan --mcp-config ~/.config/claude/claude_desktop_config.json
|
|
61
|
+
|
|
62
|
+
# JSON output (clean stdout, progress on stderr)
|
|
63
|
+
agentfort scan --repo . --format json
|
|
64
|
+
|
|
65
|
+
# Markdown report to file
|
|
66
|
+
agentfort scan --repo . --format md --output report.md
|
|
67
|
+
|
|
68
|
+
# Only show high and above
|
|
69
|
+
agentfort scan --repo . --min-severity high
|
|
70
|
+
|
|
71
|
+
# Disable CI exit code
|
|
72
|
+
agentfort scan --repo . --no-fail-on-critical
|
|
73
|
+
|
|
74
|
+
# Skip OSV live lookup (air-gapped / CI without outbound)
|
|
75
|
+
agentfort scan --repo . --offline
|
|
76
|
+
|
|
77
|
+
# Browse advisories
|
|
78
|
+
agentfort advisories list
|
|
79
|
+
agentfort advisories list --severity critical
|
|
80
|
+
agentfort advisories show AGSA-001
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
## Live CVE lookup (OSV.dev)
|
|
84
|
+
|
|
85
|
+
Every scan queries [OSV.dev](https://osv.dev) in parallel for known CVEs against every package detected in the repo. No database to update — findings reflect OSV's current state on each run.
|
|
86
|
+
|
|
87
|
+
- Results are capped at 5 CVEs per package (highest severity first) to avoid noise from very old pinned versions
|
|
88
|
+
- Uses GHSA severity labels (`database_specific.severity`) for accurate critical/high/medium/low mapping
|
|
89
|
+
- Falls back silently if the network is unreachable — use `--offline` to skip the lookup entirely
|
|
90
|
+
|
|
91
|
+
```bash
|
|
92
|
+
# Scan with live CVEs (default)
|
|
93
|
+
agentfort scan --repo .
|
|
94
|
+
|
|
95
|
+
# Air-gapped / no outbound network
|
|
96
|
+
agentfort scan --repo . --offline
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
## Advisory database
|
|
100
|
+
|
|
101
|
+
14 static advisories covering structural/behavioral risks from the [OWASP Agentic Top 10](https://owasp.org/www-project-top-10-for-large-language-model-applications/). Package CVEs are handled live by OSV.dev (see above).
|
|
102
|
+
|
|
103
|
+
| ID | Severity | Risk |
|
|
104
|
+
|---|---|---|
|
|
105
|
+
| AGSA-001 | Critical | LangChain ShellTool unrestricted shell execution |
|
|
106
|
+
| AGSA-002 | Critical | MCP server exposes shell execution tool |
|
|
107
|
+
| AGSA-003 | Critical | `eval()`/`exec()` in agent tool handler |
|
|
108
|
+
| AGSA-005 | Critical | AutoGen/CrewAI code execution without confirmation |
|
|
109
|
+
| AGSA-004 | High | Hardcoded API key or secret in MCP config |
|
|
110
|
+
| AGSA-006 | High | MCP filesystem server with overly broad path (`/`, `~`) |
|
|
111
|
+
| AGSA-007 | High | `subprocess` with `shell=True` or `os.system()` |
|
|
112
|
+
| AGSA-008 | High | Agent tool writes to arbitrary file paths |
|
|
113
|
+
| AGSA-010 | High | Prompt injection via unvalidated tool output |
|
|
114
|
+
| AGSA-013 | High | OpenAI Assistants `file_search`/`code_interpreter` without scope restriction |
|
|
115
|
+
| AGSA-009 | Medium | MCP server loaded via unverified `npx`/`uvx` package |
|
|
116
|
+
| AGSA-012 | Medium | Secrets exposed via `os.environ` in tool scope |
|
|
117
|
+
| AGSA-014 | Medium | Non-PyPI/non-npm dependency as framework component |
|
|
118
|
+
| AGSA-015 | Medium | System prompt in user-accessible config location |
|
|
119
|
+
|
|
120
|
+
## Risk score
|
|
121
|
+
|
|
122
|
+
`0–100`. Each finding adds: critical=40, high=15, medium=5, low=1. Capped at 100.
|
|
123
|
+
|
|
124
|
+
## Output formats
|
|
125
|
+
|
|
126
|
+
**Terminal** (default) — Rich panels with color-coded severity table and detail panels for critical/high findings.
|
|
127
|
+
|
|
128
|
+
**JSON** — Machine-readable. Progress messages go to stderr so stdout is clean for piping:
|
|
129
|
+
```bash
|
|
130
|
+
agentfort scan --repo . --format json 2>/dev/null | jq '.findings[] | select(.severity=="critical")'
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
**Markdown** — Full report with summary table and per-finding sections, suitable for GitHub issues or PR comments.
|
|
134
|
+
|
|
135
|
+
## Project structure
|
|
136
|
+
|
|
137
|
+
```
|
|
138
|
+
agentsentry/
|
|
139
|
+
├── agentsentry/
|
|
140
|
+
│ ├── cli.py # Click CLI entry point
|
|
141
|
+
│ ├── models.py # Finding, ScanResult dataclasses
|
|
142
|
+
│ ├── db/
|
|
143
|
+
│ │ ├── advisory.py # Advisory loader and index
|
|
144
|
+
│ │ └── data/ # AGSA-001.yaml … AGSA-015.yaml
|
|
145
|
+
│ ├── scanner/
|
|
146
|
+
│ │ ├── frameworks.py # requirements.txt / pyproject.toml / package.json
|
|
147
|
+
│ │ ├── mcp.py # MCP config file scanner
|
|
148
|
+
│ │ ├── osv.py # Live CVE lookup via OSV.dev API
|
|
149
|
+
│ │ ├── patterns.py # Python source pattern scanner
|
|
150
|
+
│ │ └── secrets.py # Hardcoded credential scanner
|
|
151
|
+
│ └── report/
|
|
152
|
+
│ └── formatter.py # Terminal, Markdown, JSON renderers
|
|
153
|
+
└── pyproject.toml
|
|
154
|
+
```
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
# AgentFort
|
|
2
|
+
|
|
3
|
+
Static security scanner for AI agent codebases. Analyzes repositories and MCP config files against a CVE-style advisory database — no live agent interaction required.
|
|
4
|
+
|
|
5
|
+
## What it does
|
|
6
|
+
|
|
7
|
+
- Scans Python repos for dangerous patterns: `eval()`, `exec()`, `shell=True`, hardcoded API keys
|
|
8
|
+
- Parses MCP config files (`claude_desktop_config.json`, `.mcp.json`) for shell execution tools, overly broad filesystem access, and unverified `npx`/`uvx` packages
|
|
9
|
+
- Detects agent framework imports (LangChain, CrewAI, AutoGen, OpenAI, Anthropic, etc.) and cross-references against known vulnerabilities
|
|
10
|
+
- **Queries OSV.dev live** for real CVEs against every detected package — findings stay current without any database updates
|
|
11
|
+
- Produces risk scores, terminal reports, Markdown, and JSON output
|
|
12
|
+
- Exits with code 1 on critical findings — CI pipeline friendly
|
|
13
|
+
|
|
14
|
+
## Install
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
# From repo root (inside a virtualenv)
|
|
18
|
+
pip install agentfort
|
|
19
|
+
|
|
20
|
+
# Or dev install from repo
|
|
21
|
+
pip install -e agentsentry/
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## Usage
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
# Scan a repo
|
|
28
|
+
agentfort scan --repo /path/to/your/agent-project
|
|
29
|
+
|
|
30
|
+
# Scan just an MCP config file
|
|
31
|
+
agentfort scan --mcp-config ~/.config/claude/claude_desktop_config.json
|
|
32
|
+
|
|
33
|
+
# JSON output (clean stdout, progress on stderr)
|
|
34
|
+
agentfort scan --repo . --format json
|
|
35
|
+
|
|
36
|
+
# Markdown report to file
|
|
37
|
+
agentfort scan --repo . --format md --output report.md
|
|
38
|
+
|
|
39
|
+
# Only show high and above
|
|
40
|
+
agentfort scan --repo . --min-severity high
|
|
41
|
+
|
|
42
|
+
# Disable CI exit code
|
|
43
|
+
agentfort scan --repo . --no-fail-on-critical
|
|
44
|
+
|
|
45
|
+
# Skip OSV live lookup (air-gapped / CI without outbound)
|
|
46
|
+
agentfort scan --repo . --offline
|
|
47
|
+
|
|
48
|
+
# Browse advisories
|
|
49
|
+
agentfort advisories list
|
|
50
|
+
agentfort advisories list --severity critical
|
|
51
|
+
agentfort advisories show AGSA-001
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## Live CVE lookup (OSV.dev)
|
|
55
|
+
|
|
56
|
+
Every scan queries [OSV.dev](https://osv.dev) in parallel for known CVEs against every package detected in the repo. No database to update — findings reflect OSV's current state on each run.
|
|
57
|
+
|
|
58
|
+
- Results are capped at 5 CVEs per package (highest severity first) to avoid noise from very old pinned versions
|
|
59
|
+
- Uses GHSA severity labels (`database_specific.severity`) for accurate critical/high/medium/low mapping
|
|
60
|
+
- Falls back silently if the network is unreachable — use `--offline` to skip the lookup entirely
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
# Scan with live CVEs (default)
|
|
64
|
+
agentfort scan --repo .
|
|
65
|
+
|
|
66
|
+
# Air-gapped / no outbound network
|
|
67
|
+
agentfort scan --repo . --offline
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
## Advisory database
|
|
71
|
+
|
|
72
|
+
14 static advisories covering structural/behavioral risks from the [OWASP Agentic Top 10](https://owasp.org/www-project-top-10-for-large-language-model-applications/). Package CVEs are handled live by OSV.dev (see above).
|
|
73
|
+
|
|
74
|
+
| ID | Severity | Risk |
|
|
75
|
+
|---|---|---|
|
|
76
|
+
| AGSA-001 | Critical | LangChain ShellTool unrestricted shell execution |
|
|
77
|
+
| AGSA-002 | Critical | MCP server exposes shell execution tool |
|
|
78
|
+
| AGSA-003 | Critical | `eval()`/`exec()` in agent tool handler |
|
|
79
|
+
| AGSA-005 | Critical | AutoGen/CrewAI code execution without confirmation |
|
|
80
|
+
| AGSA-004 | High | Hardcoded API key or secret in MCP config |
|
|
81
|
+
| AGSA-006 | High | MCP filesystem server with overly broad path (`/`, `~`) |
|
|
82
|
+
| AGSA-007 | High | `subprocess` with `shell=True` or `os.system()` |
|
|
83
|
+
| AGSA-008 | High | Agent tool writes to arbitrary file paths |
|
|
84
|
+
| AGSA-010 | High | Prompt injection via unvalidated tool output |
|
|
85
|
+
| AGSA-013 | High | OpenAI Assistants `file_search`/`code_interpreter` without scope restriction |
|
|
86
|
+
| AGSA-009 | Medium | MCP server loaded via unverified `npx`/`uvx` package |
|
|
87
|
+
| AGSA-012 | Medium | Secrets exposed via `os.environ` in tool scope |
|
|
88
|
+
| AGSA-014 | Medium | Non-PyPI/non-npm dependency as framework component |
|
|
89
|
+
| AGSA-015 | Medium | System prompt in user-accessible config location |
|
|
90
|
+
|
|
91
|
+
## Risk score
|
|
92
|
+
|
|
93
|
+
`0–100`. Each finding adds: critical=40, high=15, medium=5, low=1. Capped at 100.
|
|
94
|
+
|
|
95
|
+
## Output formats
|
|
96
|
+
|
|
97
|
+
**Terminal** (default) — Rich panels with color-coded severity table and detail panels for critical/high findings.
|
|
98
|
+
|
|
99
|
+
**JSON** — Machine-readable. Progress messages go to stderr so stdout is clean for piping:
|
|
100
|
+
```bash
|
|
101
|
+
agentfort scan --repo . --format json 2>/dev/null | jq '.findings[] | select(.severity=="critical")'
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
**Markdown** — Full report with summary table and per-finding sections, suitable for GitHub issues or PR comments.
|
|
105
|
+
|
|
106
|
+
## Project structure
|
|
107
|
+
|
|
108
|
+
```
|
|
109
|
+
agentsentry/
|
|
110
|
+
├── agentsentry/
|
|
111
|
+
│ ├── cli.py # Click CLI entry point
|
|
112
|
+
│ ├── models.py # Finding, ScanResult dataclasses
|
|
113
|
+
│ ├── db/
|
|
114
|
+
│ │ ├── advisory.py # Advisory loader and index
|
|
115
|
+
│ │ └── data/ # AGSA-001.yaml … AGSA-015.yaml
|
|
116
|
+
│ ├── scanner/
|
|
117
|
+
│ │ ├── frameworks.py # requirements.txt / pyproject.toml / package.json
|
|
118
|
+
│ │ ├── mcp.py # MCP config file scanner
|
|
119
|
+
│ │ ├── osv.py # Live CVE lookup via OSV.dev API
|
|
120
|
+
│ │ ├── patterns.py # Python source pattern scanner
|
|
121
|
+
│ │ └── secrets.py # Hardcoded credential scanner
|
|
122
|
+
│ └── report/
|
|
123
|
+
│ └── formatter.py # Terminal, Markdown, JSON renderers
|
|
124
|
+
└── pyproject.toml
|
|
125
|
+
```
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: agentfort
|
|
3
|
+
Version: 0.1.1
|
|
4
|
+
Summary: CVE-style advisory database and static scanner for AI agent security risks
|
|
5
|
+
Author-email: NeuronKnight <dev@neuronknight.com>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/NeuronKnight/agentfort
|
|
8
|
+
Project-URL: Source, https://github.com/NeuronKnight/agentfort
|
|
9
|
+
Project-URL: Issues, https://github.com/NeuronKnight/agentfort/issues
|
|
10
|
+
Keywords: security,ai,agents,mcp,llm,scanner,langchain,crewai
|
|
11
|
+
Classifier: Development Status :: 3 - Alpha
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
18
|
+
Classifier: Topic :: Security
|
|
19
|
+
Classifier: Topic :: Software Development :: Quality Assurance
|
|
20
|
+
Requires-Python: >=3.9
|
|
21
|
+
Description-Content-Type: text/markdown
|
|
22
|
+
License-File: LICENSE
|
|
23
|
+
Requires-Dist: click>=8.0
|
|
24
|
+
Requires-Dist: rich>=13.0
|
|
25
|
+
Requires-Dist: pyyaml>=6.0
|
|
26
|
+
Provides-Extra: dev
|
|
27
|
+
Requires-Dist: pytest>=8.0; extra == "dev"
|
|
28
|
+
Dynamic: license-file
|
|
29
|
+
|
|
30
|
+
# AgentFort
|
|
31
|
+
|
|
32
|
+
Static security scanner for AI agent codebases. Analyzes repositories and MCP config files against a CVE-style advisory database — no live agent interaction required.
|
|
33
|
+
|
|
34
|
+
## What it does
|
|
35
|
+
|
|
36
|
+
- Scans Python repos for dangerous patterns: `eval()`, `exec()`, `shell=True`, hardcoded API keys
|
|
37
|
+
- Parses MCP config files (`claude_desktop_config.json`, `.mcp.json`) for shell execution tools, overly broad filesystem access, and unverified `npx`/`uvx` packages
|
|
38
|
+
- Detects agent framework imports (LangChain, CrewAI, AutoGen, OpenAI, Anthropic, etc.) and cross-references against known vulnerabilities
|
|
39
|
+
- **Queries OSV.dev live** for real CVEs against every detected package — findings stay current without any database updates
|
|
40
|
+
- Produces risk scores, terminal reports, Markdown, and JSON output
|
|
41
|
+
- Exits with code 1 on critical findings — CI pipeline friendly
|
|
42
|
+
|
|
43
|
+
## Install
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
# From repo root (inside a virtualenv)
|
|
47
|
+
pip install agentfort
|
|
48
|
+
|
|
49
|
+
# Or dev install from repo
|
|
50
|
+
pip install -e agentsentry/
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## Usage
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
# Scan a repo
|
|
57
|
+
agentfort scan --repo /path/to/your/agent-project
|
|
58
|
+
|
|
59
|
+
# Scan just an MCP config file
|
|
60
|
+
agentfort scan --mcp-config ~/.config/claude/claude_desktop_config.json
|
|
61
|
+
|
|
62
|
+
# JSON output (clean stdout, progress on stderr)
|
|
63
|
+
agentfort scan --repo . --format json
|
|
64
|
+
|
|
65
|
+
# Markdown report to file
|
|
66
|
+
agentfort scan --repo . --format md --output report.md
|
|
67
|
+
|
|
68
|
+
# Only show high and above
|
|
69
|
+
agentfort scan --repo . --min-severity high
|
|
70
|
+
|
|
71
|
+
# Disable CI exit code
|
|
72
|
+
agentfort scan --repo . --no-fail-on-critical
|
|
73
|
+
|
|
74
|
+
# Skip OSV live lookup (air-gapped / CI without outbound)
|
|
75
|
+
agentfort scan --repo . --offline
|
|
76
|
+
|
|
77
|
+
# Browse advisories
|
|
78
|
+
agentfort advisories list
|
|
79
|
+
agentfort advisories list --severity critical
|
|
80
|
+
agentfort advisories show AGSA-001
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
## Live CVE lookup (OSV.dev)
|
|
84
|
+
|
|
85
|
+
Every scan queries [OSV.dev](https://osv.dev) in parallel for known CVEs against every package detected in the repo. No database to update — findings reflect OSV's current state on each run.
|
|
86
|
+
|
|
87
|
+
- Results are capped at 5 CVEs per package (highest severity first) to avoid noise from very old pinned versions
|
|
88
|
+
- Uses GHSA severity labels (`database_specific.severity`) for accurate critical/high/medium/low mapping
|
|
89
|
+
- Falls back silently if the network is unreachable — use `--offline` to skip the lookup entirely
|
|
90
|
+
|
|
91
|
+
```bash
|
|
92
|
+
# Scan with live CVEs (default)
|
|
93
|
+
agentfort scan --repo .
|
|
94
|
+
|
|
95
|
+
# Air-gapped / no outbound network
|
|
96
|
+
agentfort scan --repo . --offline
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
## Advisory database
|
|
100
|
+
|
|
101
|
+
14 static advisories covering structural/behavioral risks from the [OWASP Agentic Top 10](https://owasp.org/www-project-top-10-for-large-language-model-applications/). Package CVEs are handled live by OSV.dev (see above).
|
|
102
|
+
|
|
103
|
+
| ID | Severity | Risk |
|
|
104
|
+
|---|---|---|
|
|
105
|
+
| AGSA-001 | Critical | LangChain ShellTool unrestricted shell execution |
|
|
106
|
+
| AGSA-002 | Critical | MCP server exposes shell execution tool |
|
|
107
|
+
| AGSA-003 | Critical | `eval()`/`exec()` in agent tool handler |
|
|
108
|
+
| AGSA-005 | Critical | AutoGen/CrewAI code execution without confirmation |
|
|
109
|
+
| AGSA-004 | High | Hardcoded API key or secret in MCP config |
|
|
110
|
+
| AGSA-006 | High | MCP filesystem server with overly broad path (`/`, `~`) |
|
|
111
|
+
| AGSA-007 | High | `subprocess` with `shell=True` or `os.system()` |
|
|
112
|
+
| AGSA-008 | High | Agent tool writes to arbitrary file paths |
|
|
113
|
+
| AGSA-010 | High | Prompt injection via unvalidated tool output |
|
|
114
|
+
| AGSA-013 | High | OpenAI Assistants `file_search`/`code_interpreter` without scope restriction |
|
|
115
|
+
| AGSA-009 | Medium | MCP server loaded via unverified `npx`/`uvx` package |
|
|
116
|
+
| AGSA-012 | Medium | Secrets exposed via `os.environ` in tool scope |
|
|
117
|
+
| AGSA-014 | Medium | Non-PyPI/non-npm dependency as framework component |
|
|
118
|
+
| AGSA-015 | Medium | System prompt in user-accessible config location |
|
|
119
|
+
|
|
120
|
+
## Risk score
|
|
121
|
+
|
|
122
|
+
`0–100`. Each finding adds: critical=40, high=15, medium=5, low=1. Capped at 100.
|
|
123
|
+
|
|
124
|
+
## Output formats
|
|
125
|
+
|
|
126
|
+
**Terminal** (default) — Rich panels with color-coded severity table and detail panels for critical/high findings.
|
|
127
|
+
|
|
128
|
+
**JSON** — Machine-readable. Progress messages go to stderr so stdout is clean for piping:
|
|
129
|
+
```bash
|
|
130
|
+
agentfort scan --repo . --format json 2>/dev/null | jq '.findings[] | select(.severity=="critical")'
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
**Markdown** — Full report with summary table and per-finding sections, suitable for GitHub issues or PR comments.
|
|
134
|
+
|
|
135
|
+
## Project structure
|
|
136
|
+
|
|
137
|
+
```
|
|
138
|
+
agentsentry/
|
|
139
|
+
├── agentsentry/
|
|
140
|
+
│ ├── cli.py # Click CLI entry point
|
|
141
|
+
│ ├── models.py # Finding, ScanResult dataclasses
|
|
142
|
+
│ ├── db/
|
|
143
|
+
│ │ ├── advisory.py # Advisory loader and index
|
|
144
|
+
│ │ └── data/ # AGSA-001.yaml … AGSA-015.yaml
|
|
145
|
+
│ ├── scanner/
|
|
146
|
+
│ │ ├── frameworks.py # requirements.txt / pyproject.toml / package.json
|
|
147
|
+
│ │ ├── mcp.py # MCP config file scanner
|
|
148
|
+
│ │ ├── osv.py # Live CVE lookup via OSV.dev API
|
|
149
|
+
│ │ ├── patterns.py # Python source pattern scanner
|
|
150
|
+
│ │ └── secrets.py # Hardcoded credential scanner
|
|
151
|
+
│ └── report/
|
|
152
|
+
│ └── formatter.py # Terminal, Markdown, JSON renderers
|
|
153
|
+
└── pyproject.toml
|
|
154
|
+
```
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
LICENSE
|
|
2
|
+
README.md
|
|
3
|
+
pyproject.toml
|
|
4
|
+
agentfort.egg-info/PKG-INFO
|
|
5
|
+
agentfort.egg-info/SOURCES.txt
|
|
6
|
+
agentfort.egg-info/dependency_links.txt
|
|
7
|
+
agentfort.egg-info/entry_points.txt
|
|
8
|
+
agentfort.egg-info/requires.txt
|
|
9
|
+
agentfort.egg-info/top_level.txt
|
|
10
|
+
agentsentry/__init__.py
|
|
11
|
+
agentsentry/cli.py
|
|
12
|
+
agentsentry/models.py
|
|
13
|
+
agentsentry/db/__init__.py
|
|
14
|
+
agentsentry/db/advisory.py
|
|
15
|
+
agentsentry/db/data/AGSA-001.yaml
|
|
16
|
+
agentsentry/db/data/AGSA-002.yaml
|
|
17
|
+
agentsentry/db/data/AGSA-003.yaml
|
|
18
|
+
agentsentry/db/data/AGSA-004.yaml
|
|
19
|
+
agentsentry/db/data/AGSA-005.yaml
|
|
20
|
+
agentsentry/db/data/AGSA-006.yaml
|
|
21
|
+
agentsentry/db/data/AGSA-007.yaml
|
|
22
|
+
agentsentry/db/data/AGSA-008.yaml
|
|
23
|
+
agentsentry/db/data/AGSA-009.yaml
|
|
24
|
+
agentsentry/db/data/AGSA-010.yaml
|
|
25
|
+
agentsentry/db/data/AGSA-012.yaml
|
|
26
|
+
agentsentry/db/data/AGSA-013.yaml
|
|
27
|
+
agentsentry/db/data/AGSA-014.yaml
|
|
28
|
+
agentsentry/db/data/AGSA-015.yaml
|
|
29
|
+
agentsentry/report/__init__.py
|
|
30
|
+
agentsentry/report/formatter.py
|
|
31
|
+
agentsentry/scanner/__init__.py
|
|
32
|
+
agentsentry/scanner/frameworks.py
|
|
33
|
+
agentsentry/scanner/mcp.py
|
|
34
|
+
agentsentry/scanner/osv.py
|
|
35
|
+
agentsentry/scanner/patterns.py
|
|
36
|
+
agentsentry/scanner/secrets.py
|
|
37
|
+
tests/test_scanners.py
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
agentsentry
|
|
File without changes
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
import sys
|
|
2
|
+
import time
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
5
|
+
import click
|
|
6
|
+
from rich.console import Console
|
|
7
|
+
|
|
8
|
+
from agentsentry.models import ScanResult, SEVERITY_ORDER
|
|
9
|
+
from agentsentry.db.advisory import load_advisories
|
|
10
|
+
from agentsentry.scanner.frameworks import scan_frameworks, detect_packages
|
|
11
|
+
from agentsentry.scanner.mcp import scan_mcp
|
|
12
|
+
from agentsentry.scanner.osv import scan_osv
|
|
13
|
+
from agentsentry.scanner.patterns import scan_patterns
|
|
14
|
+
from agentsentry.scanner.secrets import scan_secrets
|
|
15
|
+
from agentsentry.report.formatter import render_terminal, render_markdown, render_json
|
|
16
|
+
|
|
17
|
+
console = Console()
|
|
18
|
+
err_console = Console(stderr=True)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
@click.group()
|
|
22
|
+
@click.version_option("0.1.1", prog_name="agentfort")
|
|
23
|
+
def cli():
|
|
24
|
+
"""AgentFort — static scanner for AI agent security risks."""
|
|
25
|
+
pass
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
@cli.command()
|
|
29
|
+
@click.option("--repo", type=click.Path(exists=True, file_okay=False, path_type=Path),
|
|
30
|
+
default=None, help="Path to the repository to scan.")
|
|
31
|
+
@click.option("--mcp-config", type=click.Path(exists=True, dir_okay=False, path_type=Path),
|
|
32
|
+
default=None, help="Path to a specific MCP config file to scan.")
|
|
33
|
+
@click.option("--format", "fmt", type=click.Choice(["terminal", "md", "json"]), default="terminal",
|
|
34
|
+
show_default=True, help="Output format.")
|
|
35
|
+
@click.option("--output", "-o", type=click.Path(path_type=Path), default=None,
|
|
36
|
+
help="Write report to a file instead of stdout.")
|
|
37
|
+
@click.option("--min-severity", type=click.Choice(SEVERITY_ORDER), default="low",
|
|
38
|
+
show_default=True, help="Only show findings at or above this severity.")
|
|
39
|
+
@click.option("--fail-on-critical/--no-fail-on-critical", default=True, show_default=True,
|
|
40
|
+
help="Exit with code 1 if any critical findings exist.")
|
|
41
|
+
@click.option("--offline/--no-offline", default=False, show_default=True,
|
|
42
|
+
help="Skip OSV.dev live CVE lookup (use for air-gapped or CI environments).")
|
|
43
|
+
def scan(repo, mcp_config, fmt, output, min_severity, fail_on_critical, offline):
|
|
44
|
+
"""Scan a repository or MCP config file for AI agent security risks."""
|
|
45
|
+
|
|
46
|
+
if not repo and not mcp_config:
|
|
47
|
+
# Default to current directory
|
|
48
|
+
repo = Path(".")
|
|
49
|
+
|
|
50
|
+
start = time.monotonic()
|
|
51
|
+
advisories = load_advisories()
|
|
52
|
+
findings = []
|
|
53
|
+
|
|
54
|
+
if repo:
|
|
55
|
+
err_console.print(f"[dim]Scanning repository: {repo.resolve()}[/dim]")
|
|
56
|
+
findings.extend(scan_frameworks(repo, advisories))
|
|
57
|
+
findings.extend(scan_mcp(repo_path=repo))
|
|
58
|
+
findings.extend(scan_patterns(repo, advisories))
|
|
59
|
+
findings.extend(scan_secrets(repo))
|
|
60
|
+
if not offline:
|
|
61
|
+
err_console.print("[dim]Querying OSV.dev for live CVEs...[/dim]")
|
|
62
|
+
packages = detect_packages(repo)
|
|
63
|
+
findings.extend(scan_osv(packages))
|
|
64
|
+
|
|
65
|
+
if mcp_config:
|
|
66
|
+
err_console.print(f"[dim]Scanning MCP config: {mcp_config.resolve()}[/dim]")
|
|
67
|
+
findings.extend(scan_mcp(config_path=mcp_config))
|
|
68
|
+
|
|
69
|
+
elapsed = time.monotonic() - start
|
|
70
|
+
result = ScanResult(
|
|
71
|
+
findings=findings,
|
|
72
|
+
scanned_path=repo or mcp_config,
|
|
73
|
+
scan_duration_seconds=elapsed,
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
if min_severity != "low":
|
|
77
|
+
result = result.filter_min_severity(min_severity)
|
|
78
|
+
|
|
79
|
+
if fmt == "terminal":
|
|
80
|
+
if output:
|
|
81
|
+
# Write terminal-stripped output to file
|
|
82
|
+
from rich.console import Console as RichConsole
|
|
83
|
+
file_console = RichConsole(file=open(output, "w"), highlight=False)
|
|
84
|
+
render_terminal(result)
|
|
85
|
+
else:
|
|
86
|
+
render_terminal(result)
|
|
87
|
+
report_str = None
|
|
88
|
+
elif fmt == "md":
|
|
89
|
+
report_str = render_markdown(result)
|
|
90
|
+
else:
|
|
91
|
+
report_str = render_json(result)
|
|
92
|
+
|
|
93
|
+
if report_str is not None:
|
|
94
|
+
if output:
|
|
95
|
+
output.write_text(report_str)
|
|
96
|
+
console.print(f"[green]Report written to {output}[/green]")
|
|
97
|
+
else:
|
|
98
|
+
click.echo(report_str)
|
|
99
|
+
|
|
100
|
+
if fail_on_critical and any(f.severity == "critical" for f in result.findings):
|
|
101
|
+
sys.exit(1)
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
@cli.group()
|
|
105
|
+
def advisories():
|
|
106
|
+
"""Manage and browse the advisory database."""
|
|
107
|
+
pass
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
@advisories.command("list")
|
|
111
|
+
@click.option("--severity", type=click.Choice(SEVERITY_ORDER + ["all"]), default="all",
|
|
112
|
+
help="Filter by severity.")
|
|
113
|
+
def advisories_list(severity):
|
|
114
|
+
"""List all advisories in the database."""
|
|
115
|
+
from rich.table import Table
|
|
116
|
+
from rich import box
|
|
117
|
+
|
|
118
|
+
adv_list = load_advisories()
|
|
119
|
+
if severity != "all":
|
|
120
|
+
adv_list = [a for a in adv_list if a.severity == severity]
|
|
121
|
+
|
|
122
|
+
table = Table(box=box.ROUNDED, expand=True)
|
|
123
|
+
table.add_column("ID", style="bold cyan", width=12)
|
|
124
|
+
table.add_column("Severity", width=10)
|
|
125
|
+
table.add_column("Frameworks", width=20)
|
|
126
|
+
table.add_column("Title")
|
|
127
|
+
|
|
128
|
+
from agentsentry.report.formatter import SEVERITY_COLORS, SEVERITY_EMOJI
|
|
129
|
+
from rich.text import Text
|
|
130
|
+
|
|
131
|
+
for adv in sorted(adv_list, key=lambda a: (SEVERITY_ORDER.index(a.severity), a.id)):
|
|
132
|
+
sev_text = Text(
|
|
133
|
+
f"{SEVERITY_EMOJI.get(adv.severity, '')} {adv.severity.upper()}",
|
|
134
|
+
style=SEVERITY_COLORS.get(adv.severity, "white"),
|
|
135
|
+
)
|
|
136
|
+
frameworks = ", ".join(adv.frameworks[:3]) + ("..." if len(adv.frameworks) > 3 else "")
|
|
137
|
+
table.add_row(adv.id, sev_text, frameworks, adv.title)
|
|
138
|
+
|
|
139
|
+
console.print(table)
|
|
140
|
+
console.print(f"\n[dim]{len(adv_list)} advisories[/dim]")
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
@advisories.command("show")
|
|
144
|
+
@click.argument("advisory_id")
|
|
145
|
+
def advisories_show(advisory_id):
|
|
146
|
+
"""Show full detail for an advisory by ID."""
|
|
147
|
+
from rich.panel import Panel
|
|
148
|
+
from rich.markdown import Markdown
|
|
149
|
+
|
|
150
|
+
adv_list = load_advisories()
|
|
151
|
+
adv = next((a for a in adv_list if a.id.upper() == advisory_id.upper()), None)
|
|
152
|
+
if not adv:
|
|
153
|
+
console.print(f"[red]Advisory {advisory_id} not found.[/red]")
|
|
154
|
+
sys.exit(1)
|
|
155
|
+
|
|
156
|
+
from agentsentry.report.formatter import SEVERITY_COLORS, SEVERITY_EMOJI
|
|
157
|
+
color = SEVERITY_COLORS.get(adv.severity, "white")
|
|
158
|
+
|
|
159
|
+
content = (
|
|
160
|
+
f"**Severity:** {adv.severity.upper()}\n"
|
|
161
|
+
f"**Frameworks:** {', '.join(adv.frameworks)}\n"
|
|
162
|
+
f"**Attack Types:** {', '.join(adv.attack_types)}\n\n"
|
|
163
|
+
f"**Description:**\n{adv.description}\n\n"
|
|
164
|
+
f"**Mitigation:**\n{adv.mitigation}\n\n"
|
|
165
|
+
f"**References:**\n" + "\n".join(f"- {r}" for r in adv.references)
|
|
166
|
+
)
|
|
167
|
+
|
|
168
|
+
console.print(Panel(
|
|
169
|
+
Markdown(content),
|
|
170
|
+
title=f"[{color}]{SEVERITY_EMOJI.get(adv.severity, '')} {adv.id}: {adv.title}[/{color}]",
|
|
171
|
+
border_style=color.replace("bold ", ""),
|
|
172
|
+
padding=(1, 2),
|
|
173
|
+
))
|