create-metamynd-agent 0.7.2 → 0.7.4
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 +29 -21
- package/index.mjs +58 -42
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -137,8 +137,9 @@ npm start
|
|
|
137
137
|
This is the other half of **without MetaMynd, you can be bypassed**: WITH it — specifically, with
|
|
138
138
|
`gateway/`, the second process this scaffolds by default — calling the tool directly instead of
|
|
139
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 [
|
|
141
|
-
below for the
|
|
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.
|
|
142
143
|
|
|
143
144
|
`guard.guardTool()` in `index.mjs` still runs — it's a fast, local, client-side pre-check that gives
|
|
144
145
|
good UX (fail fast on an obviously-blocked call, no round trip) — but it is **not** what stops a
|
|
@@ -148,30 +149,37 @@ so anything able to call that handler directly gets the same result the gate wou
|
|
|
148
149
|
What actually stops that bypass is that `bookFlight()` doesn't exist in the agent's process at all.
|
|
149
150
|
It exists only in `gateway/server.mjs` — a separate process, started separately, holding any real
|
|
150
151
|
tool credentials the agent process never sees — which independently re-verifies every request
|
|
151
|
-
against the agent's own published policy bundle before running it,
|
|
152
|
-
actual body being executed** (`@metamynd/agentsafe-http-gateway` ≥ 0.2.0)
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
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
|
|
157
|
+
counterparty check in [`@metamynd/agentsafe-mcp-guard`](https://www.npmjs.com/package/@metamynd/agentsafe-mcp-guard),
|
|
158
|
+
built with [`@metamynd/agentsafe-http-gateway`](https://www.npmjs.com/package/@metamynd/agentsafe-http-gateway).
|
|
157
159
|
It's a minimal slice of the fuller pattern proven end to end in `demo/duffel-mcp-gateway` in the
|
|
158
|
-
AgentSafe repo (mutual handshake, x402 payment binding, capability tokens) — this scaffold gives
|
|
159
|
-
|
|
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.
|
|
160
163
|
|
|
161
|
-
####
|
|
164
|
+
#### What this closes, precisely
|
|
162
165
|
|
|
163
166
|
Named precisely, not left implicit:
|
|
164
167
|
|
|
165
|
-
- **
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
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
|
+
The claim above also checks `agentDid`/`amount`/`currency`/`merchant` together against the
|
|
179
|
+
request being executed (`@metamynd/agentsafe-mcp-guard` ≥ 0.2.1) — a same-amount, same-currency
|
|
180
|
+
authorization legitimately obtained for one merchant cannot unlock a booking with a different
|
|
181
|
+
one. That gap was found while building this and closed, not left open; `gateway/README.md`'s own
|
|
182
|
+
"What this closes, precisely" section names it the same way.
|
|
175
183
|
|
|
176
184
|
Pass `--no-gateway` to opt out and get the old single-process scaffold instead — e.g. if you're
|
|
177
185
|
already running your own separate gateway and don't need this one. **You are back to being
|
package/index.mjs
CHANGED
|
@@ -26,7 +26,9 @@ 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
|
-
|
|
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
33
|
// 0.2.0 fixes a confused-deputy gap (payload not bound to the signed request) — the CLI must
|
|
32
34
|
// never scaffold a range that could resolve below it.
|
|
@@ -457,7 +459,11 @@ const GATEWAY = process.env.GATEWAY_URL || 'http://localhost:${gatewayPort}';
|
|
|
457
459
|
// --- Calls the gateway process instead of a local function. There is no raw bookFlight() in
|
|
458
460
|
// --- this file to call directly — the tool, and any real credentials it needs, live only in
|
|
459
461
|
// --- ./gateway, which independently re-verifies this signed request itself.
|
|
460
|
-
|
|
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) {
|
|
461
467
|
const signed = guard.buildSignedRequest({
|
|
462
468
|
action: '${scope}',
|
|
463
469
|
amount: args.amount,
|
|
@@ -465,6 +471,7 @@ async function bookFlightViaGateway(args) {
|
|
|
465
471
|
merchant: args.merchant,
|
|
466
472
|
context: { tool: 'book-flight', riskLevel: args.riskLevel ?? 'low' },
|
|
467
473
|
});
|
|
474
|
+
signed.authorizationId = decision?.authorizationId;
|
|
468
475
|
const res = await fetch(GATEWAY + '/book-flight', {
|
|
469
476
|
method: 'POST',
|
|
470
477
|
headers: { 'content-type': 'application/json', 'x-magp-request': JSON.stringify(signed) },
|
|
@@ -691,17 +698,18 @@ A MetaMynd/AgentSafe-governed agent, scaffolded with \`create-metamynd-agent\`.
|
|
|
691
698
|
|
|
692
699
|
${gatewaySection}${
|
|
693
700
|
withGateway
|
|
694
|
-
? `**With MetaMynd's gateway,
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
701
|
+
? `**With MetaMynd's gateway, you can't be bypassed** — that's what this section is about.
|
|
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.`
|
|
705
713
|
: `**Without MetaMynd, you can be bypassed** — this is that case. This scaffold has no
|
|
706
714
|
separate gateway process (either \`--sandbox\`, which never provisions real credentials, or
|
|
707
715
|
\`--no-gateway\` was passed): \`guard.guardTool()\` wraps a tool in the SAME process as the check
|
|
@@ -764,7 +772,11 @@ const routes = [{ method: 'POST', path: '/book-flight', action: '${scope}' }];
|
|
|
764
772
|
|
|
765
773
|
// No serviceKey: this minimal gateway only calls verifyRequest() (re-check a signed request),
|
|
766
774
|
// not the mutual-handshake methods, which are the only thing that needs it.
|
|
767
|
-
|
|
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 });
|
|
768
780
|
|
|
769
781
|
const gateway = createHttpGateway({
|
|
770
782
|
guard,
|
|
@@ -841,11 +853,9 @@ function gatewayGitignore() {
|
|
|
841
853
|
function gatewayReadme(slug, scope, port) {
|
|
842
854
|
return `# ${slug}-gateway
|
|
843
855
|
|
|
844
|
-
**With MetaMynd, you can't
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
things it does NOT yet close on its own: replaying a captured request, and spend adding up
|
|
848
|
-
across many separately-legal calls.
|
|
856
|
+
**With MetaMynd, you can't be bypassed.** This process is why. It is the **real enforcement
|
|
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.
|
|
849
859
|
|
|
850
860
|
## Why this exists
|
|
851
861
|
|
|
@@ -861,7 +871,9 @@ This process closes that gap by being a **separate** one. The agent has no way t
|
|
|
861
871
|
and call \`bookFlight()\` directly, because \`bookFlight()\` doesn't exist in the agent's process —
|
|
862
872
|
it exists only here, and every request that reaches it has already been independently
|
|
863
873
|
re-verified against this agent's OWN published policy bundle, fetched over the network by THIS
|
|
864
|
-
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.
|
|
865
877
|
|
|
866
878
|
## Run
|
|
867
879
|
|
|
@@ -888,28 +900,32 @@ add another protected route here rather than adding a local function back in \`i
|
|
|
888
900
|
\`bookFlight()\`.
|
|
889
901
|
- \`.env.example\` — where real tool credentials go (copy to \`.env\`, fill in, never commit).
|
|
890
902
|
|
|
891
|
-
##
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
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
|
+
The claim above also checks the claimed authorization's own \`agentDid\`/\`amount\`/\`currency\`/
|
|
923
|
+
\`merchant\` against the request actually being executed (\`@metamynd/agentsafe-mcp-guard\` ≥ 0.2.1)
|
|
924
|
+
— a same-amount, same-currency authorization legitimately obtained for one merchant cannot unlock
|
|
925
|
+
a booking with a different one; that gap was found while building this and closed, not left open.
|
|
926
|
+
See \`@metamynd/agentsafe-mcp-guard\`'s own README (\`requireAuthorization\`) for the full mechanism,
|
|
927
|
+
and \`demo/duffel-mcp-gateway\` in the AgentSafe repo for the fuller pattern this is a slice of
|
|
928
|
+
(mutual DID handshake, x402 payment binding, commitment-bound capability tokens).
|
|
913
929
|
`;
|
|
914
930
|
}
|
|
915
931
|
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "create-metamynd-agent",
|
|
3
|
-
"version": "0.7.
|
|
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
|
|
3
|
+
"version": "0.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": {
|
|
7
7
|
"create-metamynd-agent": "index.mjs"
|