{"record":{"id":"3a1f4605c57ad5f0","repo":"ruvnet/ruflo","slug":"policy-canonicalization-rejects-non-finite-numbers","errorCode":null,"errorMessage":"Policy canonicalization rejects non-finite numbers","messagePattern":"Policy canonicalization rejects non-finite numbers","errorType":"exception","errorClass":"TypeError","httpStatus":null,"severity":"error","filePath":"v3/@claude-flow/security/src/policy/canonical.ts","lineNumber":14,"sourceCode":"import { createHash, createHmac, timingSafeEqual } from 'node:crypto';\n\nfunction normalize(value: unknown): unknown {\n  if (Array.isArray(value)) return value.map(normalize);\n  if (value && typeof value === 'object') {\n    return Object.fromEntries(\n      Object.entries(value as Record<string, unknown>)\n        .filter(([, item]) => item !== undefined)\n        .sort(([a], [b]) => a.localeCompare(b))\n        .map(([key, item]) => [key, normalize(item)]),\n    );\n  }\n  if (typeof value === 'number' && !Number.isFinite(value)) {\n    throw new TypeError('Policy canonicalization rejects non-finite numbers');\n  }\n  return value;\n}\n\nexport function canonicalizePolicy(value: unknown): string {\n  return JSON.stringify(normalize(value));\n}\n\nexport function policyHash(value: unknown): string {\n  return `sha256:${createHash('sha256').update(canonicalizePolicy(value)).digest('hex')}`;\n}\n\nexport function signPolicyHash(hash: string, key: string | Buffer): string {\n  return createHmac('sha256', key).update(hash).digest('base64url');\n}\n\nexport function verifyPolicySignature(hash: string, signature: string, key: string | Buffer): boolean {\n  const expected = Buffer.from(signPolicyHash(hash, key));","sourceCodeStart":1,"sourceCodeEnd":32,"githubUrl":"https://github.com/ruvnet/ruflo/blob/fa13ee4ad60ac2090b1480656eb233521790d640/v3/@claude-flow/security/src/policy/canonical.ts#L1-L32","documentation":"canonicalizePolicy() recursively normalizes a policy object (dropping undefined entries, sorting keys) to produce a deterministic JSON string used for policy hashing. While walking values it throws TypeError on any NaN/Infinity/-Infinity, because JSON.stringify would silently coerce those to null and two different policies could then hash identically.","triggerScenarios":"policyHash({ limit: NaN }) after Number('abc') or parseFloat of a non-numeric env value; { cost: Infinity } from dividing by zero; any nested rule object containing a computed number that degenerated to NaN.","commonSituations":"Numeric limits parsed from environment variables or CLI flags that are unset or misspelled; arithmetic on undefined values (undefined * 2 === NaN); JSON revivers that produce non-finite results.","solutions":["Find the offending key: log JSON.stringify of the policy with a replacer that flags non-finite numbers before hashing.","Fix the upstream computation so it yields a finite number, or omit the key entirely (undefined entries are filtered out during normalization).","Sanitize numeric config at load time: coerce NaN/Infinity to undefined and validate ranges."],"exampleFix":"// before\nconst maxCost = Number(process.env.MAX_COST_USD); // NaN when unset\nconst hash = policyHash({ rules: [{ constraints: { maxCostUsd: maxCost } }] });\n\n// after\nconst raw = Number(process.env.MAX_COST_USD);\nconst maxCost = Number.isFinite(raw) ? raw : undefined;\nconst hash = policyHash({ rules: [{ constraints: { maxCostUsd: maxCost } }] });","handlingStrategy":"validation","validationCode":"function assertFiniteTree(value: unknown): void {\n  if (typeof value === 'number' && !Number.isFinite(value)) {\n    throw new TypeError(`non-finite number at path: ${pathOf(value)}`);\n  }\n  if (Array.isArray(value)) return value.forEach(assertFiniteTree);\n  if (value && typeof value === 'object') {\n    for (const v of Object.values(value)) assertFiniteTree(v);\n  }\n}\nassertFiniteTree(policy); // before policyHash(policy)","typeGuard":"function isFiniteNumber(v: unknown): v is number {\n  return typeof v === 'number' && Number.isFinite(v);\n}","tryCatchPattern":"try {\n  return policyHash(policy);\n} catch (err) {\n  if (err instanceof TypeError && /non-finite/.test(err.message)) {\n    logger.error('policy contains NaN/Infinity — check numeric config sources');\n  }\n  throw err;\n}","preventionTips":["Wrap every Number(envVar) parse with Number.isFinite before storing it in policy state.","Use undefined (omit the key) for 'no limit' — canonicalization drops undefined entries safely.","Add a config-load assertion that walks the policy tree for finite numbers once at startup."],"tags":["serialization","policy","numbers","validation"],"backgroundTag":"non-finite-number-serialization","analyzedSha":"fa13ee4ad60ac2090b1480656eb233521790d640","analyzedAt":"2026-08-18T21:34:22.708Z","contentChangedAt":"2026-08-18T21:34:22.708Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}