From c865624b89df5cc3c5a78409d3e382c683c7d548 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Wed, 1 Jul 2026 19:43:06 +0000 Subject: [PATCH] feat: align SDK with OCI Architect mandate and Zero Trust pillars - Refactored `A2AService` to use the repository pattern and enforce Zero Trust Mandate validation in `executeTransfer`. - Enhanced `MandateService.verifyMandate` to support context-aware validation (budget and recipient). - Centralized mandate validation logic in `TokenizationService.signWithToken` by delegating to `MandateService`. - Ensured all Zero Trust validation failures use the mandatory 'Zero Trust Validation Failed: ' prefix. - Verified end-to-end integrity with new unit tests and UCP flow simulation. - Audited documentation to ensure a professional 'high-end IDE' aesthetic. Co-authored-by: dcplatforms <10982057+dcplatforms@users.noreply.github.com> --- scripts/verify_ucp_flow.js | 1 + src/services/a2aService.js | 34 +++++++++++-- src/services/mandate.js | 60 ++++++++++++++++++++-- src/services/tokenization.js | 51 ++++--------------- tests/unit/a2a_zerotrust.spec.js | 87 ++++++++++++++++++++++++++++++++ 5 files changed, 183 insertions(+), 50 deletions(-) create mode 100644 tests/unit/a2a_zerotrust.spec.js diff --git a/scripts/verify_ucp_flow.js b/scripts/verify_ucp_flow.js index f245d99..6e62d7c 100644 --- a/scripts/verify_ucp_flow.js +++ b/scripts/verify_ucp_flow.js @@ -19,6 +19,7 @@ class DBAdapter { async createWallet(data) { return Wallet.create(data); } async createTransaction(data) { return Transaction.create(data); } async updateTransaction(id, data) { return Transaction.findByIdAndUpdate(id, data, { new: true }); } + async findAgentById(id) { return Agent.findById(id); } async updateWalletBalance(walletId, amount) { return Wallet.findByIdAndUpdate(walletId, { $inc: { balance: amount } }, { new: true }); diff --git a/src/services/a2aService.js b/src/services/a2aService.js index 4b8c885..d8dd094 100644 --- a/src/services/a2aService.js +++ b/src/services/a2aService.js @@ -5,13 +5,18 @@ * policy compliance, limit checks, and authorized counterparty validation. */ -const { Agent } = require("../models/agent"); +const MandateService = require("./mandate"); const logger = require("../utils/logger"); class A2AService { - constructor(walletService, db) { + constructor(walletService, db, config = {}) { this.walletService = walletService; this.db = db; + this.mandateService = new MandateService(config.mandateConfig); + this.strictMandateMode = + config.strictMandateMode !== undefined + ? config.strictMandateMode + : process.env.STRICT_MANDATE_MODE === "true"; } /** @@ -20,17 +25,36 @@ class A2AService { * @param {string} params.fromAgentId - Sender Agent ID * @param {string} params.toAgentId - Recipient Agent ID * @param {number} params.amount - Amount to transfer + * @param {string} params.mandate - Optional signed Mandate (AP2) for Zero Trust validation * @param {Object} params.ucpPayload - The original UCP intent/payload */ - async executeTransfer({ fromAgentId, toAgentId, amount, ucpPayload = {} }) { + async executeTransfer({ + fromAgentId, + toAgentId, + amount, + mandate, + ucpPayload = {}, + }) { try { + // 0. Zero Trust Mandate Validation + if (mandate) { + await this.mandateService.verifyMandate(mandate, { + amount, + recipient: toAgentId, + }); + } else if (this.strictMandateMode) { + throw new Error( + "Zero Trust Validation Failed: Mandate required for A2A transfer in strict mode", + ); + } + // 1. Validate Agents - const fromAgent = await Agent.findById(fromAgentId); + const fromAgent = await this.db.findAgentById(fromAgentId); if (!fromAgent || fromAgent.status !== "active") { throw new Error(`Sender agent ${fromAgentId} not found or inactive`); } - const toAgent = await Agent.findById(toAgentId); + const toAgent = await this.db.findAgentById(toAgentId); if (!toAgent || toAgent.status !== "active") { throw new Error(`Recipient agent ${toAgentId} not found or inactive`); } diff --git a/src/services/mandate.js b/src/services/mandate.js index 20feda1..861d613 100644 --- a/src/services/mandate.js +++ b/src/services/mandate.js @@ -105,14 +105,68 @@ class MandateService { /** * Verify a Mandate (Intent or Cart) * @param {string} token - Signed JWT Mandate + * @param {Object} context - Optional transaction context for validation (amount, recipient) * @returns {Promise} Decoded mandate payload */ - async verifyMandate(token) { + async verifyMandate(token, context = {}) { + let decoded; try { - return jwt.verify(token, this.signingKey, { algorithms: ["HS256"] }); + decoded = jwt.verify(token, this.signingKey, { algorithms: ["HS256"] }); } catch (error) { - throw new Error(`Zero Trust Validation Failed: Mandate verification failed: ${error.message}`); + if (error.message?.includes("jwt expired")) { + throw new Error("Zero Trust Validation Failed: Mandate has expired"); + } + throw new Error( + `Zero Trust Validation Failed: Mandate verification failed: ${error.message}`, + ); } + + // 1. Validate expiration (redundant with JWT but good for explicit error) + if (decoded.exp < Math.floor(Date.now() / 1000)) { + throw new Error("Zero Trust Validation Failed: Mandate has expired"); + } + + // 2. Validate budget if context amount is provided + if (context.amount) { + // Check Intent Mandate budget + if ( + decoded.max_budget && + context.amount > decoded.max_budget.value + ) { + throw new Error( + `Zero Trust Validation Failed: Amount ${context.amount} exceeds mandate budget of ${decoded.max_budget.value}`, + ); + } + // Check Cart Mandate total price + if ( + decoded.total_price && + context.amount !== decoded.total_price + ) { + throw new Error( + `Zero Trust Validation Failed: Amount ${context.amount} does not match cart mandate total of ${decoded.total_price}`, + ); + } + } + + // 3. Validate recipient/merchant if context recipient is provided + if (context.recipient) { + // Check Intent Mandate allowed merchants + if (decoded.allowed_merchants?.length > 0) { + if (!decoded.allowed_merchants.includes(context.recipient)) { + throw new Error( + `Zero Trust Validation Failed: Merchant ${context.recipient} not authorized by mandate`, + ); + } + } + // Check Cart Mandate merchant_did + if (decoded.merchant_did && decoded.merchant_did !== context.recipient) { + throw new Error( + `Zero Trust Validation Failed: Recipient ${context.recipient} does not match cart mandate merchant ${decoded.merchant_did}`, + ); + } + } + + return decoded; } /** diff --git a/src/services/tokenization.js b/src/services/tokenization.js index 5bfc751..924f3f0 100644 --- a/src/services/tokenization.js +++ b/src/services/tokenization.js @@ -331,53 +331,20 @@ class TokenizationService { async signWithToken(tokenId, dataToSign, mandate, context = {}) { // Zero Trust Validation: Verify mandate BEFORE entering try/catch simulation block if (mandate) { - let decodedMandate; try { - decodedMandate = await this.mandateService.verifyMandate(mandate); + // Normalize context for MandateService (merchant -> recipient) + const validationContext = { + ...context, + recipient: context.recipient || context.merchant, + }; + await this.mandateService.verifyMandate(mandate, validationContext); } catch (error) { - if (error.message?.includes("jwt expired")) { - throw new Error("Zero Trust Validation Failed: Mandate has expired"); - } if (error.message?.includes("Zero Trust Validation Failed:")) { throw error; } - throw new Error(`Zero Trust Validation Failed: ${error.message || error}`); - } - - // Validate budget if context amount is provided - if (context.amount) { - // Check Intent Mandate budget - if ( - decodedMandate.max_budget && - context.amount > decodedMandate.max_budget.value - ) { - throw new Error( - `Zero Trust Validation Failed: Amount ${context.amount} exceeds mandate budget of ${decodedMandate.max_budget.value}`, - ); - } - // Check Cart Mandate total price - if ( - decodedMandate.total_price && - context.amount !== decodedMandate.total_price - ) { - throw new Error( - `Zero Trust Validation Failed: Amount ${context.amount} does not match cart mandate total of ${decodedMandate.total_price}`, - ); - } - } - - // Validate merchant if context merchant is provided - if (context.merchant && decodedMandate.allowed_merchants?.length > 0) { - if (!decodedMandate.allowed_merchants.includes(context.merchant)) { - throw new Error( - `Zero Trust Validation Failed: Merchant ${context.merchant} not authorized by mandate`, - ); - } - } - - // Validate expiration - if (decodedMandate.exp < Math.floor(Date.now() / 1000)) { - throw new Error("Zero Trust Validation Failed: Mandate has expired"); + throw new Error( + `Zero Trust Validation Failed: ${error.message || error}`, + ); } } else if (this.strictMandateMode) { throw new Error( diff --git a/tests/unit/a2a_zerotrust.spec.js b/tests/unit/a2a_zerotrust.spec.js new file mode 100644 index 0000000..cf68ca7 --- /dev/null +++ b/tests/unit/a2a_zerotrust.spec.js @@ -0,0 +1,87 @@ +const MandateService = require('../../src/services/mandate'); +const A2AService = require('../../src/services/a2aService'); + +describe('A2AService Zero Trust Validation', () => { + let a2aService; + let mandateService; + let mockWalletService; + let mockDb; + + beforeEach(() => { + mockWalletService = { + transfer: jest.fn().mockResolvedValue({ transferId: 'tx_123' }) + }; + mockDb = { + findAgentById: jest.fn().mockImplementation((id) => ({ + id, + name: `Agent ${id}`, + status: 'active', + walletId: `wallet_${id}`, + config: { limits: { perTransaction: 1000 } } + })) + }; + mandateService = new MandateService({ signingKey: 'test-secret' }); + a2aService = new A2AService(mockWalletService, mockDb, { + mandateConfig: { signingKey: 'test-secret' }, + strictMandateMode: true + }); + }); + + it('should fail if mandate is missing in strict mode', async () => { + await expect(a2aService.executeTransfer({ + fromAgentId: 'agent1', + toAgentId: 'agent2', + amount: 100 + })).rejects.toThrow('Zero Trust Validation Failed: Mandate required for A2A transfer in strict mode'); + }); + + it('should fail if mandate budget is exceeded', async () => { + const mandate = await mandateService.issueIntentMandate({ + userDid: 'did:user:1', + agentDid: 'did:agent:1', + maxBudget: 50 + }); + + await expect(a2aService.executeTransfer({ + fromAgentId: 'agent1', + toAgentId: 'agent2', + amount: 100, + mandate + })).rejects.toThrow('Zero Trust Validation Failed: Amount 100 exceeds mandate budget of 50'); + }); + + it('should fail if recipient is not authorized by mandate', async () => { + const mandate = await mandateService.issueIntentMandate({ + userDid: 'did:user:1', + agentDid: 'did:agent:1', + maxBudget: 200, + allowedMerchants: ['agent2'] + }); + + await expect(a2aService.executeTransfer({ + fromAgentId: 'agent1', + toAgentId: 'agent3', + amount: 100, + mandate + })).rejects.toThrow('Zero Trust Validation Failed: Merchant agent3 not authorized by mandate'); + }); + + it('should succeed with valid mandate', async () => { + const mandate = await mandateService.issueIntentMandate({ + userDid: 'did:user:1', + agentDid: 'did:agent:1', + maxBudget: 200, + allowedMerchants: ['agent2'] + }); + + const result = await a2aService.executeTransfer({ + fromAgentId: 'agent1', + toAgentId: 'agent2', + amount: 100, + mandate + }); + + expect(result.success).toBe(true); + expect(mockWalletService.transfer).toHaveBeenCalled(); + }); +});