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/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(); + }); +});