GHSA-3rfq-4wpf-qqw3
LOWGHSA-3rfq-4wpf-qqw3 is a low-severity (CVSS 3.7) Uncontrolled Resource Consumption vulnerability in io.micronaut:micronaut-inject. O3 Security confirms whether GHSA-3rfq-4wpf-qqw3 is actually reachable in your code before you act, and blocks exploitation at runtime until you patch.
Micronaut has Unbounded `bundleCache` in `ResourceBundleMessageSource` that Allows Memory Exhaustion via `Accept-Language` Header
Exploitation Status
Proof-of-concept exploit code exists
- CISA’s SSVC triage found public proof-of-concept exploit code for this CVE, though no confirmed active exploitation.
Exploitation and automatability from CISA’s SSVC triage for GHSA-3rfq-4wpf-qqw3.
EPSS Exploitation Probability
EPSS (Exploit Prediction Scoring System) is a daily probability model maintained by FIRST.org. It estimates the likelihood a CVE will be exploited in production environments within the next 30 days, derived from real-world threat intelligence signals.
How urgent is this, really
GHSA-3rfq-4wpf-qqw3 plotted by exploitation likelihood (EPSS) against impact (CVSS). The shaded corner — EPSS 50%+ and CVSS 7.0+ — is where this CVE doesn't sit, though severity or exploitability alone can still warrant action.
Where this sits among everything scored
Of 356,453 CVEs with a current EPSS score, this one falls in the < 10% band (highlighted). Real counts from FIRST.org, not a sample — log-scaled since the landscape is heavily right-skewed.
Real-World Exposure
io.micronaut:micronaut-inject☕io.micronaut:micronaut-inject☕io.micronaut:micronaut-injectReal-time download stats are indexed for npm and PyPI packages. This vulnerability affects Maven packages — download data is not available via public APIs for these ecosystems.
Description
Summary
ResourceBundleMessageSource maintains two caches: messageCache (bounded at 100 entries via ConcurrentLinkedHashMap) and bundleCache (unbounded ConcurrentHashMap). The bundleCache is keyed by (Locale, baseName) where the locale originates from the HTTP Accept-Language header. In applications that explicitly register a ResourceBundleMessageSource bean and serve HTML error responses, an unauthenticated attacker can exhaust heap memory by sending requests with large numbers of unique Accept-Language values, each causing a new entry in the unbounded bundleCache. Unlike GHSA-2hcp-gjrf-7fhc and the sibling messageCache (both bounded), bundleCache was not updated to use a bounded cache implementation.
Details
The bundleCache is initialized in inject/src/main/java/io/micronaut/context/i18n/ResourceBundleMessageSource.java at line 150:
// ResourceBundleMessageSource.java:139-152
protected Map<MessageKey, Optional<String>> buildMessageCache() {
return new ConcurrentLinkedHashMap.Builder<MessageKey, Optional<String>>()
.maximumWeightedCapacity(100) // ← BOUNDED ✓
.build();
}
protected Map<MessageKey, Optional<ResourceBundle>> buildBundleCache() {
return new ConcurrentHashMap<>(18); // ← UNBOUNDED ✗
}
The resolveBundle() method at line 169 inserts into bundleCache with no eviction policy:
// ResourceBundleMessageSource.java:169-185
private Optional<ResourceBundle> resolveBundle(Locale locale) {
MessageKey key = new MessageKey(locale, baseName);
final Optional<ResourceBundle> resourceBundle = bundleCache.get(key);
if (resourceBundle != null) {
return resourceBundle;
} else {
Optional<ResourceBundle> opt;
try {
opt = Optional.of(ResourceBundle.getBundle(baseName, locale, getClassLoader()));
} catch (MissingResourceException e) {
opt = Optional.empty();
}
bundleCache.put(key, opt); // NO SIZE CHECK — unbounded growth
return opt;
}
}
The attack path requires:
- The application registers a
ResourceBundleMessageSourcebean (non-default, requires explicit user configuration). - The attacker sends requests that trigger HTML error responses — i.e., requests with
Accept: text/htmlto any URL that returns an error (e.g., 404 for any non-existent path). - Each request uses a unique
Accept-Languagevalue (e.g.,zz-AA,zz-AB, …). DefaultHtmlErrorResponseBodyProvider.error()callsmessageSource.getMessage(code, locale)→CompositeMessageSourcedelegates toResourceBundleMessageSource→resolveBundle(locale)inserts one entry per unique locale intobundleCache.
For locales that don't match any bundle file, ResourceBundle.getBundle() throws MissingResourceException and Optional.empty() is stored — a low-cost sentinel. For locales that DO match a bundle, a full ResourceBundle object is retained in memory. In either case, the map itself and the MessageKey objects grow without bound.
Note: the messageCache is bounded at 100 entries but does not prevent bundleCache growth, as resolveBundle() is called directly (bypassing messageCache) whenever a messageCache miss occurs.
PoC
Against a Micronaut application with a ResourceBundleMessageSource bean registered (e.g., @Bean ResourceBundleMessageSource messages() { return new ResourceBundleMessageSource("messages"); }):
# Flood bundleCache with unique locales via HTML error path
for i in $(seq 1 100000); do
curl -s -o /dev/null \
-H "Accept: text/html" \
-H "Accept-Language: zz-$(printf '%04d' $i)" \
"http://localhost:8080/nonexistent-path-$(printf '%06d' $i)" &
[ $((i % 200)) -eq 0 ] && wait
done
wait
Each unique zz-XXXX tag creates one new bundleCache entry. The MessageKey (Locale + baseName) and map overhead cost approximately 100-200 bytes per entry. At 100,000 entries, heap consumption from the cache alone reaches roughly 20 MB — significant in resource-constrained deployments. If a locale matches a bundle file, retained ResourceBundle objects cost substantially more per entry.
Impact
- Only affects applications that explicitly register a
ResourceBundleMessageSourcebean (not the default configuration). - Requires the ability to send HTTP requests with
Accept: text/htmlheaders and control over theAccept-Languagevalue. - Memory grows approximately 100-200 bytes per novel locale (for non-matching locales) up to several KB per locale if bundles are found. Sustained attack over time causes gradual heap exhaustion.
- Partial availability impact (A:L) under sustained attack in long-running services.
Recommended Fix
Apply the same bounded-cache pattern used for the sibling messageCache:
// In ResourceBundleMessageSource.java — replace buildBundleCache()
protected Map<MessageKey, Optional<ResourceBundle>> buildBundleCache() {
return new ConcurrentLinkedHashMap.Builder<MessageKey, Optional<ResourceBundle>>()
.maximumWeightedCapacity(50) // small — one entry per (locale, baseName)
.build();
}
The number of distinct resource bundle files is bounded at compile time; a limit of 50 entries is more than sufficient for any realistic i18n configuration while fully preventing unbounded growth.
Affected Packages
| Ecosystem | Package | Vulnerable range | Fix |
|---|---|---|---|
| ☕Maven | io.micronaut:micronaut-inject | ≥ 4.10.0&&< 4.10.22 | 4.10.22 |
| ☕Maven | io.micronaut:micronaut-inject | ≥ 3.10.0&&< 3.10.6 | 3.10.6 |
| ☕Maven | io.micronaut:micronaut-inject | all versions | 3.8.14 |
Detection & mitigation playbook
Open-source dependencyDetect
Scan your dependency tree (package-lock.json, pnpm-lock.yaml, requirements.txt, go.sum, etc.) for io.micronaut:micronaut-inject. O3's reachability analysis confirms whether the vulnerable code path is actually invoked in your application, so you act on real exposure instead of every transitive match.
Fix
Update io.micronaut:micronaut-inject to 4.10.22 or later, then make sure no transitive (indirect) dependency still pins the vulnerable range — O3 confirms GHSA-3rfq-4wpf-qqw3 is resolved across your whole dependency graph.
Workarounds
If you can't upgrade right away: gate or disable the affected feature, validate untrusted input at the boundary, and avoid passing attacker-controlled data into the vulnerable path. O3's runtime protection blocks exploitation in production as an interim safeguard until the upgrade lands.
How O3 protects you
O3 pinpoints whether GHSA-3rfq-4wpf-qqw3 is reachable in your code and exactly where to fix it, then blocks exploitation in production at runtime until the patched version is deployed.
Tailored to GHSA-3rfq-4wpf-qqw3. Runtime protection reduces exposure until a permanent patch is applied and verified — it complements patching, it doesn't replace it.
Frequently Asked Questions
Is GHSA-3rfq-4wpf-qqw3 in your dependencies?
O3 detects GHSA-3rfq-4wpf-qqw3 across Maven dependencies and uses function-level reachability to confirm whether the vulnerable code path is actually reachable — not just present. No false positives.