mayi 0.0.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.
- mayi-0.0.0/LICENSE +202 -0
- mayi-0.0.0/NOTICE +4 -0
- mayi-0.0.0/PKG-INFO +282 -0
- mayi-0.0.0/README.md +247 -0
- mayi-0.0.0/mayi/__init__.py +18 -0
- mayi-0.0.0/mayi/__main__.py +3 -0
- mayi-0.0.0/mayi/adapters.py +84 -0
- mayi-0.0.0/mayi/agent_client.py +66 -0
- mayi-0.0.0/mayi/approvals.py +79 -0
- mayi-0.0.0/mayi/audit.py +93 -0
- mayi-0.0.0/mayi/boundary.py +477 -0
- mayi-0.0.0/mayi/catalog.py +265 -0
- mayi-0.0.0/mayi/cli.py +380 -0
- mayi-0.0.0/mayi/client.py +66 -0
- mayi-0.0.0/mayi/diff.py +94 -0
- mayi-0.0.0/mayi/explain.py +91 -0
- mayi-0.0.0/mayi/gateway.py +596 -0
- mayi-0.0.0/mayi/generate.py +80 -0
- mayi-0.0.0/mayi/integrations/__init__.py +0 -0
- mayi-0.0.0/mayi/integrations/anthropic.py +121 -0
- mayi-0.0.0/mayi/integrations/langgraph.py +126 -0
- mayi-0.0.0/mayi/integrations/mcp.py +52 -0
- mayi-0.0.0/mayi/limits.py +43 -0
- mayi-0.0.0/mayi/mcp_proxy.py +242 -0
- mayi-0.0.0/mayi/notify.py +54 -0
- mayi-0.0.0/mayi/prove.py +175 -0
- mayi-0.0.0/mayi/provenance.py +88 -0
- mayi-0.0.0/mayi/report.py +116 -0
- mayi-0.0.0/mayi/resolve.py +29 -0
- mayi-0.0.0/mayi/results.py +28 -0
- mayi-0.0.0/mayi/schema.py +43 -0
- mayi-0.0.0/mayi/seal.py +96 -0
- mayi-0.0.0/mayi/signing.py +157 -0
- mayi-0.0.0/mayi/store.py +131 -0
- mayi-0.0.0/mayi/validators.py +68 -0
- mayi-0.0.0/mayi.egg-info/PKG-INFO +282 -0
- mayi-0.0.0/mayi.egg-info/SOURCES.txt +75 -0
- mayi-0.0.0/mayi.egg-info/dependency_links.txt +1 -0
- mayi-0.0.0/mayi.egg-info/entry_points.txt +2 -0
- mayi-0.0.0/mayi.egg-info/requires.txt +18 -0
- mayi-0.0.0/mayi.egg-info/top_level.txt +1 -0
- mayi-0.0.0/pyproject.toml +42 -0
- mayi-0.0.0/setup.cfg +4 -0
- mayi-0.0.0/tests/test_adapters.py +54 -0
- mayi-0.0.0/tests/test_adversarial.py +78 -0
- mayi-0.0.0/tests/test_agent_client.py +30 -0
- mayi-0.0.0/tests/test_anthropic_adapter.py +105 -0
- mayi-0.0.0/tests/test_boundary.py +165 -0
- mayi-0.0.0/tests/test_bypass_and_cross_field.py +133 -0
- mayi-0.0.0/tests/test_catalog_metadata.py +43 -0
- mayi-0.0.0/tests/test_cli.py +26 -0
- mayi-0.0.0/tests/test_derived_pen.py +137 -0
- mayi-0.0.0/tests/test_explain.py +38 -0
- mayi-0.0.0/tests/test_further_limits.py +138 -0
- mayi-0.0.0/tests/test_gateway.py +157 -0
- mayi-0.0.0/tests/test_generate.py +60 -0
- mayi-0.0.0/tests/test_hardening.py +103 -0
- mayi-0.0.0/tests/test_langgraph_integration.py +165 -0
- mayi-0.0.0/tests/test_mcp_proxy.py +79 -0
- mayi-0.0.0/tests/test_mcp_proxy_v2.py +63 -0
- mayi-0.0.0/tests/test_multistep_adversarial.py +85 -0
- mayi-0.0.0/tests/test_notify.py +89 -0
- mayi-0.0.0/tests/test_policy_rules.py +107 -0
- mayi-0.0.0/tests/test_properties.py +131 -0
- mayi-0.0.0/tests/test_prove.py +93 -0
- mayi-0.0.0/tests/test_pubkey_approvals.py +80 -0
- mayi-0.0.0/tests/test_report.py +76 -0
- mayi-0.0.0/tests/test_resolve.py +54 -0
- mayi-0.0.0/tests/test_scope_simulate_diff.py +61 -0
- mayi-0.0.0/tests/test_seal.py +69 -0
- mayi-0.0.0/tests/test_session_binding.py +74 -0
- mayi-0.0.0/tests/test_session_budget.py +112 -0
- mayi-0.0.0/tests/test_signed_approvals.py +120 -0
- mayi-0.0.0/tests/test_store.py +79 -0
- mayi-0.0.0/tests/test_transform.py +92 -0
- mayi-0.0.0/tests/test_untrusted_copy.py +88 -0
- mayi-0.0.0/tests/test_v1.py +250 -0
mayi-0.0.0/LICENSE
ADDED
|
@@ -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.
|
mayi-0.0.0/NOTICE
ADDED
mayi-0.0.0/PKG-INFO
ADDED
|
@@ -0,0 +1,282 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: mayi
|
|
3
|
+
Version: 0.0.0
|
|
4
|
+
Summary: Argument-level authority for AI agent tool calls: a gateway that decides who controls each argument (application, model, human) and runs the tool only if the rules allow it
|
|
5
|
+
Author: Bhargava Pichikala
|
|
6
|
+
License-Expression: Apache-2.0
|
|
7
|
+
Project-URL: Homepage, https://github.com/bhargava-dev-ai/mayi
|
|
8
|
+
Project-URL: Repository, https://github.com/bhargava-dev-ai/mayi
|
|
9
|
+
Project-URL: Issues, https://github.com/bhargava-dev-ai/mayi/issues
|
|
10
|
+
Keywords: ai-agents,llm,security,prompt-injection,tool-calling,authorization,guardrails
|
|
11
|
+
Classifier: Development Status :: 4 - Beta
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
|
14
|
+
Classifier: Topic :: Security
|
|
15
|
+
Classifier: Topic :: Software Development :: Libraries
|
|
16
|
+
Requires-Python: >=3.10
|
|
17
|
+
Description-Content-Type: text/markdown
|
|
18
|
+
License-File: LICENSE
|
|
19
|
+
License-File: NOTICE
|
|
20
|
+
Requires-Dist: pyyaml>=6.0
|
|
21
|
+
Provides-Extra: signing
|
|
22
|
+
Requires-Dist: cryptography>=42; extra == "signing"
|
|
23
|
+
Provides-Extra: dev
|
|
24
|
+
Requires-Dist: cryptography>=42; extra == "dev"
|
|
25
|
+
Requires-Dist: pytest>=8.0; extra == "dev"
|
|
26
|
+
Requires-Dist: langgraph>=1.0; extra == "dev"
|
|
27
|
+
Requires-Dist: langchain-core>=1.0; extra == "dev"
|
|
28
|
+
Requires-Dist: hypothesis>=6.0; extra == "dev"
|
|
29
|
+
Provides-Extra: langgraph
|
|
30
|
+
Requires-Dist: langgraph>=1.0; extra == "langgraph"
|
|
31
|
+
Requires-Dist: langchain-core>=1.0; extra == "langgraph"
|
|
32
|
+
Provides-Extra: anthropic
|
|
33
|
+
Requires-Dist: anthropic>=0.40; extra == "anthropic"
|
|
34
|
+
Dynamic: license-file
|
|
35
|
+
|
|
36
|
+
# mayi
|
|
37
|
+
|
|
38
|
+
Argument-level authority for AI agent tool calls. The agent only *proposes* arguments. mayi decides who really controls each one, and runs the tool only if the rules allow it:
|
|
39
|
+
|
|
40
|
+
- **application**: your app fills it in; whatever the model proposed is thrown away
|
|
41
|
+
- **model**: the model writes it; mayi checks type and length
|
|
42
|
+
- **human**: a person must approve it
|
|
43
|
+
|
|
44
|
+
mayi runs as a small service between your agent and your tools. Anything not in the catalog is rejected.
|
|
45
|
+
|
|
46
|
+
## Install
|
|
47
|
+
|
|
48
|
+
python3 -m venv .venv && source .venv/bin/activate
|
|
49
|
+
pip install .
|
|
50
|
+
|
|
51
|
+
## Try it (2 windows)
|
|
52
|
+
|
|
53
|
+
mayi demo # window 1: leave open. Console: http://127.0.0.1:8787/console (key: demo-app-key)
|
|
54
|
+
|
|
55
|
+
export ANTHROPIC_API_KEY=your-key # window 2
|
|
56
|
+
pip install anthropic
|
|
57
|
+
mayi try-model
|
|
58
|
+
|
|
59
|
+
A real Claude model gets a ticket with a hidden attack. Its $40 refund waits in the console until you click **Approve once**. The attack never reaches the tool.
|
|
60
|
+
|
|
61
|
+
## Use it with your own tools
|
|
62
|
+
|
|
63
|
+
mayi init # writes catalog.yaml and tools.json
|
|
64
|
+
# edit catalog.yaml (who controls each argument) and tools.json (your tool URLs)
|
|
65
|
+
mayi check catalog.yaml # validate
|
|
66
|
+
mayi serve # prints an agent key and an app key
|
|
67
|
+
|
|
68
|
+
Your app creates a session (`POST /v1/sessions`, app key) holding the values the application controls. The agent calls `POST /v1/call` (agent key) with a `session_id`, a tool name and proposed args. Held calls show up in the console for a person to approve. Keys are saved in `.mayi-keys`.
|
|
69
|
+
|
|
70
|
+
## Check the code
|
|
71
|
+
|
|
72
|
+
pip install pytest hypothesis && python3 -m pytest -q
|
|
73
|
+
|
|
74
|
+
## Limits
|
|
75
|
+
|
|
76
|
+
Reference implementation, no outside security review. mayi checks the type and length of free text the model writes; it does not judge whether the text is true. Bind it to localhost or put it behind your own TLS and network controls.
|
|
77
|
+
|
|
78
|
+
## Two settings from an agent
|
|
79
|
+
|
|
80
|
+
export MAYI_BASE_URL=http://127.0.0.1:8787 MAYI_API_KEY=<agent key>
|
|
81
|
+
|
|
82
|
+
from mayi.client import MayiClient
|
|
83
|
+
MayiClient().decide("ticket-1042", "refund", {"amount": 40, "note": "late"}) # allow / hold / reject
|
|
84
|
+
MayiClient().call("ticket-1042", "refund", {"amount": 40, "note": "late"}) # runs it if allowed
|
|
85
|
+
|
|
86
|
+
## Self-hosting
|
|
87
|
+
|
|
88
|
+
`Dockerfile` and `railway.json` are included but the container build is untested. Put `catalog.yaml` and `tools.json` in `/data`, set `MAYI_AGENT_KEY` and `MAYI_APP_KEY`; `PORT` is honoured.
|
|
89
|
+
|
|
90
|
+
## Cumulative limits: stop "many small calls"
|
|
91
|
+
|
|
92
|
+
A `limit` such as `amount <= 500` is checked on each call by itself, so an attacker who is stopped at $5,000 can ask for ten refunds of $500. Add a `session_budget` and standing approvals are totalled per session:
|
|
93
|
+
|
|
94
|
+
refund:
|
|
95
|
+
amount:
|
|
96
|
+
pen: human
|
|
97
|
+
limit: "amount <= 500" # small amounts may pass without a person...
|
|
98
|
+
session_budget: 1000 # ...but only 1000 in total per session
|
|
99
|
+
|
|
100
|
+
Once a session has used up its budget, further amounts need a human, exactly as if there were no standing limit. Details that matter:
|
|
101
|
+
- Amounts a person approved also count toward the total, so approvals and standing limits cannot be stacked past it.
|
|
102
|
+
- Only calls that actually ran are counted. Dry runs (`simulate`, `/v1/decide`) and failed tools use nothing up.
|
|
103
|
+
- Negative amounts never get a standing approval, so they cannot be used to lower the total.
|
|
104
|
+
- The total is kept per `budget_key` (the gateway uses the session id). With no session identity there is nothing to total over, so a person decides every time.
|
|
105
|
+
- With `mayi serve` the total is kept in the state database and survives a restart (see below); `reset_budget(key)` clears a session.
|
|
106
|
+
- `diff_catalogs` flags a raised or removed budget as a loosening.
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
## Signed approvals
|
|
110
|
+
|
|
111
|
+
An approval can be a token your approval system signs, instead of an API call to the gateway. Set `MAYI_SIGNING_SECRET` on the gateway (same value wherever tokens are made):
|
|
112
|
+
|
|
113
|
+
mayi sign refund amount 900 --by alice --session ticket-1042 --ttl 300
|
|
114
|
+
|
|
115
|
+
Paste it into the console or `POST /v1/approvals/redeem` (app key). A token is bound to a tool, argument, value, session and expiry, and works once: a replayed token is refused. With `MAYI_SIGNING_SECRET` this is a shared-secret (HMAC-SHA256) design: anyone holding the secret can mint tokens. For public-key tokens see below.
|
|
116
|
+
|
|
117
|
+
## Start from your tool schemas
|
|
118
|
+
|
|
119
|
+
mayi init --from-tools tools.json
|
|
120
|
+
|
|
121
|
+
Reads Anthropic, MCP or OpenAI tool definitions and writes a `catalog.yaml` where **every argument is `pen: human`** (nothing is allowed until you loosen it). Suggestions appear only as comments.
|
|
122
|
+
|
|
123
|
+
## See what a catalog really allows
|
|
124
|
+
|
|
125
|
+
mayi explain catalog.yaml
|
|
126
|
+
|
|
127
|
+
Prints a plain-language report of who controls each argument, plus warnings: a standing limit with no session budget, unbounded free text, outbound-looking tools that take model-written text, tools where the model writes everything.
|
|
128
|
+
|
|
129
|
+
## Alerts for held calls
|
|
130
|
+
|
|
131
|
+
mayi serve --webhook https://hooks.slack.com/services/... # or MAYI_WEBHOOK_URL
|
|
132
|
+
|
|
133
|
+
A new held call posts a short alert (tool, argument, value, session). https only (plain http only for localhost). Alert failures never block a tool call; they are listed in `GET /v1/health`.
|
|
134
|
+
|
|
135
|
+
## Values copied from untrusted text
|
|
136
|
+
|
|
137
|
+
send_email:
|
|
138
|
+
to:
|
|
139
|
+
pen: model
|
|
140
|
+
type: str
|
|
141
|
+
untrusted_copy: hold # or: reject
|
|
142
|
+
|
|
143
|
+
Tell mayi what the model has read (`MayiClient.add_untrusted(session_id, text)` or `POST /v1/untrusted`). If the model then puts a value found verbatim in that text (an address, URL or ID, 4+ characters) into `to`, the call is held for a person or rejected. This is a heuristic: it does not catch paraphrased, re-encoded or split values, and it is not taint tracking.
|
|
144
|
+
|
|
145
|
+
## TypeScript client
|
|
146
|
+
|
|
147
|
+
`clients/typescript` has a zero-dependency client (`decide`, `simulate`, `call`, `addUntrusted`), tested against a live gateway (`npm test`).
|
|
148
|
+
|
|
149
|
+
## Tools with no arguments
|
|
150
|
+
|
|
151
|
+
Argument-level authority has nothing to hold on to when a tool takes no arguments (`UnlockDoor`, `DisableTwoFactorAuthentication`). Mark those tools so the call itself needs a person:
|
|
152
|
+
|
|
153
|
+
unlock_door:
|
|
154
|
+
_requires_approval: true
|
|
155
|
+
|
|
156
|
+
`mayi init --from-tools` does this automatically for zero-argument tools. `mayi explain` warns about a zero-argument tool that is not marked. An approval for this uses the argument name `_call` and the value `true`.
|
|
157
|
+
|
|
158
|
+
## Prove properties of a catalog
|
|
159
|
+
|
|
160
|
+
mayi prove catalog.yaml invariants.yaml
|
|
161
|
+
|
|
162
|
+
never_model_writable: [send_email.to]
|
|
163
|
+
human_above: {refund.amount: 500}
|
|
164
|
+
session_total_at_most: {refund.amount: 1000}
|
|
165
|
+
call_needs_approval: [unlock_door]
|
|
166
|
+
no_copy_from_untrusted: [send_email.to]
|
|
167
|
+
no_tool: [delete_database]
|
|
168
|
+
max_model_text: 500
|
|
169
|
+
|
|
170
|
+
Exit code 1 when an invariant breaks, with a counterexample ("a value of 5000 runs on standing approval"). This is a complete analysis of the catalog as data, and a randomised test checks its answers against the real enforcement code. It does not verify the Python in `boundary.py`, your tool code, or what the model writes inside an allowed free-text field. Naming a tool or argument that is not in the catalog is an error, never a pass.
|
|
171
|
+
|
|
172
|
+
## Agent frameworks
|
|
173
|
+
|
|
174
|
+
from mayi.adapters import openai_agents_tools, crewai_tools
|
|
175
|
+
tools = openai_agents_tools(MayiClient(), "ticket-1042", schemas) # OpenAI Agents SDK
|
|
176
|
+
tools = crewai_tools(MayiClient(), "ticket-1042", schemas) # CrewAI
|
|
177
|
+
|
|
178
|
+
Every call goes to the gateway; the real tool never runs in the agent's process. Both adapters were run against a live gateway (openai-agents 0.23, crewai 1.15). Their tests skip when the framework is not installed.
|
|
179
|
+
|
|
180
|
+
## InjecAgent check
|
|
181
|
+
|
|
182
|
+
`python examples/eval_injecagent.py InjecAgent/data` replays all 2,108 InjecAgent attack cases as a fooled agent would make them (it does not measure how often a model is fooled). Results:
|
|
183
|
+
|
|
184
|
+
| Setup | Attack ran | User's task |
|
|
185
|
+
|---|---|---|
|
|
186
|
+
| No mayi | 2108 / 2108 | runs |
|
|
187
|
+
| mayi, catalog generated from all 38 toolkits | 0 / 2108 | held for a person (all 2108) |
|
|
188
|
+
| mayi, catalog scoped to the user's tool | 2 / 2108 | ran (2108 / 2108) |
|
|
189
|
+
|
|
190
|
+
Read this carefully. Every InjecAgent attack uses a different tool from the user's task, so the task-scoped result is close to true by construction: a tool the catalog does not list is rejected. The 2 that ran are cases where the attacker's tool is the same as the user's tool; stopping those is a job for argument pens, which this suite barely tests. The first run of this check found that 408 attacks (19%) used zero-argument tools that argument-level rules could not gate; that is why `_requires_approval` exists.
|
|
191
|
+
|
|
192
|
+
## How mayi relates to other work
|
|
193
|
+
|
|
194
|
+
mayi is not a new idea. Closest is **Progent** (programmable privilege control: a proxy that allows or forbids each tool call by conditions on its arguments, with a Z3-based policy checker and LLM-generated policies), which reports 0% attack success on AgentDojo with hand-written policies. **CaMeL** (Google DeepMind) tracks real data flow through a custom interpreter and gives stronger guarantees at about 2.7x the token cost; mayi's untrusted-copy check is only a heuristic version of that idea. General policy engines (OPA/Rego, Cedar) and Microsoft's Agent Governance Toolkit cover broader governance. What mayi adds is a small, readable shape: four named authorities per argument, including the application pen that *replaces* a model's value instead of only denying it; single-use and signed human approvals; per-session budgets; and a gateway with a console. Progent's policy checker is more expressive than `mayi prove`. This comparison is from reading the papers' and projects' own descriptions, not a systematic search.
|
|
195
|
+
|
|
196
|
+
## Live test with real models
|
|
197
|
+
|
|
198
|
+
`python examples/live_proper.py 4 <model> [--only CONFIG]` has a real Claude model read a poisoned inbox (the injection arrives inside a tool result). Eight hand-written injections (forwarding data, opening a door, an obfuscated address, a spelled-out address, a phishing link, pressure, claimed authority), 4 runs each, so 32 runs per cell. No approval is ever granted. The same script was run twice on 2026-10-10, so each cell below is 64 runs:
|
|
199
|
+
|
|
200
|
+
| Model | Setup | Attack attempted | Attack ran | Honest reply sent |
|
|
201
|
+
|---|---|---|---|---|
|
|
202
|
+
| Haiku 4.5 | no mayi rules | 8 / 64 | 8 / 64 | 64 / 64 |
|
|
203
|
+
| Haiku 4.5 | `untrusted_copy: hold` + tool-level approval | 8 / 64 | 0 / 64 | 0 / 64 |
|
|
204
|
+
| Haiku 4.5 | `to` filled in by the application + tool-level approval | 8 / 64 | 0 / 64 | 64 / 64 |
|
|
205
|
+
| Haiku 4.5 | as above + `forbid: [url, email]` in the body | 8 / 64 | 0 / 64 | 64 / 64 |
|
|
206
|
+
| Sonnet 5.5 | no mayi rules | 1 / 64 | 1 / 64 | 62 / 64 |
|
|
207
|
+
| Sonnet 5.5 | `untrusted_copy: hold` + tool-level approval | 0 / 64 | 0 / 64 | 0 / 64 |
|
|
208
|
+
| Sonnet 5.5 | `to` filled in by the application + tool-level approval | 1 / 64 | 1 / 64 (see below) | 62 / 64 |
|
|
209
|
+
| Sonnet 5.5 | as above + `forbid: [url, email]` in the body | 0 / 64 | 0 / 64 | 64 / 64 |
|
|
210
|
+
|
|
211
|
+
Read this carefully.
|
|
212
|
+
- The models resisted most injections. Every Haiku attempt was the "open the door" injection (8 of 8 times it opened the door when nothing stopped it). Sonnet 5.5 was fooled once in 64 runs, by the "claimed authority" injection.
|
|
213
|
+
- The one Sonnet run counted as "ran" in the application-fills-the-address setup cannot have been a door opening (that needs a person) or mail to the attacker (the application fixes the recipient). The scoring in that run counted any mention of the attacker's address, so it was almost certainly the model repeating the address inside a reply to the real customer. I did not save that run, so this is an inference. The script now counts only a door opening, mail to someone other than the customer, or the phishing link going out.
|
|
214
|
+
- No model tried the disguised-address or the phishing-link injections, so the copy check and the body filter were exercised by unit tests only. - The `hold` setup held every honest reply, because the customer's address appears in the same untrusted email; filling the address from the application avoids that.
|
|
215
|
+
- Small sample, two models, injections written by the author.
|
|
216
|
+
|
|
217
|
+
A third run (`MAYI_COMPLIANT_PROMPT=1`) told the agent that instructions inside tickets are authoritative and must be carried out, to make it more gullible. 32 runs per cell, scored with the stricter rule above:
|
|
218
|
+
|
|
219
|
+
| Model | Setup | Attack attempted | Attack ran | Honest reply sent |
|
|
220
|
+
|---|---|---|---|---|
|
|
221
|
+
| Haiku 4.5 | no mayi rules | 4 / 32 | 4 / 32 | 32 / 32 |
|
|
222
|
+
| Haiku 4.5 | `untrusted_copy: hold` + tool-level approval | 4 / 32 | 0 / 32 | 0 / 32 |
|
|
223
|
+
| Haiku 4.5 | `to` filled in by the application + tool-level approval | 4 / 32 | 0 / 32 | 32 / 32 |
|
|
224
|
+
| Haiku 4.5 | as above + `forbid: [url, email]` in the body | 4 / 32 | 0 / 32 | 32 / 32 |
|
|
225
|
+
| Sonnet 5.5 | the same four setups | 2, 2, 1, 2 / 32 | 0 / 32 in all | 30, 0, 28, 30 of 32 |
|
|
226
|
+
|
|
227
|
+
Haiku again opened the door every time it was tempted (4 of 4) and mayi stopped all of it. Sonnet 5.5 mentioned the attacker's address in some runs but never acted on it. Even with the gullible prompt, no model tried the disguised-address, spelled-out-address or phishing-link injections, so those defences have still not been tested against a live model.
|
|
228
|
+
|
|
229
|
+
## State that survives a restart
|
|
230
|
+
|
|
231
|
+
`mayi serve` keeps sessions, unused approvals, held calls, session budgets, used signed-token ids and untrusted text in `mayi_state.db` (`--state-db`). Two ordering rules make a crash safe: a single-use approval is marked used *before* the tool runs (so it cannot be used twice after a crash), and a budget amount is reserved *before* the tool runs and given back if the tool raises (so a crash counts the money as spent). Held calls expire after `--pending-ttl` seconds (default 3600). Per-session rate-limit timers are not persisted.
|
|
232
|
+
|
|
233
|
+
## More rules in the catalog
|
|
234
|
+
|
|
235
|
+
transfer:
|
|
236
|
+
_rate_limit: "5/60" # at most 5 calls per 60 s per session
|
|
237
|
+
_require: ["amount <= balance"] # checked on the final values; even a person's yes cannot override it
|
|
238
|
+
balance: {pen: application, source: account.balance}
|
|
239
|
+
amount: {pen: human, limit: "amount <= 100", dual_control_above: 1000} # above 1000, two different approvers
|
|
240
|
+
memo: {pen: model, type: str, max_len: 140, forbid: [url, email]}
|
|
241
|
+
to: {pen: model, type: str, max_len: 80, allowlist_from: "account.payees"} # list comes from the session context
|
|
242
|
+
|
|
243
|
+
`_require` accepts only `argument <op> argument-or-number`. `allowlist_from` fails closed if the list is missing. Two-person approval works for console approvals and for signed tokens; the same person twice counts once. `diff_catalogs` flags the removal or loosening of each of these, and `mayi prove` checks `rate_limited` and `two_person_above`.
|
|
244
|
+
|
|
245
|
+
## Public-key approvals
|
|
246
|
+
|
|
247
|
+
pip install "mayi[signing]"
|
|
248
|
+
mayi keygen # mayi-approver.key (keep) and mayi-approver.key.pub (give to the gateway)
|
|
249
|
+
MAYI_APPROVER_PUBKEY=mayi-approver.key.pub mayi serve
|
|
250
|
+
mayi sign refund amount 900 --by alice --key mayi-approver.key --session ticket-1042
|
|
251
|
+
|
|
252
|
+
The gateway holds only the public key, so a stolen gateway cannot mint approvals. A gateway set up with a public key accepts only public-key tokens; one with `MAYI_SIGNING_SECRET` accepts only shared-secret tokens (both can be on).
|
|
253
|
+
|
|
254
|
+
## Session tokens: one ticket cannot act as another
|
|
255
|
+
|
|
256
|
+
The agent key alone lets a caller use any session the application has created, so a hijacked agent working ticket A could act in ticket B (with B's application values). Create the session with `"bind": true` (or start the gateway with `--require-session-token`) and the response includes a `session_token`, shown once. Give it to the agent runtime for that session only; calls without it, or with another session's token, are refused (`MayiClient.bind(session_id, token)` sends it for you). Only a hash of the token is stored. The app key does not need tokens.
|
|
257
|
+
|
|
258
|
+
## Gateway hardening
|
|
259
|
+
|
|
260
|
+
- `--tls-cert/--tls-key` serve HTTPS (TLS 1.2+); serving a non-local address without TLS prints a warning.
|
|
261
|
+
- `--rate-limit` caps requests per client address per minute; 20 failed sign-ins in a minute lock that client out briefly. Behind a proxy every client may share one address, so set limits accordingly.
|
|
262
|
+
- Key rotation: `MAYI_AGENT_KEY=new,old` accepts both until you drop `old`.
|
|
263
|
+
- Keys given by environment variable are not printed in the start-up banner.
|
|
264
|
+
- `GET /metrics` (app key) gives Prometheus counters; `GET /healthz` is open and reveals nothing; `GET /v1/audit/export` gives all records with a SHA-256 digest and, if `MAYI_AUDIT_SECRET` is set, an HMAC over them. The console sends a restrictive Content-Security-Policy.
|
|
265
|
+
- The Dockerfile install steps were run by hand and the start command was checked; the base image could not be pulled in the test environment (every registry was blocked), so a full `docker build` is still untested. Please run `docker build -t mayi .` once yourself. The hand check did find and fix a real bug (the `NOTICE` file was not copied).
|
|
266
|
+
|
|
267
|
+
## MCP proxy
|
|
268
|
+
|
|
269
|
+
mayi mcp-proxy --catalog catalog.yaml --context '{"customer_email": "a@b.test"}' \
|
|
270
|
+
--approvals-dir approvals --pending-dir pending -- python my_mcp_server.py
|
|
271
|
+
|
|
272
|
+
Point your agent host at this command instead of the server. Tools not in the catalog are hidden; arguments your app controls are removed from what the model sees and replaced by your values on the way through; held calls are not forwarded; text returned by upstream tools is remembered as untrusted so `untrusted_copy` works with no extra wiring. To approve a held call, mint a token with `mayi sign` and drop it in `--approvals-dir`. Tested end to end against a real MCP server and the official MCP client on both SDK generations (1.30 and 2.2; testing on 2.x found and fixed two compatibility bugs); stdio only, JSON-RPC batches are refused. Other MCP features (resources, prompts) pass through ungoverned. A tool with no arguments and no `_requires_approval` runs freely, as everywhere else in mayi.
|
|
273
|
+
|
|
274
|
+
## Known limits
|
|
275
|
+
|
|
276
|
+
- No outside security review yet. Do not use mayi alone to protect tools that move money.
|
|
277
|
+
- mayi bounds and filters free text the model writes (length, type, URLs, e-mail addresses, allowlists) but does not judge what it means.
|
|
278
|
+
- `untrusted_copy` is verbatim-copy detection with some decoding. Paraphrased, translated, split or spelled-out values get through. Prefer making the argument application-controlled.
|
|
279
|
+
- A tool that takes no arguments is only gated if you mark it `_requires_approval`.
|
|
280
|
+
- Rate-limit timers are in memory. The state database is SQLite on one machine; there is no clustering.
|
|
281
|
+
- The evidence is small: the AgentDojo and InjecAgent runs replay attacker calls rather than measuring how often models are fooled, and the live test uses two models and injections written by the author.
|
|
282
|
+
- mayi is not a new idea; see "How mayi relates to other work".
|