@mcp-audit-gateway/core 0.1.0 → 0.2.0
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/.well-known/agent-governance.json +47 -0
- package/.well-known/security-insights-snippet.yml +9 -0
- package/CHANGELOG.md +32 -0
- package/dist/attestation/audit-log.d.ts +1 -0
- package/dist/attestation/audit-log.d.ts.map +1 -1
- package/dist/attestation/audit-log.js +19 -0
- package/dist/attestation/audit-log.js.map +1 -1
- package/dist/attestation/signer.d.ts.map +1 -1
- package/dist/attestation/signer.js +7 -0
- package/dist/attestation/signer.js.map +1 -1
- package/dist/attestation/signer.test.js +72 -0
- package/dist/attestation/signer.test.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/policy/engine.d.ts +12 -1
- package/dist/policy/engine.d.ts.map +1 -1
- package/dist/policy/engine.js +26 -4
- package/dist/policy/engine.js.map +1 -1
- package/dist/policy/engine.test.js +42 -1
- package/dist/policy/engine.test.js.map +1 -1
- package/dist/proxy/gateway.d.ts.map +1 -1
- package/dist/proxy/gateway.js +5 -1
- package/dist/proxy/gateway.js.map +1 -1
- package/dist/types.d.ts +7 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/attestation/audit-log.ts +24 -1
- package/src/attestation/signer.test.ts +84 -0
- package/src/attestation/signer.ts +8 -1
- package/src/index.ts +1 -1
- package/src/policy/engine.test.ts +47 -1
- package/src/policy/engine.ts +38 -5
- package/src/proxy/gateway.ts +6 -1
- package/src/types.ts +8 -0
- package/test/vectors/README.md +44 -0
- package/test/vectors/canonicalization.json +582 -0
- package/test/vectors/generate.mjs +354 -0
- package/test/vectors/verify.mjs +273 -0
- package/test/vectors/verify.py +277 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"gateway.js","sourceRoot":"","sources":["../../src/proxy/gateway.ts"],"names":[],"mappings":"AAOA,OAAO,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;
|
|
1
|
+
{"version":3,"file":"gateway.js","sourceRoot":"","sources":["../../src/proxy/gateway.ts"],"names":[],"mappings":"AAOA,OAAO,EAAE,YAAY,EAAE,4BAA4B,EAAE,MAAM,qBAAqB,CAAC;AACjF,OAAO,EAAE,QAAQ,EAAE,MAAM,6BAA6B,CAAC;AACvD,OAAO,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAC;AACxD,OAAO,EAAE,aAAa,EAAE,MAAM,wBAAwB,CAAC;AACvD,OAAO,EAAE,cAAc,EAAE,MAAM,yBAAyB,CAAC;AACzD,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAExD,MAAM,OAAO,OAAO;IASE;IARZ,WAAW,GAA2B,IAAI,GAAG,EAAE,CAAC;IAChD,cAAc,CAA+B;IAC7C,YAAY,CAAe;IAC3B,QAAQ,CAAW;IACnB,MAAM,CAAgB;IACtB,OAAO,CAAiB;IACxB,eAAe,CAAkB;IAEzC,YAAoB,MAAqB;QAArB,WAAM,GAAN,MAAM,CAAe;QACvC,IAAI,CAAC,YAAY,GAAG,IAAI,YAAY,CAClC,MAAM,CAAC,MAAM,CAAC,aAAa,EAC3B,MAAM,CAAC,MAAM,CAAC,KAAK,CACpB,CAAC;QAEF,MAAM,MAAM,GAAG,YAAY,CAAC,MAAM,CAAC,WAAW,CAAC,CAAC;QAChD,IAAI,CAAC,QAAQ,GAAG,IAAI,QAAQ,CAC1B,MAAM,CAAC,QAAQ,CAAC,IAAI,EACpB,MAAM,EACN,MAAM,CAAC,QAAQ,CAAC,aAAa,GAAG,IAAI,GAAG,IAAI,CAC5C,CAAC;QAEF,IAAI,CAAC,MAAM,GAAG,IAAI,aAAa,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC;QAClD,IAAI,CAAC,OAAO,GAAG,IAAI,cAAc,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC;QACpD,IAAI,CAAC,eAAe,GAAG,IAAI,eAAe,EAAE,CAAC;IAC/C,CAAC;IAED,KAAK,CAAC,IAAI;QACR,MAAM,IAAI,CAAC,QAAQ,CAAC,IAAI,EAAE,CAAC;QAC3B,KAAK,MAAM,QAAQ,IAAI,IAAI,CAAC,MAAM,CAAC,SAAS,EAAE,CAAC;YAC7C,MAAM,IAAI,CAAC,eAAe,CAAC,QAAQ,CAAC,CAAC;QACvC,CAAC;IACH,CAAC;IAEO,KAAK,CAAC,eAAe,CAAC,QAAwB;QACpD,IAAI,CAAC;YACH,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,eAAe,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;YAC1D,IAAI,CAAC,qBAAqB,CACxB,QAAQ,CAAC,IAAI,EACb,QAAQ,CAAC,SAAS,EAClB,IAAI,CAAC,KAAK,CACX,CAAC;QACJ,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,OAAO,CAAC,KAAK,CAAC,iCAAiC,QAAQ,CAAC,IAAI,KAAK,GAAG,EAAE,CAAC,CAAC;QAC1E,CAAC;IACH,CAAC;IAED,KAAK,CAAC,eAAe,CACnB,SAAkB,EAClB,KAA+B;QAE/B,MAAM,QAAQ,GAAG,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,WAAW,CAAC,MAAM,EAAE,CAAC,CAAC;QACvD,MAAM,QAAQ,GAAG,IAAI,CAAC,YAAY,CAAC,WAAW,CAAC,SAAS,EAAE,QAAQ,CAAC,CAAC;QAEpE,MAAM,KAAK,GAAG,QAAQ,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;YACrC,IAAI,EAAE,KAAK,CAAC,IAAI;YAChB,WAAW,EAAE,KAAK,CAAC,WAAW;YAC9B,WAAW,EAAE,KAAK,CAAC,WAAW;YAC9B,WAAW,EAAE,KAAK,CAAC,WAAW;YAC9B,KAAK,EAAE;gBACL,sBAAsB,EAAE;oBACtB,QAAQ,EAAE,KAAK,CAAC,QAAQ;oBACxB,SAAS,EAAE,KAAK,CAAC,SAAS;iBAC3B;aACF;SACF,CAAC,CAAC,CAAC;QAEJ,OAAO;YACL,KAAK;YACL,KAAK,EAAE;gBACL,sBAAsB,EAAE;oBACtB,gBAAgB,EAAE,IAAI;oBACtB,gBAAgB,EAAE,SAAS,IAAI,WAAW;oBAC1C,mBAAmB,EAAE,IAAI,CAAC,WAAW,CAAC,IAAI;oBAC1C,oBAAoB,EAAE,QAAQ,CAAC,MAAM;iBACtC;aACF;SACF,CAAC;IACJ,CAAC;IAED,KAAK,CAAC,eAAe,CACnB,QAAgB,EAChB,IAA6B,EAC7B,SAAkB,EAClB,YAA4D;QAE5D,MAAM,SAAS,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QAC7B,MAAM,IAAI,GAAG,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;QAE5C,IAAI,CAAC,IAAI,EAAE,CAAC;YACV,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,YAAY,EAAE;gBACtD,QAAQ;gBACR,SAAS;gBACT,UAAU,EAAE,IAAI,CAAC,GAAG,EAAE,GAAG,SAAS;gBAClC,OAAO,EAAE,KAAK;gBACd,SAAS,EAAE,CAAC,KAAK;aAClB,CAAC,CAAC;YACH,MAAM,IAAI,aAAa,CAAC,CAAC,KAAK,EAAE,iBAAiB,QAAQ,EAAE,EAAE,MAAM,CAAC,CAAC;QACvE,CAAC;QAED,MAAM,QAAQ,GAAG,IAAI,CAAC,YAAY,CAAC,QAAQ,CAAC,SAAS,EAAE,IAAI,CAAC,CAAC;QAC7D,MAAM,aAAa,GAAG,4BAA4B,CAAC,QAAQ,CAAC,eAAe,CAAC,CAAC;QAE7E,IAAI,CAAC,QAAQ,CAAC,OAAO,EAAE,CAAC;YACtB,IAAI,CAAC,OAAO,CAAC,kBAAkB,CAAC;gBAC9B,SAAS,EAAE,SAAS,IAAI,WAAW;gBACnC,IAAI,EAAE,QAAQ;gBACd,MAAM,EAAE,QAAQ,CAAC,MAAM,IAAI,cAAc;aAC1C,CAAC,CAAC;YACH,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,YAAY,EAAE;gBACtD,QAAQ;gBACR,SAAS,EAAE,IAAI,CAAC,SAAS;gBACzB,QAAQ,EAAE,IAAI,CAAC,QAAQ;gBACvB,SAAS;gBACT,UAAU,EAAE,IAAI,CAAC,GAAG,EAAE,GAAG,SAAS;gBAClC,OAAO,EAAE,KAAK;gBACd,SAAS,EAAE,CAAC,KAAK;gBACjB,qBAAqB,EAAE,aAAa;aACrC,CAAC,CAAC;YACH,MAAM,IAAI,aAAa,CACrB,CAAC,KAAK,EACN,QAAQ,CAAC,MAAM,IAAI,cAAc,EACjC,MAAM,CACP,CAAC;QACJ,CAAC;QAED,MAAM,IAAI,GAAG,IAAI,CAAC,MAAM,CAAC,cAAc,CACrC,YAAY,EACZ,QAAQ,EACR,IAAI,CAAC,QAAQ,EACb,YAAY,CACb,CAAC;QAEF,IAAI,CAAC;YACH,IAAI,CAAC,YAAY,CAAC,gBAAgB,CAAC,SAAS,EAAE,IAAI,CAAC,CAAC;YAEpD,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,eAAe,CAAC,QAAQ,CAChD,IAAI,CAAC,QAAQ,EACb,IAAI,CAAC,YAAY,EACjB,IAAI,CACL,CAAC;YACF,MAAM,UAAU,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,SAAS,CAAC;YAE1C,IAAI,CAAC,MAAM,CAAC,cAAc,CAAC,IAAI,EAAE,UAAU,CAAC,CAAC;YAC7C,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;YAEhC,IAAI,CAAC,OAAO,CAAC,cAAc,CAAC;gBAC1B,SAAS,EAAE,IAAI,CAAC,SAAS;gBACzB,IAAI,EAAE,QAAQ;gBACd,SAAS,EAAE,SAAS,IAAI,WAAW;gBACnC,OAAO,EAAE,MAAM;aAChB,CAAC,CAAC;YACH,IAAI,CAAC,OAAO,CAAC,cAAc,CAAC,UAAU,EAAE;gBACtC,SAAS,EAAE,IAAI,CAAC,SAAS;gBACzB,QAAQ,EAAE,IAAI,CAAC,QAAQ;aACxB,CAAC,CAAC;YAEH,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,eAAe,CAAC,YAAY,EAAE;gBACtD,QAAQ;gBACR,SAAS,EAAE,IAAI,CAAC,SAAS;gBACzB,QAAQ,EAAE,IAAI,CAAC,QAAQ;gBACvB,SAAS;gBACT,UAAU;gBACV,OAAO,EAAE,IAAI;gBACb,qBAAqB,EAAE,aAAa;aACrC,CAAC,CAAC;YAEH,OAAO,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,EAAE,CAAC;QACzC,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,MAAM,UAAU,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,SAAS,CAAC;YAC1C,IAAI,CAAC,MAAM,CAAC,cAAc,CAAC,IAAI,EAAE,UAAU,CAAC,CAAC;YAC7C,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;YAEjC,IAAI,CAAC,OAAO,CAAC,cAAc,CAAC;gBAC1B,SAAS,EAAE,IAAI,CAAC,SAAS;gBACzB,IAAI,EAAE,QAAQ;gBACd,SAAS,EAAE,SAAS,IAAI,WAAW;gBACnC,OAAO,EAAE,OAAO;aACjB,CAAC,CAAC;YACH,IAAI,CAAC,OAAO,CAAC,cAAc,CAAC,UAAU,EAAE;gBACtC,SAAS,EAAE,IAAI,CAAC,SAAS;gBACzB,QAAQ,EAAE,IAAI,CAAC,QAAQ;aACxB,CAAC,CAAC;YAEH,IAAI,GAAG,YAAY,aAAa;gBAAE,MAAM,GAAG,CAAC;YAE5C,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,eAAe,CAAC,YAAY,EAAE;gBACtD,QAAQ;gBACR,SAAS,EAAE,IAAI,CAAC,SAAS;gBACzB,QAAQ,EAAE,IAAI,CAAC,QAAQ;gBACvB,SAAS;gBACT,UAAU;gBACV,OAAO,EAAE,KAAK;gBACd,SAAS,EAAE,CAAC,KAAK;gBACjB,qBAAqB,EAAE,aAAa;aACrC,CAAC,CAAC;YACH,MAAM,IAAI,aAAa,CAAC,CAAC,KAAK,EAAE,uBAAuB,EAAE,MAAM,CAAC,CAAC;QACnE,CAAC;IACH,CAAC;IAED,SAAS;QAWP,MAAM,OAAO,GAAG,IAAI,CAAC,eAAe,CAAC,cAAc,EAAE,CAAC;QACtD,MAAM,MAAM,GAAG,IAAI,CAAC,cAAc,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,cAAc,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;QACnF,MAAM,SAAS,GAAG,OAAO,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC;QACxD,OAAO;YACL,OAAO,EAAE;gBACP,MAAM,EAAE,SAAS;gBACjB,OAAO,EAAE,IAAI,CAAC,MAAM,CAAC,OAAO;aAC7B;YACD,SAAS;YACT,SAAS,EAAE;gBACT,cAAc,EAAE,SAAS,CAAC,MAAM;gBAChC,gBAAgB,EAAE,SAAS,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,KAAK,SAAS,CAAC,CAAC,MAAM;gBACxE,iBAAiB,EAAE,SAAS,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,KAAK,UAAU,CAAC,CAAC,MAAM;gBAC1E,oBAAoB,EAAE,SAAS,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,KAAK,aAAa,CAAC,CAAC,MAAM;gBAChF,UAAU,EAAE,IAAI,CAAC,WAAW,CAAC,IAAI;aAClC;SACF,CAAC;IACJ,CAAC;IAED,iBAAiB;QACf,OAAO;YACL,IAAI,EAAE,IAAI,CAAC,MAAM,CAAC,IAAI;YACtB,OAAO,EAAE,IAAI,CAAC,MAAM,CAAC,OAAO;YAC5B,QAAQ,EAAE,YAAY;YACtB,YAAY,EAAE;gBACZ,KAAK,EAAE,EAAE,WAAW,EAAE,IAAI,EAAE;gBAC5B,OAAO,EAAE;oBACP,WAAW,EAAE,IAAI;oBACjB,SAAS,EAAE,IAAI;oBACf,OAAO,EAAE,IAAI;oBACb,eAAe,EAAE,IAAI;oBACrB,WAAW,EAAE,IAAI,CAAC,MAAM,CAAC,WAAW,CAAC,OAAO;oBAC5C,aAAa,EAAE,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,MAAM;iBAC5C;aACF;YACD,UAAU,EAAE,CAAC,sBAAsB,EAAE,0BAA0B,CAAC;SACjE,CAAC;IACJ,CAAC;IAED,qBAAqB,CACnB,YAAoB,EACpB,SAAiB,EACjB,KAKE;QAEF,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;YACzB,MAAM,cAAc,GAAG,GAAG,SAAS,IAAI,IAAI,CAAC,IAAI,EAAE,CAAC;YACnD,MAAM,KAAK,GAAc;gBACvB,IAAI,EAAE,cAAc;gBACpB,YAAY,EAAE,IAAI,CAAC,IAAI;gBACvB,SAAS;gBACT,QAAQ,EAAE,YAAY;gBACtB,WAAW,EAAE,IAAI,CAAC,WAAW;gBAC7B,WAAW,EAAE,IAAI,CAAC,WAAW;gBAC7B,WAAW,EAAE,IAAI,CAAC,WAAW;aAC9B,CAAC;YACF,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,cAAc,EAAE,KAAK,CAAC,CAAC;QAC9C,CAAC;QAED,IAAI,CAAC,IAAI,CAAC,cAAc;YAAE,IAAI,CAAC,cAAc,GAAG,IAAI,GAAG,EAAE,CAAC;QAC1D,IAAI,CAAC,cAAc,CAAC,GAAG,CAAC,YAAY,EAAE;YACpC,IAAI,EAAE,YAAY;YAClB,SAAS;YACT,MAAM,EAAE,SAAS;YACjB,SAAS,EAAE,KAAK,CAAC,MAAM;YACvB,qBAAqB,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;SAChD,CAAC,CAAC;QAEH,IAAI,CAAC,OAAO,CAAC,iBAAiB,CAAC,YAAY,EAAE,SAAS,EAAE,CAAC,CAAC,CAAC;IAC7D,CAAC;IAEO,KAAK,CAAC,eAAe,CAC3B,MAAc,EACd,IAAuC;QAEvC,IAAI,CAAC;YACH,OAAO,MAAM,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;QAClD,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,OAAO,CAAC,KAAK,CAAC,2BAA2B,GAAG,EAAE,CAAC,CAAC;YAChD,OAAO;gBACL,EAAE,EAAE,YAAY;gBAChB,SAAS,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;gBACnC,MAAM;gBACN,GAAG,IAAI;gBACP,WAAW,EAAE,SAAS;aACvB,CAAC;QACJ,CAAC;IACH,CAAC;IAED,KAAK,CAAC,QAAQ;QACZ,MAAM,IAAI,CAAC,eAAe,CAAC,aAAa,EAAE,CAAC;IAC7C,CAAC;CACF;AAED,MAAM,OAAO,aAAc,SAAQ,KAAK;IAE7B;IAEA;IAHT,YACS,IAAY,EACnB,OAAe,EACR,WAAwB;QAE/B,KAAK,CAAC,OAAO,CAAC,CAAC;QAJR,SAAI,GAAJ,IAAI,CAAQ;QAEZ,gBAAW,GAAX,WAAW,CAAa;QAG/B,IAAI,CAAC,IAAI,GAAG,eAAe,CAAC;IAC9B,CAAC;CACF"}
|
package/dist/types.d.ts
CHANGED
|
@@ -451,6 +451,11 @@ export interface UpstreamStatus {
|
|
|
451
451
|
degradedReason?: string;
|
|
452
452
|
unavailableReason?: string;
|
|
453
453
|
}
|
|
454
|
+
export interface PartyAttribution {
|
|
455
|
+
party: string;
|
|
456
|
+
role: "witness" | "asserter";
|
|
457
|
+
scope: string[];
|
|
458
|
+
}
|
|
454
459
|
export interface AuditRecord {
|
|
455
460
|
id: string;
|
|
456
461
|
timestamp: string;
|
|
@@ -462,6 +467,8 @@ export interface AuditRecord {
|
|
|
462
467
|
durationMs: number;
|
|
463
468
|
success: boolean;
|
|
464
469
|
errorCode?: number;
|
|
470
|
+
decisionContextDigest?: string;
|
|
471
|
+
parties?: PartyAttribution[];
|
|
465
472
|
previousHash?: string;
|
|
466
473
|
attestation?: string;
|
|
467
474
|
}
|
package/dist/types.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,eAAO,MAAM,oBAAoB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAiB/B,CAAC;AAEH,eAAO,MAAM,gBAAgB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAW3B,CAAC;AAEH,eAAO,MAAM,uBAAuB;;;;;;;;;;;;;;;;;;;;;EAOlC,CAAC;AAEH,eAAO,MAAM,qBAAqB;;;;;;;;;;;;;;;EAKhC,CAAC;AAEH,eAAO,MAAM,mBAAmB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAqB9B,CAAC;AAEH,MAAM,MAAM,cAAc,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,oBAAoB,CAAC,CAAC;AAClE,MAAM,MAAM,UAAU,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,gBAAgB,CAAC,CAAC;AAC1D,MAAM,MAAM,iBAAiB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,uBAAuB,CAAC,CAAC;AACxE,MAAM,MAAM,eAAe,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,qBAAqB,CAAC,CAAC;AACpE,MAAM,MAAM,aAAa,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,mBAAmB,CAAC,CAAC;AAEhE,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,SAAS,GAAG,UAAU,GAAG,aAAa,GAAG,UAAU,CAAC;IAC5D,SAAS,EAAE,MAAM,CAAC;IAClB,qBAAqB,EAAE,MAAM,GAAG,IAAI,CAAC;IACrC,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,iBAAiB,CAAC,EAAE,MAAM,CAAC;CAC5B;AAED,MAAM,WAAW,WAAW;IAC1B,EAAE,EAAE,MAAM,CAAC;IACX,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,OAAO,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,SAAS;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,YAAY,EAAE,MAAM,CAAC;IACrB,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,WAAW,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACvC"}
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mcp-audit-gateway/core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "MCP audit and attestation gateway — transparent proxy with cryptographic attestation, policy enforcement, and OTel export",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -1,8 +1,28 @@
|
|
|
1
1
|
import { appendFile, stat, rename, open } from "node:fs/promises";
|
|
2
2
|
import { createHash, randomUUID } from "node:crypto";
|
|
3
|
-
import type { AuditRecord } from "../types.js";
|
|
3
|
+
import type { AuditRecord, PartyAttribution } from "../types.js";
|
|
4
4
|
import type { Signer } from "./signer.js";
|
|
5
5
|
|
|
6
|
+
const GATEWAY_WITNESSED_FIELDS = [
|
|
7
|
+
"id", "timestamp", "method", "toolName", "namespace",
|
|
8
|
+
"upstream", "principal", "durationMs", "success", "errorCode",
|
|
9
|
+
"previousHash",
|
|
10
|
+
];
|
|
11
|
+
|
|
12
|
+
function buildParties(hasDecisionContext: boolean): PartyAttribution[] {
|
|
13
|
+
const parties: PartyAttribution[] = [
|
|
14
|
+
{ party: "gateway", role: "witness", scope: GATEWAY_WITNESSED_FIELDS },
|
|
15
|
+
];
|
|
16
|
+
if (hasDecisionContext) {
|
|
17
|
+
parties.push({
|
|
18
|
+
party: "policy-engine",
|
|
19
|
+
role: "asserter",
|
|
20
|
+
scope: ["decisionContextDigest"],
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
return parties;
|
|
24
|
+
}
|
|
25
|
+
|
|
6
26
|
export function hashRecord(record: AuditRecord): string {
|
|
7
27
|
const json = JSON.stringify(record);
|
|
8
28
|
return createHash("sha256").update(json).digest("hex");
|
|
@@ -29,6 +49,7 @@ export class AuditLog {
|
|
|
29
49
|
durationMs: number;
|
|
30
50
|
success: boolean;
|
|
31
51
|
errorCode?: number;
|
|
52
|
+
decisionContextDigest?: string;
|
|
32
53
|
},
|
|
33
54
|
): Promise<AuditRecord> {
|
|
34
55
|
return new Promise((resolve, reject) => {
|
|
@@ -53,6 +74,7 @@ export class AuditLog {
|
|
|
53
74
|
durationMs: number;
|
|
54
75
|
success: boolean;
|
|
55
76
|
errorCode?: number;
|
|
77
|
+
decisionContextDigest?: string;
|
|
56
78
|
},
|
|
57
79
|
): Promise<AuditRecord> {
|
|
58
80
|
const record: AuditRecord = {
|
|
@@ -60,6 +82,7 @@ export class AuditLog {
|
|
|
60
82
|
timestamp: new Date().toISOString(),
|
|
61
83
|
method,
|
|
62
84
|
...opts,
|
|
85
|
+
parties: buildParties(opts.decisionContextDigest != null),
|
|
63
86
|
previousHash: this.lastHash,
|
|
64
87
|
};
|
|
65
88
|
|
|
@@ -42,6 +42,90 @@ describe("HmacSigner", () => {
|
|
|
42
42
|
});
|
|
43
43
|
});
|
|
44
44
|
|
|
45
|
+
describe("decisionContextDigest in signatures", () => {
|
|
46
|
+
const secret = "b".repeat(64);
|
|
47
|
+
const signer = new HmacSigner(secret);
|
|
48
|
+
|
|
49
|
+
const recordWithDigest: AuditRecord = {
|
|
50
|
+
...mockRecord,
|
|
51
|
+
id: "test-uuid-002",
|
|
52
|
+
decisionContextDigest: "c".repeat(64),
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
it("includes digest in signature when present", async () => {
|
|
56
|
+
const sigWith = await signer.sign(recordWithDigest);
|
|
57
|
+
const sigWithout = await signer.sign(mockRecord);
|
|
58
|
+
expect(sigWith).not.toBe(sigWithout);
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it("verifies record with digest", async () => {
|
|
62
|
+
const sig = await signer.sign(recordWithDigest);
|
|
63
|
+
const valid = await signer.verify(recordWithDigest, sig);
|
|
64
|
+
expect(valid).toBe(true);
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
it("rejects tampered digest", async () => {
|
|
68
|
+
const sig = await signer.sign(recordWithDigest);
|
|
69
|
+
const tampered = { ...recordWithDigest, decisionContextDigest: "d".repeat(64) };
|
|
70
|
+
const valid = await signer.verify(tampered, sig);
|
|
71
|
+
expect(valid).toBe(false);
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
it("old records without digest still verify", async () => {
|
|
75
|
+
const oldRecord: AuditRecord = { ...mockRecord, id: "old-record-001" };
|
|
76
|
+
const sig = await signer.sign(oldRecord);
|
|
77
|
+
const valid = await signer.verify(oldRecord, sig);
|
|
78
|
+
expect(valid).toBe(true);
|
|
79
|
+
});
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
describe("parties in signatures", () => {
|
|
83
|
+
const secret = "c".repeat(64);
|
|
84
|
+
const signer = new HmacSigner(secret);
|
|
85
|
+
|
|
86
|
+
const recordWithParties: AuditRecord = {
|
|
87
|
+
...mockRecord,
|
|
88
|
+
id: "test-uuid-parties-001",
|
|
89
|
+
parties: [
|
|
90
|
+
{ party: "gateway", role: "witness", scope: ["id", "timestamp", "method", "toolName", "namespace", "upstream", "principal", "durationMs", "success", "errorCode", "previousHash"] },
|
|
91
|
+
],
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
it("includes parties in signature when present", async () => {
|
|
95
|
+
const sigWith = await signer.sign(recordWithParties);
|
|
96
|
+
const sigWithout = await signer.sign(mockRecord);
|
|
97
|
+
expect(sigWith).not.toBe(sigWithout);
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
it("verifies record with parties", async () => {
|
|
101
|
+
const sig = await signer.sign(recordWithParties);
|
|
102
|
+
const valid = await signer.verify(recordWithParties, sig);
|
|
103
|
+
expect(valid).toBe(true);
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
it("rejects tampered parties", async () => {
|
|
107
|
+
const sig = await signer.sign(recordWithParties);
|
|
108
|
+
const tampered = { ...recordWithParties, parties: [{ party: "attacker", role: "asserter" as const, scope: ["*"] }] };
|
|
109
|
+
const valid = await signer.verify(tampered, sig);
|
|
110
|
+
expect(valid).toBe(false);
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
it("handles both decisionContextDigest and parties together", async () => {
|
|
114
|
+
const dual: AuditRecord = {
|
|
115
|
+
...recordWithParties,
|
|
116
|
+
id: "test-uuid-parties-002",
|
|
117
|
+
decisionContextDigest: "e".repeat(64),
|
|
118
|
+
parties: [
|
|
119
|
+
{ party: "gateway", role: "witness", scope: ["id", "timestamp", "method"] },
|
|
120
|
+
{ party: "policy-engine", role: "asserter", scope: ["decisionContextDigest"] },
|
|
121
|
+
],
|
|
122
|
+
};
|
|
123
|
+
const sig = await signer.sign(dual);
|
|
124
|
+
const valid = await signer.verify(dual, sig);
|
|
125
|
+
expect(valid).toBe(true);
|
|
126
|
+
});
|
|
127
|
+
});
|
|
128
|
+
|
|
45
129
|
describe("Ed25519Signer", () => {
|
|
46
130
|
it("signs and verifies a record", async () => {
|
|
47
131
|
const signer = new Ed25519Signer();
|
|
@@ -77,7 +77,7 @@ export class Ed25519Signer implements Signer {
|
|
|
77
77
|
}
|
|
78
78
|
|
|
79
79
|
function canonicalizeRecord(record: AuditRecord): string {
|
|
80
|
-
const ordered: [string, string | number | boolean | null][] = [
|
|
80
|
+
const ordered: [string, string | number | boolean | null | unknown][] = [
|
|
81
81
|
["id", record.id],
|
|
82
82
|
["timestamp", record.timestamp],
|
|
83
83
|
["method", record.method],
|
|
@@ -90,6 +90,13 @@ function canonicalizeRecord(record: AuditRecord): string {
|
|
|
90
90
|
["errorCode", record.errorCode ?? null],
|
|
91
91
|
["previousHash", record.previousHash ?? null],
|
|
92
92
|
];
|
|
93
|
+
if (record.decisionContextDigest != null) {
|
|
94
|
+
ordered.splice(10, 0, ["decisionContextDigest", record.decisionContextDigest]);
|
|
95
|
+
}
|
|
96
|
+
if (record.parties != null) {
|
|
97
|
+
const insertAt = record.decisionContextDigest != null ? 12 : 11;
|
|
98
|
+
ordered.splice(insertAt, 0, ["parties", record.parties]);
|
|
99
|
+
}
|
|
93
100
|
return JSON.stringify(ordered);
|
|
94
101
|
}
|
|
95
102
|
|
package/src/index.ts
CHANGED
|
@@ -2,7 +2,7 @@ export { Gateway, ToolCallError } from "./proxy/gateway.js";
|
|
|
2
2
|
export { runWrapProxy } from "./wrap/proxy.js";
|
|
3
3
|
export { McpServerAdapter } from "./proxy/mcp-server-adapter.js";
|
|
4
4
|
export { UpstreamManager } from "./proxy/upstream-manager.js";
|
|
5
|
-
export { PolicyEngine } from "./policy/engine.js";
|
|
5
|
+
export { PolicyEngine, computeDecisionContextDigest, type DecisionContext } from "./policy/engine.js";
|
|
6
6
|
export { AuditLog } from "./attestation/audit-log.js";
|
|
7
7
|
export { createSigner, HmacSigner, Ed25519Signer } from "./attestation/signer.js";
|
|
8
8
|
export { verifyAuditLog } from "./attestation/verify.js";
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { describe, it, expect } from "vitest";
|
|
2
|
-
import { PolicyEngine } from "./engine.js";
|
|
2
|
+
import { PolicyEngine, computeDecisionContextDigest } from "./engine.js";
|
|
3
3
|
import type { ToolEntry } from "../types.js";
|
|
4
4
|
|
|
5
5
|
const tool = (name: string, ns: string): ToolEntry => ({
|
|
@@ -92,4 +92,50 @@ describe("PolicyEngine", () => {
|
|
|
92
92
|
expect(filtered.map((t) => t.originalName)).not.toContain("dangerous_delete");
|
|
93
93
|
});
|
|
94
94
|
});
|
|
95
|
+
|
|
96
|
+
describe("decisionContext", () => {
|
|
97
|
+
const engine = new PolicyEngine("allow", [
|
|
98
|
+
{ effect: "deny", principals: ["agent:readonly-*"], tools: ["*/write_*"] },
|
|
99
|
+
]);
|
|
100
|
+
|
|
101
|
+
it("returns decision context with evaluation", () => {
|
|
102
|
+
const t = tool("write_file", "fs");
|
|
103
|
+
const result = engine.evaluate("agent:readonly-bot", t);
|
|
104
|
+
expect(result.decisionContext).toBeDefined();
|
|
105
|
+
expect(result.decisionContext.principal).toBe("agent:readonly-bot");
|
|
106
|
+
expect(result.decisionContext.toolName).toBe("fs/write_file");
|
|
107
|
+
expect(result.decisionContext.toolNamespace).toBe("fs");
|
|
108
|
+
expect(result.decisionContext.effect).toBe("deny");
|
|
109
|
+
expect(result.decisionContext.matchedRule).toEqual({
|
|
110
|
+
effect: "deny",
|
|
111
|
+
principals: ["agent:readonly-*"],
|
|
112
|
+
tools: ["*/write_*"],
|
|
113
|
+
});
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
it("produces stable digest for same input", () => {
|
|
117
|
+
const t = tool("write_file", "fs");
|
|
118
|
+
const r1 = engine.evaluate("agent:readonly-bot", t);
|
|
119
|
+
const r2 = engine.evaluate("agent:readonly-bot", t);
|
|
120
|
+
const d1 = computeDecisionContextDigest(r1.decisionContext);
|
|
121
|
+
const d2 = computeDecisionContextDigest(r2.decisionContext);
|
|
122
|
+
expect(d1).toBe(d2);
|
|
123
|
+
expect(d1).toHaveLength(64);
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
it("produces different digest for different principal", () => {
|
|
127
|
+
const t = tool("read_data", "github");
|
|
128
|
+
const r1 = engine.evaluate("agent:alpha", t);
|
|
129
|
+
const r2 = engine.evaluate("agent:beta", t);
|
|
130
|
+
const d1 = computeDecisionContextDigest(r1.decisionContext);
|
|
131
|
+
const d2 = computeDecisionContextDigest(r2.decisionContext);
|
|
132
|
+
expect(d1).not.toBe(d2);
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
it("includes null principal when undefined", () => {
|
|
136
|
+
const t = tool("read_data", "github");
|
|
137
|
+
const result = engine.evaluate(undefined, t);
|
|
138
|
+
expect(result.decisionContext.principal).toBeNull();
|
|
139
|
+
});
|
|
140
|
+
});
|
|
95
141
|
});
|
package/src/policy/engine.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
1
2
|
import type { PolicyRule, ToolEntry } from "../types.js";
|
|
2
3
|
|
|
3
4
|
export interface PolicyDecision {
|
|
@@ -6,6 +7,28 @@ export interface PolicyDecision {
|
|
|
6
7
|
rateLimit?: { maxPerMinute?: number; maxPerHour?: number };
|
|
7
8
|
}
|
|
8
9
|
|
|
10
|
+
export interface DecisionContext {
|
|
11
|
+
principal: string | null;
|
|
12
|
+
toolName: string;
|
|
13
|
+
toolNamespace: string;
|
|
14
|
+
toolUpstream: string;
|
|
15
|
+
matchedRule: PolicyRule | null;
|
|
16
|
+
effect: "allow" | "deny";
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function computeDecisionContextDigest(ctx: DecisionContext): string {
|
|
20
|
+
const ordered: [string, unknown][] = [
|
|
21
|
+
["principal", ctx.principal],
|
|
22
|
+
["toolName", ctx.toolName],
|
|
23
|
+
["toolNamespace", ctx.toolNamespace],
|
|
24
|
+
["toolUpstream", ctx.toolUpstream],
|
|
25
|
+
["matchedRule", ctx.matchedRule],
|
|
26
|
+
["effect", ctx.effect],
|
|
27
|
+
];
|
|
28
|
+
const canonical = JSON.stringify(ordered);
|
|
29
|
+
return createHash("sha256").update(canonical).digest("hex");
|
|
30
|
+
}
|
|
31
|
+
|
|
9
32
|
interface RateLimitState {
|
|
10
33
|
minuteCounts: Map<string, { count: number; resetAt: number }>;
|
|
11
34
|
hourCounts: Map<string, { count: number; resetAt: number }>;
|
|
@@ -22,7 +45,7 @@ export class PolicyEngine {
|
|
|
22
45
|
private rules: PolicyRule[],
|
|
23
46
|
) {}
|
|
24
47
|
|
|
25
|
-
evaluate(principal: string | undefined, tool: ToolEntry): PolicyDecision {
|
|
48
|
+
evaluate(principal: string | undefined, tool: ToolEntry): PolicyDecision & { decisionContext: DecisionContext } {
|
|
26
49
|
let matchedRule: PolicyRule | null = null;
|
|
27
50
|
|
|
28
51
|
for (const rule of this.rules) {
|
|
@@ -32,23 +55,33 @@ export class PolicyEngine {
|
|
|
32
55
|
}
|
|
33
56
|
}
|
|
34
57
|
|
|
58
|
+
const buildContext = (effect: "allow" | "deny"): DecisionContext => ({
|
|
59
|
+
principal: principal ?? null,
|
|
60
|
+
toolName: tool.name,
|
|
61
|
+
toolNamespace: tool.namespace,
|
|
62
|
+
toolUpstream: tool.upstream,
|
|
63
|
+
matchedRule,
|
|
64
|
+
effect,
|
|
65
|
+
});
|
|
66
|
+
|
|
35
67
|
if (!matchedRule) {
|
|
36
|
-
|
|
68
|
+
const allowed = this.defaultEffect === "allow";
|
|
69
|
+
return { allowed, decisionContext: buildContext(allowed ? "allow" : "deny") };
|
|
37
70
|
}
|
|
38
71
|
|
|
39
72
|
if (matchedRule.effect === "deny") {
|
|
40
|
-
return { allowed: false, reason: "denied by policy rule" };
|
|
73
|
+
return { allowed: false, reason: "denied by policy rule", decisionContext: buildContext("deny") };
|
|
41
74
|
}
|
|
42
75
|
|
|
43
76
|
if (matchedRule.rateLimit) {
|
|
44
77
|
const key = `${principal ?? "anonymous"}:${tool.name}`;
|
|
45
78
|
const rateLimited = this.checkRateLimit(key, matchedRule.rateLimit);
|
|
46
79
|
if (rateLimited) {
|
|
47
|
-
return { allowed: false, reason: "rate limit exceeded", rateLimit: matchedRule.rateLimit };
|
|
80
|
+
return { allowed: false, reason: "rate limit exceeded", rateLimit: matchedRule.rateLimit, decisionContext: buildContext("deny") };
|
|
48
81
|
}
|
|
49
82
|
}
|
|
50
83
|
|
|
51
|
-
return { allowed: true, rateLimit: matchedRule.rateLimit };
|
|
84
|
+
return { allowed: true, rateLimit: matchedRule.rateLimit, decisionContext: buildContext("allow") };
|
|
52
85
|
}
|
|
53
86
|
|
|
54
87
|
filterTools(principal: string | undefined, tools: ToolEntry[]): ToolEntry[] {
|
package/src/proxy/gateway.ts
CHANGED
|
@@ -5,7 +5,7 @@ import type {
|
|
|
5
5
|
ToolEntry,
|
|
6
6
|
AuditRecord,
|
|
7
7
|
} from "../types.js";
|
|
8
|
-
import { PolicyEngine } from "../policy/engine.js";
|
|
8
|
+
import { PolicyEngine, computeDecisionContextDigest } from "../policy/engine.js";
|
|
9
9
|
import { AuditLog } from "../attestation/audit-log.js";
|
|
10
10
|
import { createSigner } from "../attestation/signer.js";
|
|
11
11
|
import { GatewayTracer } from "../telemetry/tracer.js";
|
|
@@ -113,6 +113,8 @@ export class Gateway {
|
|
|
113
113
|
}
|
|
114
114
|
|
|
115
115
|
const decision = this.policyEngine.evaluate(principal, tool);
|
|
116
|
+
const contextDigest = computeDecisionContextDigest(decision.decisionContext);
|
|
117
|
+
|
|
116
118
|
if (!decision.allowed) {
|
|
117
119
|
this.metrics.recordPolicyDenial({
|
|
118
120
|
principal: principal ?? "anonymous",
|
|
@@ -127,6 +129,7 @@ export class Gateway {
|
|
|
127
129
|
durationMs: Date.now() - startTime,
|
|
128
130
|
success: false,
|
|
129
131
|
errorCode: -32603,
|
|
132
|
+
decisionContextDigest: contextDigest,
|
|
130
133
|
});
|
|
131
134
|
throw new ToolCallError(
|
|
132
135
|
-32603,
|
|
@@ -173,6 +176,7 @@ export class Gateway {
|
|
|
173
176
|
principal,
|
|
174
177
|
durationMs,
|
|
175
178
|
success: true,
|
|
179
|
+
decisionContextDigest: contextDigest,
|
|
176
180
|
});
|
|
177
181
|
|
|
178
182
|
return { result, auditRecord: record };
|
|
@@ -202,6 +206,7 @@ export class Gateway {
|
|
|
202
206
|
durationMs,
|
|
203
207
|
success: false,
|
|
204
208
|
errorCode: -32603,
|
|
209
|
+
decisionContextDigest: contextDigest,
|
|
205
210
|
});
|
|
206
211
|
throw new ToolCallError(-32603, "Upstream server error", record);
|
|
207
212
|
}
|
package/src/types.ts
CHANGED
|
@@ -87,6 +87,12 @@ export interface UpstreamStatus {
|
|
|
87
87
|
unavailableReason?: string;
|
|
88
88
|
}
|
|
89
89
|
|
|
90
|
+
export interface PartyAttribution {
|
|
91
|
+
party: string;
|
|
92
|
+
role: "witness" | "asserter";
|
|
93
|
+
scope: string[];
|
|
94
|
+
}
|
|
95
|
+
|
|
90
96
|
export interface AuditRecord {
|
|
91
97
|
id: string;
|
|
92
98
|
timestamp: string;
|
|
@@ -98,6 +104,8 @@ export interface AuditRecord {
|
|
|
98
104
|
durationMs: number;
|
|
99
105
|
success: boolean;
|
|
100
106
|
errorCode?: number;
|
|
107
|
+
decisionContextDigest?: string;
|
|
108
|
+
parties?: PartyAttribution[];
|
|
101
109
|
previousHash?: string;
|
|
102
110
|
attestation?: string;
|
|
103
111
|
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# Conformance Vectors
|
|
2
|
+
|
|
3
|
+
Cross-implementation verification fixtures for mcp-audit-gateway's canonicalization and hash chain.
|
|
4
|
+
|
|
5
|
+
## Two hash operations, two guarantees
|
|
6
|
+
|
|
7
|
+
This implementation uses two distinct hash operations for different purposes:
|
|
8
|
+
|
|
9
|
+
**Canonical hash (signing):** SHA-256 of a tuple-array serialization with a fixed 11-field order. The attestation field is excluded (it cannot sign itself). This form is cross-language reproducible — any implementation that follows the field order and null rule will produce identical bytes.
|
|
10
|
+
|
|
11
|
+
**Chain hash (linking):** SHA-256 of `JSON.stringify(fullRecord)` including the attestation field. This binds the signature into the chain sequence. It depends on JavaScript insertion order and is JS-authoritative — other languages must serialize keys in the documented order to verify.
|
|
12
|
+
|
|
13
|
+
## Running the verifiers
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
# JavaScript (Node.js 18+)
|
|
17
|
+
node verify.mjs
|
|
18
|
+
|
|
19
|
+
# Python (3.7+)
|
|
20
|
+
python3 verify.py
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Regenerating vectors
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
node generate.mjs
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
The generator uses the same canonicalization logic as `src/attestation/signer.ts`. If the source changes, regenerate and re-run both verifiers to confirm cross-language agreement.
|
|
30
|
+
|
|
31
|
+
## Vector coverage
|
|
32
|
+
|
|
33
|
+
| Vector | Tests |
|
|
34
|
+
|--------|-------|
|
|
35
|
+
| genesis_all_fields | All fields populated, first record in chain |
|
|
36
|
+
| genesis_null_optionals | Absent optional fields serialize as null |
|
|
37
|
+
| error_with_code | success: false with negative error code |
|
|
38
|
+
| zero_duration | durationMs: 0 boundary |
|
|
39
|
+
| invalid_params_error | Different JSON-RPC error code |
|
|
40
|
+
| unicode_in_fields | CJK and emoji in string fields |
|
|
41
|
+
| max_safe_integer_duration | durationMs: 9007199254740991 (2^53-1) |
|
|
42
|
+
| empty_string_tool_name | Empty string vs null distinction |
|
|
43
|
+
| chain[0-2] | Three linked records with attestation |
|
|
44
|
+
| dual_hash_demo | Same fields, different attestation: proves canonical hash matches while chain hash differs |
|