From 757fa43469af3fb14eb01461cc0f8079c968bca4 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Wed, 22 Apr 2026 19:25:48 +0000 Subject: [PATCH] feat: implement zero trust mandate enforcement and x402 fiduciary validation - Refactor TokenizationService to validate mandates before signing - Implement strictMandateMode for mandatory authorization - Add fiduciary validation to ocp-cli x402:settle - Add unit tests for Tokenization and Web3 services - Ensure Zero Trust 'Last Line of Defense' alignment Co-authored-by: dcplatforms <10982057+dcplatforms@users.noreply.github.com> --- agent_output.txt | 3 + mandate_output.txt | 4 + scripts/ocp-cli.js | 25 ++++++- src/agents/agent_6382d61c.json | 7 ++ src/mandates/mandate_be7dc63fcbb70f38.jwt | 1 + src/services/tokenization.js | 60 ++++++++------- tests/unit/tokenization.spec.js | 83 +++++++++++++++++++++ tests/unit/web3.spec.js | 91 +++++++++++++++++++++++ 8 files changed, 248 insertions(+), 26 deletions(-) create mode 100644 agent_output.txt create mode 100644 mandate_output.txt create mode 100644 src/agents/agent_6382d61c.json create mode 100644 src/mandates/mandate_be7dc63fcbb70f38.jwt create mode 100644 tests/unit/tokenization.spec.js create mode 100644 tests/unit/web3.spec.js diff --git a/agent_output.txt b/agent_output.txt new file mode 100644 index 0000000..7622cc8 --- /dev/null +++ b/agent_output.txt @@ -0,0 +1,3 @@ +Agent created: TestAgent +ID: agent_6382d61c +DID: did:key:06ffeafa6f71c7bd83ad21ce4fb56e2c diff --git a/mandate_output.txt b/mandate_output.txt new file mode 100644 index 0000000..609688d --- /dev/null +++ b/mandate_output.txt @@ -0,0 +1,4 @@ +Intent Mandate issued for agent agent_6382d61c +Mandate ID: mandate_be7dc63fcbb70f38 +Budget: 100 USD +Saved to: src/mandates/mandate_be7dc63fcbb70f38.jwt diff --git a/scripts/ocp-cli.js b/scripts/ocp-cli.js index 8eacb92..65b1ba7 100644 --- a/scripts/ocp-cli.js +++ b/scripts/ocp-cli.js @@ -156,7 +156,30 @@ program.command('x402:settle') return; } - // Simulation of x402 settlement + // Simulation of x402 settlement with fiduciary validation + const signingKey = process.env.MANDATE_SIGNING_KEY || 'default-secret-key'; + const mandateService = new MandateService({ signingKey }); + + try { + const decodedMandate = await mandateService.verifyMandate(mandateToken); + const amountNum = parseFloat(amount); + + // Validate budget + if (decodedMandate.max_budget && amountNum > decodedMandate.max_budget.value) { + console.error(`Fiduciary Validation Failed: Amount ${amountNum} exceeds mandate budget of ${decodedMandate.max_budget.value} ${decodedMandate.max_budget.currency}`); + return; + } + + // Validate expiration + if (decodedMandate.exp < Math.floor(Date.now() / 1000)) { + console.error('Fiduciary Validation Failed: Mandate has expired'); + return; + } + } catch (error) { + console.error(`Fiduciary Validation Failed: ${error.message}`); + return; + } + const settlementId = `x402_${crypto.randomBytes(8).toString('hex')}`; const txHash = `0x${crypto.randomBytes(32).toString('hex')}`; diff --git a/src/agents/agent_6382d61c.json b/src/agents/agent_6382d61c.json new file mode 100644 index 0000000..c34a11d --- /dev/null +++ b/src/agents/agent_6382d61c.json @@ -0,0 +1,7 @@ +{ + "id": "agent_6382d61c", + "name": "TestAgent", + "did": "did:key:06ffeafa6f71c7bd83ad21ce4fb56e2c", + "wallet_address": "0x9fab4a57fb46aafd8ef02cbdaa192bfa14aa10e7", + "created_at": "2026-04-22T19:23:42.590Z" +} \ No newline at end of file diff --git a/src/mandates/mandate_be7dc63fcbb70f38.jwt b/src/mandates/mandate_be7dc63fcbb70f38.jwt new file mode 100644 index 0000000..5ffb214 --- /dev/null +++ b/src/mandates/mandate_be7dc63fcbb70f38.jwt @@ -0,0 +1 @@ +eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJkaWQ6d2ViOm9wZW4tY29tbWVyY2UtcHJvdG9jb2wuaW8iLCJzdWIiOiJkaWQ6a2V5OmFnZW50XzYzODJkNjFjIiwidXNlcl9kaWQiOiJkaWQ6a2V5OnVzZXItbG9jYWwiLCJhZ2VudF9kaWQiOiJkaWQ6a2V5OmFnZW50XzYzODJkNjFjIiwibWFuZGF0ZV9pZCI6Im1hbmRhdGVfYmU3ZGM2M2ZjYmI3MGYzOCIsIm1heF9idWRnZXQiOnsidmFsdWUiOjEwMCwiY3VycmVuY3kiOiJVU0QifSwiZXhwIjoxNzc2OTcyMjIyLCJwdXJwb3NlX2NvZGUiOiJDTElfSVNTVUVEIiwiYWxsb3dlZF9tZXJjaGFudHMiOltdLCJpYXQiOjE3NzY4ODU4MjIsInR5cGUiOiJpbnRlbnRfbWFuZGF0ZSJ9.gDTkeD6IbOAQ04Yi0mX-lJLwNru3y4GfXMz4Rc999kQ \ No newline at end of file diff --git a/src/services/tokenization.js b/src/services/tokenization.js index eef3f39..92d45f4 100644 --- a/src/services/tokenization.js +++ b/src/services/tokenization.js @@ -15,6 +15,8 @@ class TokenizationService { this.baseURL = config.baseURL || process.env.TOKENIZATION_BASE_URL || 'https://api.basistheory.com'; this.tenantId = config.tenantId || process.env.TOKENIZATION_TENANT_ID; this.timeout = config.timeout || 30000; + this.strictMandateMode = config.strictMandateMode !== undefined ? + config.strictMandateMode : (process.env.STRICT_MANDATE_MODE === 'true'); if (!this.apiKey) { throw new Error('Tokenization API key is required'); @@ -318,38 +320,46 @@ class TokenizationService { * @returns {Promise} Signature */ async signWithToken(tokenId, dataToSign, mandate, context = {}) { - try { - // Zero Trust Validation: Verify mandate if provided - if (mandate) { - const decodedMandate = await this.mandateService.verifyMandate(mandate); - - // 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}`); - } + // Zero Trust Validation: Verify mandate BEFORE entering try/catch simulation block + if (mandate) { + let decodedMandate; + try { + decodedMandate = await this.mandateService.verifyMandate(mandate); + } catch (error) { + if (error.message.includes('jwt expired')) { + throw new Error('Zero Trust Validation Failed: Mandate has expired'); } + throw new Error(`Zero Trust Validation Failed: ${error.message}`); + } - // 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 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 expiration - if (decodedMandate.exp < Math.floor(Date.now() / 1000)) { - throw new Error('Zero Trust Validation Failed: Mandate has expired'); + // 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`); } - } else if (process.env.STRICT_MANDATE_MODE === 'true') { - throw new Error('Zero Trust Validation Failed: Mandate required for signing in strict mode'); } + // Validate expiration + if (decodedMandate.exp < Math.floor(Date.now() / 1000)) { + throw new Error('Zero Trust Validation Failed: Mandate has expired'); + } + } else if (this.strictMandateMode) { + throw new Error('Zero Trust Validation Failed: Mandate required for signing in strict mode'); + } + + try { // In a real implementation, this would call a Basis Theory Reactor // providing the tokenId. The Reactor would securely retrieve the // secret and sign the data without exposing the key. diff --git a/tests/unit/tokenization.spec.js b/tests/unit/tokenization.spec.js new file mode 100644 index 0000000..c6a5aa9 --- /dev/null +++ b/tests/unit/tokenization.spec.js @@ -0,0 +1,83 @@ +const TokenizationService = require('../../src/services/tokenization'); +const MandateService = require('../../src/services/mandate'); +const jwt = require('jsonwebtoken'); + +describe('TokenizationService', () => { + let tokenizationService; + const signingKey = 'test-secret'; + const apiKey = 'test-key'; + + beforeEach(() => { + tokenizationService = new TokenizationService({ + apiKey, + mandateConfig: { signingKey }, + strictMandateMode: true + }); + process.env.NODE_ENV = 'test'; + }); + + describe('signWithToken - Zero Trust Validation', () => { + it('should throw error if mandate is required but not provided in strict mode', async () => { + await expect(tokenizationService.signWithToken('token_123', 'data')) + .rejects.toThrow('Zero Trust Validation Failed: Mandate required for signing in strict mode'); + }); + + it('should throw error if amount exceeds mandate budget', async () => { + const mandateService = new MandateService({ signingKey }); + const mandate = await mandateService.issueIntentMandate({ + userDid: 'did:key:user', + agentDid: 'did:key:agent', + maxBudget: 100 + }); + + await expect(tokenizationService.signWithToken('token_123', 'data', mandate, { amount: 150 })) + .rejects.toThrow('Zero Trust Validation Failed: Amount 150 exceeds mandate budget of 100'); + }); + + it('should throw error if merchant is not authorized', async () => { + const mandateService = new MandateService({ signingKey }); + const mandate = await mandateService.issueIntentMandate({ + userDid: 'did:key:user', + agentDid: 'did:key:agent', + maxBudget: 1000, + allowedMerchants: ['did:key:merchant_a'] + }); + + await expect(tokenizationService.signWithToken('token_123', 'data', mandate, { merchant: 'did:key:merchant_b' })) + .rejects.toThrow('Zero Trust Validation Failed: Merchant did:key:merchant_b not authorized by mandate'); + }); + + it('should throw error if mandate has expired', async () => { + const mandateService = new MandateService({ signingKey }); + const mandate = await mandateService.issueIntentMandate({ + userDid: 'did:key:user', + agentDid: 'did:key:agent', + maxBudget: 1000, + expiration: Math.floor(Date.now() / 1000) - 100 // Expired 100s ago + }); + + await expect(tokenizationService.signWithToken('token_123', 'data', mandate)) + .rejects.toThrow('Zero Trust Validation Failed: Mandate has expired'); + }); + + it('should successfully sign if mandate is valid', async () => { + const mandateService = new MandateService({ signingKey }); + const mandate = await mandateService.issueIntentMandate({ + userDid: 'did:key:user', + agentDid: 'did:key:agent', + maxBudget: 1000, + allowedMerchants: ['did:key:merchant_a'] + }); + + const signature = await tokenizationService.signWithToken( + 'token_123', + 'data', + mandate, + { amount: 500, merchant: 'did:key:merchant_a' } + ); + + expect(signature).toContain('0x_mock_signature'); + expect(signature).toContain('validated_by_mandate'); + }); + }); +}); diff --git a/tests/unit/web3.spec.js b/tests/unit/web3.spec.js new file mode 100644 index 0000000..0e22e44 --- /dev/null +++ b/tests/unit/web3.spec.js @@ -0,0 +1,91 @@ +const Web3Service = require('../../src/services/web3'); +const TokenizationService = require('../../src/services/tokenization'); +const MandateService = require('../../src/services/mandate'); + +describe('Web3Service', () => { + let web3Service; + let tokenizationService; + const signingKey = 'test-secret'; + const apiKey = 'test-key'; + + beforeEach(() => { + tokenizationService = new TokenizationService({ + apiKey, + mandateConfig: { signingKey }, + strictMandateMode: true + }); + web3Service = new Web3Service(tokenizationService); + process.env.NODE_ENV = 'test'; + }); + + describe('executeX402Settlement', () => { + it('should successfully execute settlement with a valid mandate', async () => { + const mandateService = new MandateService({ signingKey }); + const mandate = await mandateService.issueIntentMandate({ + userDid: 'did:key:user', + agentDid: 'did:key:agent', + maxBudget: 1000 + }); + + const result = await web3Service.executeX402Settlement({ + keyTokenId: 'token_123', + to: '0xRecipient', + amount: 500, + stablecoin: 'USDC', + mandate + }); + + expect(result.status).toBe('finalized'); + expect(result.amount).toBe(500); + expect(result.stablecoin).toBe('USDC'); + }); + + it('should fail if mandate budget is exceeded', async () => { + const mandateService = new MandateService({ signingKey }); + const mandate = await mandateService.issueIntentMandate({ + userDid: 'did:key:user', + agentDid: 'did:key:agent', + maxBudget: 100 + }); + + await expect(web3Service.executeX402Settlement({ + keyTokenId: 'token_123', + to: '0xRecipient', + amount: 500, + stablecoin: 'USDC', + mandate + })).rejects.toThrow('Zero Trust Validation Failed: Amount 500 exceeds mandate budget of 100'); + }); + + it('should fail if no mandate is provided in strict mode', async () => { + await expect(web3Service.executeX402Settlement({ + keyTokenId: 'token_123', + to: '0xRecipient', + amount: 500, + stablecoin: 'USDC' + })).rejects.toThrow('Zero Trust Validation Failed: Mandate required for signing in strict mode'); + }); + }); + + describe('sendTransaction', () => { + it('should successfully sign and "broadcast" transaction with valid mandate', async () => { + const mandateService = new MandateService({ signingKey }); + const mandate = await mandateService.issueIntentMandate({ + userDid: 'did:key:user', + agentDid: 'did:key:agent', + maxBudget: 1000 + }); + + const result = await web3Service.sendTransaction({ + keyTokenId: 'token_123', + to: '0xRecipient', + value: '0.1', + mandate, + context: { amount: 500 } // Simulation context + }); + + expect(result.status).toBe('pending'); + expect(result.signedData).toBeDefined(); + }); + }); +});