{"record":{"id":"3d600df1f6fd3560","repo":"ruvnet/ruflo","slug":"invalid-budget-limit","errorCode":null,"errorMessage":"invalid-budget-limit","messagePattern":"invalid-budget-limit","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"v3/@claude-flow/security/src/policy/engine.ts","lineNumber":99,"sourceCode":"      rule.constraints?.maxCostUsd,\n      rule.constraints?.maxTokens,\n      rule.constraints?.maxConcurrency,\n    ]) {\n      if (value !== undefined && (!Number.isFinite(value) || value < 0)) {\n        throw new Error('invalid-policy-rule-limit');\n      }\n    }\n    const index = this.state.rules.findIndex((item) => item.id === rule.id);\n    if (index >= 0) this.state.rules[index] = structuredClone(rule);\n    else this.state.rules.push(structuredClone(rule));\n  }\n\n  setBudget(limit: BudgetLimit): void {\n    if (limit.periodMs <= 0) throw new Error('invalid-budget-period');\n    if (!Number.isFinite(limit.periodMs)\n      || [limit.maxCostUsd, limit.maxTokens].some((value) => (\n        value !== undefined && (!Number.isFinite(value) || value < 0)\n      ))) throw new Error('invalid-budget-limit');\n    const index = this.state.budgets.findIndex((item) => item.id === limit.id);\n    if (index >= 0) this.state.budgets[index] = structuredClone(limit);\n    else this.state.budgets.push(structuredClone(limit));\n  }\n\n  issueApproval(approval: Omit<PolicyApproval, 'uses' | 'issuedAt'> & { uses?: number; issuedAt?: number }): PolicyApproval {\n    if (approval.issuedBy === approval.principal) throw new Error('self-approval-forbidden');\n    if (this.approvalIssuerVerifier?.(approval.issuedBy) !== true) {\n      throw new Error('untrusted-approval-issuer');\n    }\n    const issuedAt = approval.issuedAt ?? this.now();\n    const record: PolicyApproval = { ...approval, issuedAt, uses: approval.uses ?? 0 };\n    if (this.state.approvals.some((item) => item.id === record.id)) throw new Error('duplicate-approval-id');\n    if (!record.id\n      || record.expiresAt <= issuedAt\n      || !Number.isInteger(record.maxUses)\n      || record.maxUses <= 0\n      || !Number.isInteger(record.uses)","sourceCodeStart":81,"sourceCodeEnd":117,"githubUrl":"https://github.com/ruvnet/ruflo/blob/fa13ee4ad60ac2090b1480656eb233521790d640/v3/@claude-flow/security/src/policy/engine.ts#L81-L117","documentation":"The second gate in setBudget(): periodMs must itself be a finite number, and optional caps maxCostUsd / maxTokens must be finite and non-negative when present. Error('invalid-budget-limit'). This is distinct from invalid-budget-period, which fires first for periodMs <= 0 — this one catches Infinity periodMs and bad cap values.","triggerScenarios":"periodMs: Infinity (e.g. from a bad 'never resets' computation); maxCostUsd: NaN parsed from an unset env var; maxTokens: -100 from inverted arithmetic; maxCostUsd: Infinity intended as 'unlimited'.","commonSituations":"Env-parsed caps with unset variables; expressing 'unlimited' with Infinity or negative sentinels; copy-pasting limits between systems with different conventions.","solutions":["Omit maxCostUsd/maxTokens (leave undefined) when there is no cap for that dimension.","Sanitize parsed numbers at config load: keep only finite, non-negative values, else undefined.","Ensure periodMs is a finite positive integer of milliseconds."],"exampleFix":"// before\nengine.setBudget({ id: 'b', periodMs: 3600_000, maxCostUsd: Number('') }); // NaN throws\n\n// after\nconst cap = Number(env.MAX_COST);\nengine.setBudget({\n  id: 'b',\n  periodMs: 3600_000,\n  maxCostUsd: Number.isFinite(cap) && cap >= 0 ? cap : undefined,\n});","handlingStrategy":"validation","validationCode":"const cap = (v: number | undefined) =>\n  v === undefined ? undefined : Number.isFinite(v) && v >= 0 ? v : undefined;\nengine.setBudget({\n  id,\n  periodMs: 3600_000,\n  maxCostUsd: cap(rawCost),\n  maxTokens: cap(rawTokens),\n});","typeGuard":"function isSanitizedBudget(b: BudgetLimit): boolean {\n  const caps = [b.maxCostUsd, b.maxTokens].filter((v) => v !== undefined);\n  return Number.isFinite(b.periodMs) && caps.every((v) => Number.isFinite(v as number) && (v as number) >= 0);\n}","tryCatchPattern":"try {\n  engine.setBudget(limit);\n} catch (err) {\n  if (err instanceof Error && err.message === 'invalid-budget-limit') {\n    throw new ConfigError(`budget ${limit.id}: caps must be finite and >= 0`);\n  }\n  throw err;\n}","preventionTips":["Omit cap fields rather than passing NaN/Infinity/negatives — undefined means 'no cap' here.","Share one numeric-sanitizer between setBudget, upsertRule, and evaluate inputs.","Log raw parsed env values at startup so bad caps are visible before they reach the engine."],"tags":["policy","budget","numbers","validation"],"backgroundTag":"invalid-config-value","analyzedSha":"fa13ee4ad60ac2090b1480656eb233521790d640","analyzedAt":"2026-08-18T21:34:22.708Z","contentChangedAt":"2026-08-18T21:34:22.708Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}