Skip to content

Commit 5b9c8ca

Browse files
authored
Merge pull request #13 from dcplatforms/feature/zero-trust-mandate-enforcement-4461897649862775355
Implement Zero Trust Mandate Enforcement and x402 Fiduciary Validation
2 parents d2b3325 + 757fa43 commit 5b9c8ca

8 files changed

Lines changed: 248 additions & 26 deletions

File tree

agent_output.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Agent created: TestAgent
2+
ID: agent_6382d61c
3+
DID: did:key:06ffeafa6f71c7bd83ad21ce4fb56e2c

mandate_output.txt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
Intent Mandate issued for agent agent_6382d61c
2+
Mandate ID: mandate_be7dc63fcbb70f38
3+
Budget: 100 USD
4+
Saved to: src/mandates/mandate_be7dc63fcbb70f38.jwt

scripts/ocp-cli.js

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -156,7 +156,30 @@ program.command('x402:settle')
156156
return;
157157
}
158158

159-
// Simulation of x402 settlement
159+
// Simulation of x402 settlement with fiduciary validation
160+
const signingKey = process.env.MANDATE_SIGNING_KEY || 'default-secret-key';
161+
const mandateService = new MandateService({ signingKey });
162+
163+
try {
164+
const decodedMandate = await mandateService.verifyMandate(mandateToken);
165+
const amountNum = parseFloat(amount);
166+
167+
// Validate budget
168+
if (decodedMandate.max_budget && amountNum > decodedMandate.max_budget.value) {
169+
console.error(`Fiduciary Validation Failed: Amount ${amountNum} exceeds mandate budget of ${decodedMandate.max_budget.value} ${decodedMandate.max_budget.currency}`);
170+
return;
171+
}
172+
173+
// Validate expiration
174+
if (decodedMandate.exp < Math.floor(Date.now() / 1000)) {
175+
console.error('Fiduciary Validation Failed: Mandate has expired');
176+
return;
177+
}
178+
} catch (error) {
179+
console.error(`Fiduciary Validation Failed: ${error.message}`);
180+
return;
181+
}
182+
160183
const settlementId = `x402_${crypto.randomBytes(8).toString('hex')}`;
161184
const txHash = `0x${crypto.randomBytes(32).toString('hex')}`;
162185

src/agents/agent_6382d61c.json

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"id": "agent_6382d61c",
3+
"name": "TestAgent",
4+
"did": "did:key:06ffeafa6f71c7bd83ad21ce4fb56e2c",
5+
"wallet_address": "0x9fab4a57fb46aafd8ef02cbdaa192bfa14aa10e7",
6+
"created_at": "2026-04-22T19:23:42.590Z"
7+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJkaWQ6d2ViOm9wZW4tY29tbWVyY2UtcHJvdG9jb2wuaW8iLCJzdWIiOiJkaWQ6a2V5OmFnZW50XzYzODJkNjFjIiwidXNlcl9kaWQiOiJkaWQ6a2V5OnVzZXItbG9jYWwiLCJhZ2VudF9kaWQiOiJkaWQ6a2V5OmFnZW50XzYzODJkNjFjIiwibWFuZGF0ZV9pZCI6Im1hbmRhdGVfYmU3ZGM2M2ZjYmI3MGYzOCIsIm1heF9idWRnZXQiOnsidmFsdWUiOjEwMCwiY3VycmVuY3kiOiJVU0QifSwiZXhwIjoxNzc2OTcyMjIyLCJwdXJwb3NlX2NvZGUiOiJDTElfSVNTVUVEIiwiYWxsb3dlZF9tZXJjaGFudHMiOltdLCJpYXQiOjE3NzY4ODU4MjIsInR5cGUiOiJpbnRlbnRfbWFuZGF0ZSJ9.gDTkeD6IbOAQ04Yi0mX-lJLwNru3y4GfXMz4Rc999kQ

src/services/tokenization.js

Lines changed: 35 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ class TokenizationService {
1515
this.baseURL = config.baseURL || process.env.TOKENIZATION_BASE_URL || 'https://api.basistheory.com';
1616
this.tenantId = config.tenantId || process.env.TOKENIZATION_TENANT_ID;
1717
this.timeout = config.timeout || 30000;
18+
this.strictMandateMode = config.strictMandateMode !== undefined ?
19+
config.strictMandateMode : (process.env.STRICT_MANDATE_MODE === 'true');
1820

1921
if (!this.apiKey) {
2022
throw new Error('Tokenization API key is required');
@@ -318,38 +320,46 @@ class TokenizationService {
318320
* @returns {Promise<string>} Signature
319321
*/
320322
async signWithToken(tokenId, dataToSign, mandate, context = {}) {
321-
try {
322-
// Zero Trust Validation: Verify mandate if provided
323-
if (mandate) {
324-
const decodedMandate = await this.mandateService.verifyMandate(mandate);
325-
326-
// Validate budget if context amount is provided
327-
if (context.amount) {
328-
// Check Intent Mandate budget
329-
if (decodedMandate.max_budget && context.amount > decodedMandate.max_budget.value) {
330-
throw new Error(`Zero Trust Validation Failed: Amount ${context.amount} exceeds mandate budget of ${decodedMandate.max_budget.value}`);
331-
}
332-
// Check Cart Mandate total price
333-
if (decodedMandate.total_price && context.amount !== decodedMandate.total_price) {
334-
throw new Error(`Zero Trust Validation Failed: Amount ${context.amount} does not match cart mandate total of ${decodedMandate.total_price}`);
335-
}
323+
// Zero Trust Validation: Verify mandate BEFORE entering try/catch simulation block
324+
if (mandate) {
325+
let decodedMandate;
326+
try {
327+
decodedMandate = await this.mandateService.verifyMandate(mandate);
328+
} catch (error) {
329+
if (error.message.includes('jwt expired')) {
330+
throw new Error('Zero Trust Validation Failed: Mandate has expired');
336331
}
332+
throw new Error(`Zero Trust Validation Failed: ${error.message}`);
333+
}
337334

338-
// Validate merchant if context merchant is provided
339-
if (context.merchant && decodedMandate.allowed_merchants?.length > 0) {
340-
if (!decodedMandate.allowed_merchants.includes(context.merchant)) {
341-
throw new Error(`Zero Trust Validation Failed: Merchant ${context.merchant} not authorized by mandate`);
342-
}
335+
// Validate budget if context amount is provided
336+
if (context.amount) {
337+
// Check Intent Mandate budget
338+
if (decodedMandate.max_budget && context.amount > decodedMandate.max_budget.value) {
339+
throw new Error(`Zero Trust Validation Failed: Amount ${context.amount} exceeds mandate budget of ${decodedMandate.max_budget.value}`);
343340
}
341+
// Check Cart Mandate total price
342+
if (decodedMandate.total_price && context.amount !== decodedMandate.total_price) {
343+
throw new Error(`Zero Trust Validation Failed: Amount ${context.amount} does not match cart mandate total of ${decodedMandate.total_price}`);
344+
}
345+
}
344346

345-
// Validate expiration
346-
if (decodedMandate.exp < Math.floor(Date.now() / 1000)) {
347-
throw new Error('Zero Trust Validation Failed: Mandate has expired');
347+
// Validate merchant if context merchant is provided
348+
if (context.merchant && decodedMandate.allowed_merchants?.length > 0) {
349+
if (!decodedMandate.allowed_merchants.includes(context.merchant)) {
350+
throw new Error(`Zero Trust Validation Failed: Merchant ${context.merchant} not authorized by mandate`);
348351
}
349-
} else if (process.env.STRICT_MANDATE_MODE === 'true') {
350-
throw new Error('Zero Trust Validation Failed: Mandate required for signing in strict mode');
351352
}
352353

354+
// Validate expiration
355+
if (decodedMandate.exp < Math.floor(Date.now() / 1000)) {
356+
throw new Error('Zero Trust Validation Failed: Mandate has expired');
357+
}
358+
} else if (this.strictMandateMode) {
359+
throw new Error('Zero Trust Validation Failed: Mandate required for signing in strict mode');
360+
}
361+
362+
try {
353363
// In a real implementation, this would call a Basis Theory Reactor
354364
// providing the tokenId. The Reactor would securely retrieve the
355365
// secret and sign the data without exposing the key.

tests/unit/tokenization.spec.js

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
const TokenizationService = require('../../src/services/tokenization');
2+
const MandateService = require('../../src/services/mandate');
3+
const jwt = require('jsonwebtoken');
4+
5+
describe('TokenizationService', () => {
6+
let tokenizationService;
7+
const signingKey = 'test-secret';
8+
const apiKey = 'test-key';
9+
10+
beforeEach(() => {
11+
tokenizationService = new TokenizationService({
12+
apiKey,
13+
mandateConfig: { signingKey },
14+
strictMandateMode: true
15+
});
16+
process.env.NODE_ENV = 'test';
17+
});
18+
19+
describe('signWithToken - Zero Trust Validation', () => {
20+
it('should throw error if mandate is required but not provided in strict mode', async () => {
21+
await expect(tokenizationService.signWithToken('token_123', 'data'))
22+
.rejects.toThrow('Zero Trust Validation Failed: Mandate required for signing in strict mode');
23+
});
24+
25+
it('should throw error if amount exceeds mandate budget', async () => {
26+
const mandateService = new MandateService({ signingKey });
27+
const mandate = await mandateService.issueIntentMandate({
28+
userDid: 'did:key:user',
29+
agentDid: 'did:key:agent',
30+
maxBudget: 100
31+
});
32+
33+
await expect(tokenizationService.signWithToken('token_123', 'data', mandate, { amount: 150 }))
34+
.rejects.toThrow('Zero Trust Validation Failed: Amount 150 exceeds mandate budget of 100');
35+
});
36+
37+
it('should throw error if merchant is not authorized', async () => {
38+
const mandateService = new MandateService({ signingKey });
39+
const mandate = await mandateService.issueIntentMandate({
40+
userDid: 'did:key:user',
41+
agentDid: 'did:key:agent',
42+
maxBudget: 1000,
43+
allowedMerchants: ['did:key:merchant_a']
44+
});
45+
46+
await expect(tokenizationService.signWithToken('token_123', 'data', mandate, { merchant: 'did:key:merchant_b' }))
47+
.rejects.toThrow('Zero Trust Validation Failed: Merchant did:key:merchant_b not authorized by mandate');
48+
});
49+
50+
it('should throw error if mandate has expired', async () => {
51+
const mandateService = new MandateService({ signingKey });
52+
const mandate = await mandateService.issueIntentMandate({
53+
userDid: 'did:key:user',
54+
agentDid: 'did:key:agent',
55+
maxBudget: 1000,
56+
expiration: Math.floor(Date.now() / 1000) - 100 // Expired 100s ago
57+
});
58+
59+
await expect(tokenizationService.signWithToken('token_123', 'data', mandate))
60+
.rejects.toThrow('Zero Trust Validation Failed: Mandate has expired');
61+
});
62+
63+
it('should successfully sign if mandate is valid', async () => {
64+
const mandateService = new MandateService({ signingKey });
65+
const mandate = await mandateService.issueIntentMandate({
66+
userDid: 'did:key:user',
67+
agentDid: 'did:key:agent',
68+
maxBudget: 1000,
69+
allowedMerchants: ['did:key:merchant_a']
70+
});
71+
72+
const signature = await tokenizationService.signWithToken(
73+
'token_123',
74+
'data',
75+
mandate,
76+
{ amount: 500, merchant: 'did:key:merchant_a' }
77+
);
78+
79+
expect(signature).toContain('0x_mock_signature');
80+
expect(signature).toContain('validated_by_mandate');
81+
});
82+
});
83+
});

tests/unit/web3.spec.js

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
const Web3Service = require('../../src/services/web3');
2+
const TokenizationService = require('../../src/services/tokenization');
3+
const MandateService = require('../../src/services/mandate');
4+
5+
describe('Web3Service', () => {
6+
let web3Service;
7+
let tokenizationService;
8+
const signingKey = 'test-secret';
9+
const apiKey = 'test-key';
10+
11+
beforeEach(() => {
12+
tokenizationService = new TokenizationService({
13+
apiKey,
14+
mandateConfig: { signingKey },
15+
strictMandateMode: true
16+
});
17+
web3Service = new Web3Service(tokenizationService);
18+
process.env.NODE_ENV = 'test';
19+
});
20+
21+
describe('executeX402Settlement', () => {
22+
it('should successfully execute settlement with a valid mandate', async () => {
23+
const mandateService = new MandateService({ signingKey });
24+
const mandate = await mandateService.issueIntentMandate({
25+
userDid: 'did:key:user',
26+
agentDid: 'did:key:agent',
27+
maxBudget: 1000
28+
});
29+
30+
const result = await web3Service.executeX402Settlement({
31+
keyTokenId: 'token_123',
32+
to: '0xRecipient',
33+
amount: 500,
34+
stablecoin: 'USDC',
35+
mandate
36+
});
37+
38+
expect(result.status).toBe('finalized');
39+
expect(result.amount).toBe(500);
40+
expect(result.stablecoin).toBe('USDC');
41+
});
42+
43+
it('should fail if mandate budget is exceeded', async () => {
44+
const mandateService = new MandateService({ signingKey });
45+
const mandate = await mandateService.issueIntentMandate({
46+
userDid: 'did:key:user',
47+
agentDid: 'did:key:agent',
48+
maxBudget: 100
49+
});
50+
51+
await expect(web3Service.executeX402Settlement({
52+
keyTokenId: 'token_123',
53+
to: '0xRecipient',
54+
amount: 500,
55+
stablecoin: 'USDC',
56+
mandate
57+
})).rejects.toThrow('Zero Trust Validation Failed: Amount 500 exceeds mandate budget of 100');
58+
});
59+
60+
it('should fail if no mandate is provided in strict mode', async () => {
61+
await expect(web3Service.executeX402Settlement({
62+
keyTokenId: 'token_123',
63+
to: '0xRecipient',
64+
amount: 500,
65+
stablecoin: 'USDC'
66+
})).rejects.toThrow('Zero Trust Validation Failed: Mandate required for signing in strict mode');
67+
});
68+
});
69+
70+
describe('sendTransaction', () => {
71+
it('should successfully sign and "broadcast" transaction with valid mandate', async () => {
72+
const mandateService = new MandateService({ signingKey });
73+
const mandate = await mandateService.issueIntentMandate({
74+
userDid: 'did:key:user',
75+
agentDid: 'did:key:agent',
76+
maxBudget: 1000
77+
});
78+
79+
const result = await web3Service.sendTransaction({
80+
keyTokenId: 'token_123',
81+
to: '0xRecipient',
82+
value: '0.1',
83+
mandate,
84+
context: { amount: 500 } // Simulation context
85+
});
86+
87+
expect(result.status).toBe('pending');
88+
expect(result.signedData).toBeDefined();
89+
});
90+
});
91+
});

0 commit comments

Comments
 (0)