Skip to content

Commit 21f764f

Browse files
Handle non-finite policy risk scores
1 parent 8a86d14 commit 21f764f

2 files changed

Lines changed: 37 additions & 1 deletion

File tree

packages/account-core/src/index.test.ts

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,12 @@ describe("account-core", () => {
2424
expect(riskBandForScore(score)).toBe("high");
2525
});
2626

27+
it("treats non-finite risk scores as critical", () => {
28+
expect(riskBandForScore(Number.NaN)).toBe("critical");
29+
expect(riskBandForScore(Number.POSITIVE_INFINITY)).toBe("critical");
30+
expect(riskBandForScore(Number.NEGATIVE_INFINITY)).toBe("critical");
31+
});
32+
2733
it("requires approval for gated actions with matching grants", () => {
2834
const result = evaluateAccountPolicy({
2935
action: "email:send",
@@ -41,6 +47,25 @@ describe("account-core", () => {
4147
expect(result.decision).toBe("approval_required");
4248
});
4349

50+
it.each([Number.NaN, Number.POSITIVE_INFINITY, Number.NEGATIVE_INFINITY])("fails closed for non-finite policy risk score %s", (riskScore) => {
51+
const result = evaluateAccountPolicy({
52+
action: "social:profile:read",
53+
riskScore,
54+
principal: { type: "agent", id: "reader-agent" },
55+
grant: {
56+
id: "grant_read",
57+
accountId: "account_1",
58+
principal: { type: "agent", id: "reader-agent" },
59+
permissions: ["social:profile:read"],
60+
policy: [],
61+
createdAt: new Date(0).toISOString()
62+
}
63+
});
64+
65+
expect(result.riskScore).toBe(1);
66+
expect(result.decision).toBe("deny");
67+
});
68+
4469
it.each([
4570
{ principal: { type: "agent" as const, id: "trusted-agent", trusted: true }, expected: "allow" },
4671
{ principal: { type: "agent" as const, id: "untrusted-agent", trusted: false }, expected: "approval_required" },

packages/account-core/src/policy.ts

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,9 @@ const WRITE_ACTIONS = new Set([
1414
]);
1515

1616
export function riskBandForScore(score: number): LogicSrcRiskBand {
17+
if (!Number.isFinite(score)) {
18+
return "critical";
19+
}
1720
if (score >= 0.75) {
1821
return "critical";
1922
}
@@ -50,8 +53,16 @@ export function scoreAccountActionRisk(input: {
5053
return Math.min(1, Number(score.toFixed(2)));
5154
}
5255

56+
function normalizeRiskScore(score: number): number {
57+
if (!Number.isFinite(score)) {
58+
return 1;
59+
}
60+
61+
return Math.min(1, Math.max(0, score));
62+
}
63+
5364
export function evaluateAccountPolicy(input: LogicSrcPolicyEvaluationInput): LogicSrcPolicyEvaluationResult {
54-
const riskScore = Math.min(1, Math.max(0, input.riskScore ?? scoreAccountActionRisk({ action: input.action })));
65+
const riskScore = normalizeRiskScore(input.riskScore ?? scoreAccountActionRisk({ action: input.action }));
5566
const grantActive = input.grant && !input.grant.revokedAt && (!input.grant.expiresAt || Date.parse(input.grant.expiresAt) > Date.now());
5667
const hasPermission = Boolean(grantActive && input.grant?.permissions.includes(input.action));
5768

0 commit comments

Comments
 (0)