@metamynd/agentsafe-mcp-guard 0.2.0 → 0.2.1

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 CHANGED
@@ -83,11 +83,14 @@ When set, a PERMIT verdict (allow/observe) additionally requires `signed.authori
83
83
  **atomically claim single-use execution** against the issuer (`AUTHORIZED → DISPATCHING`, the
84
84
  effect-safety state machine) — a second claim of the same id, whether a genuine replay or a race,
85
85
  fails, because that transition is legal exactly once. The claimed hold's own bound
86
- `agentDid`/`amount`/`currency` are checked against what's actually being executed, too — a claim
87
- alone only proves *some* real, unclaimed authorization exists; without this check, a cheap
88
- legitimate hold's id could be presented to unlock a completely different, more expensive
86
+ `agentDid`/`amount`/`currency`/`merchant` are checked against what's actually being executed, too
87
+ — a claim alone only proves *some* real, unclaimed authorization exists; without this check, a
88
+ cheap legitimate hold's id could be presented to unlock a completely different, more expensive
89
89
  execution (`AUTHORIZATION_AGENT_MISMATCH` / `AUTHORIZATION_AMOUNT_MISMATCH` /
90
- `AUTHORIZATION_CURRENCY_MISMATCH`).
90
+ `AUTHORIZATION_CURRENCY_MISMATCH` / `AUTHORIZATION_MERCHANT_MISMATCH`). A field the backend
91
+ response omits (e.g. an older, not-yet-migrated deployment with no `merchant` column) is skipped,
92
+ not treated as a mismatch — this degrades gracefully, it doesn't silently under-check going
93
+ forward once the backend does report it.
91
94
 
92
95
  The `authorizationId` has to come from a **real** `guard.authorize()` call on the agent side —
93
96
  not `buildSignedRequest()`, which never talks to the network. In practice this usually needs no
@@ -90,11 +90,12 @@ export function createMcpGuard({ serviceDid, serviceKey, issuerApi, fetchBundle,
90
90
  * against the stateful issuer gate already checked both (agentsafe-guard.mjs's authorizeLocal()
91
91
  * seals any value-bearing action through the real remote authorize() by default).
92
92
  *
93
- * On success, also returns the hold's OWN bound `agentDid`/`amount`/`currency` — the caller MUST
94
- * compare these to the request actually being executed. A claim alone only proves "some real,
95
- * unclaimed authorization exists"; without this check, a legitimately-obtained authorization for
96
- * a small, honest transaction could be presented to unlock a completely different one — the same
97
- * confused-deputy shape payload binding closes at the request layer, recurring one layer deeper.
93
+ * On success, also returns the hold's OWN bound `agentDid`/`amount`/`currency`/`merchant` — the
94
+ * caller MUST compare these to the request actually being executed. A claim alone only proves
95
+ * "some real, unclaimed authorization exists"; without this check, a legitimately-obtained
96
+ * authorization for a small, honest transaction could be presented to unlock a completely
97
+ * different one — the same confused-deputy shape payload binding closes at the request layer,
98
+ * recurring one layer deeper.
98
99
  */
99
100
  async function claimAuthorization({ authorizationId } = {}) {
100
101
  if (!authorizationId) return { claimed: false, reasonCode: 'AUTHORIZATION_REQUIRED' };
@@ -103,7 +104,7 @@ export function createMcpGuard({ serviceDid, serviceKey, issuerApi, fetchBundle,
103
104
  const res = await fetch(`${base}/policy/mandate/authorize/${encodeURIComponent(authorizationId)}/effect/dispatching`, { method: 'POST' });
104
105
  const body = await res.json().catch(() => null);
105
106
  if (!res.ok) return { claimed: false, reasonCode: body?.message ?? body?.data?.reasonCode ?? `AUTHORIZATION_CLAIM_HTTP_${res.status}` };
106
- return { claimed: true, agentDid: body?.data?.agentDid, amount: body?.data?.amount, currency: body?.data?.currency };
107
+ return { claimed: true, agentDid: body?.data?.agentDid, amount: body?.data?.amount, currency: body?.data?.currency, merchant: body?.data?.merchant };
107
108
  } catch (err) {
108
109
  return { claimed: false, reasonCode: 'AUTHORIZATION_CLAIM_UNREACHABLE', error: String(err?.message ?? err) };
109
110
  }
@@ -218,6 +219,7 @@ export function createMcpGuard({ serviceDid, serviceKey, issuerApi, fetchBundle,
218
219
  if (claim.agentDid !== undefined && claim.agentDid !== agentDid) return { decision: 'block', reasonCode: 'AUTHORIZATION_AGENT_MISMATCH' };
219
220
  if (claim.amount !== undefined && Number(claim.amount) !== Number(amount)) return { decision: 'block', reasonCode: 'AUTHORIZATION_AMOUNT_MISMATCH' };
220
221
  if (claim.currency !== undefined && claim.currency !== currency) return { decision: 'block', reasonCode: 'AUTHORIZATION_CURRENCY_MISMATCH' };
222
+ if (claim.merchant !== undefined && claim.merchant !== merchant) return { decision: 'block', reasonCode: 'AUTHORIZATION_MERCHANT_MISMATCH' };
221
223
  }
222
224
  return final;
223
225
  } catch (err) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@metamynd/agentsafe-mcp-guard",
3
- "version": "0.2.0",
3
+ "version": "0.2.1",
4
4
  "description": "Zero-dependency trustless governance for the SERVICE side. An MCP server or API re-verifies a calling agent's signed request against the agent's own published policy \u00e2\u20ac\u201d so an agent that ignores its own guard still cannot make your service act.",
5
5
  "type": "module",
6
6
  "main": "./agentsafe-mcp-guard.mjs",