llmsafe 0.2.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.
- llmsafe-0.2.1/LICENSE +21 -0
- llmsafe-0.2.1/PKG-INFO +268 -0
- llmsafe-0.2.1/README.md +234 -0
- llmsafe-0.2.1/benchmarks/__init__.py +1 -0
- llmsafe-0.2.1/benchmarks/cases/insecure_mcp.json +10 -0
- llmsafe-0.2.1/benchmarks/cases/interprocedural_agent.py +24 -0
- llmsafe-0.2.1/benchmarks/cases/safe_agent.py +17 -0
- llmsafe-0.2.1/benchmarks/cases/safe_interprocedural.py +16 -0
- llmsafe-0.2.1/benchmarks/cases/vulnerable_agent.py +28 -0
- llmsafe-0.2.1/benchmarks/manifest.json +35 -0
- llmsafe-0.2.1/benchmarks/run.py +54 -0
- llmsafe-0.2.1/llmsafe/__init__.py +3 -0
- llmsafe-0.2.1/llmsafe/__main__.py +6 -0
- llmsafe-0.2.1/llmsafe/baseline.py +168 -0
- llmsafe-0.2.1/llmsafe/catalog.py +228 -0
- llmsafe-0.2.1/llmsafe/cli.py +216 -0
- llmsafe-0.2.1/llmsafe/config.py +125 -0
- llmsafe-0.2.1/llmsafe/models.py +97 -0
- llmsafe-0.2.1/llmsafe/rules/__init__.py +19 -0
- llmsafe-0.2.1/llmsafe/rules/agents.py +116 -0
- llmsafe-0.2.1/llmsafe/rules/ast_helpers.py +27 -0
- llmsafe-0.2.1/llmsafe/rules/base.py +30 -0
- llmsafe-0.2.1/llmsafe/rules/dataflow.py +581 -0
- llmsafe-0.2.1/llmsafe/rules/eval.py +74 -0
- llmsafe-0.2.1/llmsafe/rules/mcp.py +98 -0
- llmsafe-0.2.1/llmsafe/rules/prompts.py +83 -0
- llmsafe-0.2.1/llmsafe/rules/secrets.py +98 -0
- llmsafe-0.2.1/llmsafe/rules/shell.py +67 -0
- llmsafe-0.2.1/llmsafe/sarif.py +133 -0
- llmsafe-0.2.1/llmsafe/scanner.py +187 -0
- llmsafe-0.2.1/llmsafe.egg-info/PKG-INFO +268 -0
- llmsafe-0.2.1/llmsafe.egg-info/SOURCES.txt +45 -0
- llmsafe-0.2.1/llmsafe.egg-info/dependency_links.txt +1 -0
- llmsafe-0.2.1/llmsafe.egg-info/entry_points.txt +2 -0
- llmsafe-0.2.1/llmsafe.egg-info/requires.txt +10 -0
- llmsafe-0.2.1/llmsafe.egg-info/top_level.txt +2 -0
- llmsafe-0.2.1/pyproject.toml +74 -0
- llmsafe-0.2.1/setup.cfg +4 -0
- llmsafe-0.2.1/tests/test_baseline.py +88 -0
- llmsafe-0.2.1/tests/test_benchmark.py +18 -0
- llmsafe-0.2.1/tests/test_catalog.py +99 -0
- llmsafe-0.2.1/tests/test_cli.py +100 -0
- llmsafe-0.2.1/tests/test_config.py +71 -0
- llmsafe-0.2.1/tests/test_dataflow.py +233 -0
- llmsafe-0.2.1/tests/test_rules.py +96 -0
- llmsafe-0.2.1/tests/test_sarif.py +66 -0
- llmsafe-0.2.1/tests/test_scanner.py +67 -0
llmsafe-0.2.1/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 LLMSafe 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.
|
llmsafe-0.2.1/PKG-INFO
ADDED
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: llmsafe
|
|
3
|
+
Version: 0.2.1
|
|
4
|
+
Summary: Open-source security scanner for AI-powered and agentic applications.
|
|
5
|
+
Author: LLMSafe contributors
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/rezerpaul-crypto/llmsafe
|
|
8
|
+
Project-URL: Documentation, https://github.com/rezerpaul-crypto/llmsafe/tree/main/docs
|
|
9
|
+
Project-URL: Source, https://github.com/rezerpaul-crypto/llmsafe
|
|
10
|
+
Project-URL: Changelog, https://github.com/rezerpaul-crypto/llmsafe/blob/main/CHANGELOG.md
|
|
11
|
+
Project-URL: Issues, https://github.com/rezerpaul-crypto/llmsafe/issues
|
|
12
|
+
Keywords: ai,agents,llm,mcp,security,static-analysis
|
|
13
|
+
Classifier: Development Status :: 3 - Alpha
|
|
14
|
+
Classifier: Environment :: Console
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
22
|
+
Classifier: Topic :: Security
|
|
23
|
+
Requires-Python: >=3.9
|
|
24
|
+
Description-Content-Type: text/markdown
|
|
25
|
+
License-File: LICENSE
|
|
26
|
+
Requires-Dist: tomli>=2.0; python_version < "3.11"
|
|
27
|
+
Provides-Extra: dev
|
|
28
|
+
Requires-Dist: build>=1.2; extra == "dev"
|
|
29
|
+
Requires-Dist: pytest>=7.4; extra == "dev"
|
|
30
|
+
Requires-Dist: pytest-cov>=4.1; extra == "dev"
|
|
31
|
+
Requires-Dist: ruff>=0.6; extra == "dev"
|
|
32
|
+
Requires-Dist: twine>=6.0; extra == "dev"
|
|
33
|
+
Dynamic: license-file
|
|
34
|
+
|
|
35
|
+
# LLMSafe
|
|
36
|
+
|
|
37
|
+
[](https://github.com/rezerpaul-crypto/llmsafe/actions/workflows/ci.yml)
|
|
38
|
+
[](https://github.com/rezerpaul-crypto/llmsafe/actions/workflows/code-scanning.yml)
|
|
39
|
+
[](https://pypi.org/project/llmsafe/)
|
|
40
|
+
[](LICENSE)
|
|
41
|
+
[](pyproject.toml)
|
|
42
|
+
|
|
43
|
+
LLMSafe is an open-source static security scanner for AI-powered and agentic Python
|
|
44
|
+
applications. It traces user input and model-controlled data into dangerous capabilities such as
|
|
45
|
+
code execution, shells, SQL, outbound requests, and dynamic tool dispatch.
|
|
46
|
+
|
|
47
|
+
It runs locally. Source code is not uploaded to a model or external analysis service.
|
|
48
|
+
|
|
49
|
+
> **Status:** `v0.2.1` is an early release. LLMSafe provides reviewable security signals, not a
|
|
50
|
+
> guarantee that an AI system is secure.
|
|
51
|
+
|
|
52
|
+
## Why another security scanner?
|
|
53
|
+
|
|
54
|
+
Traditional Python scanners are good at finding dangerous APIs. Agentic applications add a
|
|
55
|
+
different question: **can untrusted user or model output reach that capability?**
|
|
56
|
+
|
|
57
|
+
```mermaid
|
|
58
|
+
flowchart LR
|
|
59
|
+
A["User input"] --> C["Assignments and transforms"]
|
|
60
|
+
B["Model output"] --> C
|
|
61
|
+
C --> D["Shell / eval / SQL / HTTP / tool dispatch"]
|
|
62
|
+
D --> E["Finding with source-to-sink evidence"]
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
LLMSafe combines focused API checks with AST-based dataflow and agent-framework rules:
|
|
66
|
+
|
|
67
|
+
```python
|
|
68
|
+
def run_agent(client, user_input):
|
|
69
|
+
response = client.responses.create(input=user_input)
|
|
70
|
+
generated_code = response.output_text
|
|
71
|
+
return eval(generated_code)
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
The scanner reports both the dangerous `eval()` and the path from the model response to that
|
|
75
|
+
sink:
|
|
76
|
+
|
|
77
|
+
```text
|
|
78
|
+
agent.py:4:12: CRITICAL FLOW001 Untrusted data reaches code execution
|
|
79
|
+
Untrusted or model-controlled data flows into eval(). Source: model, user.
|
|
80
|
+
Trace 2:16: model source: client.responses.create
|
|
81
|
+
Trace 1:23: user source: user_input
|
|
82
|
+
Trace 4:12: reaches eval
|
|
83
|
+
Fix: Replace dynamic execution with a typed parser and an allow-listed operation.
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
## Detection coverage
|
|
87
|
+
|
|
88
|
+
| Family | Rule IDs | Examples |
|
|
89
|
+
| --- | --- | --- |
|
|
90
|
+
| Dataflow | `FLOW001`–`FLOW005` | Model/user data reaching code, shell, SQL, URL, or tool dispatch |
|
|
91
|
+
| Agent tools | `AGENT001`–`AGENT003` | Python/shell tools, dangerous capability flags, disabled approval |
|
|
92
|
+
| Secrets | `SECRET001`–`SECRET005` | Provider keys, tokens, private keys, hard-coded credentials |
|
|
93
|
+
| Python | `PY001`–`PY004` | `eval`, `exec`, unsafe pickle and YAML deserialization |
|
|
94
|
+
| Shell | `SHELL001`–`SHELL002` | `os.system` and `subprocess(..., shell=True)` |
|
|
95
|
+
| Prompt trust | `LLM001` | Dynamic data interpolated into system/developer instructions |
|
|
96
|
+
| MCP | `MCP001`–`MCP003` | Shell launch, remote HTTP, wildcard tool permissions |
|
|
97
|
+
|
|
98
|
+
See the [complete rule catalog](docs/rules.md) and [threat model](docs/threat-model.md).
|
|
99
|
+
|
|
100
|
+
Integrations can query the same catalog without parsing documentation:
|
|
101
|
+
|
|
102
|
+
```bash
|
|
103
|
+
llmsafe --list-rules
|
|
104
|
+
llmsafe --list-rules --format json
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
The versioned JSON output includes every stable ID, severity, family, description, and remediation.
|
|
108
|
+
|
|
109
|
+
## Install
|
|
110
|
+
|
|
111
|
+
LLMSafe supports Python 3.9 and newer.
|
|
112
|
+
|
|
113
|
+
```bash
|
|
114
|
+
python3 -m venv .venv
|
|
115
|
+
source .venv/bin/activate
|
|
116
|
+
python -m pip install llmsafe
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
For development:
|
|
120
|
+
|
|
121
|
+
```bash
|
|
122
|
+
git clone https://github.com/rezerpaul-crypto/llmsafe.git
|
|
123
|
+
cd llmsafe
|
|
124
|
+
python3 -m venv .venv
|
|
125
|
+
source .venv/bin/activate
|
|
126
|
+
python -m pip install -e ".[dev]"
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
## Use the CLI
|
|
130
|
+
|
|
131
|
+
Scan the current repository:
|
|
132
|
+
|
|
133
|
+
```bash
|
|
134
|
+
llmsafe .
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
Scan selected paths and fail on medium-or-higher findings:
|
|
138
|
+
|
|
139
|
+
```bash
|
|
140
|
+
llmsafe src agent.py --fail-on medium --exclude "generated/**"
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
Generate machine-readable reports:
|
|
144
|
+
|
|
145
|
+
```bash
|
|
146
|
+
llmsafe . --format json --output reports/llmsafe.json
|
|
147
|
+
llmsafe . --format sarif --output reports/llmsafe.sarif
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
Exit codes are stable for automation:
|
|
151
|
+
|
|
152
|
+
| Code | Meaning |
|
|
153
|
+
| --- | --- |
|
|
154
|
+
| `0` | No finding at or above the selected threshold |
|
|
155
|
+
| `1` | At least one finding reached the selected threshold |
|
|
156
|
+
| `2` | Invalid configuration, missing target, or scan error |
|
|
157
|
+
|
|
158
|
+
## Repository policy
|
|
159
|
+
|
|
160
|
+
Commit a `.llmsafe.toml` file:
|
|
161
|
+
|
|
162
|
+
```toml
|
|
163
|
+
[llmsafe]
|
|
164
|
+
exclude = ["generated/**", "vendor/**"]
|
|
165
|
+
fail_on = "high"
|
|
166
|
+
max_file_size = 1000000
|
|
167
|
+
disabled_rules = ["PY004"]
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
CLI options override or extend repository policy. Policy can also live under `[tool.llmsafe]` in
|
|
171
|
+
`pyproject.toml`. See [configuration](docs/configuration.md).
|
|
172
|
+
|
|
173
|
+
### Adopt LLMSafe without ignoring new risk
|
|
174
|
+
|
|
175
|
+
Existing repositories can review and commit a baseline of current findings:
|
|
176
|
+
|
|
177
|
+
```bash
|
|
178
|
+
llmsafe . --write-baseline .llmsafe-baseline.json
|
|
179
|
+
llmsafe . --baseline .llmsafe-baseline.json
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
The second command reports and fails only on findings not represented in the baseline. Matching is
|
|
183
|
+
line-independent, duplicate-aware, and deterministic so ordinary code movement does not create
|
|
184
|
+
noise while an additional dangerous operation is still reported. Baselines are review artifacts,
|
|
185
|
+
not permanent suppressions; see [incremental adoption](docs/baselines.md).
|
|
186
|
+
|
|
187
|
+
### Suppress one reviewed finding
|
|
188
|
+
|
|
189
|
+
Place a narrow suppression on the finding line or immediately above it:
|
|
190
|
+
|
|
191
|
+
```python
|
|
192
|
+
# llmsafe: ignore[PY001] -- expression is generated from a fixed internal grammar
|
|
193
|
+
result = eval(TRUSTED_EXPRESSION)
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
Prefer a rule-specific suppression over a bare `llmsafe: ignore`.
|
|
197
|
+
|
|
198
|
+
## GitHub Code Scanning
|
|
199
|
+
|
|
200
|
+
The repository includes a reusable composite action. A consumer workflow can scan, upload SARIF,
|
|
201
|
+
then enforce the configured threshold:
|
|
202
|
+
|
|
203
|
+
```yaml
|
|
204
|
+
permissions:
|
|
205
|
+
contents: read
|
|
206
|
+
security-events: write
|
|
207
|
+
|
|
208
|
+
steps:
|
|
209
|
+
- uses: actions/checkout@v7
|
|
210
|
+
- uses: actions/setup-python@v7
|
|
211
|
+
with:
|
|
212
|
+
python-version: "3.12"
|
|
213
|
+
- id: llmsafe
|
|
214
|
+
continue-on-error: true
|
|
215
|
+
uses: rezerpaul-crypto/llmsafe@v0.2.1
|
|
216
|
+
with:
|
|
217
|
+
path: .
|
|
218
|
+
fail-on: high
|
|
219
|
+
- if: always()
|
|
220
|
+
uses: github/codeql-action/upload-sarif@v4
|
|
221
|
+
with:
|
|
222
|
+
sarif_file: ${{ steps.llmsafe.outputs.sarif-file }}
|
|
223
|
+
- if: steps.llmsafe.outcome == 'failure'
|
|
224
|
+
run: exit 1
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
The workflow uses only `contents: read` and `security-events: write`.
|
|
228
|
+
|
|
229
|
+
## Pre-commit
|
|
230
|
+
|
|
231
|
+
```yaml
|
|
232
|
+
repos:
|
|
233
|
+
- repo: https://github.com/rezerpaul-crypto/llmsafe
|
|
234
|
+
rev: v0.2.1
|
|
235
|
+
hooks:
|
|
236
|
+
- id: llmsafe
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
## Benchmark
|
|
240
|
+
|
|
241
|
+
The checked-in benchmark exercises vulnerable and safe agent boundaries:
|
|
242
|
+
|
|
243
|
+
```bash
|
|
244
|
+
python -m benchmarks.run
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
Current expectations cover 18 rule-level signals across direct and local-helper code execution,
|
|
248
|
+
shell execution, SQL, SSRF, tool dispatch, prompt boundaries, high-impact tools, approval bypasses,
|
|
249
|
+
and MCP. This is a regression corpus—not an industry benchmark or a claim of real-world detection
|
|
250
|
+
rate. See the [benchmark methodology](docs/benchmark.md).
|
|
251
|
+
|
|
252
|
+
## How LLMSafe fits
|
|
253
|
+
|
|
254
|
+
| Tool category | Primary strength | LLMSafe relationship |
|
|
255
|
+
| --- | --- | --- |
|
|
256
|
+
| General Python SAST | Broad language and API security checks | Complementary; LLMSafe focuses on AI/agent trust boundaries |
|
|
257
|
+
| Pattern-rule engines | Highly customizable organizational rules | LLMSafe supplies opinionated agent rules without rule authoring |
|
|
258
|
+
| Dependency scanners | Known vulnerable packages and supply chain | Out of scope; run alongside LLMSafe |
|
|
259
|
+
| Runtime guardrails | Enforce live policy and monitor model/tool calls | Out of scope; LLMSafe reviews source and configuration before runtime |
|
|
260
|
+
|
|
261
|
+
Read the [architecture](docs/architecture.md) for implementation boundaries and tradeoffs.
|
|
262
|
+
|
|
263
|
+
## Contributing and security
|
|
264
|
+
|
|
265
|
+
Contributions are welcome. Start with [CONTRIBUTING.md](CONTRIBUTING.md) and the public
|
|
266
|
+
[roadmap](ROADMAP.md). Report vulnerabilities privately according to [SECURITY.md](SECURITY.md).
|
|
267
|
+
|
|
268
|
+
LLMSafe is released under the [MIT License](LICENSE).
|
llmsafe-0.2.1/README.md
ADDED
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
# LLMSafe
|
|
2
|
+
|
|
3
|
+
[](https://github.com/rezerpaul-crypto/llmsafe/actions/workflows/ci.yml)
|
|
4
|
+
[](https://github.com/rezerpaul-crypto/llmsafe/actions/workflows/code-scanning.yml)
|
|
5
|
+
[](https://pypi.org/project/llmsafe/)
|
|
6
|
+
[](LICENSE)
|
|
7
|
+
[](pyproject.toml)
|
|
8
|
+
|
|
9
|
+
LLMSafe is an open-source static security scanner for AI-powered and agentic Python
|
|
10
|
+
applications. It traces user input and model-controlled data into dangerous capabilities such as
|
|
11
|
+
code execution, shells, SQL, outbound requests, and dynamic tool dispatch.
|
|
12
|
+
|
|
13
|
+
It runs locally. Source code is not uploaded to a model or external analysis service.
|
|
14
|
+
|
|
15
|
+
> **Status:** `v0.2.1` is an early release. LLMSafe provides reviewable security signals, not a
|
|
16
|
+
> guarantee that an AI system is secure.
|
|
17
|
+
|
|
18
|
+
## Why another security scanner?
|
|
19
|
+
|
|
20
|
+
Traditional Python scanners are good at finding dangerous APIs. Agentic applications add a
|
|
21
|
+
different question: **can untrusted user or model output reach that capability?**
|
|
22
|
+
|
|
23
|
+
```mermaid
|
|
24
|
+
flowchart LR
|
|
25
|
+
A["User input"] --> C["Assignments and transforms"]
|
|
26
|
+
B["Model output"] --> C
|
|
27
|
+
C --> D["Shell / eval / SQL / HTTP / tool dispatch"]
|
|
28
|
+
D --> E["Finding with source-to-sink evidence"]
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
LLMSafe combines focused API checks with AST-based dataflow and agent-framework rules:
|
|
32
|
+
|
|
33
|
+
```python
|
|
34
|
+
def run_agent(client, user_input):
|
|
35
|
+
response = client.responses.create(input=user_input)
|
|
36
|
+
generated_code = response.output_text
|
|
37
|
+
return eval(generated_code)
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
The scanner reports both the dangerous `eval()` and the path from the model response to that
|
|
41
|
+
sink:
|
|
42
|
+
|
|
43
|
+
```text
|
|
44
|
+
agent.py:4:12: CRITICAL FLOW001 Untrusted data reaches code execution
|
|
45
|
+
Untrusted or model-controlled data flows into eval(). Source: model, user.
|
|
46
|
+
Trace 2:16: model source: client.responses.create
|
|
47
|
+
Trace 1:23: user source: user_input
|
|
48
|
+
Trace 4:12: reaches eval
|
|
49
|
+
Fix: Replace dynamic execution with a typed parser and an allow-listed operation.
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
## Detection coverage
|
|
53
|
+
|
|
54
|
+
| Family | Rule IDs | Examples |
|
|
55
|
+
| --- | --- | --- |
|
|
56
|
+
| Dataflow | `FLOW001`–`FLOW005` | Model/user data reaching code, shell, SQL, URL, or tool dispatch |
|
|
57
|
+
| Agent tools | `AGENT001`–`AGENT003` | Python/shell tools, dangerous capability flags, disabled approval |
|
|
58
|
+
| Secrets | `SECRET001`–`SECRET005` | Provider keys, tokens, private keys, hard-coded credentials |
|
|
59
|
+
| Python | `PY001`–`PY004` | `eval`, `exec`, unsafe pickle and YAML deserialization |
|
|
60
|
+
| Shell | `SHELL001`–`SHELL002` | `os.system` and `subprocess(..., shell=True)` |
|
|
61
|
+
| Prompt trust | `LLM001` | Dynamic data interpolated into system/developer instructions |
|
|
62
|
+
| MCP | `MCP001`–`MCP003` | Shell launch, remote HTTP, wildcard tool permissions |
|
|
63
|
+
|
|
64
|
+
See the [complete rule catalog](docs/rules.md) and [threat model](docs/threat-model.md).
|
|
65
|
+
|
|
66
|
+
Integrations can query the same catalog without parsing documentation:
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
llmsafe --list-rules
|
|
70
|
+
llmsafe --list-rules --format json
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
The versioned JSON output includes every stable ID, severity, family, description, and remediation.
|
|
74
|
+
|
|
75
|
+
## Install
|
|
76
|
+
|
|
77
|
+
LLMSafe supports Python 3.9 and newer.
|
|
78
|
+
|
|
79
|
+
```bash
|
|
80
|
+
python3 -m venv .venv
|
|
81
|
+
source .venv/bin/activate
|
|
82
|
+
python -m pip install llmsafe
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
For development:
|
|
86
|
+
|
|
87
|
+
```bash
|
|
88
|
+
git clone https://github.com/rezerpaul-crypto/llmsafe.git
|
|
89
|
+
cd llmsafe
|
|
90
|
+
python3 -m venv .venv
|
|
91
|
+
source .venv/bin/activate
|
|
92
|
+
python -m pip install -e ".[dev]"
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
## Use the CLI
|
|
96
|
+
|
|
97
|
+
Scan the current repository:
|
|
98
|
+
|
|
99
|
+
```bash
|
|
100
|
+
llmsafe .
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
Scan selected paths and fail on medium-or-higher findings:
|
|
104
|
+
|
|
105
|
+
```bash
|
|
106
|
+
llmsafe src agent.py --fail-on medium --exclude "generated/**"
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
Generate machine-readable reports:
|
|
110
|
+
|
|
111
|
+
```bash
|
|
112
|
+
llmsafe . --format json --output reports/llmsafe.json
|
|
113
|
+
llmsafe . --format sarif --output reports/llmsafe.sarif
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
Exit codes are stable for automation:
|
|
117
|
+
|
|
118
|
+
| Code | Meaning |
|
|
119
|
+
| --- | --- |
|
|
120
|
+
| `0` | No finding at or above the selected threshold |
|
|
121
|
+
| `1` | At least one finding reached the selected threshold |
|
|
122
|
+
| `2` | Invalid configuration, missing target, or scan error |
|
|
123
|
+
|
|
124
|
+
## Repository policy
|
|
125
|
+
|
|
126
|
+
Commit a `.llmsafe.toml` file:
|
|
127
|
+
|
|
128
|
+
```toml
|
|
129
|
+
[llmsafe]
|
|
130
|
+
exclude = ["generated/**", "vendor/**"]
|
|
131
|
+
fail_on = "high"
|
|
132
|
+
max_file_size = 1000000
|
|
133
|
+
disabled_rules = ["PY004"]
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
CLI options override or extend repository policy. Policy can also live under `[tool.llmsafe]` in
|
|
137
|
+
`pyproject.toml`. See [configuration](docs/configuration.md).
|
|
138
|
+
|
|
139
|
+
### Adopt LLMSafe without ignoring new risk
|
|
140
|
+
|
|
141
|
+
Existing repositories can review and commit a baseline of current findings:
|
|
142
|
+
|
|
143
|
+
```bash
|
|
144
|
+
llmsafe . --write-baseline .llmsafe-baseline.json
|
|
145
|
+
llmsafe . --baseline .llmsafe-baseline.json
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
The second command reports and fails only on findings not represented in the baseline. Matching is
|
|
149
|
+
line-independent, duplicate-aware, and deterministic so ordinary code movement does not create
|
|
150
|
+
noise while an additional dangerous operation is still reported. Baselines are review artifacts,
|
|
151
|
+
not permanent suppressions; see [incremental adoption](docs/baselines.md).
|
|
152
|
+
|
|
153
|
+
### Suppress one reviewed finding
|
|
154
|
+
|
|
155
|
+
Place a narrow suppression on the finding line or immediately above it:
|
|
156
|
+
|
|
157
|
+
```python
|
|
158
|
+
# llmsafe: ignore[PY001] -- expression is generated from a fixed internal grammar
|
|
159
|
+
result = eval(TRUSTED_EXPRESSION)
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
Prefer a rule-specific suppression over a bare `llmsafe: ignore`.
|
|
163
|
+
|
|
164
|
+
## GitHub Code Scanning
|
|
165
|
+
|
|
166
|
+
The repository includes a reusable composite action. A consumer workflow can scan, upload SARIF,
|
|
167
|
+
then enforce the configured threshold:
|
|
168
|
+
|
|
169
|
+
```yaml
|
|
170
|
+
permissions:
|
|
171
|
+
contents: read
|
|
172
|
+
security-events: write
|
|
173
|
+
|
|
174
|
+
steps:
|
|
175
|
+
- uses: actions/checkout@v7
|
|
176
|
+
- uses: actions/setup-python@v7
|
|
177
|
+
with:
|
|
178
|
+
python-version: "3.12"
|
|
179
|
+
- id: llmsafe
|
|
180
|
+
continue-on-error: true
|
|
181
|
+
uses: rezerpaul-crypto/llmsafe@v0.2.1
|
|
182
|
+
with:
|
|
183
|
+
path: .
|
|
184
|
+
fail-on: high
|
|
185
|
+
- if: always()
|
|
186
|
+
uses: github/codeql-action/upload-sarif@v4
|
|
187
|
+
with:
|
|
188
|
+
sarif_file: ${{ steps.llmsafe.outputs.sarif-file }}
|
|
189
|
+
- if: steps.llmsafe.outcome == 'failure'
|
|
190
|
+
run: exit 1
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
The workflow uses only `contents: read` and `security-events: write`.
|
|
194
|
+
|
|
195
|
+
## Pre-commit
|
|
196
|
+
|
|
197
|
+
```yaml
|
|
198
|
+
repos:
|
|
199
|
+
- repo: https://github.com/rezerpaul-crypto/llmsafe
|
|
200
|
+
rev: v0.2.1
|
|
201
|
+
hooks:
|
|
202
|
+
- id: llmsafe
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
## Benchmark
|
|
206
|
+
|
|
207
|
+
The checked-in benchmark exercises vulnerable and safe agent boundaries:
|
|
208
|
+
|
|
209
|
+
```bash
|
|
210
|
+
python -m benchmarks.run
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
Current expectations cover 18 rule-level signals across direct and local-helper code execution,
|
|
214
|
+
shell execution, SQL, SSRF, tool dispatch, prompt boundaries, high-impact tools, approval bypasses,
|
|
215
|
+
and MCP. This is a regression corpus—not an industry benchmark or a claim of real-world detection
|
|
216
|
+
rate. See the [benchmark methodology](docs/benchmark.md).
|
|
217
|
+
|
|
218
|
+
## How LLMSafe fits
|
|
219
|
+
|
|
220
|
+
| Tool category | Primary strength | LLMSafe relationship |
|
|
221
|
+
| --- | --- | --- |
|
|
222
|
+
| General Python SAST | Broad language and API security checks | Complementary; LLMSafe focuses on AI/agent trust boundaries |
|
|
223
|
+
| Pattern-rule engines | Highly customizable organizational rules | LLMSafe supplies opinionated agent rules without rule authoring |
|
|
224
|
+
| Dependency scanners | Known vulnerable packages and supply chain | Out of scope; run alongside LLMSafe |
|
|
225
|
+
| Runtime guardrails | Enforce live policy and monitor model/tool calls | Out of scope; LLMSafe reviews source and configuration before runtime |
|
|
226
|
+
|
|
227
|
+
Read the [architecture](docs/architecture.md) for implementation boundaries and tradeoffs.
|
|
228
|
+
|
|
229
|
+
## Contributing and security
|
|
230
|
+
|
|
231
|
+
Contributions are welcome. Start with [CONTRIBUTING.md](CONTRIBUTING.md) and the public
|
|
232
|
+
[roadmap](ROADMAP.md). Report vulnerabilities privately according to [SECURITY.md](SECURITY.md).
|
|
233
|
+
|
|
234
|
+
LLMSafe is released under the [MIT License](LICENSE).
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Reproducible LLMSafe security benchmark."""
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
"""Vulnerable local-helper flows used by the LLMSafe regression benchmark."""
|
|
2
|
+
|
|
3
|
+
import subprocess
|
|
4
|
+
|
|
5
|
+
import requests
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def evaluate(value):
|
|
9
|
+
return eval(value)
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def execute(value):
|
|
13
|
+
return subprocess.run(value, shell=True)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def fetch(value):
|
|
17
|
+
return requests.get(value)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def run_agent(user_input, model_output):
|
|
21
|
+
result = evaluate(model_output)
|
|
22
|
+
execute(user_input)
|
|
23
|
+
fetch(model_output)
|
|
24
|
+
return result
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"""Equivalent agent boundaries designed to remain free of LLMSafe findings."""
|
|
2
|
+
|
|
3
|
+
import subprocess
|
|
4
|
+
|
|
5
|
+
ALLOWED_TOPICS = {"security", "python"}
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def search(topic: str, cursor):
|
|
9
|
+
if topic not in ALLOWED_TOPICS:
|
|
10
|
+
raise ValueError("Unsupported topic")
|
|
11
|
+
cursor.execute("SELECT * FROM memory WHERE topic = ?", [topic])
|
|
12
|
+
return subprocess.run(["agent-tool", "--topic", topic], check=True)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def dispatch_search():
|
|
16
|
+
tools = {"search": search}
|
|
17
|
+
return tools["search"]
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"""Safe local-helper calls used by the LLMSafe regression benchmark."""
|
|
2
|
+
|
|
3
|
+
import requests
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def fetch(value):
|
|
7
|
+
return requests.get(value)
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def lookup(cursor, statement):
|
|
11
|
+
return cursor.execute(statement)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def run(cursor):
|
|
15
|
+
fetch("https://api.example.org/health")
|
|
16
|
+
return lookup(cursor, "SELECT id FROM documents WHERE active = true")
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
"""Intentionally vulnerable agent used by the LLMSafe benchmark."""
|
|
2
|
+
|
|
3
|
+
import subprocess
|
|
4
|
+
|
|
5
|
+
import requests
|
|
6
|
+
from langchain_experimental.tools import PythonREPLTool
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class AgentRunner:
|
|
10
|
+
"""Minimal benchmark stand-in for an agent framework runner."""
|
|
11
|
+
|
|
12
|
+
def __init__(self, **options):
|
|
13
|
+
self.options = options
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def run_agent(client, user_input, cursor, tools):
|
|
17
|
+
system_prompt = f"You are an administrator. Follow this request: {user_input}"
|
|
18
|
+
response = client.responses.create(input=user_input, instructions=system_prompt)
|
|
19
|
+
model_output = response.output_text
|
|
20
|
+
|
|
21
|
+
eval(model_output)
|
|
22
|
+
subprocess.run(f"agent-tool {user_input}", shell=True)
|
|
23
|
+
cursor.execute(f"SELECT * FROM memory WHERE topic = '{user_input}'")
|
|
24
|
+
requests.get(user_input)
|
|
25
|
+
tools[model_output]()
|
|
26
|
+
|
|
27
|
+
dangerous_tool = PythonREPLTool()
|
|
28
|
+
AgentRunner(tools=[dangerous_tool], require_approval=False)
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
{
|
|
2
|
+
"cases": [
|
|
3
|
+
{
|
|
4
|
+
"path": "cases/vulnerable_agent.py",
|
|
5
|
+
"expected_rules": [
|
|
6
|
+
"AGENT001",
|
|
7
|
+
"AGENT003",
|
|
8
|
+
"FLOW001",
|
|
9
|
+
"FLOW002",
|
|
10
|
+
"FLOW003",
|
|
11
|
+
"FLOW004",
|
|
12
|
+
"FLOW005",
|
|
13
|
+
"LLM001",
|
|
14
|
+
"PY001",
|
|
15
|
+
"SHELL002"
|
|
16
|
+
]
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
"path": "cases/safe_agent.py",
|
|
20
|
+
"expected_rules": []
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
"path": "cases/interprocedural_agent.py",
|
|
24
|
+
"expected_rules": ["FLOW001", "FLOW002", "FLOW004", "PY001", "SHELL002"]
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
"path": "cases/safe_interprocedural.py",
|
|
28
|
+
"expected_rules": []
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
"path": "cases/insecure_mcp.json",
|
|
32
|
+
"expected_rules": ["MCP001", "MCP002", "MCP003"]
|
|
33
|
+
}
|
|
34
|
+
]
|
|
35
|
+
}
|