@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 +7 -4
- package/agentsafe-mcp-guard.mjs +8 -6
- package/package.json +1 -1
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
|
|
87
|
-
alone only proves *some* real, unclaimed authorization exists; without this check, a
|
|
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
|
package/agentsafe-mcp-guard.mjs
CHANGED
|
@@ -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
|
|
94
|
-
* compare these to the request actually being executed. A claim alone only proves
|
|
95
|
-
* unclaimed authorization exists"; without this check, a legitimately-obtained
|
|
96
|
-
* a small, honest transaction could be presented to unlock a completely
|
|
97
|
-
* confused-deputy shape payload binding closes at the request layer,
|
|
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.
|
|
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",
|