diff --git a/docs/tools/skills-config.md b/docs/tools/skills-config.md index 6cdf71a07b4b..174ee5d7ecca 100644 --- a/docs/tools/skills-config.md +++ b/docs/tools/skills-config.md @@ -180,7 +180,8 @@ write one JSON object on stdout with an `allow`, `warn`, or `block` decision. `findings` array. Each finding requires non-empty string `ruleId` and `message` fields plus a `severity` of `info`, `warn`, or `critical`. Optional `file` and `evidence` values must be non-empty strings; a finite numeric `line` is rounded -down and clamped to at least 1. Malformed finding entries are ignored, and +down and clamped to the safe-integer range from 1 through `Number.MAX_SAFE_INTEGER`. +Malformed finding entries are ignored, and invalid optional fields are omitted. A non-array `findings` value is treated as absent. Operator-facing reason and finding text are limited to 1,000 characters. OpenClaw retains at most 100 normalized findings for display. Only a `warn` diff --git a/src/security/install-policy-response.ts b/src/security/install-policy-response.ts index f16edbe56c49..c6443ac67e92 100644 --- a/src/security/install-policy-response.ts +++ b/src/security/install-policy-response.ts @@ -52,7 +52,7 @@ const installPolicyFindingSchema = z line: z .number() .finite() - .transform((value) => Math.max(1, Math.floor(value))) + .transform((value) => Math.min(Number.MAX_SAFE_INTEGER, Math.max(1, Math.floor(value)))) .optional() .catch(undefined), evidence: optionalFindingTextSchema, diff --git a/src/security/install-policy.test.ts b/src/security/install-policy.test.ts index a81698e61f03..daa8597576b6 100644 --- a/src/security/install-policy.test.ts +++ b/src/security/install-policy.test.ts @@ -451,6 +451,31 @@ describe("runInstallPolicy", () => { expect(debugLogs.filter((message) => message.endsWith(": warned"))).toHaveLength(1); }); + it("normalizes warning finding lines to positive safe integers", async () => { + const result = await runInstallPolicy({ + config: configWithPolicy(scriptPath, { + POLICY_RESPONSE: JSON.stringify({ + protocolVersion: 1, + decision: "warn", + reason: "review line normalization", + findings: [-2, 12.9, 1e100].map((line, index) => ({ + ruleId: `line-${String(index)}`, + severity: "warn", + message: "Review line", + line, + })), + }), + }), + request: baseRequest(sourceDir), + }); + + expect(result?.findings?.map((finding) => finding.line)).toEqual([ + 1, + 12, + Number.MAX_SAFE_INTEGER, + ]); + }); + it("fingerprints warning reason changes beyond the display limit", async () => { const sharedPrefix = "r".repeat(1000); const runWarning = async (reason: string) =>