@metamynd/agentsafe-guard 0.3.1 → 0.4.2
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 +15 -0
- package/agentsafe-guard.mjs +86 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -15,6 +15,21 @@ compliance team edits in the dashboard, changeable live with no redeploy.
|
|
|
15
15
|
`policy-core` the gate runs (MAGP §9.2 cooperative mode) — identical inputs give the identical
|
|
16
16
|
verdict, with no network round-trip. See §4.
|
|
17
17
|
|
|
18
|
+
## The protocol
|
|
19
|
+
|
|
20
|
+
This guard is one implementation of an open specification. You do not have to use it — the
|
|
21
|
+
protocol is published so you can implement it yourself, verify a verdict independently, or
|
|
22
|
+
re-implement the gate:
|
|
23
|
+
|
|
24
|
+
**[MAGP v1.0 — MetaMynd Agentic Governance Protocol](https://metamynd.ai/en/developers/spec)**
|
|
25
|
+
([markdown](https://metamynd.ai/specs/magp-v1.0.md))
|
|
26
|
+
|
|
27
|
+
It defines agent identity, the canonical signed message (§8.3), the sixteen-stage order of
|
|
28
|
+
checks (§8.5), all 59 reason codes (Appendix A), delegation narrowing (§5.4), and evidence
|
|
29
|
+
you can verify offline without MetaMynd (§13.4). If you are writing a client in a language
|
|
30
|
+
other than JavaScript, read §8.3.3–8.3.5 first: key encoding, number stringification and
|
|
31
|
+
signed-vs-sent field identity each surface only as `SIGNATURE_INVALID`.
|
|
32
|
+
|
|
18
33
|
## Try it first — no account, no network
|
|
19
34
|
|
|
20
35
|
```bash
|
package/agentsafe-guard.mjs
CHANGED
|
@@ -14,6 +14,26 @@ import { evaluate, buildAuthMessage, applySignedLast, operatingModeGate } from '
|
|
|
14
14
|
import { verifyDidSignature } from './magp-did.mjs';
|
|
15
15
|
import { checkSettlementBinding } from './x402.mjs';
|
|
16
16
|
|
|
17
|
+
/**
|
|
18
|
+
* Replay a Merkle sibling chain and report whether it reconstructs `root`.
|
|
19
|
+
*
|
|
20
|
+
* Byte-identical to backend/src/features/magp/merkle.ts: leaves and siblings are hex
|
|
21
|
+
* sha256 digests, and an internal node is sha256 over the CONCATENATED RAW BYTES of its
|
|
22
|
+
* children (not the hex text), in left-then-right order. Hashing the hex strings instead
|
|
23
|
+
* would produce a self-consistent but incompatible tree — one that verified nothing the
|
|
24
|
+
* backend ever anchored, while appearing to work.
|
|
25
|
+
*/
|
|
26
|
+
function verifyMerkleInclusion(leaf, proof, root) {
|
|
27
|
+
const sha = (buf) => crypto.createHash('sha256').update(buf).digest('hex');
|
|
28
|
+
const hashNodes = (a, b) => sha(Buffer.concat([Buffer.from(a, 'hex'), Buffer.from(b, 'hex')]));
|
|
29
|
+
let computed = leaf;
|
|
30
|
+
for (const step of proof) {
|
|
31
|
+
if (!step || typeof step.sibling !== 'string') return false;
|
|
32
|
+
computed = step.position === 'left' ? hashNodes(step.sibling, computed) : hashNodes(computed, step.sibling);
|
|
33
|
+
}
|
|
34
|
+
return computed === root;
|
|
35
|
+
}
|
|
36
|
+
|
|
17
37
|
/**
|
|
18
38
|
* ExecutionAdapter (SAFR §19, Phase-4 PR-4) — the seam between a PERMITTING verdict
|
|
19
39
|
* (allow / observe) and the real side-effect. Before this, a guarded tool called its
|
|
@@ -538,6 +558,71 @@ export function createGuard(opts = {}) {
|
|
|
538
558
|
}
|
|
539
559
|
}
|
|
540
560
|
|
|
561
|
+
/**
|
|
562
|
+
* Merkle inclusion proof for a decision's evidence record — fetched, then VERIFIED
|
|
563
|
+
* HERE rather than taken on trust.
|
|
564
|
+
*
|
|
565
|
+
* The point of an inclusion proof is that its holder can check it WITHOUT trusting the
|
|
566
|
+
* party that issued it. A helper that returned the server's payload as-is would look
|
|
567
|
+
* like proof and function as assertion: the caller would be believing MetaMynd's claim
|
|
568
|
+
* that the record is in the anchored batch, which is exactly the thing the proof exists
|
|
569
|
+
* to make unnecessary. So the sibling chain is replayed locally and the recomputed root
|
|
570
|
+
* is compared to the anchored one; `verified` is this SDK's own conclusion.
|
|
571
|
+
*
|
|
572
|
+
* Absence and falsification are reported as DIFFERENT outcomes, because they mean
|
|
573
|
+
* opposite things to whoever is asking:
|
|
574
|
+
*
|
|
575
|
+
* status 'verified' the record is provably in the batch anchored at anchorTxId
|
|
576
|
+
* status 'pending' no anchored batch contains it YET — anchoring is asynchronous
|
|
577
|
+
* (§10.2), so a recent decision is normally pending, not missing
|
|
578
|
+
* status 'failed' a proof was returned and it does NOT reconstruct the root.
|
|
579
|
+
* This is the alarming one and must never be conflated with
|
|
580
|
+
* 'pending'
|
|
581
|
+
* status 'unreachable' the gate could not be asked; nothing is implied either way
|
|
582
|
+
*
|
|
583
|
+
* Note the trust boundary this does NOT cross: it proves the record belongs to the
|
|
584
|
+
* batch that claims `root`. Proving that root was published on Hedera is a separate,
|
|
585
|
+
* stronger check against the mirror node — see integrations/magp-evidence/, the offline
|
|
586
|
+
* auditor, which does it with MetaMynd entirely absent.
|
|
587
|
+
*/
|
|
588
|
+
async function proof(eventId) {
|
|
589
|
+
if (!eventId) throw new Error('proof requires the evidence eventId');
|
|
590
|
+
let body;
|
|
591
|
+
let httpStatus;
|
|
592
|
+
try {
|
|
593
|
+
const res = await fetch(`${base}/magp/evidence/${encodeURIComponent(eventId)}/proof`);
|
|
594
|
+
httpStatus = res.status;
|
|
595
|
+
body = await res.json().catch(() => null);
|
|
596
|
+
} catch (err) {
|
|
597
|
+
return { status: 'unreachable', verified: false, eventId, reason: 'GATE_UNREACHABLE', error: String(err?.message ?? err) };
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
if (httpStatus === 404) {
|
|
601
|
+
// Not an error: batching is asynchronous, so a decision made seconds ago has
|
|
602
|
+
// genuinely not been anchored yet. Saying "unverified" here would read as doubt
|
|
603
|
+
// about a record that is simply young.
|
|
604
|
+
return { status: 'pending', verified: false, eventId, reason: 'NOT_YET_ANCHORED' };
|
|
605
|
+
}
|
|
606
|
+
const data = body?.data;
|
|
607
|
+
if (!data?.leaf || !data?.root || !Array.isArray(data?.proof)) {
|
|
608
|
+
return { status: 'unreachable', verified: false, eventId, reason: `GATE_HTTP_${httpStatus}` };
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
const verified = verifyMerkleInclusion(data.leaf, data.proof, data.root);
|
|
612
|
+
return {
|
|
613
|
+
status: verified ? 'verified' : 'failed',
|
|
614
|
+
verified,
|
|
615
|
+
eventId,
|
|
616
|
+
leaf: data.leaf,
|
|
617
|
+
root: data.root,
|
|
618
|
+
proof: data.proof,
|
|
619
|
+
anchorTxId: data.anchorTxId ?? null,
|
|
620
|
+
anchorRef: data.anchorRef ?? null,
|
|
621
|
+
anchoredAt: data.anchoredAt ?? null,
|
|
622
|
+
...(verified ? {} : { reason: 'MERKLE_ROOT_MISMATCH' }),
|
|
623
|
+
};
|
|
624
|
+
}
|
|
625
|
+
|
|
541
626
|
/**
|
|
542
627
|
* Effect-safety runtime (E2): report the external-effect lifecycle so an AMBIGUOUS
|
|
543
628
|
* connector outcome never becomes a blind capture/void. Call effectDispatching() just
|
|
@@ -602,5 +687,5 @@ export function createGuard(opts = {}) {
|
|
|
602
687
|
return sign(challenge);
|
|
603
688
|
}
|
|
604
689
|
|
|
605
|
-
return { authorize, authorizeLocal, check, loadBundle, policyAnchor: _currentAnchor, watchPolicy, mode, verifyOnChain, buildSignedRequest, capture, guardTool, evaluateLocally, guardToolLocal, handshake, preparePayment, escalationStatus, effectDispatching, effectDispatched, effectUnknown, effectStatus, verifyKey, signChallenge, agentDid, executionAdapter: defaultExecutionAdapter };
|
|
690
|
+
return { authorize, authorizeLocal, check, loadBundle, policyAnchor: _currentAnchor, watchPolicy, mode, verifyOnChain, buildSignedRequest, capture, guardTool, evaluateLocally, guardToolLocal, handshake, preparePayment, escalationStatus, proof, effectDispatching, effectDispatched, effectUnknown, effectStatus, verifyKey, signChallenge, agentDid, executionAdapter: defaultExecutionAdapter };
|
|
606
691
|
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@metamynd/agentsafe-guard",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "Zero-dependency runtime governance for any Node AI agent
|
|
3
|
+
"version": "0.4.2",
|
|
4
|
+
"description": "Zero-dependency runtime governance for any Node AI agent \u2014 gate tool calls through MetaMynd/AgentSafe (allow / block / escalate) against the agent's mandate, enforced Standards, and SOPs. Ed25519-signed, deterministic, fail-closed.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./agentsafe-guard.mjs",
|
|
7
7
|
"module": "./agentsafe-guard.mjs",
|