create-metamynd-agent 0.7.1 → 0.7.3

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 +38 -8
  2. package/index.mjs +63 -20
  3. package/package.json +2 -2
package/README.md CHANGED
@@ -135,23 +135,53 @@ npm start
135
135
  ### Separate tool gateway (default)
136
136
 
137
137
  This is the other half of **without MetaMynd, you can be bypassed**: WITH it — specifically, with
138
- `gateway/`, the second process this scaffolds by default — you can't be, the same way the hosted
139
- platform's own MCP counterparty can't be talked around by a compromised agent.
138
+ `gateway/`, the second process this scaffolds by default — calling the tool directly instead of
139
+ through the check no longer works, the same way the hosted platform's own MCP counterparty can't
140
+ be talked around by a compromised agent. See [What this closes, precisely](#what-this-closes-precisely)
141
+ below for exactly what that covers, including the one gap found while building it that isn't
142
+ closed yet.
140
143
 
141
144
  `guard.guardTool()` in `index.mjs` still runs — it's a fast, local, client-side pre-check that gives
142
145
  good UX (fail fast on an obviously-blocked call, no round trip) — but it is **not** what stops a
143
146
  bypass. It still calls its handler in the SAME process regardless of where the decision came from,
144
147
  so anything able to call that handler directly gets the same result the gate would have given it.
145
148
 
146
- What actually stops a bypass is that `bookFlight()` doesn't exist in the agent's process at all.
149
+ What actually stops that bypass is that `bookFlight()` doesn't exist in the agent's process at all.
147
150
  It exists only in `gateway/server.mjs` — a separate process, started separately, holding any real
148
151
  tool credentials the agent process never sees — which independently re-verifies every request
149
- against the agent's own published policy bundle before running it (same shape as the mutual
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.2.0), and requires the
154
+ agent's `authorizationId` to atomically claim single-use execution against the real stateful gate
155
+ (`requireAuthorization`, `@metamynd/agentsafe-mcp-guard` ≥ 0.2.0) — closing a confused-deputy gap
156
+ and a replay/cumulative-spend gap, both found during independent testing. Same shape as the mutual
150
157
  counterparty check in [`@metamynd/agentsafe-mcp-guard`](https://www.npmjs.com/package/@metamynd/agentsafe-mcp-guard),
151
- built with [`@metamynd/agentsafe-http-gateway`](https://www.npmjs.com/package/@metamynd/agentsafe-http-gateway)).
152
- It's a minimal slice of the same pattern proven end to end in `demo/duffel-mcp-gateway` in the
153
- AgentSafe repo (mutual handshake, x402 payment binding, capability tokens) — this scaffold gives you
154
- just the part that closes the bypass, not the whole protocol.
158
+ built with [`@metamynd/agentsafe-http-gateway`](https://www.npmjs.com/package/@metamynd/agentsafe-http-gateway).
159
+ It's a minimal slice of the fuller pattern proven end to end in `demo/duffel-mcp-gateway` in the
160
+ AgentSafe repo (mutual handshake, x402 payment binding, capability tokens) — this scaffold gives
161
+ you the parts that close direct-call, confused-deputy, replay, and cumulative-spend bypasses, not
162
+ the whole protocol.
163
+
164
+ #### What this closes, precisely
165
+
166
+ Named precisely, not left implicit:
167
+
168
+ - **Direct call.** `bookFlight()` doesn't exist in the agent's process.
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.
172
+ - **Replay.** A captured, resent request fails to atomically claim single-use execution the second
173
+ time — `requireAuthorization`.
174
+ - **Cumulative spend.** The claimed authorization only exists because the real stateful gate
175
+ already checked it against the mandate's TOTAL budget when minted, not just this one request's
176
+ amount — so many small legal-looking calls can't add up past the cap this way.
177
+
178
+ **One narrower gap, found while building this and disclosed rather than left implicit:** the claim
179
+ above verifies the claimed authorization's own `agentDid`/`amount`/`currency` match the request —
180
+ not `merchant`, because the backend's hold record doesn't currently store it. A same-amount,
181
+ same-currency authorization legitimately obtained for one merchant could in principle unlock a
182
+ booking with a different merchant. Closing this needs a small backend change (storing `merchant`
183
+ on the hold); it isn't done here. `gateway/README.md`'s own "What this closes, precisely" section
184
+ has the same disclosure.
155
185
 
156
186
  Pass `--no-gateway` to opt out and get the old single-process scaffold instead — e.g. if you're
157
187
  already running your own separate gateway and don't need this one. **You are back to being
package/index.mjs CHANGED
@@ -26,9 +26,13 @@ const GUARD_PKG = '@metamynd/agentsafe-guard';
26
26
  const GUARD_VERSION = '^0.5.0';
27
27
  // The default hosted scaffold's SECOND process — the tool gateway (see scaffoldProject).
28
28
  const MCP_GUARD_PKG = '@metamynd/agentsafe-mcp-guard';
29
- const MCP_GUARD_VERSION = '^0.1.0';
29
+ // 0.2.0 adds requireAuthorization (closes replay + cumulative spend) — this scaffold sets that
30
+ // option, so a range that could resolve below 0.2.0 would silently scaffold a no-op.
31
+ const MCP_GUARD_VERSION = '^0.2.0';
30
32
  const GATEWAY_PKG = '@metamynd/agentsafe-http-gateway';
31
- const GATEWAY_VERSION = '^0.1.0';
33
+ // 0.2.0 fixes a confused-deputy gap (payload not bound to the signed request) — the CLI must
34
+ // never scaffold a range that could resolve below it.
35
+ const GATEWAY_VERSION = '^0.2.0';
32
36
  const DEFAULT_API = 'https://metamynd.ai/api/v1';
33
37
  const DEFAULT_GATEWAY_PORT = 4401; // distinct from --harness's dashboard (4400)
34
38
 
@@ -455,7 +459,11 @@ const GATEWAY = process.env.GATEWAY_URL || 'http://localhost:${gatewayPort}';
455
459
  // --- Calls the gateway process instead of a local function. There is no raw bookFlight() in
456
460
  // --- this file to call directly — the tool, and any real credentials it needs, live only in
457
461
  // --- ./gateway, which independently re-verifies this signed request itself.
458
- async function bookFlightViaGateway(args) {
462
+ // --- \`decision\` is guardTool()'s own verdict, already produced by the REAL remote gate for any
463
+ // --- value-bearing action (sealValueActions, on by default) — its authorizationId is what lets
464
+ // --- the gateway atomically claim single-use execution, closing replay + cumulative spend, not
465
+ // --- just re-checking policy. See ./gateway/README.md.
466
+ async function bookFlightViaGateway(args, decision) {
459
467
  const signed = guard.buildSignedRequest({
460
468
  action: '${scope}',
461
469
  amount: args.amount,
@@ -463,6 +471,7 @@ async function bookFlightViaGateway(args) {
463
471
  merchant: args.merchant,
464
472
  context: { tool: 'book-flight', riskLevel: args.riskLevel ?? 'low' },
465
473
  });
474
+ signed.authorizationId = decision?.authorizationId;
466
475
  const res = await fetch(GATEWAY + '/book-flight', {
467
476
  method: 'POST',
468
477
  headers: { 'content-type': 'application/json', 'x-magp-request': JSON.stringify(signed) },
@@ -690,14 +699,17 @@ A MetaMynd/AgentSafe-governed agent, scaffolded with \`create-metamynd-agent\`.
690
699
  ${gatewaySection}${
691
700
  withGateway
692
701
  ? `**With MetaMynd's gateway, you can't be bypassed** — that's what this section is about.
693
- This scaffold's default shape (agent + separate gateway process, port ${gatewayPort} by
694
- default) is the actual enforcement boundary: \`guard.guardTool()\` in \`index.mjs\` is a
695
- client-side convenience, not a boundary — it still runs its handler in-process regardless of
696
- where the decision came from. What actually stops a bypass is that \`bookFlight()\` itself only
697
- exists in \`./gateway\`, a process this one cannot reach into, which independently re-verifies
698
- every request against this agent's own policy bundle. Re-scaffold with \`--no-gateway\` for the
699
- old single-process shape it is NOT a separate enforcement boundary; see its own generated
700
- README for why.`
702
+ This scaffold's default shape (agent + separate gateway process, port ${gatewayPort} by default)
703
+ is the actual enforcement boundary: \`guard.guardTool()\` in \`index.mjs\` is a client-side
704
+ convenience, not a boundary — it still runs its handler in-process regardless of where the
705
+ decision came from. What actually stops direct-call and confused-deputy bypasses is that
706
+ \`bookFlight()\` itself only exists in \`./gateway\`, a process this one cannot reach into, which
707
+ independently re-verifies every request against this agent's own policy bundle AND binds it to
708
+ the actual body being executed. Replay and cumulative spend are closed too, via
709
+ \`requireAuthorization\` — see \`./gateway/README.md\`'s "What this closes, precisely" section for
710
+ exactly what that covers, including the one narrower gap disclosed there. Re-scaffold with
711
+ \`--no-gateway\` for the old single-process shape — it is NOT a separate enforcement boundary at
712
+ all; see its own generated README for why.`
701
713
  : `**Without MetaMynd, you can be bypassed** — this is that case. This scaffold has no
702
714
  separate gateway process (either \`--sandbox\`, which never provisions real credentials, or
703
715
  \`--no-gateway\` was passed): \`guard.guardTool()\` wraps a tool in the SAME process as the check
@@ -760,7 +772,11 @@ const routes = [{ method: 'POST', path: '/book-flight', action: '${scope}' }];
760
772
 
761
773
  // No serviceKey: this minimal gateway only calls verifyRequest() (re-check a signed request),
762
774
  // not the mutual-handshake methods, which are the only thing that needs it.
763
- const guard = createMcpGuard({ serviceDid: 'did:local:${scope}-gateway', issuerApi: MAGP_API });
775
+ //
776
+ // requireAuthorization: true is what closes replay and cumulative spend, not just per-request
777
+ // policy — it requires the agent's authorizationId (from a REAL guard.authorize() call) to
778
+ // atomically claim single-use execution against the issuer before this gateway runs the tool.
779
+ const guard = createMcpGuard({ serviceDid: 'did:local:${scope}-gateway', issuerApi: MAGP_API, requireAuthorization: true });
764
780
 
765
781
  const gateway = createHttpGateway({
766
782
  guard,
@@ -838,7 +854,8 @@ function gatewayReadme(slug, scope, port) {
838
854
  return `# ${slug}-gateway
839
855
 
840
856
  **With MetaMynd, you can't be bypassed.** This process is why. It is the **real enforcement
841
- boundary** for \`${slug}\`'s tool(s) — not \`../index.mjs\`.
857
+ boundary** for \`${slug}\`'s tool(s) — not \`../index.mjs\`. See
858
+ [What this closes, precisely](#what-this-closes-precisely) below for exactly what that covers.
842
859
 
843
860
  ## Why this exists
844
861
 
@@ -854,7 +871,9 @@ This process closes that gap by being a **separate** one. The agent has no way t
854
871
  and call \`bookFlight()\` directly, because \`bookFlight()\` doesn't exist in the agent's process —
855
872
  it exists only here, and every request that reaches it has already been independently
856
873
  re-verified against this agent's OWN published policy bundle, fetched over the network by THIS
857
- process, not trusted from the agent's say-so.
874
+ process, not trusted from the agent's say-so — AND bound to the actual body being executed
875
+ (payload binding) AND to a real, single-use, stateful authorization (\`requireAuthorization\`) —
876
+ see below for what each of those means precisely.
858
877
 
859
878
  ## Run
860
879
 
@@ -881,12 +900,36 @@ add another protected route here rather than adding a local function back in \`i
881
900
  \`bookFlight()\`.
882
901
  - \`.env.example\` — where real tool credentials go (copy to \`.env\`, fill in, never commit).
883
902
 
884
- ## Beyond this minimal slice
885
-
886
- This gateway only re-verifies a signed request (§9.3/§9.6 of the MAGP spec). It does not do the
887
- mutual DID handshake, x402 payment binding, or commitment-bound capability tokens that a
888
- production Service integration would add — see \`@metamynd/agentsafe-mcp-guard\`'s own README for
889
- those, and \`demo/duffel-mcp-gateway\` in the AgentSafe repo for a full worked example.
903
+ ## What this closes, precisely
904
+
905
+ Four independent checks, each closing a different bypass an agent (or anything able to call its
906
+ own code, or a network attacker) might attempt:
907
+
908
+ - **Direct call.** \`bookFlight()\` doesn't exist in the agent's process. There's nothing to call.
909
+ - **Confused deputy (payload).** The gateway re-verifies the signed request against this agent's
910
+ own policy AND binds it to the actual request body (payload binding,
911
+ \`@metamynd/agentsafe-http-gateway\` ≥ 0.2.0) — signing a cheap request while executing an
912
+ expensive one is refused before the tool ever runs.
913
+ - **Replay.** \`requireAuthorization: true\` (set in \`server.mjs\`) requires the agent's
914
+ \`authorizationId\` — from a REAL \`guard.authorize()\` call, which \`index.mjs\` already makes for
915
+ any value-bearing action by default — to atomically claim single-use execution against the
916
+ issuer. A captured, replayed request fails the claim the second time.
917
+ - **Cumulative spend.** The same \`authorizationId\` only exists because the real stateful gate
918
+ already checked it against the mandate's TOTAL budget when it was minted — not just this one
919
+ request's amount. Many small legal-looking calls can't add up past the mandate cap this way,
920
+ because each needed its own real authorization first.
921
+
922
+ **One narrower gap, found while building this and disclosed rather than left implicit:** the
923
+ claim above verifies the claimed authorization's own \`agentDid\`/\`amount\`/\`currency\` match the
924
+ request being executed — but not \`merchant\`, because the backend's hold record doesn't currently
925
+ store it. A same-amount, same-currency authorization legitimately obtained for one merchant could
926
+ in principle be presented to unlock a booking with a different merchant. Closing this needs a
927
+ small backend change (storing \`merchant\` on the hold so it can be compared too); it isn't done
928
+ here. See \`@metamynd/agentsafe-mcp-guard\`'s own README (\`requireAuthorization\`) for the full
929
+ mechanism, and \`demo/duffel-mcp-gateway\` in the AgentSafe repo for the fuller pattern this is a
930
+ slice of (mutual DID handshake, x402 payment binding, commitment-bound capability tokens — which
931
+ WOULD close the merchant gap too, by binding the whole transaction to a cryptographic commitment
932
+ rather than comparing individual stored fields).
890
933
  `;
891
934
  }
892
935
 
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "create-metamynd-agent",
3
- "version": "0.7.1",
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 (the real enforcement boundary). --harness scaffolds a free, local, zero-network governance harness instead.",
3
+ "version": "0.7.3",
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": {
7
7
  "create-metamynd-agent": "index.mjs"