create-metamynd-agent 0.7.6 → 0.7.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +17 -12
- package/index.mjs +40 -18
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -150,7 +150,7 @@ What actually stops that bypass is that `bookFlight()` doesn't exist in the agen
|
|
|
150
150
|
It exists only in `gateway/server.mjs` — a separate process, started separately, holding any real
|
|
151
151
|
tool credentials the agent process never sees — which independently re-verifies every request
|
|
152
152
|
against the agent's own published policy bundle before running it, **binds that request to the
|
|
153
|
-
actual body being executed** (`@metamynd/agentsafe-http-gateway` ≥ 0.
|
|
153
|
+
actual body being executed** (`@metamynd/agentsafe-http-gateway` ≥ 0.4.0), and requires the
|
|
154
154
|
agent's `authorizationId` to atomically claim single-use execution against the real stateful gate
|
|
155
155
|
(`requireAuthorization`, `@metamynd/agentsafe-mcp-guard` ≥ 0.3.0) — closing a confused-deputy gap
|
|
156
156
|
and a replay/cumulative-spend gap, both found during independent testing. Same shape as the mutual
|
|
@@ -167,22 +167,27 @@ Named precisely, not left implicit:
|
|
|
167
167
|
|
|
168
168
|
- **Direct call.** `bookFlight()` doesn't exist in the agent's process.
|
|
169
169
|
- **Confused deputy (payload).** Signing a cheap request while executing an expensive one (a
|
|
170
|
-
different amount/
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
170
|
+
different amount/merchant in the body than what was signed) is refused before the tool runs —
|
|
171
|
+
payload binding (`@metamynd/agentsafe-http-gateway` ≥ 0.4.0). The default binder requires
|
|
172
|
+
`amount`/`merchant` to actually be found in the body whenever the signed request names a real
|
|
173
|
+
value for them — not just "did the body offer at least one correct-looking field." A first
|
|
174
|
+
attempt at this (0.3.0) checked the weaker version and was re-tested and closed the same day: a
|
|
175
|
+
correct decoy in one field (e.g. a matching `merchant`) let the OTHER field hide anywhere —
|
|
176
|
+
nested, renamed, an array, or an entirely empty/non-JSON body.
|
|
177
177
|
- **Replay.** A captured, resent request fails to atomically claim single-use execution the second
|
|
178
178
|
time — `requireAuthorization`.
|
|
179
179
|
- **Cumulative spend.** The claimed authorization only exists because the real stateful gate
|
|
180
180
|
already checked it against the mandate's TOTAL budget when minted, not just this one request's
|
|
181
181
|
amount — so many small legal-looking calls can't add up past the cap this way.
|
|
182
|
-
- **Amount unknown.**
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
182
|
+
- **Amount unknown.** Two separate places this matters, both actually authored, not just
|
|
183
|
+
available: the platform's own custodial-signing tools (`@metamynd/agentsafe-guard` ≥ 0.6.0,
|
|
184
|
+
`@metamynd/agentsafe-mcp-guard` ≥ 0.3.0) block by default when a signed-transaction tool's raw
|
|
185
|
+
bytes or a nested x402 payload hide the amount from a naive spend cap — AND this agent's own
|
|
186
|
+
starter SOP puts the same `amount-unknown` check ahead of its per-transaction cap (both the
|
|
187
|
+
hosted default and `--harness`'s local one). The atom existing was not the gap: for a while
|
|
188
|
+
this SOP still only ever authored `amount-over`, which silently does not fire on a missing or
|
|
189
|
+
string amount (`typeof c.amount === 'number'` is false either way) — a real, live-confirmed way
|
|
190
|
+
to slip a booking's cap untested. Fixed at the template, not just the atom registry.
|
|
186
191
|
|
|
187
192
|
The claim above also checks `agentDid`/`amount`/`currency`/`merchant` together against the
|
|
188
193
|
request being executed (`@metamynd/agentsafe-mcp-guard` ≥ 0.2.1) — a same-amount, same-currency
|
package/index.mjs
CHANGED
|
@@ -36,11 +36,12 @@ const MCP_GUARD_VERSION = '^0.3.0';
|
|
|
36
36
|
const GATEWAY_PKG = '@metamynd/agentsafe-http-gateway';
|
|
37
37
|
// 0.2.0 fixes a confused-deputy gap (payload not bound to the signed request) — the CLI must
|
|
38
38
|
// never scaffold a range that could resolve below it.
|
|
39
|
-
// 0.3.0
|
|
40
|
-
// fields
|
|
41
|
-
//
|
|
42
|
-
//
|
|
43
|
-
|
|
39
|
+
// 0.3.0 was a first, INCOMPLETE attempt at the follow-on gap (checked only "did the body offer
|
|
40
|
+
// NONE of the three fields" — a correct decoy in one field let the other hide anywhere). 0.4.0
|
|
41
|
+
// is the actual fix: requires amount/merchant specifically, whenever the signature names a real
|
|
42
|
+
// value for them. Re-tested live and closed same day; ^0.3.0 here would still resolve to the
|
|
43
|
+
// broken version.
|
|
44
|
+
const GATEWAY_VERSION = '^0.4.0';
|
|
44
45
|
const DEFAULT_API = 'https://metamynd.ai/api/v1';
|
|
45
46
|
const DEFAULT_GATEWAY_PORT = 4401; // distinct from --harness's dashboard (4400)
|
|
46
47
|
|
|
@@ -776,7 +777,12 @@ async function bookFlight(args) {
|
|
|
776
777
|
|
|
777
778
|
// One protected route: only a request signed by this agent, for exactly this action, and
|
|
778
779
|
// re-verified against this agent's own mandate/SOP, reaches bookFlight() below.
|
|
779
|
-
|
|
780
|
+
//
|
|
781
|
+
// valueFields is explicit on purpose, not left to the gateway's own default (which would be
|
|
782
|
+
// this exact list anyway): a route with a real amount/merchant should always say so itself,
|
|
783
|
+
// rather than relying on a library default to guess right. A route with NO value concept at
|
|
784
|
+
// all (a read, a status check) should set valueFields: [] instead — see the gateway's README.
|
|
785
|
+
const routes = [{ method: 'POST', path: '/book-flight', action: '${scope}', valueFields: ['amount', 'merchant'] }];
|
|
780
786
|
|
|
781
787
|
// No serviceKey: this minimal gateway only calls verifyRequest() (re-check a signed request),
|
|
782
788
|
// not the mutual-handshake methods, which are the only thing that needs it.
|
|
@@ -916,13 +922,18 @@ own code, or a network attacker) might attempt:
|
|
|
916
922
|
- **Direct call.** \`bookFlight()\` doesn't exist in the agent's process. There's nothing to call.
|
|
917
923
|
- **Confused deputy (payload).** The gateway re-verifies the signed request against this agent's
|
|
918
924
|
own policy AND binds it to the actual request body (payload binding,
|
|
919
|
-
\`@metamynd/agentsafe-http-gateway\` ≥ 0.
|
|
920
|
-
expensive one is refused before the tool ever runs.
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
925
|
+
\`@metamynd/agentsafe-http-gateway\` ≥ 0.4.5) — signing a cheap request while executing an
|
|
926
|
+
expensive one is refused before the tool ever runs. This route's \`valueFields: ['amount',
|
|
927
|
+
'merchant']\` (see \`server.mjs\`) is an explicit, server-controlled requirement, not a guess
|
|
928
|
+
inferred from anything the signed request itself declares — that distinction is what closes
|
|
929
|
+
the full history below, not just the most recent case in it. Earlier attempts checked
|
|
930
|
+
progressively weaker versions of "is this real": 0.3.0 only refused a body offering NONE of
|
|
931
|
+
the governed fields (a correct decoy in one field let the other hide nested, renamed, an
|
|
932
|
+
array, or an entirely empty/non-JSON body); 0.4.0–0.4.2 required a field only when the
|
|
933
|
+
SIGNED request's own value for it looked "real," which a signer could defeat by signing
|
|
934
|
+
\`amount: 0\` — or, identically, by never signing an amount at all, since both verify against
|
|
935
|
+
the exact same canonical message. \`valueFields\` moves the requirement to something the
|
|
936
|
+
signer never controls at all.
|
|
926
937
|
- **Replay.** \`requireAuthorization: true\` (set in \`server.mjs\`) requires the agent's
|
|
927
938
|
\`authorizationId\` — from a REAL \`guard.authorize()\` call, which \`index.mjs\` already makes for
|
|
928
939
|
any value-bearing action by default — to atomically claim single-use execution against the
|
|
@@ -931,10 +942,13 @@ own code, or a network attacker) might attempt:
|
|
|
931
942
|
already checked it against the mandate's TOTAL budget when it was minted — not just this one
|
|
932
943
|
request's amount. Many small legal-looking calls can't add up past the mandate cap this way,
|
|
933
944
|
because each needed its own real authorization first.
|
|
934
|
-
- **Amount unknown.**
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
945
|
+
- **Amount unknown.** \`amount-unknown\` (\`@metamynd/agentsafe-mcp-guard\` ≥ 0.3.0) blocks a
|
|
946
|
+
platform tool by default when its raw bytes or a nested payload hide the amount from a naive
|
|
947
|
+
spend cap — AND this agent's OWN starter SOP (see \`agent.metamynd.json\` /
|
|
948
|
+
\`harness-rules.json\`) puts the same check ahead of its per-transaction cap. That second part
|
|
949
|
+
didn't used to be true: the SOP only ever authored \`amount-over\`, which silently does not fire
|
|
950
|
+
on a missing or string amount, so either one slipped the cap untested — the atom existing
|
|
951
|
+
wasn't the gap, this template never authoring it was.
|
|
938
952
|
|
|
939
953
|
The claim above also checks the claimed authorization's own \`agentDid\`/\`amount\`/\`currency\`/
|
|
940
954
|
\`merchant\` against the request actually being executed (\`@metamynd/agentsafe-mcp-guard\` ≥ 0.2.1)
|
|
@@ -1064,10 +1078,17 @@ async function runSandbox(args) {
|
|
|
1064
1078
|
|
|
1065
1079
|
/** Mirrors defaultSopDocument() in backend/src/features/onboarding/onboarding.provision.ts —
|
|
1066
1080
|
* same starter rules the hosted platform issues, so a harness project behaves identically
|
|
1067
|
-
* to a freshly-provisioned one before anyone edits either.
|
|
1081
|
+
* to a freshly-provisioned one before anyone edits either.
|
|
1082
|
+
*
|
|
1083
|
+
* `amount-unknown` first matters MORE here than on the hosted path: evaluateLocally() runs
|
|
1084
|
+
* entirely client-side with no schema boundary in front of it, so nothing stops a caller from
|
|
1085
|
+
* passing amount: "5000" (a string) or omitting amount entirely — `amount-over` silently does
|
|
1086
|
+
* not fire on either (`typeof c.amount === 'number'` is false), so the cap passes untested,
|
|
1087
|
+
* not safe. Ordering amount-unknown first blocks that instead of letting it through. */
|
|
1068
1088
|
function harnessDefaultSop(perTxnMax) {
|
|
1069
1089
|
return {
|
|
1070
1090
|
molecules: [
|
|
1091
|
+
{ id: 'amount-known', name: 'Amount must be determinable', combinator: 'any', atoms: [{ id: 'a0', predicate: 'amount-unknown' }], decision: 'block', reasonCode: 'AMOUNT_NOT_DETERMINABLE' },
|
|
1071
1092
|
{ id: 'cap', name: 'Per-transaction cap', combinator: 'any', atoms: [{ id: 'a1', predicate: 'amount-over', config: { limit: perTxnMax } }], decision: 'block', reasonCode: 'SOP_SPEND_CAP' },
|
|
1072
1093
|
{ id: 'review', name: 'High-risk review', combinator: 'any', atoms: [{ id: 'a2', predicate: 'risk-at-or-above', config: { level: 'high' } }], decision: 'escalate', reasonCode: 'RISK_REVIEW' },
|
|
1073
1094
|
],
|
|
@@ -1139,6 +1160,7 @@ function renderConstraint(c) {
|
|
|
1139
1160
|
function renderAtom(a) {
|
|
1140
1161
|
const c = a.config || {};
|
|
1141
1162
|
switch (a.predicate) {
|
|
1163
|
+
case 'amount-unknown': return \`transaction amount must be a real, determinable number\`;
|
|
1142
1164
|
case 'amount-over': return \`transaction amount must not exceed \${c.limit}\`;
|
|
1143
1165
|
case 'cumulative-over': return \`cumulative spend must not exceed \${c.limit}\`;
|
|
1144
1166
|
case 'jurisdiction-not-allowed': return \`jurisdiction must be one of [\${(c.allowed || []).join(', ')}]\`;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "create-metamynd-agent",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.8",
|
|
4
4
|
"description": "Scaffold a MetaMynd/AgentSafe-governed AI agent in one command — logs in, provisions the agent (identity + mandate + SOP + Standards) in a single call, writes agent.metamynd.json plus a runnable agent + separate tool-gateway process that closes direct-call, confused-deputy, replay, and cumulative-spend bypasses. --harness scaffolds a free, local, zero-network governance harness instead.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|