Skip to content

Reject Money values with more than 1000 integer digits - #536

Open
elfassy wants to merge 1 commit into
mainfrom
Vault-78027
Open

elfassy wants to merge 1 commit into
mainfrom
Vault-78027

Conversation

@elfassy

@elfassy elfassy commented Sep 29, 2026

Copy link
Copy Markdown
Contributor

Problem

Money.new accepts compact exponent-form amounts like "1e1000000000". Only NaN and Infinity are rejected. Storing the value is cheap, but #subunits (and every converter) calls to_i, and #to_s renders the full number. Both build an Integer or String sized by the exponent, so a 12-byte input can exhaust CPU and memory.

Measured locally on main:

Value #subunits time
1e1000000 0.05 s
1e10000000 1.5 s
1e1000000000 not run (would hang the process)

The gem's own parsers (Simple, Fuzzy, Accounting, LocaleAware) already reject or neutralize this input. The exposure is apps that pass an untrusted string, BigDecimal or YAML value straight into Money.new or Money.from_amount.

Fix

Money#initialize raises ArgumentError when value.exponent > Helpers::MAX_INTEGER_DIGITS (1000). The check sits next to the NaN and Infinity checks, so it covers every construction path: strings, BigDecimals, from_subunits, YAML init_with, and arithmetic results.

Why 1000 digits

A 21-digit limit (to match the README's DECIMAL(21,3) column) was considered. It could break code that works today, such as intermediate arithmetic above 1e21 or using Money with the null currency as a general number container. 1000 digits is far beyond any real amount, and the largest allowed value still converts to an Integer under 1 KB.

Tests

  • 8 new specs under magnitude bound: strings, BigDecimals, from_subunits, arithmetic overflow, YAML loading, the largest allowed value, values above DECIMAL(21,3), and ordinary exponent strings like "1.5e3".
  • Without the fix, the 5 "raises" specs fail. The "largest value" spec also fails because it reads the new constant.
  • With the fix: 747 examples, 0 failures, 100% line coverage. steep check is clean.

Notes

🤖 Generated with Claude Code

A compact exponent-form amount such as "1e1000000000" is cheap to parse
and store, but `#subunits` and `#to_s` expand it into an Integer or
String proportional to the exponent, exhausting CPU and memory.

`Money#initialize` now raises `ArgumentError` when the value's exponent
exceeds `Helpers::MAX_INTEGER_DIGITS` (1000), next to the existing NaN
and Infinity checks. Because the check lives in `initialize`, it covers
strings, BigDecimals, `from_subunits`, YAML loading and arithmetic
results.

The bound is deliberately far above any real amount, so values larger
than a `DECIMAL(21,3)` column keep working, while the largest accepted
value still converts to an Integer under 1 KB.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@elfassy
elfassy requested a review from robinbrandt September 29, 2026 22:31

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant