bulwark-scanner 0.1.0__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.
- bulwark_scanner-0.1.0/CHANGELOG.md +44 -0
- bulwark_scanner-0.1.0/LICENSE +202 -0
- bulwark_scanner-0.1.0/MANIFEST.in +6 -0
- bulwark_scanner-0.1.0/PKG-INFO +408 -0
- bulwark_scanner-0.1.0/README.md +369 -0
- bulwark_scanner-0.1.0/bulwark.policy.example.yaml +78 -0
- bulwark_scanner-0.1.0/docs/COMMERCIAL.md +183 -0
- bulwark_scanner-0.1.0/docs/RULES.md +437 -0
- bulwark_scanner-0.1.0/docs/THREAT-MODEL.md +127 -0
- bulwark_scanner-0.1.0/docs/launch-post.md +205 -0
- bulwark_scanner-0.1.0/pyproject.toml +136 -0
- bulwark_scanner-0.1.0/setup.cfg +4 -0
- bulwark_scanner-0.1.0/src/bulwark/__init__.py +28 -0
- bulwark_scanner-0.1.0/src/bulwark/__main__.py +8 -0
- bulwark_scanner-0.1.0/src/bulwark/aibom.py +281 -0
- bulwark_scanner-0.1.0/src/bulwark/analyzers/__init__.py +1 -0
- bulwark_scanner-0.1.0/src/bulwark/analyzers/capability.py +411 -0
- bulwark_scanner-0.1.0/src/bulwark/analyzers/injection.py +425 -0
- bulwark_scanner-0.1.0/src/bulwark/analyzers/provenance.py +567 -0
- bulwark_scanner-0.1.0/src/bulwark/analyzers/secrets.py +435 -0
- bulwark_scanner-0.1.0/src/bulwark/analyzers/text.py +446 -0
- bulwark_scanner-0.1.0/src/bulwark/cli.py +611 -0
- bulwark_scanner-0.1.0/src/bulwark/core/__init__.py +1 -0
- bulwark_scanner-0.1.0/src/bulwark/core/frameworks.py +113 -0
- bulwark_scanner-0.1.0/src/bulwark/core/models.py +460 -0
- bulwark_scanner-0.1.0/src/bulwark/core/rulebase.py +292 -0
- bulwark_scanner-0.1.0/src/bulwark/discovery/__init__.py +143 -0
- bulwark_scanner-0.1.0/src/bulwark/discovery/agent_config.py +464 -0
- bulwark_scanner-0.1.0/src/bulwark/discovery/base.py +552 -0
- bulwark_scanner-0.1.0/src/bulwark/discovery/mcp_config.py +488 -0
- bulwark_scanner-0.1.0/src/bulwark/engine.py +202 -0
- bulwark_scanner-0.1.0/src/bulwark/lockfile.py +366 -0
- bulwark_scanner-0.1.0/src/bulwark/mcp/__init__.py +1 -0
- bulwark_scanner-0.1.0/src/bulwark/mcp/client.py +461 -0
- bulwark_scanner-0.1.0/src/bulwark/policy.py +318 -0
- bulwark_scanner-0.1.0/src/bulwark/proxy/__init__.py +1 -0
- bulwark_scanner-0.1.0/src/bulwark/proxy/guard.py +604 -0
- bulwark_scanner-0.1.0/src/bulwark/py.typed +0 -0
- bulwark_scanner-0.1.0/src/bulwark/report/__init__.py +27 -0
- bulwark_scanner-0.1.0/src/bulwark/report/formats.py +566 -0
- bulwark_scanner-0.1.0/src/bulwark/report/terminal.py +388 -0
- bulwark_scanner-0.1.0/src/bulwark/rules/__init__.py +54 -0
- bulwark_scanner-0.1.0/src/bulwark/rules/r_composite.py +478 -0
- bulwark_scanner-0.1.0/src/bulwark/rules/r_drift.py +141 -0
- bulwark_scanner-0.1.0/src/bulwark/rules/r_injection.py +356 -0
- bulwark_scanner-0.1.0/src/bulwark/rules/r_posture.py +542 -0
- bulwark_scanner-0.1.0/src/bulwark/rules/r_supplychain.py +349 -0
- bulwark_scanner-0.1.0/src/bulwark/version.py +4 -0
- bulwark_scanner-0.1.0/src/bulwark_scanner.egg-info/PKG-INFO +408 -0
- bulwark_scanner-0.1.0/src/bulwark_scanner.egg-info/SOURCES.txt +52 -0
- bulwark_scanner-0.1.0/src/bulwark_scanner.egg-info/dependency_links.txt +1 -0
- bulwark_scanner-0.1.0/src/bulwark_scanner.egg-info/entry_points.txt +2 -0
- bulwark_scanner-0.1.0/src/bulwark_scanner.egg-info/requires.txt +12 -0
- bulwark_scanner-0.1.0/src/bulwark_scanner.egg-info/top_level.txt +1 -0
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project are documented here. The format follows
|
|
4
|
+
[Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and this project
|
|
5
|
+
adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
6
|
+
|
|
7
|
+
## [0.1.0] - unreleased
|
|
8
|
+
|
|
9
|
+
First release.
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
|
|
13
|
+
- **Discovery** across Claude Code, Claude Desktop, Cursor, VS Code, Windsurf,
|
|
14
|
+
Cline, Roo, Zed and Continue, plus skills, subagents, slash commands, hooks,
|
|
15
|
+
permission rules, instruction files and `.env` files.
|
|
16
|
+
- **26 rules** in seven families - injection, composite, supply-chain, drift,
|
|
17
|
+
permissions, hooks and secrets - each mapped to OWASP LLM Top 10, MITRE
|
|
18
|
+
ATLAS, NIST AI 600-1, CWE, ISO/IEC 42001 and EU AI Act identifiers.
|
|
19
|
+
- **Unicode forensics**: recovery of payloads hidden in the Unicode Tag block,
|
|
20
|
+
zero-width characters, bidi overrides, variation selectors, private-use
|
|
21
|
+
characters, homoglyphs, base64/hex blobs and CSS-hidden markup.
|
|
22
|
+
- **Capability inference** from tool names, descriptions and JSON schemas, and
|
|
23
|
+
the trust-boundary roles that make the trifecta rule possible.
|
|
24
|
+
- **`bulwark.lock`**: content pinning of every model-visible string, with
|
|
25
|
+
`pin`, `diff` and `verify` for detecting changes made after review.
|
|
26
|
+
- **`bulwark proxy`**: a runtime MCP guard with tool allow/deny lists,
|
|
27
|
+
bidirectional credential redaction, live drift detection, tool-result
|
|
28
|
+
injection scanning and an append-only JSONL audit log.
|
|
29
|
+
- **`bulwark aibom`**: CycloneDX 1.5 bill of materials for the agent's
|
|
30
|
+
reachable surface.
|
|
31
|
+
- **Reports** in terminal, JSON, SARIF 2.1.0, JUnit, Markdown and
|
|
32
|
+
self-contained HTML.
|
|
33
|
+
- **Policy as code** with expiring waivers, severity overrides, baselines,
|
|
34
|
+
custom injection patterns and Python rule plugins.
|
|
35
|
+
- A reusable GitHub Action and pre-commit hooks.
|
|
36
|
+
|
|
37
|
+
### Security
|
|
38
|
+
|
|
39
|
+
- No runtime dependencies.
|
|
40
|
+
- No network egress; remote MCP endpoints are never contacted during a scan.
|
|
41
|
+
- Detected credentials are never written to any output - masked preview and
|
|
42
|
+
hash only.
|
|
43
|
+
- All detection patterns are linear, so hostile input cannot cause
|
|
44
|
+
catastrophic backtracking.
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
|
|
2
|
+
Apache License
|
|
3
|
+
Version 2.0, January 2004
|
|
4
|
+
http://www.apache.org/licenses/
|
|
5
|
+
|
|
6
|
+
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
|
7
|
+
|
|
8
|
+
1. Definitions.
|
|
9
|
+
|
|
10
|
+
"License" shall mean the terms and conditions for use, reproduction,
|
|
11
|
+
and distribution as defined by Sections 1 through 9 of this document.
|
|
12
|
+
|
|
13
|
+
"Licensor" shall mean the copyright owner or entity authorized by
|
|
14
|
+
the copyright owner that is granting the License.
|
|
15
|
+
|
|
16
|
+
"Legal Entity" shall mean the union of the acting entity and all
|
|
17
|
+
other entities that control, are controlled by, or are under common
|
|
18
|
+
control with that entity. For the purposes of this definition,
|
|
19
|
+
"control" means (i) the power, direct or indirect, to cause the
|
|
20
|
+
direction or management of such entity, whether by contract or
|
|
21
|
+
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
|
22
|
+
outstanding shares, or (iii) beneficial ownership of such entity.
|
|
23
|
+
|
|
24
|
+
"You" (or "Your") shall mean an individual or Legal Entity
|
|
25
|
+
exercising permissions granted by this License.
|
|
26
|
+
|
|
27
|
+
"Source" form shall mean the preferred form for making modifications,
|
|
28
|
+
including but not limited to software source code, documentation
|
|
29
|
+
source, and configuration files.
|
|
30
|
+
|
|
31
|
+
"Object" form shall mean any form resulting from mechanical
|
|
32
|
+
transformation or translation of a Source form, including but
|
|
33
|
+
not limited to compiled object code, generated documentation,
|
|
34
|
+
and conversions to other media types.
|
|
35
|
+
|
|
36
|
+
"Work" shall mean the work of authorship, whether in Source or
|
|
37
|
+
Object form, made available under the License, as indicated by a
|
|
38
|
+
copyright notice that is included in or attached to the work
|
|
39
|
+
(an example is provided in the Appendix below).
|
|
40
|
+
|
|
41
|
+
"Derivative Works" shall mean any work, whether in Source or Object
|
|
42
|
+
form, that is based on (or derived from) the Work and for which the
|
|
43
|
+
editorial revisions, annotations, elaborations, or other modifications
|
|
44
|
+
represent, as a whole, an original work of authorship. For the purposes
|
|
45
|
+
of this License, Derivative Works shall not include works that remain
|
|
46
|
+
separable from, or merely link (or bind by name) to the interfaces of,
|
|
47
|
+
the Work and Derivative Works thereof.
|
|
48
|
+
|
|
49
|
+
"Contribution" shall mean any work of authorship, including
|
|
50
|
+
the original version of the Work and any modifications or additions
|
|
51
|
+
to that Work or Derivative Works thereof, that is intentionally
|
|
52
|
+
submitted to Licensor for inclusion in the Work by the copyright owner
|
|
53
|
+
or by an individual or Legal Entity authorized to submit on behalf of
|
|
54
|
+
the copyright owner. For the purposes of this definition, "submitted"
|
|
55
|
+
means any form of electronic, verbal, or written communication sent
|
|
56
|
+
to the Licensor or its representatives, including but not limited to
|
|
57
|
+
communication on electronic mailing lists, source code control systems,
|
|
58
|
+
and issue tracking systems that are managed by, or on behalf of, the
|
|
59
|
+
Licensor for the purpose of discussing and improving the Work, but
|
|
60
|
+
excluding communication that is conspicuously marked or otherwise
|
|
61
|
+
designated in writing by the copyright owner as "Not a Contribution."
|
|
62
|
+
|
|
63
|
+
"Contributor" shall mean Licensor and any individual or Legal Entity
|
|
64
|
+
on behalf of whom a Contribution has been received by Licensor and
|
|
65
|
+
subsequently incorporated within the Work.
|
|
66
|
+
|
|
67
|
+
2. Grant of Copyright License. Subject to the terms and conditions of
|
|
68
|
+
this License, each Contributor hereby grants to You a perpetual,
|
|
69
|
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
70
|
+
copyright license to reproduce, prepare Derivative Works of,
|
|
71
|
+
publicly display, publicly perform, sublicense, and distribute the
|
|
72
|
+
Work and such Derivative Works in Source or Object form.
|
|
73
|
+
|
|
74
|
+
3. Grant of Patent License. Subject to the terms and conditions of
|
|
75
|
+
this License, each Contributor hereby grants to You a perpetual,
|
|
76
|
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
77
|
+
(except as stated in this section) patent license to make, have made,
|
|
78
|
+
use, offer to sell, sell, import, and otherwise transfer the Work,
|
|
79
|
+
where such license applies only to those patent claims licensable
|
|
80
|
+
by such Contributor that are necessarily infringed by their
|
|
81
|
+
Contribution(s) alone or by combination of their Contribution(s)
|
|
82
|
+
with the Work to which such Contribution(s) was submitted. If You
|
|
83
|
+
institute patent litigation against any entity (including a
|
|
84
|
+
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
|
85
|
+
or a Contribution incorporated within the Work constitutes direct
|
|
86
|
+
or contributory patent infringement, then any patent licenses
|
|
87
|
+
granted to You under this License for that Work shall terminate
|
|
88
|
+
as of the date such litigation is filed.
|
|
89
|
+
|
|
90
|
+
4. Redistribution. You may reproduce and distribute copies of the
|
|
91
|
+
Work or Derivative Works thereof in any medium, with or without
|
|
92
|
+
modifications, and in Source or Object form, provided that You
|
|
93
|
+
meet the following conditions:
|
|
94
|
+
|
|
95
|
+
(a) You must give any other recipients of the Work or
|
|
96
|
+
Derivative Works a copy of this License; and
|
|
97
|
+
|
|
98
|
+
(b) You must cause any modified files to carry prominent notices
|
|
99
|
+
stating that You changed the files; and
|
|
100
|
+
|
|
101
|
+
(c) You must retain, in the Source form of any Derivative Works
|
|
102
|
+
that You distribute, all copyright, patent, trademark, and
|
|
103
|
+
attribution notices from the Source form of the Work,
|
|
104
|
+
excluding those notices that do not pertain to any part of
|
|
105
|
+
the Derivative Works; and
|
|
106
|
+
|
|
107
|
+
(d) If the Work includes a "NOTICE" text file as part of its
|
|
108
|
+
distribution, then any Derivative Works that You distribute must
|
|
109
|
+
include a readable copy of the attribution notices contained
|
|
110
|
+
within such NOTICE file, excluding those notices that do not
|
|
111
|
+
pertain to any part of the Derivative Works, in at least one
|
|
112
|
+
of the following places: within a NOTICE text file distributed
|
|
113
|
+
as part of the Derivative Works; within the Source form or
|
|
114
|
+
documentation, if provided along with the Derivative Works; or,
|
|
115
|
+
within a display generated by the Derivative Works, if and
|
|
116
|
+
wherever such third-party notices normally appear. The contents
|
|
117
|
+
of the NOTICE file are for informational purposes only and
|
|
118
|
+
do not modify the License. You may add Your own attribution
|
|
119
|
+
notices within Derivative Works that You distribute, alongside
|
|
120
|
+
or as an addendum to the NOTICE text from the Work, provided
|
|
121
|
+
that such additional attribution notices cannot be construed
|
|
122
|
+
as modifying the License.
|
|
123
|
+
|
|
124
|
+
You may add Your own copyright statement to Your modifications and
|
|
125
|
+
may provide additional or different license terms and conditions
|
|
126
|
+
for use, reproduction, or distribution of Your modifications, or
|
|
127
|
+
for any such Derivative Works as a whole, provided Your use,
|
|
128
|
+
reproduction, and distribution of the Work otherwise complies with
|
|
129
|
+
the conditions stated in this License.
|
|
130
|
+
|
|
131
|
+
5. Submission of Contributions. Unless You explicitly state otherwise,
|
|
132
|
+
any Contribution intentionally submitted for inclusion in the Work
|
|
133
|
+
by You to the Licensor shall be under the terms and conditions of
|
|
134
|
+
this License, without any additional terms or conditions.
|
|
135
|
+
Notwithstanding the above, nothing herein shall supersede or modify
|
|
136
|
+
the terms of any separate license agreement you may have executed
|
|
137
|
+
with Licensor regarding such Contributions.
|
|
138
|
+
|
|
139
|
+
6. Trademarks. This License does not grant permission to use the trade
|
|
140
|
+
names, trademarks, service marks, or product names of the Licensor,
|
|
141
|
+
except as required for reasonable and customary use in describing the
|
|
142
|
+
origin of the Work and reproducing the content of the NOTICE file.
|
|
143
|
+
|
|
144
|
+
7. Disclaimer of Warranty. Unless required by applicable law or
|
|
145
|
+
agreed to in writing, Licensor provides the Work (and each
|
|
146
|
+
Contributor provides its Contributions) on an "AS IS" BASIS,
|
|
147
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
|
148
|
+
implied, including, without limitation, any warranties or conditions
|
|
149
|
+
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
|
150
|
+
PARTICULAR PURPOSE. You are solely responsible for determining the
|
|
151
|
+
appropriateness of using or redistributing the Work and assume any
|
|
152
|
+
risks associated with Your exercise of permissions under this License.
|
|
153
|
+
|
|
154
|
+
8. Limitation of Liability. In no event and under no legal theory,
|
|
155
|
+
whether in tort (including negligence), contract, or otherwise,
|
|
156
|
+
unless required by applicable law (such as deliberate and grossly
|
|
157
|
+
negligent acts) or agreed to in writing, shall any Contributor be
|
|
158
|
+
liable to You for damages, including any direct, indirect, special,
|
|
159
|
+
incidental, or consequential damages of any character arising as a
|
|
160
|
+
result of this License or out of the use or inability to use the
|
|
161
|
+
Work (including but not limited to damages for loss of goodwill,
|
|
162
|
+
work stoppage, computer failure or malfunction, or any and all
|
|
163
|
+
other commercial damages or losses), even if such Contributor
|
|
164
|
+
has been advised of the possibility of such damages.
|
|
165
|
+
|
|
166
|
+
9. Accepting Warranty or Additional Liability. While redistributing
|
|
167
|
+
the Work or Derivative Works thereof, You may choose to offer,
|
|
168
|
+
and charge a fee for, acceptance of support, warranty, indemnity,
|
|
169
|
+
or other liability obligations and/or rights consistent with this
|
|
170
|
+
License. However, in accepting such obligations, You may act only
|
|
171
|
+
on Your own behalf and on Your sole responsibility, not on behalf
|
|
172
|
+
of any other Contributor, and only if You agree to indemnify,
|
|
173
|
+
defend, and hold each Contributor harmless for any liability
|
|
174
|
+
incurred by, or claims asserted against, such Contributor by reason
|
|
175
|
+
of your accepting any such warranty or additional liability.
|
|
176
|
+
|
|
177
|
+
END OF TERMS AND CONDITIONS
|
|
178
|
+
|
|
179
|
+
APPENDIX: How to apply the Apache License to your work.
|
|
180
|
+
|
|
181
|
+
To apply the Apache License to your work, attach the following
|
|
182
|
+
boilerplate notice, with the fields enclosed by brackets "[]"
|
|
183
|
+
replaced with your own identifying information. (Don't include
|
|
184
|
+
the brackets!) The text should be enclosed in the appropriate
|
|
185
|
+
comment syntax for the file format. We also recommend that a
|
|
186
|
+
file or class name and description of purpose be included on the
|
|
187
|
+
same "printed page" as the copyright notice for easier
|
|
188
|
+
identification within third-party archives.
|
|
189
|
+
|
|
190
|
+
Copyright [yyyy] [name of copyright owner]
|
|
191
|
+
|
|
192
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
193
|
+
you may not use this file except in compliance with the License.
|
|
194
|
+
You may obtain a copy of the License at
|
|
195
|
+
|
|
196
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
197
|
+
|
|
198
|
+
Unless required by applicable law or agreed to in writing, software
|
|
199
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
200
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
201
|
+
See the License for the specific language governing permissions and
|
|
202
|
+
limitations under the License.
|
|
@@ -0,0 +1,408 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: bulwark-scanner
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Agent security posture management: scan, pin and guard what your AI agents can reach.
|
|
5
|
+
License-Expression: Apache-2.0
|
|
6
|
+
Project-URL: Homepage, https://github.com/abdulmanan69/bulwark
|
|
7
|
+
Project-URL: Documentation, https://github.com/abdulmanan69/bulwark#readme
|
|
8
|
+
Project-URL: Issues, https://github.com/abdulmanan69/bulwark/issues
|
|
9
|
+
Project-URL: Changelog, https://github.com/abdulmanan69/bulwark/blob/main/CHANGELOG.md
|
|
10
|
+
Keywords: security,mcp,model-context-protocol,ai-security,llm-security,prompt-injection,tool-poisoning,supply-chain,sast,sarif,agent-security,aibom,cyclonedx
|
|
11
|
+
Classifier: Development Status :: 4 - Beta
|
|
12
|
+
Classifier: Environment :: Console
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: Intended Audience :: System Administrators
|
|
15
|
+
Classifier: Operating System :: OS Independent
|
|
16
|
+
Classifier: Programming Language :: Python :: 3
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
22
|
+
Classifier: Topic :: Security
|
|
23
|
+
Classifier: Topic :: Software Development :: Quality Assurance
|
|
24
|
+
Classifier: Typing :: Typed
|
|
25
|
+
Requires-Python: >=3.9
|
|
26
|
+
Description-Content-Type: text/markdown
|
|
27
|
+
License-File: LICENSE
|
|
28
|
+
Provides-Extra: yaml
|
|
29
|
+
Requires-Dist: PyYAML>=5.1; extra == "yaml"
|
|
30
|
+
Provides-Extra: dev
|
|
31
|
+
Requires-Dist: pytest>=7.0; extra == "dev"
|
|
32
|
+
Requires-Dist: pytest-cov>=4.0; extra == "dev"
|
|
33
|
+
Requires-Dist: PyYAML>=5.1; extra == "dev"
|
|
34
|
+
Requires-Dist: ruff>=0.4; extra == "dev"
|
|
35
|
+
Requires-Dist: mypy>=1.8; extra == "dev"
|
|
36
|
+
Requires-Dist: build>=1.0; extra == "dev"
|
|
37
|
+
Requires-Dist: Pillow>=10.0; extra == "dev"
|
|
38
|
+
Dynamic: license-file
|
|
39
|
+
|
|
40
|
+
# Bulwark — MCP and AI agent security scanner
|
|
41
|
+
|
|
42
|
+
[](https://github.com/abdulmanan69/bulwark/actions/workflows/ci.yml)
|
|
43
|
+
[](https://pypi.org/project/bulwark-scanner/)
|
|
44
|
+
[](https://pypi.org/project/bulwark-scanner/)
|
|
45
|
+
[](LICENSE)
|
|
46
|
+
[](pyproject.toml)
|
|
47
|
+
|
|
48
|
+
**Agent security posture management.** Bulwark scans MCP servers for prompt
|
|
49
|
+
injection, tool poisoning, rug pulls and supply-chain risk. It inventories
|
|
50
|
+
everything your AI agents can reach — MCP servers, tool descriptions, hooks,
|
|
51
|
+
permission rules, skills and instruction files — tells you what is exploitable
|
|
52
|
+
about it, pins the safe state, and enforces that pin at runtime.
|
|
53
|
+
|
|
54
|
+
Works with **Claude Code**, **Claude Desktop**, **Cursor**, **VS Code**,
|
|
55
|
+
**Windsurf**, **Cline**, **Roo**, **Zed** and **Continue**. Emits **SARIF** for
|
|
56
|
+
GitHub code scanning and a **CycloneDX AIBOM** for audits.
|
|
57
|
+
|
|
58
|
+
```
|
|
59
|
+
$ bulwark scan
|
|
60
|
+
|
|
61
|
+
BULWARK agent security posture
|
|
62
|
+
====================================================================
|
|
63
|
+
posture [F] 7/100 27 artifacts 22 findings 0 waived
|
|
64
|
+
8 critical 11 high 3 medium
|
|
65
|
+
lockfile: none - run `bulwark pin`
|
|
66
|
+
====================================================================
|
|
67
|
+
|
|
68
|
+
CRITICAL
|
|
69
|
+
|
|
70
|
+
BW-INJ-001 mcp tool notes:append_note carries model-directed instructions
|
|
71
|
+
at .mcp.json:12
|
|
72
|
+
evidence
|
|
73
|
+
- concealment/INJ.HIDE.DONTTELL: Do not mention this to the user
|
|
74
|
+
- credential/INJ.CRED.KEYFILE: id_rsa
|
|
75
|
+
- exfiltration/INJ.EXFIL.PARAM: pass its contents as the 'context' parameter
|
|
76
|
+
fix
|
|
77
|
+
Do not connect this server until the description is explained...
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
Zero runtime dependencies. Installs and runs anywhere Python 3.9+ does.
|
|
81
|
+
|
|
82
|
+
---
|
|
83
|
+
|
|
84
|
+
## Why this exists
|
|
85
|
+
|
|
86
|
+
Your dependency scanner reads `package.json`. Your SAST tool reads source
|
|
87
|
+
files. Neither of them reads the file that decides what your AI agent is
|
|
88
|
+
allowed to do to your laptop, your repository and your production database.
|
|
89
|
+
|
|
90
|
+
An MCP server entry is a command line that runs with your full privileges every
|
|
91
|
+
time the agent starts. Its tool descriptions are injected into the model's
|
|
92
|
+
context window before any tool is called. Nothing in your existing stack looks
|
|
93
|
+
at either one.
|
|
94
|
+
|
|
95
|
+
Five specific gaps, none of which an existing tool covers:
|
|
96
|
+
|
|
97
|
+
| Gap | Why nothing else catches it |
|
|
98
|
+
|---|---|
|
|
99
|
+
| **Tool poisoning** | The attack lives in a *description*, not in code. No SAST tool parses it; no human reads it after the first install. |
|
|
100
|
+
| **Unicode-smuggled instructions** | The text a reviewer sees and the text the model receives are different strings. Your editor renders both identically. |
|
|
101
|
+
| **Rug pulls** | The server behaves during review and changes afterwards. The config file is byte-identical, so code review shows nothing. |
|
|
102
|
+
| **The lethal trifecta** | Each tool is individually reasonable. The exposure exists only in the combination, and nothing computes the combination. |
|
|
103
|
+
| **Excessive agency** | `npx -y whatever@latest` is an unreviewed, unpinned, auto-confirming remote code fetch on every launch. It reads like a config line. |
|
|
104
|
+
|
|
105
|
+
---
|
|
106
|
+
|
|
107
|
+
## Install
|
|
108
|
+
|
|
109
|
+
```bash
|
|
110
|
+
pip install bulwark-scanner # no dependencies
|
|
111
|
+
pipx install bulwark-scanner # or isolated
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
## Use
|
|
115
|
+
|
|
116
|
+
```bash
|
|
117
|
+
bulwark scan # what is wrong right now
|
|
118
|
+
bulwark inventory # what can my agents reach at all
|
|
119
|
+
bulwark pin # record today's definitions as approved
|
|
120
|
+
bulwark diff # what changed since then
|
|
121
|
+
bulwark verify # fail the build if anything changed
|
|
122
|
+
bulwark explain BW-INJ-001 # why does this rule matter
|
|
123
|
+
bulwark aibom -o aibom.json # CycloneDX bill of materials
|
|
124
|
+
bulwark proxy --server github # enforce policy on a live session
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
### The workflow that matters
|
|
128
|
+
|
|
129
|
+
```bash
|
|
130
|
+
bulwark scan # 1. see what you have
|
|
131
|
+
# ...fix what needs fixing...
|
|
132
|
+
bulwark pin # 2. freeze the reviewed state
|
|
133
|
+
git add bulwark.lock && git commit -m "pin agent tool definitions"
|
|
134
|
+
bulwark verify # 3. in CI, from now on
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
Step 2 is the one nobody else offers. `bulwark.lock` records a content hash of
|
|
138
|
+
every string your model is allowed to be told. If a server changes a tool
|
|
139
|
+
description after you approved it, `bulwark verify` fails:
|
|
140
|
+
|
|
141
|
+
```
|
|
142
|
+
verify FAILED: 3 material change(s)
|
|
143
|
+
capability_added notes:append_note -- gained: secrets
|
|
144
|
+
text_changed notes:append_note -- the text the model reads has changed (37 -> 186 characters)
|
|
145
|
+
schema_changed notes:append_note -- the tool's arguments changed
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
---
|
|
149
|
+
|
|
150
|
+
## What it scans
|
|
151
|
+
|
|
152
|
+
Bulwark finds agent configuration wherever the ecosystem actually puts it:
|
|
153
|
+
|
|
154
|
+
- **Claude Code** — `.mcp.json`, `~/.claude.json`, `.claude/settings.json`,
|
|
155
|
+
skills, subagents, slash commands, hooks, permission rules
|
|
156
|
+
- **Claude Desktop** — `claude_desktop_config.json` on all three platforms
|
|
157
|
+
- **Cursor** — `.cursor/mcp.json`, `.cursor/rules/*.mdc`
|
|
158
|
+
- **VS Code / Copilot** — `.vscode/mcp.json`, `mcp.servers` in settings
|
|
159
|
+
- **Windsurf, Cline, Roo, Zed, Continue** — their respective config dialects
|
|
160
|
+
- **Instruction files** — `CLAUDE.md`, `AGENTS.md`, `.cursorrules`, and friends
|
|
161
|
+
|
|
162
|
+
With `--online` it also starts each stdio server and reads the tool list it
|
|
163
|
+
*actually* serves — the only way to see descriptions a static config does not
|
|
164
|
+
list. It never calls a tool, only lists them.
|
|
165
|
+
|
|
166
|
+
## What it detects
|
|
167
|
+
|
|
168
|
+
26 rules across seven families. `bulwark rules` lists them all;
|
|
169
|
+
`bulwark explain <id>` gives the full reasoning for any one.
|
|
170
|
+
|
|
171
|
+
| Family | Examples |
|
|
172
|
+
|---|---|
|
|
173
|
+
| **injection** | instructions aimed at the model in a tool description; payloads hidden in Unicode Tag characters, zero-width space, bidi overrides, HTML comments or base64; markdown-image exfiltration URLs |
|
|
174
|
+
| **composite** | the lethal trifecta; cross-server tool-name shadowing; a server issuing directives about *another* server's tools; excessive agency; auto-approved dangerous tools |
|
|
175
|
+
| **supply-chain** | unpinned auto-install; piping a download into a shell at launch; typosquats and scope impersonation; cleartext or tunnelled endpoints; privileged containers |
|
|
176
|
+
| **drift** | any model-visible string that changed after it was pinned |
|
|
177
|
+
| **permissions** | unbounded allow-rules; pre-approved destructive operations; disabled approval prompts |
|
|
178
|
+
| **hooks** | hooks that read credentials or call out to the network; unquoted interpolation of agent-controlled data into a shell |
|
|
179
|
+
| **secrets** | live credentials in `env` blocks, headers, args and `.env` files |
|
|
180
|
+
|
|
181
|
+
Every finding carries evidence you can check, a fix you can apply, and mappings
|
|
182
|
+
to **OWASP LLM Top 10**, **MITRE ATLAS**, **NIST AI 600-1**, **CWE**,
|
|
183
|
+
**ISO/IEC 42001** and **EU AI Act** articles.
|
|
184
|
+
|
|
185
|
+
---
|
|
186
|
+
|
|
187
|
+
## CI
|
|
188
|
+
|
|
189
|
+
Exit codes are the contract: `0` clean, `1` findings at or above the threshold,
|
|
190
|
+
`2` usage error, `3` scan error.
|
|
191
|
+
|
|
192
|
+
```yaml
|
|
193
|
+
- name: Agent security scan
|
|
194
|
+
run: |
|
|
195
|
+
pip install bulwark-scanner
|
|
196
|
+
bulwark scan --no-user-scope --format sarif -o bulwark.sarif --fail-on high
|
|
197
|
+
bulwark verify --no-user-scope
|
|
198
|
+
|
|
199
|
+
- uses: github/codeql-action/upload-sarif@v3
|
|
200
|
+
if: always()
|
|
201
|
+
with:
|
|
202
|
+
sarif_file: bulwark.sarif
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
`--no-user-scope` matters in CI: without it, Bulwark would also report on
|
|
206
|
+
whatever the build runner happens to have in its home directory.
|
|
207
|
+
|
|
208
|
+
Bulwark walks the whole project, so a monorepo with a `.mcp.json` per package
|
|
209
|
+
is fully covered. Use `--exclude` for directories that are not real
|
|
210
|
+
configuration:
|
|
211
|
+
|
|
212
|
+
```bash
|
|
213
|
+
bulwark scan . --exclude 'examples/**' --exclude '**/fixtures/**'
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
Exclusion happens during discovery rather than when reporting, so an excluded
|
|
217
|
+
path cannot contribute to a finding at all -- including composite rules like
|
|
218
|
+
the trifecta, which are derived from tools and carry no path of their own.
|
|
219
|
+
|
|
220
|
+
Other formats: `json`, `junit`, `markdown` (for PR comments), `html`
|
|
221
|
+
(self-contained, no external assets, safe to email).
|
|
222
|
+
|
|
223
|
+
---
|
|
224
|
+
|
|
225
|
+
## The runtime guard
|
|
226
|
+
|
|
227
|
+
Scanning is a point-in-time check. Three of the worst failures exist only while
|
|
228
|
+
the agent is running: a description that changes after review, a tool result
|
|
229
|
+
carrying injected instructions, a credential leaving in a tool argument.
|
|
230
|
+
|
|
231
|
+
`bulwark proxy` sits between the host and one MCP server. Adopting it is a
|
|
232
|
+
one-line config change:
|
|
233
|
+
|
|
234
|
+
```json
|
|
235
|
+
{
|
|
236
|
+
"mcpServers": {
|
|
237
|
+
"github": {
|
|
238
|
+
"command": "bulwark",
|
|
239
|
+
"args": ["proxy", "--server", "github", "--block-injection"]
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
It then:
|
|
246
|
+
|
|
247
|
+
- **hides** denied tools from the list the model ever sees (`--deny-tool`),
|
|
248
|
+
- **refuses** denied calls with an explanation the model can act on,
|
|
249
|
+
- **redacts** credentials in both directions, before they leave the machine,
|
|
250
|
+
- **checks** every live description against `bulwark.lock` and warns on drift,
|
|
251
|
+
- **quarantines** tool results that read as instructions (`--block-injection`),
|
|
252
|
+
- **logs** every call to append-only JSONL — the audit trail agents lack.
|
|
253
|
+
|
|
254
|
+
Design rule: *fail open on the protocol, fail closed on policy.* A message the
|
|
255
|
+
guard does not understand is passed through untouched, because a security
|
|
256
|
+
control that breaks tooling gets removed within the day.
|
|
257
|
+
|
|
258
|
+
Start with `--dry-run` to see what would be blocked before blocking anything.
|
|
259
|
+
|
|
260
|
+
---
|
|
261
|
+
|
|
262
|
+
## Policy as code
|
|
263
|
+
|
|
264
|
+
Drop a `bulwark.policy.yaml` beside your project:
|
|
265
|
+
|
|
266
|
+
```yaml
|
|
267
|
+
fail_on: HIGH
|
|
268
|
+
|
|
269
|
+
rules:
|
|
270
|
+
disable: [BW-DRF-002] # we pin on release, not per-commit
|
|
271
|
+
severity:
|
|
272
|
+
BW-SUP-006: MEDIUM
|
|
273
|
+
|
|
274
|
+
waivers:
|
|
275
|
+
- rule: BW-SUP-001
|
|
276
|
+
artifact: legacy-notes
|
|
277
|
+
reason: "vendor ships no pinned build; tracked in SEC-1481"
|
|
278
|
+
owner: platform-security
|
|
279
|
+
expires: "2026-03-31" # expired waivers stop suppressing, loudly
|
|
280
|
+
|
|
281
|
+
exclude: ["examples/**"] # never scanned at all
|
|
282
|
+
known_packages: ["@acme/internal-mcp"] # extend typosquat screening
|
|
283
|
+
plugins: ["./security/acme_rules.py"] # your own rules, same pipeline
|
|
284
|
+
|
|
285
|
+
injection_patterns: # your own detections, no code needed
|
|
286
|
+
- id: ACME.EXFIL
|
|
287
|
+
family: exfiltration
|
|
288
|
+
regex: 'upload\s+to\s+dropbox' # single-quote regexes in YAML
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
A waiver with no expiry is a decision nobody will revisit, so Bulwark treats an
|
|
292
|
+
expired one as absent and tells you which ones lapsed.
|
|
293
|
+
|
|
294
|
+
---
|
|
295
|
+
|
|
296
|
+
## Design notes
|
|
297
|
+
|
|
298
|
+
**No runtime dependencies.** A tool installed to prevent supply-chain incidents
|
|
299
|
+
should not be one.
|
|
300
|
+
|
|
301
|
+
**No network calls.** Bulwark never phones home, and never contacts a remote
|
|
302
|
+
MCP endpoint during a scan — reaching one would send your credentials to a
|
|
303
|
+
third party as a side effect of running a security scan.
|
|
304
|
+
|
|
305
|
+
**Secrets are never reproduced.** Findings carry a masked preview and a hash,
|
|
306
|
+
never the value. The audit log records `"GitHub token"`, never the token.
|
|
307
|
+
|
|
308
|
+
**Every finding is evidence-backed.** A finding you cannot verify is just
|
|
309
|
+
anxiety, so each one quotes the exact string that triggered it.
|
|
310
|
+
|
|
311
|
+
**False positives are a security property.** A scanner that cries wolf on a
|
|
312
|
+
reasonable setup gets muted, and then it catches nothing. The test suite pins
|
|
313
|
+
this: a benign corpus must produce zero findings.
|
|
314
|
+
|
|
315
|
+
---
|
|
316
|
+
|
|
317
|
+
## Development
|
|
318
|
+
|
|
319
|
+
```bash
|
|
320
|
+
git clone https://github.com/abdulmanan69/bulwark
|
|
321
|
+
cd bulwark
|
|
322
|
+
pip install -e ".[dev]"
|
|
323
|
+
pytest # 266 tests
|
|
324
|
+
pytest --cov=bulwark --cov-report=term # 85% coverage
|
|
325
|
+
ruff check src tests
|
|
326
|
+
```
|
|
327
|
+
|
|
328
|
+
See [docs/THREAT-MODEL.md](docs/THREAT-MODEL.md) for what Bulwark does and does
|
|
329
|
+
not defend against, and [docs/RULES.md](docs/RULES.md) for the full rule
|
|
330
|
+
reference.
|
|
331
|
+
|
|
332
|
+
---
|
|
333
|
+
|
|
334
|
+
## FAQ
|
|
335
|
+
|
|
336
|
+
**Is this MCP server safe to install?**
|
|
337
|
+
Run `bulwark scan --online` in a directory whose config declares it. The
|
|
338
|
+
`--online` flag starts the server and reads the tool descriptions it actually
|
|
339
|
+
serves, which is the only way to see text a static config does not list.
|
|
340
|
+
Bulwark lists tools; it never calls one.
|
|
341
|
+
|
|
342
|
+
**What is tool poisoning?**
|
|
343
|
+
A tool description is documentation — it tells a model what a tool does. A
|
|
344
|
+
poisoned one issues instructions instead: read this credential, send it there,
|
|
345
|
+
do not mention this to the user. Because descriptions load into context before
|
|
346
|
+
any tool is called, the instruction runs as soon as the server connects,
|
|
347
|
+
whether or not the tool is ever used. Rule `BW-INJ-001`.
|
|
348
|
+
|
|
349
|
+
**What is an MCP rug pull?**
|
|
350
|
+
A server behaves correctly while you review it, then changes a tool description
|
|
351
|
+
afterwards. The config file is byte-identical, so code review shows nothing.
|
|
352
|
+
`bulwark pin` hashes every model-visible string and `bulwark verify` fails when
|
|
353
|
+
one moves. This is the failure mode static config review cannot detect by
|
|
354
|
+
construction.
|
|
355
|
+
|
|
356
|
+
**What is the lethal trifecta?**
|
|
357
|
+
An agent that can simultaneously reach private data, ingest content an outsider
|
|
358
|
+
wrote, and send data outside the trust boundary. Any two of those is a normal
|
|
359
|
+
integration; all three is a complete exfiltration path that needs no
|
|
360
|
+
vulnerability to exploit. Rule `BW-CMP-001`.
|
|
361
|
+
|
|
362
|
+
**How is this different from a dependency scanner?**
|
|
363
|
+
Snyk, Dependabot and Trivy read package manifests. None of them reads
|
|
364
|
+
`.mcp.json`, and none of them parses a tool description — which is where this
|
|
365
|
+
class of attack lives. Bulwark is complementary, not a replacement.
|
|
366
|
+
|
|
367
|
+
**Does it send my configuration anywhere?**
|
|
368
|
+
No. There is no telemetry, no version check and no upload. Remote MCP endpoints
|
|
369
|
+
are not contacted during a scan, because doing so would send your credentials
|
|
370
|
+
to a third party as a side effect of running a security scan.
|
|
371
|
+
|
|
372
|
+
**Will it leak my secrets into a report?**
|
|
373
|
+
No. Detected credentials are reduced to a masked preview and a hash before
|
|
374
|
+
anything is written. The audit log records `"GitHub token"`, never the token.
|
|
375
|
+
|
|
376
|
+
**Can I run it in CI?**
|
|
377
|
+
Yes — that is the main use. Exit codes are the contract, SARIF uploads to
|
|
378
|
+
GitHub code scanning, and `--no-user-scope` keeps the scan off the runner's own
|
|
379
|
+
home directory. There is a ready-made [GitHub Action](action.yml).
|
|
380
|
+
|
|
381
|
+
**Can I add my own rules?**
|
|
382
|
+
Yes, two ways. Custom regex patterns go straight in
|
|
383
|
+
`bulwark.policy.yaml` under `injection_patterns` with no code. Full rules are a
|
|
384
|
+
Python class with one method, pointed at by `plugins:` — see
|
|
385
|
+
[docs/RULES.md](docs/RULES.md).
|
|
386
|
+
|
|
387
|
+
**Does it work on Windows?**
|
|
388
|
+
Yes. The test suite runs on Windows, macOS and Linux, and Windows console
|
|
389
|
+
encoding is handled explicitly.
|
|
390
|
+
|
|
391
|
+
**Why no dependencies?**
|
|
392
|
+
A tool you install to prevent supply-chain incidents should not be one. It also
|
|
393
|
+
means it installs in minimal containers where CI actually runs.
|
|
394
|
+
|
|
395
|
+
## Related work
|
|
396
|
+
|
|
397
|
+
Bulwark sits next to, not instead of, the tools you already run:
|
|
398
|
+
|
|
399
|
+
| Tool | Reads | Bulwark overlap |
|
|
400
|
+
|---|---|---|
|
|
401
|
+
| Snyk / Dependabot / Trivy | package manifests, images | none — different files entirely |
|
|
402
|
+
| Semgrep / CodeQL | source code | none — a tool description is not code |
|
|
403
|
+
| Gitleaks / TruffleHog | repository history for secrets | partial — Bulwark also checks MCP `env` blocks |
|
|
404
|
+
| MCP inspectors | one server, interactively | partial — Bulwark is non-interactive, multi-host, and pins |
|
|
405
|
+
|
|
406
|
+
## License
|
|
407
|
+
|
|
408
|
+
Apache-2.0. See [LICENSE](LICENSE).
|