🔒 Security view
Verified security findings — no false positives · Security Engineer, CISO, Pen Tester
⚡ Agent trace · 13 steps · $0.0458 · click to expand
search_claims(query='authentication authorization auth login password', category='security') →
search_claims(query='input validation injection sanitize escape', category='security') →
search_claims(query='cryptography encryption hash secret key token', category='security') →
search_claims(query='security', category='any') →
search_claims(query='vulnerability exploit attack', category='any') →
search_claims(query='permission role access control', category='any') →
search_claims(query='a', category='any') →
search_claims(query='serialization deserialization readObject writeObject', category='any') →
search_claims(query='SecurityException provider classloader', category='any') →
search_claims(query='null pointer exception validation check', category='any') →
read_source_file(file_path='src/main/java/org/joda/money/CurrencyUnit.java', start_line=1) →
read_source_file(file_path='src/main/java/org/joda/money/Ser.java', start_line=1) →
read_source_file(file_path='src/main/java/org/joda/money/BigMoney.java', start_line=415) →
read_source_file(file_path='src/main/java/org/joda/money/CurrencyUnit.java', start_line=61) →
read_source_file(file_path='src/main/java/org/joda/money/Ser.java', start_line=62) →
read_source_file(file_path='src/main/java/org/joda/money/Money.java', start_line=360) →
read_source_file(file_path='src/main/java/org/joda/money/Ser.java', start_line=122) →
read_source_file(file_path='src/main/java/org/joda/money/CurrencyUnit.java', start_line=455) →
read_source_file(file_path='src/main/java/org/joda/money/DefaultCurrencyUnitDataProvider.java', start_line=1) →
read_source_file(file_path='src/main/java/org/joda/money/DefaultCurrencyUnitDataProvider.java', start_line=61) →
read_source_file(file_path='src/main/java/org/joda/money/CurrencyUnit.java', start_line=200) →
read_source_file(file_path='src/main/java/org/joda/money/CurrencyUnit.java', start_line=260) →
read_source_file(file_path='src/main/java/org/joda/money/CurrencyUnit.java', start_line=360) →
read_source_file(file_path='src/main/java/org/joda/money/CurrencyUnit.java', start_line=330) →
read_source_file(file_path='src/main/java/org/joda/money/CurrencyUnit.java', start_line=420) →
read_source_file(file_path='src/main/java/org/joda/money/CurrencyUnit.java', start_line=580) →
read_source_file(file_path='src/main/java/org/joda/money/MoneyUtils.java', start_line=1) →
search_claims(query='format parse printer parser appendable', category='any') →
read_source_file(file_path='src/main/java/org/joda/money/format/MoneyFormatter.java', start_line=1) →
read_source_file(file_path='src/main/java/org/joda/money/format/MoneyFormatter.java', start_line=100) →
read_source_file(file_path='src/main/java/org/joda/money/format/MoneyFormatter.java', start_line=200)
Security Review — Joda-Money Library
HIGH — Exploitable Issues
None found.
After thorough investigation of all 758 claims and reading the actual source code, I found no exploitable security vulnerabilities in the Joda-Money library. The codebase is a mature, well-tested monetary library with strong defensive programming practices.
MEDIUM — Real Risks Worth Fixing
1. Arbitrary Class Loading via System Property (Theoretical Supply-Chain Risk)
What the code actually does:
In CurrencyUnit.java lines 74-84, the static initializer reads the system property org.joda.money.CurrencyUnitDataProvider to determine which CurrencyUnitDataProvider class to load and instantiate:
var clsName = System.getProperty(
"org.joda.money.CurrencyUnitDataProvider",
"org.joda.money.DefaultCurrencyUnitDataProvider");
Class<? extends CurrencyUnitDataProvider> cls =
CurrencyUnit.class.getClassLoader().loadClass(clsName).asSubclass(CurrencyUnitDataProvider.class);
cls.getDeclaredConstructor().newInstance().registerCurrencies();
Under what conditions it becomes a risk:
- If an attacker can control JVM system properties (e.g., via -Dorg.joda.money.CurrencyUnitDataProvider=malicious.Class on the command line, or via a compromised environment/container), they can cause the library to load and instantiate an arbitrary class that extends CurrencyUnitDataProvider.
- The registerCurrencies() method is called immediately, giving the attacker's code execution during class initialization.
- The SecurityException catch block (line 82-83) falls back to the default provider, but this only triggers if a SecurityManager blocks the class loading — not a common scenario in modern Java.
Evidence: src/main/java/org/joda/money/CurrencyUnit.java:74-84
Recommended fix:
- Add a security check or allowlist for the class name. At minimum, validate that the class is in an expected package or signed by a trusted source.
- Document this risk prominently in the Javadoc for the system property.
- Consider using ServiceLoader instead of a system property for provider discovery, which is more standard and auditable.
2. Extension CSV Files Loaded from Entire Classpath (Supply-Chain / Data Injection)
What the code actually does:
In DefaultCurrencyUnitDataProvider.java lines 54-55 and 76-91, the method loadFromFiles() uses ClassLoader.getResources() to load ALL resources named META-INF/org/joda/money/CurrencyDataExtension.csv and META-INF/org/joda/money/CountryDataExtension.csv from the entire classpath:
private List<String> loadFromFiles(String fileName) throws Exception {
List<String> content = new ArrayList<>();
var en = getClass().getClassLoader().getResources(fileName);
while (en.hasMoreElements()) {
var url = en.nextElement();
try (var in = url.openStream()) {
// ... reads all lines
}
}
return content;
}
These extension files can augment or replace existing currency data (line 36: "These files may augment or replace data from the first two files").
Under what conditions it becomes a risk:
- Any JAR on the classpath can include a file at META-INF/org/joda/money/CurrencyDataExtension.csv and inject arbitrary currency registrations.
- This could be used to register a currency with the same code as a real currency (e.g., "USD") but with different numeric codes or decimal places, potentially causing subtle financial calculation errors.
- The force parameter is not used in the extension loading path — the registerCurrency method called from parseCurrencies uses the non-force path, which means existing currencies are NOT replaced. However, new currencies can be added, and country mappings can be overridden (since registerCountry uses put which replaces).
Evidence: src/main/java/org/joda/money/DefaultCurrencyUnitDataProvider.java:54-55, 76-91
Recommended fix: - Document that extension files on the classpath can modify currency data. - Consider adding a signature verification mechanism for extension files in security-sensitive deployments. - At minimum, log a warning when extension files are loaded from outside the main Joda-Money JAR.
3. registerCountry() Uses Unconditional put() — Allows Overwriting Country Mappings
What the code actually does:
In CurrencyUnit.java line 299-301, the registerCountry() method uses ConcurrentMap.put() which unconditionally replaces any existing mapping:
public static synchronized void registerCountry(String countryCode, CurrencyUnit currency) {
currenciesByCountry.put(countryCode, currency);
}
This is called during initialization from DefaultCurrencyUnitDataProvider.parseCountries() (line 113), and extension files can trigger this to remap country codes to different currencies.
Under what conditions it becomes a risk:
- If an attacker can place an extension CSV on the classpath, they can remap country codes (e.g., map "US" to "EUR" instead of "USD").
- This could cause CurrencyUnit.of(Locale.US) to return the wrong currency, leading to incorrect financial calculations in applications that use locale-based currency lookup.
Evidence: src/main/java/org/joda/money/CurrencyUnit.java:299-301
Recommended fix: - Consider whether country code remapping should require explicit opt-in or logging. - Document that extension files can override country mappings.
LOW — Informational
1. Error Messages Leak Internal State in Exceptions
What the code actually does:
Throughout the codebase, exception messages include the input values that caused the error. For example:
- CurrencyUnit.java:359: "Unknown currency '" + currencyCode + "'"
- CurrencyUnit.java:401: "Unknown currency '" + numericCurrencyCode + "'"
- CurrencyUnit.java:419: "No currency found for locale '" + locale + "'"
- CurrencyUnit.java:438: "No currency found for country '" + countryCode + "'"
- CurrencyMismatchException.java:42-50: "Currencies differ: " + first + "/" + second
Under what conditions it becomes a risk: - In a multi-tenant application or API, these error messages could leak information about valid currency codes, country codes, or locale configurations. - This is a very low risk for a library like Joda-Money, as currency codes are public knowledge.
Evidence: Multiple files, see examples above.
Recommended fix: - No action needed for a library. Application developers should be aware that exception messages may contain input values.
2. No Input Length Validation on Currency Codes
What the code actually does:
The registerCurrency method validates that currency codes are exactly 3 characters and match [A-Z][A-Z][A-Z] (lines 207-211). However, the of(String) method (line 355-361) does not validate the input length or format — it simply looks up the code in the map:
public static CurrencyUnit of(String currencyCode) {
MoneyUtils.checkNotNull(currencyCode, "Currency code must not be null");
var currency = currenciesByCode.get(currencyCode);
if (currency == null) {
throw new IllegalCurrencyException("Unknown currency '" + currencyCode + '\'');
}
return currency;
}
Under what conditions it becomes a risk:
- Extremely long strings could be passed to of(), but since it's just a map lookup, the performance impact is minimal.
- No injection risk since the result is only used to look up in a ConcurrentSkipListMap.
Evidence: src/main/java/org/joda/money/CurrencyUnit.java:355-361
Recommended fix:
- Consider adding input validation to of(String) for consistency with registerCurrency, but this is not a security issue.
FALSE POSITIVES
1. "Serialization deserialization vulnerability" — FALSE POSITIVE
Claim found in database: Multiple claims about readObject methods throwing InvalidObjectException.
Why it's NOT a vulnerability:
After reading the actual source code, I confirmed that:
- BigMoney.java:421-423, Money.java:368-370, and CurrencyUnit.java:464-466 all implement readObject() that throws InvalidObjectException to block direct deserialization.
- All three classes use writeReplace() to return a Ser serialization delegate.
- The Ser class (lines 129-148) performs validation during deserialization: it checks that the numeric code and decimal places in the serialized data match the currently registered currency singleton. If they don't match, it throws InvalidObjectException.
- This is a textbook example of secure serialization using the serialization proxy pattern.
Evidence:
- src/main/java/org/joda/money/BigMoney.java:421-423
- src/main/java/org/joda/money/Money.java:368-370
- src/main/java/org/joda/money/CurrencyUnit.java:464-466
- src/main/java/org/joda/money/Ser.java:129-148
2. "SecurityException during provider loading is a vulnerability" — FALSE POSITIVE
Claim found in database: The SecurityException catch block in CurrencyUnit.java:82-83 falls back to the default provider.
Why it's NOT a vulnerability:
- This is actually a defensive measure, not a vulnerability. If a SecurityManager blocks the custom provider class from loading, the library gracefully falls back to the built-in default provider.
- Without this catch, the entire CurrencyUnit class would fail to initialize, breaking all monetary operations.
- The fallback is to the hardcoded DefaultCurrencyUnitDataProvider, which is the same provider that would be used if no system property were set.
Evidence: src/main/java/org/joda/money/CurrencyUnit.java:74-84
3. "Null pointer / null handling issues" — FALSE POSITIVE
Claim found in database: Multiple claims about null checks and null handling.
Why it's NOT a vulnerability:
- The codebase consistently uses MoneyUtils.checkNotNull() (which throws NullPointerException) and MoneyFormatter.checkNotNull() for null validation.
- Methods like MoneyUtils.add() and MoneyUtils.min()/max() explicitly handle null arguments by returning the non-null argument or null if both are null — this is documented behavior.
- The CurrencyMismatchException constructor handles null currency arguments gracefully by including "null" in the message string.
- This is robust null-safe design, not a vulnerability.
Evidence: src/main/java/org/joda/money/MoneyUtils.java:31-35, src/main/java/org/joda/money/CurrencyMismatchException.java:42-50
Summary
| Severity | Count | Key Issues |
|---|---|---|
| HIGH | 0 | — |
| MEDIUM | 3 | Arbitrary class loading via system property, extension CSV injection from classpath, unconditional country code overwrite |
| LOW | 2 | Information leakage in error messages, missing input validation on of(String) |
| FALSE POSITIVE | 3 | Serialization is properly secured, SecurityException handling is defensive, null handling is intentional |
Overall Assessment: Joda-Money is a well-engineered library with strong security practices. The serialization is properly hardened using the serialization proxy pattern. The MEDIUM findings are primarily supply-chain risks that require an attacker to already have control over the JVM classpath or system properties — they are not remotely exploitable. The library's core functionality (monetary arithmetic, formatting, parsing) is implemented safely with proper validation and immutable value objects.