create-metamynd-agent 0.7.6 → 0.7.7

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.
Files changed (3) hide show
  1. package/README.md +17 -12
  2. package/index.mjs +29 -17
  3. 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.3.0), and requires the
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/currency/merchant in the body than what was signed) is refused before the tool
171
- runs — payload binding (`@metamynd/agentsafe-http-gateway` ≥ 0.3.0). The default binder fails
172
- the request CLOSED, not just when it finds a mismatched flat field, but also when it can't find
173
- the governed fields at all nested JSON, an array, a renamed or differently-cased key. That
174
- gap was found and closed the same way: a signed $250/skyward-air request had previously been
175
- able to execute $5000/evil-corp via `{ booking: { amount, merchant } }`, because the flat
176
- matcher found nothing to compare and treated "nothing found" as "nothing to check."
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.** A signed-transaction tool (raw bytes) or a nested x402 payload carries its
183
- amount somewhere a naive spend cap never looks — `amount-unknown` (`@metamynd/agentsafe-guard`
184
- ≥ 0.6.0, `@metamynd/agentsafe-mcp-guard` ≥ 0.3.0) blocks by default when the gate can't
185
- determine the value, instead of letting it slip past the cap untested.
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 fixes the follow-on gap: the DEFAULT binder only sees flat, top-level, exactly-named
40
- // fields, and silently skipped binding (not blocked) for anything else nested JSON, an array,
41
- // a renamed or differently-cased key. Verified live: a signed $250/skyward-air request executed
42
- // $5000/evil-corp via `{ booking: { amount, merchant } }`. 0.3.0 fails that CLOSED instead.
43
- const GATEWAY_VERSION = '^0.3.0';
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
 
@@ -916,13 +917,13 @@ own code, or a network attacker) might attempt:
916
917
  - **Direct call.** \`bookFlight()\` doesn't exist in the agent's process. There's nothing to call.
917
918
  - **Confused deputy (payload).** The gateway re-verifies the signed request against this agent's
918
919
  own policy AND binds it to the actual request body (payload binding,
919
- \`@metamynd/agentsafe-http-gateway\` ≥ 0.3.0) — signing a cheap request while executing an
920
- expensive one is refused before the tool ever runs. The default binder fails CLOSED not just
921
- on a mismatched flat field but whenever it can't find the governed fields at all nested
922
- JSON, an array, a renamed or differently-cased key. A signed \$250 request had previously been
923
- able to execute \$5000 via \`{ booking: { amount, merchant } }\`, because the flat matcher found
924
- nothing to compare and treated "nothing found" as "nothing to check" fixed the same way this
925
- list gets fixed: found, closed, named here.
920
+ \`@metamynd/agentsafe-http-gateway\` ≥ 0.4.0) — signing a cheap request while executing an
921
+ expensive one is refused before the tool ever runs. The default binder requires \`amount\`/
922
+ \`merchant\` to actually be found in the body whenever the signature names a real value for
923
+ them not just "did the body offer at least one correct-looking field." A first attempt at
924
+ this (0.3.0) checked the weaker version and was re-tested and closed the same day: a correct
925
+ decoy in one field let the OTHER field hide anywherenested, renamed, an array, or an
926
+ entirely empty/non-JSON body.
926
927
  - **Replay.** \`requireAuthorization: true\` (set in \`server.mjs\`) requires the agent's
927
928
  \`authorizationId\` — from a REAL \`guard.authorize()\` call, which \`index.mjs\` already makes for
928
929
  any value-bearing action by default — to atomically claim single-use execution against the
@@ -931,10 +932,13 @@ own code, or a network attacker) might attempt:
931
932
  already checked it against the mandate's TOTAL budget when it was minted — not just this one
932
933
  request's amount. Many small legal-looking calls can't add up past the mandate cap this way,
933
934
  because each needed its own real authorization first.
934
- - **Amount unknown.** A signed-transaction tool or a nested payload can carry its amount
935
- somewhere a naive spend cap never looks \`amount-unknown\`
936
- (\`@metamynd/agentsafe-mcp-guard\` 0.3.0) blocks by default when the gate can't determine the
937
- value, instead of letting it slip past the cap untested.
935
+ - **Amount unknown.** \`amount-unknown\` (\`@metamynd/agentsafe-mcp-guard\` 0.3.0) blocks a
936
+ platform tool by default when its raw bytes or a nested payload hide the amount from a naive
937
+ spend cap AND this agent's OWN starter SOP (see \`agent.metamynd.json\` /
938
+ \`harness-rules.json\`) puts the same check ahead of its per-transaction cap. That second part
939
+ didn't used to be true: the SOP only ever authored \`amount-over\`, which silently does not fire
940
+ on a missing or string amount, so either one slipped the cap untested — the atom existing
941
+ wasn't the gap, this template never authoring it was.
938
942
 
939
943
  The claim above also checks the claimed authorization's own \`agentDid\`/\`amount\`/\`currency\`/
940
944
  \`merchant\` against the request actually being executed (\`@metamynd/agentsafe-mcp-guard\` ≥ 0.2.1)
@@ -1064,10 +1068,17 @@ async function runSandbox(args) {
1064
1068
 
1065
1069
  /** Mirrors defaultSopDocument() in backend/src/features/onboarding/onboarding.provision.ts —
1066
1070
  * same starter rules the hosted platform issues, so a harness project behaves identically
1067
- * to a freshly-provisioned one before anyone edits either. */
1071
+ * to a freshly-provisioned one before anyone edits either.
1072
+ *
1073
+ * `amount-unknown` first matters MORE here than on the hosted path: evaluateLocally() runs
1074
+ * entirely client-side with no schema boundary in front of it, so nothing stops a caller from
1075
+ * passing amount: "5000" (a string) or omitting amount entirely — `amount-over` silently does
1076
+ * not fire on either (`typeof c.amount === 'number'` is false), so the cap passes untested,
1077
+ * not safe. Ordering amount-unknown first blocks that instead of letting it through. */
1068
1078
  function harnessDefaultSop(perTxnMax) {
1069
1079
  return {
1070
1080
  molecules: [
1081
+ { id: 'amount-known', name: 'Amount must be determinable', combinator: 'any', atoms: [{ id: 'a0', predicate: 'amount-unknown' }], decision: 'block', reasonCode: 'AMOUNT_NOT_DETERMINABLE' },
1071
1082
  { id: 'cap', name: 'Per-transaction cap', combinator: 'any', atoms: [{ id: 'a1', predicate: 'amount-over', config: { limit: perTxnMax } }], decision: 'block', reasonCode: 'SOP_SPEND_CAP' },
1072
1083
  { 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
1084
  ],
@@ -1139,6 +1150,7 @@ function renderConstraint(c) {
1139
1150
  function renderAtom(a) {
1140
1151
  const c = a.config || {};
1141
1152
  switch (a.predicate) {
1153
+ case 'amount-unknown': return \`transaction amount must be a real, determinable number\`;
1142
1154
  case 'amount-over': return \`transaction amount must not exceed \${c.limit}\`;
1143
1155
  case 'cumulative-over': return \`cumulative spend must not exceed \${c.limit}\`;
1144
1156
  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.6",
3
+ "version": "0.7.7",
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": {