Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion scripts/ocp-cli.js
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,7 @@ program.command('x402:settle')
return;
}
} else if (process.env.STRICT_MANDATE_MODE === 'true') {
console.error(`Error: Mandate required for x402 settlement in STRICT_MANDATE_MODE.`);
console.error(`Zero Trust Validation Failed: Mandate required for x402 settlement in STRICT_MANDATE_MODE.`);
return;
}

Expand Down
26 changes: 24 additions & 2 deletions src/services/a2aService.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

const MandateService = require("./mandate");
const logger = require("../utils/logger");
const MandateService = require("./mandate");

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Duplicate MandateService module import

High Severity

A duplicate const MandateService declaration in a2aService.js creates a JavaScript syntax error. This prevents the file from parsing, so the A2A service cannot load or start.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 26491c4. Configure here.


class A2AService {
constructor(walletService, db, config = {}) {
Expand Down Expand Up @@ -67,10 +68,22 @@ class A2AService {
);
}

// 2. Policy Checks (Sender)
// 2. Zero Trust Mandate Validation
if (mandate) {
await this.mandateService.verifyMandate(mandate, {
amount,
recipient: toAgentId,
});

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mandate verified twice per transfer

Medium Severity

When a mandate is present, executeTransfer calls verifyMandate before agent lookup without transaction context, then calls it again with amount and recipient. The first pass cannot enforce budget or whitelist; the second pass is the one that matters, so the early call is redundant and the outer try/catch can nest Zero Trust error prefixes.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 26491c4. Configure here.

} else if (this.strictMandateMode) {
throw new Error(
"Zero Trust Validation Failed: Mandate required for A2A transfer in strict mode",
);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unreachable strict mode branch

Low Severity

The else if (this.strictMandateMode) block after contextual mandate validation duplicates the same strict-mode rejection already thrown when no mandate is supplied at the start of executeTransfer. That branch cannot run and adds maintenance noise.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 26491c4. Configure here.


// 3. Policy Checks (Sender)
await this._validateAgentPolicy(fromAgent, toAgentId, amount);

// 3. Execute Wallet Transfer
// 4. Execute Wallet Transfer
const transferResult = await this.walletService.transfer({
fromWalletId: fromAgent.walletId,
toWalletId: toAgent.walletId,
Expand Down Expand Up @@ -136,6 +149,15 @@ class A2AService {
*/
_handleError(method, error) {
logger.error(`A2AService.${method} error:`, error);

// Normalize Zero Trust errors
if (
error.message &&
error.message.includes("Zero Trust Validation Failed:")
) {
return error;
}

return error instanceof Error ? error : new Error(error);
}
}
Expand Down
10 changes: 9 additions & 1 deletion src/services/tokenization.js
Original file line number Diff line number Diff line change
Expand Up @@ -382,6 +382,14 @@ class TokenizationService {
_handleError(method, error) {
logger.error(`TokenizationService.${method} error:`, error);

// Normalize Zero Trust errors
if (
error.message &&
error.message.includes("Zero Trust Validation Failed:")
) {
return error;
}

if (error.response) {
const { status, data } = error.response;
return new Error(
Expand All @@ -393,7 +401,7 @@ class TokenizationService {
return new Error("Tokenization service unavailable");
}

return error;
return error instanceof Error ? error : new Error(error);
}
}

Expand Down
65 changes: 65 additions & 0 deletions tests/unit/mandate_context.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
const MandateService = require("../../src/services/mandate");
const jwt = require("jsonwebtoken");

describe("MandateService - Context Validation", () => {
let mandateService;
const signingKey = "test-secret";

beforeEach(() => {
mandateService = new MandateService({ signingKey });
});

it("should validate amount against intent mandate budget", async () => {
const mandate = await mandateService.issueIntentMandate({
userDid: "did:key:user",
agentDid: "did:key:agent",
maxBudget: 100,
});

// Valid amount
await expect(
mandateService.verifyMandate(mandate, { amount: 50 }),
).resolves.toBeDefined();

// Invalid amount
await expect(
mandateService.verifyMandate(mandate, { amount: 150 }),
).rejects.toThrow(
"Zero Trust Validation Failed: Amount 150 exceeds mandate budget of 100",
);
});

it("should validate recipient against allowed_merchants whitelist", async () => {
const mandate = await mandateService.issueIntentMandate({
userDid: "did:key:user",
agentDid: "did:key:agent",
maxBudget: 100,
allowedMerchants: ["did:key:merchant-1"],
});

// Authorized merchant
await expect(
mandateService.verifyMandate(mandate, { recipient: "did:key:merchant-1" }),
).resolves.toBeDefined();

// Unauthorized merchant
await expect(
mandateService.verifyMandate(mandate, { recipient: "did:key:merchant-2" }),
).rejects.toThrow(
"Zero Trust Validation Failed: Merchant did:key:merchant-2 not authorized by mandate",
);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Whitelist test expects wrong error

Medium Severity

The new whitelist test expects Merchant … not authorized by mandate, but MandateService.verifyMandate throws Recipient … not authorized by mandate for the same failure. The assertion does not match production behavior, so this test fails despite correct enforcement.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 26491c4. Configure here.

});

it("should handle expired tokens with correct prefix", async () => {
const mandate = jwt.sign(
{
exp: Math.floor(Date.now() / 1000) - 60,
},
signingKey,
);

await expect(mandateService.verifyMandate(mandate)).rejects.toThrow(
"Zero Trust Validation Failed: Mandate has expired",
);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Expiry test expects wrong message

Medium Severity

The expired-mandate test expects Zero Trust Validation Failed: Mandate has expired, but verifyMandate wraps JWT verification failures as Zero Trust Validation Failed: Mandate verification failed: … (for example jwt expired). The test will fail with the current implementation.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 26491c4. Configure here.

});
});
Loading