From 21f764f06d17e4f8317c1f8700fa7628252ff421 Mon Sep 17 00:00:00 2001 From: phucnguyen1707 Date: Mon, 15 Jun 2026 12:39:18 +0700 Subject: [PATCH] Handle non-finite policy risk scores --- packages/account-core/src/index.test.ts | 25 +++++++++++++++++++++++++ packages/account-core/src/policy.ts | 13 ++++++++++++- 2 files changed, 37 insertions(+), 1 deletion(-) diff --git a/packages/account-core/src/index.test.ts b/packages/account-core/src/index.test.ts index f3fd66b..c021b87 100644 --- a/packages/account-core/src/index.test.ts +++ b/packages/account-core/src/index.test.ts @@ -24,6 +24,12 @@ describe("account-core", () => { expect(riskBandForScore(score)).toBe("high"); }); + it("treats non-finite risk scores as critical", () => { + expect(riskBandForScore(Number.NaN)).toBe("critical"); + expect(riskBandForScore(Number.POSITIVE_INFINITY)).toBe("critical"); + expect(riskBandForScore(Number.NEGATIVE_INFINITY)).toBe("critical"); + }); + it("requires approval for gated actions with matching grants", () => { const result = evaluateAccountPolicy({ action: "email:send", @@ -41,6 +47,25 @@ describe("account-core", () => { expect(result.decision).toBe("approval_required"); }); + it.each([Number.NaN, Number.POSITIVE_INFINITY, Number.NEGATIVE_INFINITY])("fails closed for non-finite policy risk score %s", (riskScore) => { + const result = evaluateAccountPolicy({ + action: "social:profile:read", + riskScore, + principal: { type: "agent", id: "reader-agent" }, + grant: { + id: "grant_read", + accountId: "account_1", + principal: { type: "agent", id: "reader-agent" }, + permissions: ["social:profile:read"], + policy: [], + createdAt: new Date(0).toISOString() + } + }); + + expect(result.riskScore).toBe(1); + expect(result.decision).toBe("deny"); + }); + it.each([ { principal: { type: "agent" as const, id: "trusted-agent", trusted: true }, expected: "allow" }, { principal: { type: "agent" as const, id: "untrusted-agent", trusted: false }, expected: "approval_required" }, diff --git a/packages/account-core/src/policy.ts b/packages/account-core/src/policy.ts index ec31ac7..44f8257 100644 --- a/packages/account-core/src/policy.ts +++ b/packages/account-core/src/policy.ts @@ -14,6 +14,9 @@ const WRITE_ACTIONS = new Set([ ]); export function riskBandForScore(score: number): LogicSrcRiskBand { + if (!Number.isFinite(score)) { + return "critical"; + } if (score >= 0.75) { return "critical"; } @@ -50,8 +53,16 @@ export function scoreAccountActionRisk(input: { return Math.min(1, Number(score.toFixed(2))); } +function normalizeRiskScore(score: number): number { + if (!Number.isFinite(score)) { + return 1; + } + + return Math.min(1, Math.max(0, score)); +} + export function evaluateAccountPolicy(input: LogicSrcPolicyEvaluationInput): LogicSrcPolicyEvaluationResult { - const riskScore = Math.min(1, Math.max(0, input.riskScore ?? scoreAccountActionRisk({ action: input.action }))); + const riskScore = normalizeRiskScore(input.riskScore ?? scoreAccountActionRisk({ action: input.action })); const grantActive = input.grant && !input.grant.revokedAt && (!input.grant.expiresAt || Date.parse(input.grant.expiresAt) > Date.now()); const hasPermission = Boolean(grantActive && input.grant?.permissions.includes(input.action));