jenkinsci / jenkinsci/support-core-plugin
Case-folding mismatch in SensitiveContentFilter: NPE during bundle generation, and some names silently never anonymized
- Dominant language
- Java
- Stars
- 19
- Forks
- 77
- Avg merge
- 17h 53m
- Merged PRs (30d)
- 3
Description
## Summary
`SensitiveContentFilter` builds its lookup keys with one case-folding rule and matches with another. The compiled `Pattern` uses `CASE_INSENSITIVE | UNICODE_CASE`, which compares characters via `toLowerCase(toUpperCase(c))`, while the trie words and `replacementsMap` keys are built with `String.toLowerCase(Locale.ENGLISH)`:
```java
// reload()
String lowerCaseOriginal = name.toLowerCase(Locale.ENGLISH);
trie.add(lowerCaseOriginal);
replacementsMap.putIfAbsent(lowerCaseOriginal, ...);
...
Pattern.compile("(? "ſap", which is NOT the map key "sap" => map.get() == null => NPE
// failure mode 2: the literal itself is corrupted by a length-changing lowercase
String literal = "İdalium".toLowerCase(Locale.ENGLISH); // U+0130 -> i + U+0307
Pattern q = Pattern.compile("(? " + q.matcher("x " + in + " y").find()); // all false
}
}
}
```
In the plugin, failure 1 surfaces as an aborted support bundle whenever such a character appears in filtered content; failure 2 surfaces as a name that is listed as anonymized but never actually redacted.
## Frequency
Rare. Only a handful of characters have lowercase mappings that change a string's length (U+0130 is essentially the canonical one), and the `ſ`-style divergence needs an unusual character to appear in filtered content. Reporting it because the feature accepts arbitrary Unicode names and claims to redact them, so a name it cannot match is a correctness gap regardless of frequency — and failure 2 fails *open*, which is the wrong direction for an anonymization feature.
## Environment
- support-core 1744.vf4f8e8f2b_0b_5 (the affected code path is long-standing and unchanged on `master`)
- Jenkins 2.541.3, JDK 21
- Anonymization enabled (`ContentFilters.enabled = true`)
## Fix
Deriving both the key and the lookup with the same per-code-point fold — `Character.toLowerCase(Character.toUpperCase(cp))`, which mirrors what the matcher actually does — resolves both failures and makes the `null` return impossible by construction. Verified that Java's `CASE_INSENSITIVE | UNICODE_CASE` single-character matching is exactly equality under that fold (4,000,000 random code-point pairs, 0 mismatches) and that the fold is idempotent across all 1,112,064 valid code points, so folding the pattern literal only ever broadens matching and never loses a match the current code catches.
Happy to open a PR with the fix and a regression test that fails on `master`.
Contributor guide
Research direction
Start with SensitiveContentFilter.java and WordReplacer.java, then run the plain JDK reproducer from the issue to observe both Unicode failures. Add regression coverage for the NPE case and the silently unmatched name case, and confirm filtered bundle generation handles both without exposing the original names.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- java
- Domain
- security
- Issue type
- Bug
- Difficulty
- 3/5
- Estimated time
- 1-2 days
- Activity status
- Quiet
- Clarity
- Clearly specified
- Newbie friendliness
- 74/100