create-metamynd-agent 0.12.0 → 0.12.6

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 +8 -3
  2. package/index.mjs +74 -23
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -10,9 +10,14 @@ default, a second `gateway/` process** built on
10
10
  The agent's own `guardTool()` call is a fast, local, client-side check; the gateway is the real
11
11
  enforcement boundary — see [Separate tool gateway](#separate-tool-gateway-default) below.
12
12
 
13
- > **Prerequisite:** an agent is always owned by a person/org with a **MetaMynd account**. Verification
14
- > is *reported*, not required, to get started: an unverified owner works on Testnet within the
15
- > free-tier limits, and verifying once in the dashboard (KYC/KYB) is what unlocks mainnet.
13
+ > **Prerequisite:** an agent is always owned by a person/org with a **MetaMynd account**. The hosted
14
+ > flow provisions under your account's **verified principal** (KYC/KYB; on a beta deployment the
15
+ > platform can auto-approve one on first use) — if there is none, provisioning stops with "complete
16
+ > KYC/KYB first". That is all it needs: this CLI always provisions **Testnet** agents, so mainnet
17
+ > eligibility is not required. A mainnet agent is launched from the dashboard's **Launchpad**
18
+ > (`/dashboard/launchpad`) and needs a **mainnet-eligible** principal (verified by the identity
19
+ > provider, or reviewed by a person on the platform's team). `--harness` and `--sandbox` need no
20
+ > account or verification at all.
16
21
 
17
22
  ## Free local harness (no account, no network, `--harness`)
18
23
 
package/index.mjs CHANGED
@@ -1,7 +1,8 @@
1
1
  #!/usr/bin/env node
2
2
  // create-metamynd-agent — scaffold a MetaMynd/AgentSafe-governed agent in one command.
3
3
  //
4
- // Logs a KYB-verified owner in, provisions the agent in ONE call
4
+ // Logs the owner in (provisioning uses the owner's verified principal; the agent is always
5
+ // Testnet — mainnet goes through the dashboard Launchpad), provisions the agent in ONE call
5
6
  // (POST /onboarding/agent → identity + mandate + starter SOP + enforced Standards),
6
7
  // writes the portable `agent.metamynd.json` and a runnable agent example, PLUS (by default)
7
8
  // a separate `gateway/` process — a second, independent guard that re-verifies every request
@@ -102,7 +103,13 @@ const MCP_GUARD_PKG = '@metamynd/agentsafe-mcp-guard';
102
103
  // 0.6.0 brings buildAuthMessage's `resource` field and buildLocalDecisionMessage into this
103
104
  // package's own bundled policy-core.mjs (alongside the guard's own 0.10.0) — no scaffolded
104
105
  // template code changes, but the floor must still cover the real current version.
105
- const MCP_GUARD_VERSION = '^0.11.0';
106
+ // 0.13.0: a daemon-held service key (keyProvider: 'daemon') now signs claims and settlements as the gateway's
107
+ // DID instead of claiming anonymously — refused on every mainnet hold before. No template change.
108
+ // 0.14.0: the claim can declare an x402 payment (x402: true) so the issuer observes lowered settlements. No template
109
+ // change (the scaffolded gateways don't pay by x402), but the floor must cover the real current version.
110
+ // 0.15.0: payloadDigestOf/toWireJson exported and refundAuthorization (its own signed `refund` action). No template
111
+ // change (the scaffolded gateways don't refund), but the floor must cover the real current version.
112
+ const MCP_GUARD_VERSION = '^0.15.0';
106
113
  const GATEWAY_PKG = '@metamynd/agentsafe-http-gateway';
107
114
  // 0.2.0 fixes a confused-deputy gap (payload not bound to the signed request) — the CLI must
108
115
  // never scaffold a range that could resolve below it.
@@ -114,7 +121,10 @@ const GATEWAY_PKG = '@metamynd/agentsafe-http-gateway';
114
121
  // 0.5.0 adds the OPTIONAL Credential Vault `resolveCredential` hook on createHttpGateway (Module
115
122
  // G) — additive and backward-compatible (every existing consumer sees zero behavior change), but
116
123
  // the floor must still cover the real current version per this repo's own package-version check.
117
- const GATEWAY_VERSION = '^0.11.0';
124
+ // 0.12.0: per-route `x402: true` declares an x402 payment on the claim. No template change.
125
+ // 0.13.0: a configured resolveCredential that refuses now FAILS CLOSED (502 CREDENTIAL_UNAVAILABLE) instead of forwarding
126
+ // without a credential. No template change (the scaffolded gateway does not use the Credential Vault).
127
+ const GATEWAY_VERSION = '^0.13.0';
118
128
  const DEFAULT_API = 'https://metamynd.ai/api/v1';
119
129
  const DEFAULT_GATEWAY_PORT = 4401; // distinct from --harness's dashboard (4400)
120
130
 
@@ -205,7 +215,7 @@ ${c.b('Options')}
205
215
  --gateway --harness only: ALSO scaffold a second local process (still zero
206
216
  network, zero account) that independently re-verifies every request
207
217
  against the same rules file, using the real @metamynd/agentsafe-mcp-guard.
208
- Off by default. Does not close nonce replay/cumulative spend — see the
218
+ Off by default. Refuses a replayed request; does not track cumulative spend — see the
209
219
  generated README#--gateway for exactly what it does and does not.
210
220
  --gateway-port <n> The gateway process's port, hosted flow or --harness --gateway (default 4401)
211
221
  --port <n> --harness only: the local dashboard's port (default 4400)
@@ -216,8 +226,16 @@ ${c.b('Options')}
216
226
  ${c.b('Environment')}
217
227
  METAMYND_API, METAMYND_EMAIL, METAMYND_PASSWORD — fallbacks for the flags above
218
228
 
229
+ ${c.b('Network')}
230
+ Every hosted agent this CLI provisions is a ${c.b('Testnet')} agent, whatever your verification status —
231
+ there is deliberately no mainnet flag. To launch a ${c.b('Mainnet')} agent, use the Launchpad in the
232
+ dashboard (https://metamynd.ai/dashboard/launchpad): its Mainnet option needs a mainnet-eligible
233
+ principal (verified by the identity provider, or reviewed by a person on the platform's team).
234
+
219
235
  ${c.b('What it does')}
220
- 1. Logs in as a KYB-verified owner → owner access token
236
+ 1. Logs in as the owner → owner access token
237
+ (provisioning uses your account's verified principal — KYC/KYB, or one the platform
238
+ auto-approves on a beta deployment; mainnet eligibility is NOT needed for Testnet)
221
239
  2. POST /onboarding/agent (one call) → identity + mandate + SOP + Standards
222
240
  3. Writes agent.metamynd.json + index.mjs, PLUS (by default) a separate gateway/ process —
223
241
  the real enforcement boundary, not index.mjs's own guard.guardTool() call. --no-gateway
@@ -2319,12 +2337,14 @@ function harnessRulesFile(mandate, sopDocument) {
2319
2337
  // that skips its own guardToolLocal() call, or calls bookFlight() directly, gets nothing —
2320
2338
  // the tool only runs in the gateway process now.
2321
2339
  //
2340
+ // Replay of the SAME signed request is refused too (REPLAY_DETECTED): the gateway remembers each nonce for as long as
2341
+ // the guard would accept it, in memory (a restart forgets them). An independent tester's rerun (2026-09-24) found the
2342
+ // same bytes accepted twice before this.
2343
+ //
2322
2344
  // What this does NOT close (be precise, this is a local demo, not the hosted platform):
2323
- // nonce replay and cumulative-spend across many calls. Those need a STATEFUL authority — the
2324
- // hosted gate's `requireAuthorization` claims a real, single-use authorizationId against a
2325
- // database. A local harness has no such database (that is the whole point of --harness), so
2326
- // this gateway does per-request re-evaluation only, same as the hosted gateway's baseline
2327
- // before `requireAuthorization` is added. The README says so.
2345
+ // cumulative spend across many DIFFERENT calls. That needs a STATEFUL authority — the hosted gate's
2346
+ // `requireAuthorization` claims a real, single-use authorizationId against a database and holds the budget there. A
2347
+ // local harness has no such database (that is the whole point of --harness). The README says so.
2328
2348
 
2329
2349
  function harnessGatewayServerFile(scope, gatewayPort, agentDid, neutral = false) {
2330
2350
  return `#!/usr/bin/env node
@@ -2415,9 +2435,35 @@ function readBody(req) {
2415
2435
  });
2416
2436
  }
2417
2437
 
2438
+ // Replay: every signed request carries a fresh nonce, and the guard refuses one more than 5 minutes old. So a nonce
2439
+ // only has to be remembered for that long to refuse the SAME signed request twice. Checked and recorded in one
2440
+ // synchronous step, before anything awaits, so two simultaneous copies cannot both get through. In memory: restarting
2441
+ // this process forgets it (a replay inside the 5-minute window after a restart is not caught). Cumulative spend across
2442
+ // many DIFFERENT requests is still not tracked here — that needs the hosted gate.
2443
+ const NONCE_TTL_MS = 6 * 60 * 1000;
2444
+ const seenNonces = new Map(); // agentDid|nonce -> expiry
2445
+ function firstUseOfNonce(headers) {
2446
+ let signed;
2447
+ try { signed = JSON.parse(headers['x-magp-request'] ?? 'null'); } catch { return true; } // malformed: the guard refuses it
2448
+ if (!signed || typeof signed.nonce !== 'string') return true; // no nonce: the guard refuses it
2449
+ const now = Date.now();
2450
+ if (seenNonces.size > 10000) for (const [k, exp] of seenNonces) if (exp <= now) seenNonces.delete(k);
2451
+ const key = String(signed.agentDid) + '|' + signed.nonce;
2452
+ const exp = seenNonces.get(key);
2453
+ if (exp !== undefined && exp > now) return false;
2454
+ seenNonces.set(key, now + NONCE_TTL_MS);
2455
+ return true;
2456
+ }
2457
+
2418
2458
  const server = http.createServer(async (req, res) => {
2419
2459
  try {
2420
2460
  const rawBody = await readBody(req);
2461
+ if (!firstUseOfNonce(req.headers)) {
2462
+ console.log('[harness-gateway] BLOCK ' + req.url + ' — REPLAY_DETECTED (this exact signed request already came through)');
2463
+ res.writeHead(403, { 'content-type': 'application/json', 'x-agentsafe-decision': 'block' });
2464
+ res.end(JSON.stringify({ decision: 'block', reasonCode: 'REPLAY_DETECTED' }));
2465
+ return;
2466
+ }
2421
2467
  const result = await gateway({ method: req.method, path: req.url, headers: req.headers, rawBody });
2422
2468
  const headers = { 'content-type': 'application/json' };
2423
2469
  const decision = result.governance?.decision;
@@ -2978,9 +3024,9 @@ console.log(bold(' Checked twice, by two processes.') + ' bookFlight() lives in
2978
3024
  console.log(dim(' not here. It independently re-verified every attempt above against the SAME'));
2979
3025
  console.log(dim(' ./metamynd-rules.json, over a signed request, before running your tool.'));
2980
3026
  console.log('');
2981
- console.log(dim(' What --gateway does NOT close: nonce replay and cumulative spend across many'));
2982
- console.log(dim(' calls. Those need a STATEFUL authority (the hosted gate\\'s requireAuthorization'));
2983
- console.log(dim(' claims a real, single-use id against a database) - a local harness has none.'));
3027
+ console.log(dim(' It also refuses a replay of the same signed request. What --gateway does NOT close:'));
3028
+ console.log(dim(' cumulative spend across many different calls. That needs a STATEFUL authority (the'));
3029
+ console.log(dim(' hosted gate holds the budget in a database) - a local harness has none.'));
2984
3030
  console.log(dim(' See README.md#--gateway for exactly what this does and does not prove.'));
2985
3031
  console.log('');
2986
3032
  console.log(' Edit ./metamynd-rules.json (or the dashboard) and run again - the outcome');
@@ -3175,9 +3221,8 @@ console.log(dim(' ./metamynd-rules.json, over a signed request, before running
3175
3221
  console.log(dim(' It enforces scope and identity firmly; the rule inputs above are NOT signed, so an'));
3176
3222
  console.log(dim(' agent that can sign could omit or forge one and its rule would not fire.'));
3177
3223
  console.log('');
3178
- console.log(dim(' What --gateway does NOT close: nonce replay across many calls. That needs a STATEFUL'));
3179
- console.log(dim(' authority (the hosted gate claims a real, single-use id against a database) - a local'));
3180
- console.log(dim(' harness has none. See README.md#--gateway for exactly what this does and does not prove.'));
3224
+ console.log(dim(' It also refuses a replay of the same signed request (remembered in memory, so a restart'));
3225
+ console.log(dim(' forgets). See README.md#--gateway for exactly what this does and does not prove.'));
3181
3226
  console.log('');
3182
3227
  console.log(' Edit ./metamynd-rules.json (or the dashboard) and run again - the outcome');
3183
3228
  console.log(dim(' changes, in BOTH processes, from the one file. That is the point.'));` : `
@@ -3296,13 +3341,19 @@ not cover. An agent that can sign could omit a field or send a different one, an
3296
3341
  it would not fire. The gateway enforces scope and identity firmly; an input-dependent rule is only as
3297
3342
  strong as whatever supplies its input.` : ''}
3298
3343
 
3299
- **NOT closed:** nonce replay and cumulative spend across many calls. Those need a STATEFUL
3300
- authority — the hosted gate's \`requireAuthorization\` atomically claims a real, single-use
3301
- \`authorizationId\` against a database before a Service executes anything (see
3302
- \`@metamynd/agentsafe-mcp-guard\`'s own README). A local harness has no database; that is the
3303
- whole point of \`--harness\`. \`harness-gateway.mjs\` re-checks POLICY per request, which is
3304
- real and worth having, but replaying the exact same signed request twice is NOT refused here
3305
- the way it would be against the hosted gate.
3344
+ **Also closed — replaying the same signed request.** \`harness-gateway.mjs\` remembers every nonce for as long as
3345
+ the guard would accept the request (about 5 minutes) and refuses the second copy (\`REPLAY_DETECTED\`). The memory is
3346
+ in-process: restarting the gateway forgets it.
3347
+
3348
+ ${demo ? `**NOT closed:** anything that depends on history across many DIFFERENT calls (rate limits, circuit breakers,
3349
+ patterns). That needs a STATEFUL authority — the hosted gate's \`requireAuthorization\` atomically claims a real,
3350
+ single-use \`authorizationId\` against a database (see \`@metamynd/agentsafe-mcp-guard\`'s own README). A local
3351
+ harness has no database; that is the whole point of \`--harness\`. \`harness-gateway.mjs\` re-checks POLICY per
3352
+ request, which is real and worth having, but it does not remember what earlier calls did.` : `**NOT closed:** cumulative spend across many DIFFERENT calls. That needs a STATEFUL authority — the hosted gate's
3353
+ \`requireAuthorization\` atomically claims a real, single-use \`authorizationId\` against a database and holds the
3354
+ budget there (see \`@metamynd/agentsafe-mcp-guard\`'s own README). A local harness has no database; that is the
3355
+ whole point of \`--harness\`. \`harness-gateway.mjs\` re-checks POLICY per request, so each call is held to the
3356
+ per-transaction cap, but many calls under the cap are not added up here the way the hosted gate adds them up.`}
3306
3357
 
3307
3358
  Also not closed by \`--gateway\` alone: cross-party trust (nobody but you can verify this agent's
3308
3359
  identity or its decisions), evidence anyone but you can audit, a dashboard reachable when this
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-metamynd-agent",
3
- "version": "0.12.0",
3
+ "version": "0.12.6",
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": {